From 6652b19eb8364ca27c92c064df7b77065aa4f7b2 Mon Sep 17 00:00:00 2001 From: Elliot Nelson Date: Wed, 19 Apr 2023 12:31:27 -0400 Subject: [PATCH] Use the build-cache.json configuration instead of a separate file --- .../common/config/rush/build-cache.json | 43 +++++++++++ .../src/schemas/build-cache.schema.json | 72 ++++++++++++++++++- .../rush-http-build-cache-plugin/README.md | 44 +++++++++--- .../src/RushHttpBuildCachePlugin.ts | 16 ++--- 4 files changed, 155 insertions(+), 20 deletions(-) diff --git a/libraries/rush-lib/assets/rush-init/common/config/rush/build-cache.json b/libraries/rush-lib/assets/rush-init/common/config/rush/build-cache.json index 45571cbd709..4e26cde258a 100644 --- a/libraries/rush-lib/assets/rush-init/common/config/rush/build-cache.json +++ b/libraries/rush-lib/assets/rush-init/common/config/rush/build-cache.json @@ -96,5 +96,48 @@ * If set to true, allow writing to the cache. Defaults to false. */ // "isCacheWriteAllowed": true + }, + + /** + * Use this configuration with "cacheProvider"="http" + */ + "httpConfiguration": { + /** + * (Required) The URL of the server that stores the caches. + * Example: "https://build-cacches.example.com/" + */ + // "url": "https://build-cacches.example.com/", + + /** + * (Optional) The HTTP method to use when writing to the cache (defaults to PUT). + * Should be one of PUT, POST, or PATCH. + * Example: "PUT" + */ + // "uploadMethod": "PUT", + + /** + * (Optional) HTTP headers to pass to the cache server. + * Example: { "X-HTTP-Company-Id": "109283" } + */ + // "headers": {}, + + /** + * (Optional) Shell command that prints the authorization token needed to communicate with the + * cache server, and exits with exit code 0. This command will be executed from the root of + * the monorepo. + * Example: { "exec": "node", "args": ["common/scripts/auth.js"] } + */ + // "tokenHandler": { "exec": "node", "args": ["common/scripts/auth.js"] }, + + /** + * (Optional) Prefix for cache keys. + * Example: "my-company-" + */ + // "cacheKeyPrefix": "", + + /** + * (Optional) If set to true, allow writing to the cache. Defaults to false. + */ + // "isCacheWriteAllowed": true } } diff --git a/libraries/rush-lib/src/schemas/build-cache.schema.json b/libraries/rush-lib/src/schemas/build-cache.schema.json index 0baf023b961..e91b4d361c7 100644 --- a/libraries/rush-lib/src/schemas/build-cache.schema.json +++ b/libraries/rush-lib/src/schemas/build-cache.schema.json @@ -85,6 +85,56 @@ "description": "If set to true, allow writing to the cache. Defaults to false." } } + }, + "httpConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "description": "(Required) The URL of the server that stores the caches (e.g. \"https://build-caches.example.com\").", + "format": "uri" + }, + "uploadMethod": { + "type": "string", + "description": "(Optional) The HTTP method to use when writing to the cache (defaults to PUT).", + "enum": ["PUT", "POST", "PATCH"], + "default": "PUT" + }, + "headers": { + "type": "object", + "description": "(Optional) HTTP headers to pass to the cache server", + "properties": {}, + "additionalProperties": { + "type": "string" + } + }, + "tokenHandler": { + "type": "object", + "description": "(Optional) Shell command that prints the authorization token needed to communicate with the HTTPS server and exits with code 0. This command will be executed from the root of the monorepo.", + "properties": { + "exec": { + "type": "string", + "description": "(Required) The command or script to execute." + }, + "args": { + "type": "array", + "description": "(Optional) Arguments to pass to the command or script.", + "items": { + "type": "string" + } + } + } + }, + "cacheKeyPrefix": { + "type": "string", + "description": "(Optional) prefix for cache keys." + }, + "isCacheWriteAllowed": { + "type": "boolean", + "description": "(Optional) If set to true, allow writing to the cache. Defaults to false." + } + } } }, "oneOf": [ @@ -94,7 +144,7 @@ "properties": { "cacheProvider": { "type": "string", - "pattern": "^(?:(?!azure-blob-storage|amazon-s3).)*$" + "pattern": "^(?:(?!azure-blob-storage|amazon-s3|http).)*$" } } }, @@ -160,6 +210,26 @@ ] } } + }, + { + "type": "object", + "additionalProperties": true, + "properties": { + "cacheProvider": { + "type": "string", + "enum": ["http"] + }, + "httpConfiguration": { + "type": "object", + "additionalProperties": true, + "required": ["url"], + "properties": { + "url": { + "$ref": "#/definitions/anything" + } + } + } + } } ] } diff --git a/rush-plugins/rush-http-build-cache-plugin/README.md b/rush-plugins/rush-http-build-cache-plugin/README.md index 5cf942de160..d8468855d6f 100644 --- a/rush-plugins/rush-http-build-cache-plugin/README.md +++ b/rush-plugins/rush-http-build-cache-plugin/README.md @@ -16,19 +16,41 @@ To use the HTTP build cache plugin, enable it in `common/config/rush/build-cache } ``` -Once enabled, configure the HTTP build cache in config file `common/config/rush-plugins/rush-http-build-cache-plugin.json`: +Then customize the `httpConfiguration` block. For typical use cases, where you'll use a remote HTTP URL with authentication, you'll need to provide at least the `url` and `tokenHandler` options: ```json { - "url": "https://build-cache.example.com", - "tokenHandler": "node common/scripts/custom-script-that-returns-an-authentication-header.js", - "isCacheWriteAllowed": false + "httpConfiguration": { + "url": "https://build-cache.example.com", + "tokenHandler": { + "exec": "node", + "args": ["common/scripts/custom-script-that-returns-an-authentication-header.js"] + }, + "isCacheWriteAllowed": false + } } ``` -- `url`: The server to store cache objects. -- `tokenHandler`: A script that can print the Authorization header expected by the server. The value printed to `stdout` by this command should be an exact header, for example, `Bearer ab98d8c878d937290d979a9097c90dfffff` or `Basic 098abc7dff==`. -- `isCacheWriteAllowed`: A flag that determines if the plugin should write to the cache. +(For more detail on the above properties and additional optional properties, consult the default `build-cache.json` file.) + +## Authorization Details + +The HTTP build cache plugin offloads authorization to an external executable script that you define. A typical use case would be to create a simple script, for example `common/scripts/cache-auth.js`, that prints an Authorization header value when executed. + +For example: + +```console +node common/scripts/cache-auth.js +# => Bearer 0284357923592790DDb979dBcd2zz +``` + +How the script generates authorization values is up to you, and depends on the configuration of your remote cache server. + +Possible implementations: + + - The script could read simple environment variables (`CACHE_USER` and `CACHE_TOKEN`) defined by the developer + - It could reuse existing credentials your developers have, like NPM tokens, GitHub credentials, or jFrog API tokens + - It could make an HTTP call to another server accessible by your developers that returns temporary credentials ## HTTP Cache Server Requirements @@ -36,7 +58,7 @@ The HTTP build cache plugin can use almost any HTTP/HTTPS backend for remote cac - Uses `Authorization: Bearer xxx` or `Authorization: Basic xxx` headers for authentication. - Accepts GET requests for cache reads. - - Accepts PUT requests for cache writes (with a raw request body -- no `form/multipart`). + - Accepts PUT requests for cache writes (with a raw request body -- no `form/multipart` MIME types). - Cache hits return HTTP 200 with the file in the response body. - Successful cache writes return HTTP 2xx (200-299). - Cache misses return HTTP 404 or HTTP 403. @@ -56,13 +78,13 @@ First, start up and configure your build cache node locally: - Enter the temporary username/password credentials printed in the startup banner - Click Build Cache > Settings > Cache Access Control and grant "Read & write" access to Anonymous -Second, create your `rush-http-build-cache-plugin.json` file as described in the Configuration section: +Second, configure your `build-cache.json` file as described in the Configuration section: - - Note that your `url` should end with `/cache/`, for example, `http://localhost:5071/cache/` + - Note that your `url` must end with `/cache/`, for example, `http://localhost:5071/cache/`. - To test reading and writing, set `isCacheWriteAllowed: true`. - Configure `tokenHandler` to point to a script that prints a Basic or Bearer Authorization value (this can be a dummy string if you granted Read and Write to Anonymous in your build cache node configuration). -Note that the Gradle Build Cache Server has a stricter format for its cache keys (they should be a simple hexadecimal hash with no non-alphanumeric characters). Configure this setting in your `common/config/rush/build-cache.json` file: +Note that the Gradle Build Cache Server has a stricter format for its cache keys (they should be a simple hexadecimal hash with no non-alphanumeric characters). Configure this setting in your `build-cache.json` file: ```json { diff --git a/rush-plugins/rush-http-build-cache-plugin/src/RushHttpBuildCachePlugin.ts b/rush-plugins/rush-http-build-cache-plugin/src/RushHttpBuildCachePlugin.ts index 5a248e64387..00dda437c4c 100644 --- a/rush-plugins/rush-http-build-cache-plugin/src/RushHttpBuildCachePlugin.ts +++ b/rush-plugins/rush-http-build-cache-plugin/src/RushHttpBuildCachePlugin.ts @@ -12,7 +12,7 @@ const PLUGIN_NAME: string = 'HttpBuildCachePlugin'; /** * @public */ -export interface IRushHttpBuildCachePluginOptions { +export interface IRushHttpBuildCachePluginConfig { /** * The URL of the server that stores the caches (e.g. "https://build-caches.example.com"). */ @@ -53,19 +53,19 @@ export interface IRushHttpBuildCachePluginOptions { */ export class RushHttpBuildCachePlugin implements IRushPlugin { public readonly pluginName: string = PLUGIN_NAME; - private readonly _options: IRushHttpBuildCachePluginOptions; - - public constructor(options: IRushHttpBuildCachePluginOptions) { - this._options = options; - } public apply(rushSession: RushSession, rushConfig: RushConfiguration): void { rushSession.hooks.initialize.tap(this.pluginName, () => { rushSession.registerCloudBuildCacheProviderFactory( 'http', (buildCacheConfig): HttpBuildCacheProvider => { - const { url, uploadMethod, headers, tokenHandler, cacheKeyPrefix, isCacheWriteAllowed } = - this._options; + const config: IRushHttpBuildCachePluginConfig = ( + buildCacheConfig as typeof buildCacheConfig & { + httpConfiguration: IRushHttpBuildCachePluginConfig; + } + ).httpConfiguration; + + const { url, uploadMethod, headers, tokenHandler, cacheKeyPrefix, isCacheWriteAllowed } = config; const options: IHttpBuildCacheProviderOptions = { pluginName: this.pluginName,