mirror of
https://github.com/google-github-actions/setup-gcloud.git
synced 2026-09-01 12:19:23 +00:00
Add support for specifying version constraints (#612)
This adds support for specifying a version _constraint_ for the version of gcloud. Since the GitHub Actions runners pre-install a version of gcloud, this can dramatically reduce CI times.
This commit is contained in:
parent
fb149a7b94
commit
10b5bd2b6d
8 changed files with 338 additions and 2152 deletions
12
.github/workflows/integration.yml
vendored
12
.github/workflows/integration.yml
vendored
|
|
@ -38,6 +38,18 @@ jobs:
|
|||
- name: 'Build'
|
||||
run: 'npm ci && npm run build'
|
||||
|
||||
# No installation
|
||||
- name: 'Install constraint'
|
||||
uses: './'
|
||||
with:
|
||||
skip_install: true
|
||||
|
||||
# Constraint installation
|
||||
- name: 'Install constraint'
|
||||
uses: './'
|
||||
with:
|
||||
version: '>= 1.0.0'
|
||||
|
||||
# Default installation
|
||||
- name: 'Install version'
|
||||
uses: './'
|
||||
|
|
|
|||
41
README.md
41
README.md
|
|
@ -41,6 +41,8 @@ jobs:
|
|||
|
||||
- name: 'Set up Cloud SDK'
|
||||
uses: 'google-github-actions/setup-gcloud@v1'
|
||||
with:
|
||||
version: '>= 363.0.0'
|
||||
|
||||
- name: 'Use gcloud CLI'
|
||||
run: 'gcloud info'
|
||||
|
|
@ -50,13 +52,42 @@ jobs:
|
|||
|
||||
### Cloud SDK inputs
|
||||
|
||||
- `version`: (Optional) A string representing the version of the Cloud SDK
|
||||
(`gcloud`) to install (e.g. `"290.0.1"`). The default value is "latest",
|
||||
which will install the latest available Cloud SDK version.
|
||||
- `skip_install`: (Optional) Skip the `gcloud` installation and use the
|
||||
[system-installed gcloud][github-runners] instead. This can dramatically
|
||||
improve workflow speeds at the expense of a slightly older gcloud version.
|
||||
Setting this to `true` ignores any value for the `version` input. Even if
|
||||
installation is skipped, this GitHub Action will still configure any
|
||||
components or project metadata. The default value is `false`.
|
||||
|
||||
- `version`: (Optional) A string representing the version or version
|
||||
constraint of the Cloud SDK (`gcloud`) to install (e.g. `"290.0.1"` or `">=
|
||||
197.0.1"`). The default value is `"latest"`, which will always download and
|
||||
install the latest available Cloud SDK version.
|
||||
|
||||
```yaml
|
||||
- uses: 'google-github-actions/setup-gcloud@v1'
|
||||
with:
|
||||
version: '>= 416.0.0'
|
||||
```
|
||||
|
||||
If there is no installed `gcloud` version that matches the given constraint,
|
||||
this GitHub Action will download and install the latest available version
|
||||
that still matches the constraint.
|
||||
|
||||
**Warning!** Workload Identity Federation requires version
|
||||
[363.0.0](https://cloud.google.com/sdk/docs/release-notes#36300_2021-11-02)
|
||||
or newer.
|
||||
or newer. If you need support for Workload Identity Federation, specify your
|
||||
version constraint as such:
|
||||
|
||||
```yaml
|
||||
- uses: 'google-github-actions/setup-gcloud@v1'
|
||||
with:
|
||||
version: '>= 363.0.0'
|
||||
```
|
||||
|
||||
You are responsible for ensuring the `gcloud` version matches the features
|
||||
and components required. See the [gcloud release
|
||||
notes][gcloud-release-notes] for a full list of versions.
|
||||
|
||||
- `project_id`: (Optional) Project ID (**not** project _number_) of the Google
|
||||
Cloud project. If provided, this will configure the `gcloud` CLI to use that
|
||||
|
|
@ -233,3 +264,5 @@ See [LICENSE](LICENSE).
|
|||
[sa-iam-docs]: https://cloud.google.com/iam/docs/service-accounts
|
||||
[sa]: https://cloud.google.com/iam/docs/creating-managing-service-accounts
|
||||
[wif]: https://cloud.google.com/iam/docs/workload-identity-federation
|
||||
[github-runners]: https://github.com/actions/runner-images
|
||||
[gcloud-release-notes]: https://cloud.google.com/sdk/docs/release-notes
|
||||
|
|
|
|||
18
action.yml
18
action.yml
|
|
@ -19,12 +19,21 @@ description: |-
|
|||
Adds the `gcloud` CLI command to the $PATH.
|
||||
|
||||
inputs:
|
||||
skip_install:
|
||||
description: |-
|
||||
Skip installation of the gcloud SDK and use the system-supplied version
|
||||
instead. The "version" input will be ignored.
|
||||
default: false
|
||||
required: false
|
||||
|
||||
version:
|
||||
description: |-
|
||||
Version of the gcloud SDK to install. If unspecified or set to "latest",
|
||||
the latest available gcloud SDK version for the target platform will be
|
||||
installed. Example: "290.0.1".
|
||||
default: latest
|
||||
Version or version constraint of the gcloud SDK to install. If
|
||||
unspecified, it will accept any installed version of the gcloud SDK. If
|
||||
set to "latest", it will download the latest available SDK. If set to a
|
||||
version constraint, it will download the latest available version that
|
||||
matches the constraint. Examples: "290.0.1" or ">= 197.0.1".
|
||||
default: 'latest'
|
||||
required: false
|
||||
|
||||
project_id:
|
||||
|
|
@ -32,7 +41,6 @@ inputs:
|
|||
ID of the Google Cloud project. If provided, this will configure gcloud to
|
||||
use this project ID by default for commands. Individual commands can still
|
||||
override the project using the --project flag which takes precedence.
|
||||
default: null
|
||||
required: false
|
||||
|
||||
install_components:
|
||||
|
|
|
|||
2
dist/main/index.js
vendored
2
dist/main/index.js
vendored
File diff suppressed because one or more lines are too long
2182
package-lock.json
generated
2182
package-lock.json
generated
File diff suppressed because it is too large
Load diff
18
package.json
18
package.json
|
|
@ -28,24 +28,24 @@
|
|||
"@actions/core": "^1.10.0",
|
||||
"@actions/tool-cache": "^2.0.1",
|
||||
"@google-github-actions/actions-utils": "^0.4.6",
|
||||
"@google-github-actions/setup-cloud-sdk": "^1.0.1"
|
||||
"@google-github-actions/setup-cloud-sdk": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^18.11.15",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/sinon": "^10.0.13",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"@vercel/ncc": "^0.36.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"chai": "^4.3.7",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"mocha": "^10.2.0",
|
||||
"prettier": "^2.8.1",
|
||||
"prettier": "^2.8.3",
|
||||
"sinon": "^15.0.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
src/main.ts
39
src/main.ts
|
|
@ -18,16 +18,17 @@ import * as core from '@actions/core';
|
|||
import * as toolCache from '@actions/tool-cache';
|
||||
import {
|
||||
authenticateGcloudSDK,
|
||||
getLatestGcloudSDKVersion,
|
||||
bestVersion,
|
||||
installComponent,
|
||||
installGcloudSDK,
|
||||
isInstalled,
|
||||
setProject,
|
||||
} from '@google-github-actions/setup-cloud-sdk';
|
||||
import {
|
||||
errorMessage,
|
||||
isPinnedToHead,
|
||||
pinnedToHeadWarning,
|
||||
parseBoolean,
|
||||
presence,
|
||||
} from '@google-github-actions/actions-utils';
|
||||
import path from 'path';
|
||||
|
||||
|
|
@ -49,17 +50,33 @@ export async function run(): Promise<void> {
|
|||
}
|
||||
|
||||
try {
|
||||
let version = core.getInput('version');
|
||||
if (!version || version == 'latest') {
|
||||
version = await getLatestGcloudSDKVersion();
|
||||
}
|
||||
|
||||
// Install the gcloud if not already present
|
||||
if (!isInstalled(version)) {
|
||||
await installGcloudSDK(version);
|
||||
const skipInstall = parseBoolean(core.getInput('skip_install'));
|
||||
if (skipInstall) {
|
||||
core.info(`Skipping installation ("skip_install" was true)`);
|
||||
} else {
|
||||
// Compute the version information. If the version was not specified,
|
||||
// accept any installed version. If the version was specified as "latest",
|
||||
// compute the latest version. Otherwise, accept the version/version
|
||||
// constraint as-is.
|
||||
let version = presence(core.getInput('version'));
|
||||
if (!version) {
|
||||
core.debug(`version was unset, defaulting to any version`);
|
||||
version = '> 0.0.0';
|
||||
}
|
||||
if (version === 'latest') {
|
||||
core.debug(`resolving latest version`);
|
||||
version = await bestVersion('> 0.0.0');
|
||||
core.debug(`resolved latest version to ${version}`);
|
||||
}
|
||||
|
||||
// Install the gcloud if not already present
|
||||
const toolPath = toolCache.find('gcloud', version);
|
||||
core.addPath(path.join(toolPath, 'bin'));
|
||||
if (toolPath !== '') {
|
||||
core.addPath(path.join(toolPath, 'bin'));
|
||||
} else {
|
||||
core.debug(`no version of gcloud matching "${version}" is installed`);
|
||||
await installGcloudSDK(version);
|
||||
}
|
||||
}
|
||||
|
||||
// Install additional components
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@ import * as sinon from 'sinon';
|
|||
|
||||
import { promises as fs } from 'fs';
|
||||
import * as setupGcloud from '@google-github-actions/setup-cloud-sdk';
|
||||
import { TestToolCache } from '@google-github-actions/setup-cloud-sdk';
|
||||
import * as core from '@actions/core';
|
||||
import * as toolCache from '@actions/tool-cache';
|
||||
import { clearEnv } from '@google-github-actions/actions-utils';
|
||||
|
||||
import { run } from '../src/main';
|
||||
|
||||
|
|
@ -44,84 +46,138 @@ describe('#run', function () {
|
|||
getInput: sinon.stub(core, 'getInput').callsFake(getInputMock),
|
||||
getBooleanInput: sinon.stub(core, 'getBooleanInput').returns(false),
|
||||
exportVariable: sinon.stub(core, 'exportVariable'),
|
||||
setFailed: sinon.stub(core, 'setFailed'),
|
||||
warning: sinon.stub(core, 'warning'),
|
||||
authenticateGcloudSDK: sinon.stub(setupGcloud, 'authenticateGcloudSDK'),
|
||||
installGcloudSDK: sinon.stub(setupGcloud, 'installGcloudSDK'),
|
||||
isInstalled: sinon.stub(setupGcloud, 'isInstalled').returns(false),
|
||||
setProject: sinon.stub(setupGcloud, 'setProject'),
|
||||
installComponent: sinon.stub(setupGcloud, 'installComponent'),
|
||||
toolCacheFind: sinon.stub(toolCache, 'find').returns('/'),
|
||||
writeFile: sinon.stub(fs, 'writeFile'),
|
||||
env: sinon.stub(process, 'env').value({ GITHUB_PATH: '/' }),
|
||||
};
|
||||
|
||||
process.env.GITHUB_PATH = '/';
|
||||
|
||||
sinon.stub(core, 'setFailed').throwsArg(0); // make setFailed throw exceptions
|
||||
sinon.stub(core, 'addPath').callsFake(sinon.fake());
|
||||
sinon.stub(core, 'debug').callsFake(sinon.fake());
|
||||
sinon.stub(core, 'endGroup').callsFake(sinon.fake());
|
||||
sinon.stub(core, 'info').callsFake(sinon.fake());
|
||||
sinon.stub(core, 'startGroup').callsFake(sinon.fake());
|
||||
sinon.stub(core, 'warning').callsFake(sinon.fake());
|
||||
|
||||
await TestToolCache.start();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
afterEach(async function () {
|
||||
Object.keys(this.stubs).forEach((k) => this.stubs[k].restore());
|
||||
sinon.restore();
|
||||
clearEnv((key: string) => key.startsWith(`GITHUB_`));
|
||||
|
||||
await TestToolCache.stop();
|
||||
});
|
||||
|
||||
it('downloads the latest gcloud SDK if version is not provided', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('');
|
||||
await run();
|
||||
// getLatestGcloudSDKVersion is implemented as a getter on on exports and so stubbing doesn't work.
|
||||
// Instead, make sure that installGcloudSDK was called with an expected version string.
|
||||
expect(this.stubs.installGcloudSDK.withArgs(sinon.match(/\d+\.\d+\.\d+/)).callCount).to.eq(1);
|
||||
describe('download', () => {
|
||||
it('downloads when no version is provided', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('');
|
||||
await run();
|
||||
|
||||
const call = this.stubs.installGcloudSDK.firstCall;
|
||||
expect(call.firstArg).to.match(/\d+\.\d+\.\d+/);
|
||||
});
|
||||
|
||||
it('downloads when version is "latest"', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('latest');
|
||||
await run();
|
||||
|
||||
const call = this.stubs.installGcloudSDK.firstCall;
|
||||
expect(call.firstArg).to.match(/\d+\.\d+\.\d+/);
|
||||
});
|
||||
|
||||
it('downloads when version is not installed', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('5.6.7');
|
||||
await run();
|
||||
|
||||
const call = this.stubs.installGcloudSDK.firstCall;
|
||||
expect(call.firstArg).to.eql('5.6.7');
|
||||
});
|
||||
|
||||
it('downloads when version constraint is not satisfied', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('>= 10.0.0');
|
||||
await run();
|
||||
|
||||
const call = this.stubs.installGcloudSDK.firstCall;
|
||||
expect(call.firstArg).to.eql('>= 10.0.0');
|
||||
});
|
||||
|
||||
it('downloads when a version is installed but the version constraint is not satisfied', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('>= 10.0.0');
|
||||
await installFakeGcloud('9.8.7');
|
||||
await run();
|
||||
|
||||
const call = this.stubs.installGcloudSDK.firstCall;
|
||||
expect(call.firstArg).to.eql('>= 10.0.0');
|
||||
});
|
||||
|
||||
it('does not download when version is installed', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('1.2.3');
|
||||
await installFakeGcloud('1.2.3');
|
||||
|
||||
await run();
|
||||
expect(this.stubs.installGcloudSDK.callCount).to.eq(0);
|
||||
});
|
||||
|
||||
it('does not download when a version constraint is satisfied', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('>= 0.0.0');
|
||||
await installFakeGcloud('1.2.3');
|
||||
|
||||
await run();
|
||||
expect(this.stubs.installGcloudSDK.callCount).to.eq(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('downloads the latest gcloud SDK if version is "latest"', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('latest');
|
||||
await run();
|
||||
expect(this.stubs.installGcloudSDK.withArgs(sinon.match(/\d+\.\d+\.\d+/)).callCount).to.eq(1);
|
||||
describe('component installation', () => {
|
||||
it('installs 1 additional component', async function () {
|
||||
this.stubs.getInput.withArgs('install_components').returns('beta');
|
||||
await run();
|
||||
|
||||
const call = this.stubs.installComponent.firstCall;
|
||||
expect(call.firstArg).to.eql(['beta']);
|
||||
});
|
||||
|
||||
it('installs additional components', async function () {
|
||||
this.stubs.getInput.withArgs('install_components').returns('beta, alpha');
|
||||
await run();
|
||||
|
||||
const call = this.stubs.installComponent.firstCall;
|
||||
expect(call.firstArg).to.eql(['beta', 'alpha']);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not download the SDK if version is provided', async function () {
|
||||
this.stubs.getInput.withArgs('version').returns('999');
|
||||
await run();
|
||||
expect(this.stubs.installGcloudSDK.withArgs('999').callCount).to.eq(1);
|
||||
describe('authentication', () => {
|
||||
it('authenticates if GOOGLE_GHA_CREDS is set', async function () {
|
||||
process.env.GOOGLE_GHA_CREDS_PATH = '/foo/bar/path.json';
|
||||
await run();
|
||||
expect(this.stubs.authenticateGcloudSDK.callCount).to.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('installs the gcloud SDK if it is not already installed', async function () {
|
||||
this.stubs.isInstalled.returns(false);
|
||||
this.stubs.getInput.withArgs('version').returns('888');
|
||||
await run();
|
||||
expect(this.stubs.installGcloudSDK.withArgs('888').callCount).to.eq(1);
|
||||
});
|
||||
describe('configuration', () => {
|
||||
it('sets the project ID if provided', async function () {
|
||||
this.stubs.getInput.withArgs('project_id').returns('test');
|
||||
await run();
|
||||
expect(this.stubs.setProject.withArgs('test').callCount).to.eq(1);
|
||||
});
|
||||
|
||||
it('uses the cached gcloud SDK if it was already installed', async function () {
|
||||
this.stubs.isInstalled.returns(true);
|
||||
this.stubs.getInput.withArgs('version').returns('777');
|
||||
await run();
|
||||
expect(this.stubs.toolCacheFind.withArgs('gcloud', '777').callCount).to.eq(1);
|
||||
});
|
||||
|
||||
it('installs 1 additional component', async function () {
|
||||
this.stubs.getInput.withArgs('install_components').returns('beta');
|
||||
await run();
|
||||
expect(this.stubs.installComponent.callCount).to.eq(1);
|
||||
});
|
||||
|
||||
it('installs additional components', async function () {
|
||||
this.stubs.getInput.withArgs('install_components').returns('beta, alpha');
|
||||
await run();
|
||||
expect(this.stubs.installComponent.callCount).to.eq(1);
|
||||
});
|
||||
|
||||
it('authenticates if GOOGLE_GHA_CREDS is set', async function () {
|
||||
this.stubs.env.value({ GOOGLE_GHA_CREDS_PATH: 'foo/bar/path.json' }).returns('{}');
|
||||
await run();
|
||||
expect(this.stubs.authenticateGcloudSDK.callCount).to.eq(1);
|
||||
});
|
||||
|
||||
it('sets the project ID if provided', async function () {
|
||||
this.stubs.getInput.withArgs('project_id').returns('test');
|
||||
await run();
|
||||
expect(this.stubs.setProject.withArgs('test').callCount).to.eq(1);
|
||||
});
|
||||
|
||||
it('does not set the project ID if not provided', async function () {
|
||||
this.stubs.getInput.withArgs('project_id').returns('');
|
||||
await run();
|
||||
expect(this.stubs.setProject.callCount).to.eq(0);
|
||||
it('does not set the project ID if not provided', async function () {
|
||||
this.stubs.getInput.withArgs('project_id').returns('');
|
||||
await run();
|
||||
expect(this.stubs.setProject.callCount).to.eq(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* installFakeGcloud puts a fake gcloud version into the temporary toolcache.
|
||||
* It's not actually gcloud and not actually executable.
|
||||
*/
|
||||
async function installFakeGcloud(version: string): Promise<string> {
|
||||
return await toolCache.cacheFile('action.yml', 'action.yml', 'gcloud', version);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue