feat: emit a warning when the workspace is empty (#117)
There have been a number of GitHub issues recently due to users not adding actions/checkout before calling "auth", which makes the credentials unavailable to future steps. Worse, some people are putting checkout _after_ auth, which overwrites the generated credentials with a checkout of the repo. This adds a feature that emits a warning with the workspace is empty.
This commit is contained in:
parent
ac489d50bb
commit
983a037dfb
@ -32,6 +32,10 @@ and permissions on Google Cloud.
|
||||
configure a Google Cloud Workload Identity Provider. See [setup](#setup)
|
||||
for instructions.
|
||||
|
||||
- You must run the `actions/checkout@v2` step _before_ this action. Omitting
|
||||
the checkout step or putting it after `auth` will cause future steps to be
|
||||
unable to authenticate.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
|
50
dist/main/index.js
vendored
50
dist/main/index.js
vendored
@ -2198,6 +2198,22 @@ function run() {
|
||||
if (!githubWorkspace) {
|
||||
throw new Error('$GITHUB_WORKSPACE is not set');
|
||||
}
|
||||
// There have been a number of issues where users have not used the
|
||||
// "actions/checkout" step before our action. Our action relies on the
|
||||
// creation of that directory; worse, if a user puts "actions/checkout"
|
||||
// after our action, it will delete the exported credential. This
|
||||
// following code does a small check to see if there are any files in the
|
||||
// directory. It emits a warning if there are no files, since there may be
|
||||
// legitimate use cases for authenticating without checking out the
|
||||
// repository.
|
||||
const githubWorkspaceIsEmpty = yield (0, utils_1.isEmptyDir)(githubWorkspace);
|
||||
if (githubWorkspaceIsEmpty) {
|
||||
(0, core_1.warning)(`The "create_credentials_file" option is true, but the current ` +
|
||||
`GitHub workspace is empty. Did you forget to use ` +
|
||||
`"actions/checkout" before this step? If you do not intend to ` +
|
||||
`share authentication with future steps in this job, set ` +
|
||||
`"create_credentials_file" to false.`);
|
||||
}
|
||||
// Create credentials file.
|
||||
const credentialsPath = yield client.createCredentialsFile(githubWorkspace);
|
||||
(0, core_1.info)(`Created credentials file at "${credentialsPath}"`);
|
||||
@ -2292,12 +2308,22 @@ run();
|
||||
/***/ }),
|
||||
|
||||
/***/ 314:
|
||||
/***/ ((__unused_webpack_module, exports) => {
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.buildDomainWideDelegationJWT = void 0;
|
||||
exports.isEmptyDir = exports.buildDomainWideDelegationJWT = void 0;
|
||||
const fs_1 = __nccwpck_require__(147);
|
||||
/**
|
||||
* buildDomainWideDelegationJWT constructs an _unsigned_ JWT to be used for a
|
||||
* DWD exchange. The JWT must be signed and then exchanged with the OAuth
|
||||
@ -2327,6 +2353,26 @@ function buildDomainWideDelegationJWT(serviceAccount, subject, scopes, lifetime)
|
||||
return JSON.stringify(body);
|
||||
}
|
||||
exports.buildDomainWideDelegationJWT = buildDomainWideDelegationJWT;
|
||||
/**
|
||||
* isEmptyDir returns true if the given directory does not exist, or exists but
|
||||
* contains no files. It also returns true if the current user does not have
|
||||
* permission to read the directory, since it is effectively empty from the
|
||||
* viewpoint of the caller.
|
||||
*
|
||||
* @param dir Path to a directory.
|
||||
*/
|
||||
function isEmptyDir(dir) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const files = yield fs_1.promises.readdir(dir);
|
||||
return files.length <= 0;
|
||||
}
|
||||
catch (e) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.isEmptyDir = isEmptyDir;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
21
src/main.ts
21
src/main.ts
@ -25,7 +25,7 @@ import { WorkloadIdentityClient } from './client/workload_identity_client';
|
||||
import { CredentialsJSONClient } from './client/credentials_json_client';
|
||||
import { AuthClient } from './client/auth_client';
|
||||
import { BaseClient } from './base';
|
||||
import { buildDomainWideDelegationJWT } from './utils';
|
||||
import { buildDomainWideDelegationJWT, isEmptyDir } from './utils';
|
||||
|
||||
const secretsWarning =
|
||||
`If you are specifying input values via GitHub secrets, ensure the secret ` +
|
||||
@ -132,6 +132,25 @@ async function run(): Promise<void> {
|
||||
throw new Error('$GITHUB_WORKSPACE is not set');
|
||||
}
|
||||
|
||||
// There have been a number of issues where users have not used the
|
||||
// "actions/checkout" step before our action. Our action relies on the
|
||||
// creation of that directory; worse, if a user puts "actions/checkout"
|
||||
// after our action, it will delete the exported credential. This
|
||||
// following code does a small check to see if there are any files in the
|
||||
// directory. It emits a warning if there are no files, since there may be
|
||||
// legitimate use cases for authenticating without checking out the
|
||||
// repository.
|
||||
const githubWorkspaceIsEmpty = await isEmptyDir(githubWorkspace);
|
||||
if (githubWorkspaceIsEmpty) {
|
||||
logWarning(
|
||||
`The "create_credentials_file" option is true, but the current ` +
|
||||
`GitHub workspace is empty. Did you forget to use ` +
|
||||
`"actions/checkout" before this step? If you do not intend to ` +
|
||||
`share authentication with future steps in this job, set ` +
|
||||
`"create_credentials_file" to false.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Create credentials file.
|
||||
const credentialsPath = await client.createCredentialsFile(githubWorkspace);
|
||||
logInfo(`Created credentials file at "${credentialsPath}"`);
|
||||
|
19
src/utils.ts
19
src/utils.ts
@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
/**
|
||||
* buildDomainWideDelegationJWT constructs an _unsigned_ JWT to be used for a
|
||||
* DWD exchange. The JWT must be signed and then exchanged with the OAuth
|
||||
@ -35,3 +37,20 @@ export function buildDomainWideDelegationJWT(
|
||||
|
||||
return JSON.stringify(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* isEmptyDir returns true if the given directory does not exist, or exists but
|
||||
* contains no files. It also returns true if the current user does not have
|
||||
* permission to read the directory, since it is effectively empty from the
|
||||
* viewpoint of the caller.
|
||||
*
|
||||
* @param dir Path to a directory.
|
||||
*/
|
||||
export async function isEmptyDir(dir: string): Promise<boolean> {
|
||||
try {
|
||||
const files = await fs.readdir(dir);
|
||||
return files.length <= 0;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { buildDomainWideDelegationJWT } from '../src/utils';
|
||||
import { tmpdir } from 'os';
|
||||
|
||||
import { buildDomainWideDelegationJWT, isEmptyDir } from '../src/utils';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('#buildDomainWideDelegationJWT', () => {
|
||||
@ -54,4 +56,26 @@ describe('Utils', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isEmptyDir', async () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'non-existent dir',
|
||||
dir: '/this/path/definitely/does/not/exist',
|
||||
exp: true,
|
||||
},
|
||||
{
|
||||
name: 'exists',
|
||||
dir: tmpdir(),
|
||||
exp: false,
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, async () => {
|
||||
const isEmpty = await isEmptyDir(tc.dir);
|
||||
expect(isEmpty).to.eq(tc.exp);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user