Skip to content

Commit

Permalink
feat: add secret deletion (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
Plabick committed Jun 3, 2021
1 parent dd05465 commit 178c0ff
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 12 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ The number of allowed concurrent calls to the set secret endpoint. Lower this nu

Run everything except for secret create and update functionality.

### `delete`

When set to `true`, the action will find and delete the selected secrets from repositories. Defaults to `false`.

## Usage

```yaml
Expand Down
9 changes: 6 additions & 3 deletions __tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe("getConfig", () => {
const DRY_RUN = false;
const RETRIES = 3;
const CONCURRENCY = 50;
const RUN_DELETE = false;

const inputs = {
INPUT_GITHUB_TOKEN: GITHUB_TOKEN,
Expand All @@ -40,7 +41,8 @@ describe("getConfig", () => {
INPUT_REPOSITORIES_LIST_REGEX: String(REPOSITORIES_LIST_REGEX),
INPUT_DRY_RUN: String(DRY_RUN),
INPUT_RETRIES: String(RETRIES),
INPUT_CONCURRENCY: String(CONCURRENCY)
INPUT_CONCURRENCY: String(CONCURRENCY),
INPUT_RUN_DELETE: String(RUN_DELETE)
};

beforeEach(() => {
Expand All @@ -65,7 +67,8 @@ describe("getConfig", () => {
REPOSITORIES_LIST_REGEX,
DRY_RUN,
RETRIES,
CONCURRENCY
CONCURRENCY,
RUN_DELETE
});
});

Expand All @@ -85,7 +88,7 @@ describe("getConfig", () => {
["", false]
];

for (let [value, expected] of cases) {
for (const [value, expected] of cases) {
process.env["INPUT_DRY_RUN"] = value;
const actual = getConfig().DRY_RUN;
expect(`${value}=${actual}`).toEqual(`${value}=${expected}`);
Expand Down
29 changes: 28 additions & 1 deletion __tests__/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
filterReposByPatterns,
listAllMatchingRepos,
publicKeyCache,
setSecretForRepo
setSecretForRepo,
deleteSecretForRepo
} from "../src/github";

// @ts-ignore-next-line
Expand Down Expand Up @@ -137,3 +138,29 @@ describe("setSecretForRepo", () => {
expect(nock.isDone()).toBeTruthy();
});
});

describe("deleteSecretForRepo", () => {
const repo = fixture[0].response;

jest.setTimeout(30000);

const secrets = { FOO: "BAR" };
let deleteSecretMock: nock.Scope;

beforeEach(() => {
nock.cleanAll();
deleteSecretMock = nock("https://api.github.com")
.delete(`/repos/${repo.full_name}/actions/secrets/FOO`)
.reply(200);
});

test("deleteSecretForRepo should not delete secret with dry run", async () => {
await deleteSecretForRepo(octokit, "FOO", secrets.FOO, repo, true);
expect(deleteSecretMock.isDone()).toBeFalsy();
});

test("deleteSecretForRepo should call set secret endpoint", async () => {
await deleteSecretForRepo(octokit, "FOO", secrets.FOO, repo, false);
expect(nock.isDone()).toBeTruthy();
});
});
20 changes: 20 additions & 0 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,23 @@ test("run should succeed with a repo and secret with repository_list_regex as fa

expect(process.exitCode).toBe(undefined);
});

test("run should succeed with delete enabled, a repo and secret with repository_list_regex as false", async () => {
(github.deleteSecretForRepo as jest.Mock) = jest
.fn()
.mockImplementation(async () => null);

(config.getConfig as jest.Mock) = jest.fn().mockReturnValue({
GITHUB_TOKEN: "token",
SECRETS: ["BAZ"],
REPOSITORIES: [fixture[0].response.full_name],
REPOSITORIES_LIST_REGEX: false,
DRY_RUN: false,
RUN_DELETE: true,
CONCURRENCY: 1
});
await run();

expect(github.deleteSecretForRepo as jest.Mock).toBeCalledTimes(1);
expect(process.exitCode).toBe(undefined);
});
2 changes: 1 addition & 1 deletion __tests__/secrets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import * as core from "@actions/core";

import { getSecrets } from "../src/secrets";

let setSecretMock: jest.Mock = jest.fn();
const setSecretMock: jest.Mock = jest.fn();

beforeAll(() => {
// @ts-ignore-next-line
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ inputs:
number to avoid abuse limits.
default: "10"
required: false
delete:
description: |
When set to `true`, the action will find and delete the selected secrets from repositories. Defaults to `false`.
default: false
required: false
runs:
using: 'node12'
main: 'dist/index.js'
24 changes: 22 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2300,7 +2300,10 @@ function run() {
const calls = [];
for (const repo of repos) {
for (const k of Object.keys(secrets)) {
calls.push(limit(() => github_1.setSecretForRepo(octokit, k, secrets[k], repo, config.DRY_RUN)));
const action = config.RUN_DELETE
? github_1.deleteSecretForRepo
: github_1.setSecretForRepo;
calls.push(limit(() => action(octokit, k, secrets[k], repo, config.DRY_RUN)));
}
}
yield Promise.all(calls);
Expand Down Expand Up @@ -5728,7 +5731,8 @@ function getConfig() {
REPOSITORIES_LIST_REGEX: ["1", "true"].includes(core
.getInput("REPOSITORIES_LIST_REGEX", { required: false })
.toLowerCase()),
DRY_RUN: ["1", "true"].includes(core.getInput("DRY_RUN", { required: false }).toLowerCase())
DRY_RUN: ["1", "true"].includes(core.getInput("DRY_RUN", { required: false }).toLowerCase()),
RUN_DELETE: ["1", "true"].includes(core.getInput("DELETE", { required: false }).toLowerCase())
};
if (config.DRY_RUN) {
core.info("[DRY_RUN='true'] No changes will be written to secrets");
Expand Down Expand Up @@ -7744,6 +7748,22 @@ function setSecretForRepo(octokit, name, secret, repo, dry_run) {
});
}
exports.setSecretForRepo = setSecretForRepo;
function deleteSecretForRepo(octokit, name, secret, repo, dry_run) {
return __awaiter(this, void 0, void 0, function* () {
core.info(`Remove ${name} from ${repo.full_name}`);
try {
if (!dry_run) {
const action = "DELETE";
const request = `/repos/${repo.full_name}/actions/secrets/${name}`;
return octokit.request(`${action} ${request}`);
}
}
catch (HttpError) {
//If secret is not found in target repo, silently continue
}
});
}
exports.deleteSecretForRepo = deleteSecretForRepo;


/***/ }),
Expand Down
4 changes: 4 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface Config {
DRY_RUN: boolean;
RETRIES: number;
CONCURRENCY: number;
RUN_DELETE: boolean;
}

export function getConfig(): Config {
Expand All @@ -40,6 +41,9 @@ export function getConfig(): Config {
),
DRY_RUN: ["1", "true"].includes(
core.getInput("DRY_RUN", { required: false }).toLowerCase()
),
RUN_DELETE: ["1", "true"].includes(
core.getInput("DELETE", { required: false }).toLowerCase()
)
};

Expand Down
20 changes: 20 additions & 0 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,23 @@ export async function setSecretForRepo(
});
}
}

export async function deleteSecretForRepo(
octokit: any,
name: string,
secret: string,
repo: Repository,
dry_run: boolean
): Promise<void> {
core.info(`Remove ${name} from ${repo.full_name}`);

try {
if (!dry_run) {
const action = "DELETE";
const request = `/repos/${repo.full_name}/actions/secrets/${name}`;
return octokit.request(`${action} ${request}`);
}
} catch (HttpError) {
//If secret is not found in target repo, silently continue
}
}
12 changes: 7 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
DefaultOctokit,
Repository,
listAllMatchingRepos,
setSecretForRepo
setSecretForRepo,
deleteSecretForRepo
} from "./github";

import { getConfig } from "./config";
Expand Down Expand Up @@ -84,13 +85,14 @@ export async function run(): Promise<void> {

const limit = pLimit(config.CONCURRENCY);
const calls: Promise<void>[] = [];

for (const repo of repos) {
for (const k of Object.keys(secrets)) {
const action = config.RUN_DELETE
? deleteSecretForRepo
: setSecretForRepo;

calls.push(
limit(() =>
setSecretForRepo(octokit, k, secrets[k], repo, config.DRY_RUN)
)
limit(() => action(octokit, k, secrets[k], repo, config.DRY_RUN))
);
}
}
Expand Down

0 comments on commit 178c0ff

Please sign in to comment.