Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: emit a warning when the workspace is empty #117

Merged
merged 1 commit into from Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -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

Expand Down
50 changes: 48 additions & 2 deletions dist/main/index.js
Expand Up @@ -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}"`);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;


/***/ }),
Expand Down
21 changes: 20 additions & 1 deletion src/main.ts
Expand Up @@ -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 ` +
Expand Down Expand Up @@ -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}"`);
Expand Down
19 changes: 19 additions & 0 deletions 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
Expand Down Expand Up @@ -35,3 +37,20 @@ export function buildDomainWideDelegationJWT(

return JSON.stringify(body);
}

/**
sethvargo marked this conversation as resolved.
Show resolved Hide resolved
* 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;
}
}
26 changes: 25 additions & 1 deletion tests/utils.test.ts
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
});
});
});
});