Skip to content

Commit

Permalink
fix: support export auth export with envvar for backwards compat (#426)
Browse files Browse the repository at this point in the history
* add export auth json test

* support export from envvar

* refactor export logic

* missing quotes

* fix stubs, add more tests

* add quotes

* unused dep

* project id precedence

* fix err

* add warning for future removal
  • Loading branch information
bharathkkb committed Dec 22, 2021
1 parent a9839f1 commit aaf4f27
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 15 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/setup-gcloud-it.yml
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,44 @@ jobs:
shell: 'bash'
run: |-
gcloud secrets versions access "latest" --secret "${{ secrets.OIDC_AUTH_TEST_SECRET_NAME }}"
credentials_json_exported:
if: ${{ github.event_name == 'push' || github.repository == github.event.pull_request.head.repo.full_name && github.actor != 'dependabot[bot]' }}
name: 'auth action with json key and setup-gcloud export'
runs-on: '${{ matrix.os }}'
strategy:
fail-fast: false
matrix:
os:
- 'ubuntu-latest'
- 'windows-latest'
- 'macos-latest'

steps:
- uses: 'actions/checkout@v2'

- uses: 'actions/setup-node@v2'
with:
node-version: '12.x'

- id: 'build'
name: 'Build dist'
run: 'npm ci && npm run build'

- uses: 'google-github-actions/auth@main'
with:
credentials_json: '${{ secrets.SETUP_GCLOUD_IT_KEY }}'

- name: 'setup-gcloud'
uses: './'
with:
export_default_credentials: true

- id: 'gcloud'
shell: 'bash'
run: |-
gcloud secrets versions access "latest" --secret "${{ secrets.OIDC_AUTH_TEST_SECRET_NAME }}"
multi_credentials:
if: ${{ github.event_name == 'push' || github.repository == github.event.pull_request.head.repo.full_name && github.actor != 'dependabot[bot]' }}
name: 'with two setup gcloud with different SA'
Expand Down
51 changes: 40 additions & 11 deletions src/setup-gcloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export async function run(): Promise<void> {
}

// Set the project ID, if given.
const projectId = core.getInput('project_id');
let projectId = core.getInput('project_id');
if (projectId) {
await setProject(projectId);
core.info('Successfully set default project');
Expand Down Expand Up @@ -99,18 +99,47 @@ export async function run(): Promise<void> {
const uniqueName = crypto.randomBytes(12).toString('hex');
credsPath = path.join(githubWorkspace, uniqueName);
}
let saProjectID = '';

const serviceAccountKeyObj = parseServiceAccountKey(serviceAccountKey);
await writeSecureFile(
credsPath,
JSON.stringify(serviceAccountKeyObj, null, 2), // Print to file as string w/ indents
);
// If explicit SA input, parse it, write to disk and set GCLOUD_PROJECT.
if (serviceAccountKey) {
const serviceAccountKeyObj = parseServiceAccountKey(serviceAccountKey);
await writeSecureFile(
credsPath,
JSON.stringify(serviceAccountKeyObj, null, 2), // Print to file as string w/ indents
);
saProjectID = serviceAccountKeyObj.project_id;
} else if (process.env.GOOGLE_GHA_CREDS_PATH) {
// No explicit SA input but process.env.GOOGLE_GHA_CREDS_PATH is set.
// process.env.GOOGLE_GHA_CREDS_PATH already contains credentials written to disk,
// so set credsPath to the existing cred filepath.
credsPath = process.env.GOOGLE_GHA_CREDS_PATH;
// User is likely using google-github-actions/auth for auth followed by setup-gcloud with export_default_credentials.
// This is unnecessary as auth already exports credentials.
core.warning(
'Credentials detected and possibly exported using google-github-actions/auth. ' +
'google-github-actions/auth exports credentials by default. ' +
'This will be an error in a future release.',
);
} else {
throw new Error('No credentials provided to export');
}

// If both explicit project id and sa key project id, warn user if they are different
if (projectId && saProjectID && saProjectID != projectId) {
core.warning(
`Service Account project id ${saProjectID} and` +
` input project_id ${projectId} differ. Input project_id ${projectId} will be exported.`,
);
} else if (!projectId && saProjectID) {
// no explicit project id, use sa key project id if set
projectId = saProjectID;
}
if (projectId) {
core.exportVariable('GCLOUD_PROJECT', projectId);
core.info(`Successfully exported GCLOUD_PROJECT ${projectId}`);
}

// If projectId is set export it, else export projectId from SA
core.exportVariable(
'GCLOUD_PROJECT',
projectId ? projectId : serviceAccountKeyObj.project_id,
);
core.exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credsPath);
core.exportVariable('GOOGLE_GHA_CREDS_PATH', credsPath);
core.info('Successfully exported Default Application Credentials');
Expand Down
126 changes: 122 additions & 4 deletions tests/setup-gcloud.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const fakeInputs: { [key: string]: string } = {
service_account_key: 'abc',
credentials_file_path: '/creds',
};
const fakeCreds = '{"json":"key","project_id":"foo"}';

function getInputMock(name: string): string {
return fakeInputs[name];
Expand All @@ -48,11 +49,14 @@ describe('#run', function () {
getBooleanInput: sinon.stub(core, 'getBooleanInput').returns(false),
exportVariable: sinon.stub(core, 'exportVariable'),
setFailed: sinon.stub(core, 'setFailed'),
warning: sinon.stub(core, 'warning'),
installGcloudSDK: sinon.stub(setupGcloud, 'installGcloudSDK'),
authenticateGcloudSDK: sinon.stub(setupGcloud, 'authenticateGcloudSDK'),
isInstalled: sinon.stub(setupGcloud, 'isInstalled').returns(false),
setProject: sinon.stub(setupGcloud, 'setProject'),
parseServiceAccountKey: sinon.stub(setupGcloud, 'parseServiceAccountKey'),
parseServiceAccountKey: sinon
.stub(setupGcloud, 'parseServiceAccountKey')
.returns(JSON.parse(fakeCreds)),
toolCacheFind: sinon.stub(toolCache, 'find').returns('/'),
writeFile: sinon.stub(fs, 'writeFile'),
env: sinon.stub(process, 'env').value({ GITHUB_PATH: '/' }),
Expand Down Expand Up @@ -138,9 +142,7 @@ describe('#run', function () {
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
.returns(true);
this.stubs.getInput
.withArgs('service_account_key')
.returns('{"json":"key"}');
this.stubs.getInput.withArgs('service_account_key').returns(fakeCreds);

await run();

Expand All @@ -156,6 +158,7 @@ describe('#run', function () {
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
.returns(true);
this.stubs.getInput.withArgs('service_account_key').returns(fakeCreds);
this.stubs.getInput.withArgs('credentials_file_path').returns('/usr/creds');

await run();
Expand All @@ -169,6 +172,121 @@ describe('#run', function () {
).to.eq(1);
});

it('writes service_account_key credentials input and ignores GOOGLE_GHA_CREDS_PATH if both are set', async function () {
this.stubs.env.value({ GOOGLE_GHA_CREDS_PATH: 'foo/bar/credpath' });
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
.returns(true);
this.stubs.getInput.withArgs('service_account_key').returns(fakeCreds);
this.stubs.getInput.withArgs('credentials_file_path').returns('/usr/creds');

await run();

expect(this.stubs.writeFile.withArgs('/usr/creds').callCount).to.eq(1);
expect(
this.stubs.exportVariable.withArgs(
'GOOGLE_APPLICATION_CREDENTIALS',
'/usr/creds',
).callCount,
).to.eq(1);
});

it('throws an error if export_default_credentials is set without credentials', async function () {
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
.returns(true);
this.stubs.getInput.withArgs('service_account_key').returns('');

await run();

expect(this.stubs.setFailed.callCount).to.eq(1);
expect(this.stubs.setFailed.args[0][0]).to.eq(
'google-github-actions/setup-gcloud failed with: No credentials provided to export',
);
});

it('warns if export_default_credentials is set with only GOOGLE_GHA_CREDS_PATH and no explicit SA input', async function () {
this.stubs.env.value({ GOOGLE_GHA_CREDS_PATH: 'foo/bar/credpath' });
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
.returns(true);
this.stubs.getInput.withArgs('service_account_key').returns('');

await run();

expect(this.stubs.warning.callCount).to.eq(1);
expect(this.stubs.warning.args[0][0]).to.contains(
'Credentials detected and possibly exported using google-github-actions/auth. ' +
'google-github-actions/auth exports credentials by default. ' +
'This will be an error in a future release.',
);
});

it('warns if input project_id is different from SA key project_id ', async function () {
const projectIDInput = 'baz';
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
.returns(true);
this.stubs.getInput.withArgs('service_account_key').returns(fakeCreds);
this.stubs.getInput.withArgs('project_id').returns(projectIDInput);

await run();

expect(this.stubs.warning.args[1][0]).to.contains(
`Service Account project id foo and input project_id ${projectIDInput} differ. ` +
`Input project_id ${projectIDInput} will be exported.`,
);
expect(
this.stubs.exportVariable.withArgs('GCLOUD_PROJECT', projectIDInput)
.callCount,
).to.eq(1);
});

it('exports project id input if no SA key', async function () {
this.stubs.env.value({ GOOGLE_GHA_CREDS_PATH: 'foo/bar/credpath' });
const projectIDInput = 'baz';
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
.returns(true);
this.stubs.getInput.withArgs('service_account_key').returns('');
this.stubs.getInput.withArgs('project_id').returns(projectIDInput);

await run();

expect(
this.stubs.exportVariable.withArgs('GCLOUD_PROJECT', projectIDInput)
.callCount,
).to.eq(1);
});

it('exports SA key project_id if no explicit project id input', async function () {
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
.returns(true);
this.stubs.getInput.withArgs('service_account_key').returns(fakeCreds);
this.stubs.getInput.withArgs('project_id').returns('');

await run();

expect(
this.stubs.exportVariable.withArgs('GCLOUD_PROJECT', 'foo').callCount,
).to.eq(1);
});

it('skips project id export if neither SA key project_id nor project id input', async function () {
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
.returns(true);
this.stubs.getInput.withArgs('service_account_key').returns('');
this.stubs.getInput.withArgs('project_id').returns('');

await run();

expect(
this.stubs.exportVariable.withArgs('GCLOUD_PROJECT').callCount,
).to.eq(0);
});

it('throws an error if credentials_file_path is not provided and GITHUB_WORKSPACE is not set', async function () {
this.stubs.getBooleanInput
.withArgs('export_default_credentials')
Expand Down

0 comments on commit aaf4f27

Please sign in to comment.