diff --git a/CHANGELOG.md b/CHANGELOG.md index b794be7c9..497c96e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - +## Unreleased + +### Added + +- Added support for the `v2` version of the + `google/wireit@setup-github-actions-caching` action, which provides improved + security. All users are advised to upgrade to + `google/wireit@setup-github-actions-caching/v2`. ## [0.14.5] - 2024-07-08 diff --git a/README.md b/README.md index 0f23f20d7..6eea67cc9 100644 --- a/README.md +++ b/README.md @@ -360,7 +360,7 @@ clause to your workflow. It can appear anywhere before the first `npm run` or `npm test` command: ```yaml -- uses: google/wireit@setup-github-actions-caching/v1 +- uses: google/wireit@setup-github-actions-caching/v2 ``` #### Example workflow @@ -381,7 +381,7 @@ jobs: cache: npm # Set up GitHub Actions caching for Wireit. - - uses: google/wireit@setup-github-actions-caching/v1 + - uses: google/wireit@setup-github-actions-caching/v2 # Install npm dependencies. - run: npm ci @@ -857,7 +857,7 @@ The following environment variables affect the behavior of Wireit: | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `WIREIT_FAILURES` | [How to handle script failures](#failures-and-errors).

Options:
| | `WIREIT_PARALLEL` | [Maximum number of scripts to run at one time](#parallelism).

Defaults to 2×logical CPU cores.

Must be a positive integer or `infinity`. | -| `WIREIT_CACHE` | [Caching mode](#caching).

Defaults to `local` unless `CI` is `true`, in which case defaults to `none`.

Automatically set to `github` by the [`google/wireit@setup-github-actions-caching/v1`](#github-actions-caching) action.

Options: | +| `WIREIT_CACHE` | [Caching mode](#caching).

Defaults to `local` unless `CI` is `true`, in which case defaults to `none`.

Automatically set to `github` by the [`google/wireit@setup-github-actions-caching/v2`](#github-actions-caching) action.

Options: | | `CI` | Affects the default value of `WIREIT_CACHE`.

Automatically set to `true` by [GitHub Actions](https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables) and most other CI (continuous integration) services.

Must be exactly `true`. If unset or any other value, interpreted as `false`. | | `WIREIT_MAX_OPEN_FILES` | Limits the number of file descriptors Wireit will have open concurrently. Prevents resource exhaustion when checking large numbers of cached files. Set to a lower number if you hit file descriptor limits. | | `WIREIT_LOGGER` | How to present progress and results on the command line.

Options:
| diff --git a/src/caching/github-actions-cache.ts b/src/caching/github-actions-cache.ts index ab21f8090..7d1a51573 100644 --- a/src/caching/github-actions-cache.ts +++ b/src/caching/github-actions-cache.ts @@ -23,6 +23,7 @@ import type {Fingerprint} from '../fingerprint.js'; import type {Logger} from '../logging/logger.js'; import type {AbsoluteEntry} from '../util/glob.js'; import type {Result} from '../error.js'; +import type {InvalidUsage, UnknownErrorThrown} from '../event.js'; /** * Caches script output to the GitHub Actions caching service. @@ -48,9 +49,80 @@ export class GitHubActionsCache implements Cache { this.#logger = logger; } - static create( + static async create( logger: Logger, - ): Result { + ): Promise< + Result< + GitHubActionsCache, + Omit | Omit + > + > { + const custodianPort = process.env['WIREIT_CACHE_GITHUB_CUSTODIAN_PORT']; + if (custodianPort === undefined) { + if ( + process.env['ACTIONS_RUNTIME_TOKEN'] !== undefined || + process.env['ACTIONS_CACHE_URL'] !== undefined + ) { + console.warn( + '⚠️ Please upgrade to google/wireit@setup-github-cache/v2. ' + + 'In the future, Wireit caching for this project will stop working.\n', + ); + return GitHubActionsCache.#deprecatedCreate(logger); + } + return { + ok: false, + error: { + type: 'failure', + reason: 'invalid-usage', + message: + 'The WIREIT_CACHE_GITHUB_CUSTODIAN_PORT environment variable was ' + + 'not set, but is required when WIREIT_CACHE=github. Use the ' + + 'google/wireit@setup-github-cache/v2 action to automatically set ' + + 'this environment variable.', + }, + }; + } + const custodianUrl = `http://localhost:${custodianPort}`; + let result: { + caching: { + github: { + ACTIONS_CACHE_URL: string; + ACTIONS_RUNTIME_TOKEN: string; + }; + }; + }; + try { + const response = await fetch(custodianUrl); + if (!response.ok) { + throw new Error(`HTTP status ${response.status}`); + } + result = (await response.json()) as typeof result; + } catch (error) { + return { + ok: false, + error: { + type: 'failure', + reason: 'unknown-error-thrown', + error: new Error( + `Error communicating with cache token mediator service: ` + + String(error), + ), + }, + }; + } + return { + ok: true, + value: new GitHubActionsCache( + logger, + result.caching.github.ACTIONS_CACHE_URL, + result.caching.github.ACTIONS_RUNTIME_TOKEN, + ), + }; + } + + static #deprecatedCreate( + logger: Logger, + ): Result> { // The ACTIONS_CACHE_URL and ACTIONS_RUNTIME_TOKEN environment variables are // automatically provided to GitHub Actions re-usable workflows. However, // they are _not_ provided to regular "run" scripts. For this reason, we @@ -63,6 +135,7 @@ export class GitHubActionsCache implements Cache { return { ok: false, error: { + type: 'failure', reason: 'invalid-usage', message: 'The ACTIONS_CACHE_URL variable was not set, but is required when ' + @@ -78,6 +151,7 @@ export class GitHubActionsCache implements Cache { return { ok: false, error: { + type: 'failure', reason: 'invalid-usage', message: `The ACTIONS_CACHE_URL must end in a forward-slash, got ${JSON.stringify( baseUrl, @@ -92,6 +166,7 @@ export class GitHubActionsCache implements Cache { return { ok: false, error: { + type: 'failure', reason: 'invalid-usage', message: 'The ACTIONS_RUNTIME_TOKEN variable was not set, but is required when ' + diff --git a/src/cli.ts b/src/cli.ts index 30defe6e0..12976cc82 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -30,28 +30,17 @@ const run = async (options: Options): Promise> => { const {GitHubActionsCache} = await import( './caching/github-actions-cache.js' ); - const cacheResult = GitHubActionsCache.create(logger); + const cacheResult = await GitHubActionsCache.create(logger); if (!cacheResult.ok) { - if (cacheResult.error.reason === 'invalid-usage') { - return { - ok: false, - error: [ - { - script: options.script, - type: 'failure', - reason: 'invalid-usage', - message: cacheResult.error.message, - }, - ], - }; - } else { - const never: never = cacheResult.error.reason; - throw new Error( - `Internal error: unexpected cache result error reason: ${String( - never, - )}`, - ); - } + return { + ok: false, + error: [ + { + script: options.script, + ...cacheResult.error, + }, + ], + }; } cache = cacheResult.value; break; diff --git a/src/test/cache-common.ts b/src/test/cache-common.ts index 6f4bca0fe..ca7b67f0e 100644 --- a/src/test/cache-common.ts +++ b/src/test/cache-common.ts @@ -990,7 +990,7 @@ export const registerCommonCacheTests = ( assert.equal(res.code, 1); checkScriptOutput( res.stderr, - ` + `${cacheMode === 'github' ? '⚠️ Please upgrade to google/wireit@setup-github-cache/v2. In the future, Wireit caching for this project will stop working.\n' : ''} ❌ package.json:9:17 Output files must be within the package: ${JSON.stringify( pathlib.join(rig.temp, 'outside'), )} was outside ${JSON.stringify(pathlib.join(rig.temp, 'foo'))}