diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b79caef..607f122 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,18 @@ name: "Build" -on: +on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 - run: | npm i npm run all + - name: Upload coverage reports to Codecov + if: always() + uses: codecov/codecov-action@v3 diff --git a/.gitignore b/.gitignore index b1bead8..8cf4e42 100644 --- a/.gitignore +++ b/.gitignore @@ -97,4 +97,7 @@ Thumbs.db # Ignore built ts files __tests__/runner/* -lib/**/* \ No newline at end of file +lib/**/* + +# Ignore IntelliJ files +.idea diff --git a/README.md b/README.md index 3db55ec..1c060d0 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,10 @@ If this value is set to the name of a valid environment in the target repositori Target where secrets should be stored: `actions` (default), `codespaces` or `dependabot`. +### `new_secret_prefix` + +If this value is set, the action will prefix the name of the secret with the provided value. This is useful when you want to use the same secret name in multiple repositories but want to avoid conflicts. + ## Usage ```yaml diff --git a/__tests__/config.test.ts b/__tests__/config.test.ts index f688ce8..685b7b7 100644 --- a/__tests__/config.test.ts +++ b/__tests__/config.test.ts @@ -37,6 +37,7 @@ describe("getConfig", () => { const RUN_DELETE = false; const ENVIRONMENT = "production"; const TARGET = "actions"; + const NEW_SECRET_PREFIX = "PREFIX_"; // Must implement because operands for delete must be optional in typescript >= 4.0 interface Inputs { @@ -51,6 +52,7 @@ describe("getConfig", () => { INPUT_RUN_DELETE: string; INPUT_ENVIRONMENT: string; INPUT_TARGET: string; + INPUT_NEW_SECRET_PREFIX: string; } const inputs: Inputs = { INPUT_GITHUB_API_URL: String(GITHUB_API_URL), @@ -64,6 +66,7 @@ describe("getConfig", () => { INPUT_RUN_DELETE: String(RUN_DELETE), INPUT_ENVIRONMENT: String(ENVIRONMENT), INPUT_TARGET: String(TARGET), + INPUT_NEW_SECRET_PREFIX: String(NEW_SECRET_PREFIX), }; beforeEach(() => { @@ -93,6 +96,7 @@ describe("getConfig", () => { RUN_DELETE, ENVIRONMENT, TARGET, + NEW_SECRET_PREFIX, }); }); diff --git a/__tests__/github.test.ts b/__tests__/github.test.ts index 44dee31..8e8e15d 100644 --- a/__tests__/github.test.ts +++ b/__tests__/github.test.ts @@ -18,12 +18,12 @@ import * as config from "../src/config"; import { DefaultOctokit, + deleteSecretForRepo, filterReposByPatterns, - listAllMatchingRepos, getRepos, + listAllMatchingRepos, publicKeyCache, setSecretForRepo, - deleteSecretForRepo, } from "../src/github"; // @ts-ignore-next-line @@ -41,6 +41,7 @@ beforeAll(() => { REPOSITORIES_LIST_REGEX: true, DRY_RUN: false, RETRIES: 3, + NEW_SECRET_PREFIX: "", }); octokit = DefaultOctokit({ @@ -189,6 +190,7 @@ describe("setSecretForRepo", () => { secrets.FOO, repo, "", + "", true, "actions" ); @@ -202,6 +204,7 @@ describe("setSecretForRepo", () => { secrets.FOO, repo, "", + "", true, "dependabot" ); @@ -215,6 +218,7 @@ describe("setSecretForRepo", () => { secrets.FOO, repo, "", + "", true, "codespaces" ); @@ -228,6 +232,7 @@ describe("setSecretForRepo", () => { secrets.FOO, repo, "", + "", true, "actions" ); @@ -242,6 +247,7 @@ describe("setSecretForRepo", () => { secrets.FOO, repo, "", + "", false, "actions" ); @@ -255,6 +261,7 @@ describe("setSecretForRepo", () => { secrets.FOO, repo, "", + "", false, "dependabot" ); @@ -268,6 +275,7 @@ describe("setSecretForRepo", () => { secrets.FOO, repo, "", + "", false, "codespaces" ); @@ -320,6 +328,7 @@ describe("setSecretForRepo with environment", () => { secrets.FOO, repo, repoEnvironment, + "", true, "actions" ); @@ -333,6 +342,7 @@ describe("setSecretForRepo with environment", () => { secrets.FOO, repo, repoEnvironment, + "", true, "actions" ); @@ -347,6 +357,7 @@ describe("setSecretForRepo with environment", () => { secrets.FOO, repo, repoEnvironment, + "", true, "dependabot" ); @@ -361,6 +372,7 @@ describe("setSecretForRepo with environment", () => { secrets.FOO, repo, repoEnvironment, + "", false, "actions" ); @@ -401,6 +413,7 @@ describe("deleteSecretForRepo", () => { secrets.FOO, repo, "", + "", true, "actions" ); @@ -414,6 +427,7 @@ describe("deleteSecretForRepo", () => { secrets.FOO, repo, "", + "", false, "actions" ); @@ -427,6 +441,7 @@ describe("deleteSecretForRepo", () => { secrets.FOO, repo, "", + "", false, "dependabot" ); @@ -440,6 +455,7 @@ describe("deleteSecretForRepo", () => { secrets.FOO, repo, "", + "", false, "codespaces" ); @@ -473,6 +489,7 @@ describe("deleteSecretForRepo with environment", () => { secrets.FOO, repo, repoEnvironment, + "", true, "actions" ); @@ -486,6 +503,7 @@ describe("deleteSecretForRepo with environment", () => { secrets.FOO, repo, repoEnvironment, + "", true, "dependabot" ); @@ -499,6 +517,7 @@ describe("deleteSecretForRepo with environment", () => { secrets.FOO, repo, repoEnvironment, + "", false, "actions" ); diff --git a/action.yml b/action.yml index 56531e7..a3daaab 100644 --- a/action.yml +++ b/action.yml @@ -1,7 +1,7 @@ # action.yml name: "Secrets Sync Action" branding: - icon: 'copy' + icon: 'copy' color: 'red' description: "Copies secrets from the action's environment to many other repos." inputs: @@ -66,6 +66,11 @@ inputs: Target where secrets should be stored: `actions` (default), `codespaces` or `dependabot`. default: "actions" required: false + new_secret_prefix: + default: "" + description: | + If this value is set, the action will prefix the secret name with this value. + required: false runs: using: 'node20' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 4223fe2..e325a7d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -60,6 +60,7 @@ function getConfig() { RUN_DELETE: ["1", "true"].includes(core.getInput("DELETE", { required: false }).toLowerCase()), ENVIRONMENT: core.getInput("ENVIRONMENT", { required: false }), TARGET: core.getInput("TARGET", { required: false }), + NEW_SECRET_PREFIX: core.getInput("NEW_SECRET_PREFIX", { required: false }), }; if (config.DRY_RUN) { core.info("[DRY_RUN='true'] No changes will be written to secrets"); @@ -265,12 +266,13 @@ function getPublicKey(octokit, repo, environment, target) { }); } exports.getPublicKey = getPublicKey; -function setSecretForRepo(octokit, name, secret, repo, environment, dry_run, target) { +function setSecretForRepo(octokit, name, secret, repo, environment, new_secret_prefix, dry_run, target) { return __awaiter(this, void 0, void 0, function* () { const [repo_owner, repo_name] = repo.full_name.split("/"); const publicKey = yield getPublicKey(octokit, repo, environment, target); const encrypted_value = (0, utils_1.encrypt)(secret, publicKey.key); - core.info(`Set \`${name} = ***\` on ${repo.full_name}`); + const final_name = new_secret_prefix ? new_secret_prefix + name : name; + core.info(`Set \`${final_name} = ***\` on ${repo.full_name}`); if (!dry_run) { switch (target) { case "codespaces": @@ -314,9 +316,10 @@ function setSecretForRepo(octokit, name, secret, repo, environment, dry_run, tar }); } exports.setSecretForRepo = setSecretForRepo; -function deleteSecretForRepo(octokit, name, secret, repo, environment, dry_run, target) { +function deleteSecretForRepo(octokit, name, secret, repo, environment, new_secret_prefix, dry_run, target) { return __awaiter(this, void 0, void 0, function* () { - core.info(`Remove ${name} from ${repo.full_name}`); + const final_name = new_secret_prefix ? new_secret_prefix + name : name; + core.info(`Remove ${final_name} from ${repo.full_name}`); try { if (!dry_run) { const action = "DELETE"; @@ -513,6 +516,7 @@ function run() { FOUND_SECRETS: Object.keys(secrets), ENVIRONMENT: config.ENVIRONMENT, TARGET: config.TARGET, + NEW_SECRET_PREFIX: config.NEW_SECRET_PREFIX, }, null, 2)); const limit = (0, p_limit_1.default)(config.CONCURRENCY); const calls = []; @@ -521,7 +525,7 @@ function run() { const action = config.RUN_DELETE ? github_1.deleteSecretForRepo : github_1.setSecretForRepo; - calls.push(limit(() => action(octokit, k, secrets[k], repo, config.ENVIRONMENT, config.DRY_RUN, config.TARGET))); + calls.push(limit(() => action(octokit, k, secrets[k], repo, config.ENVIRONMENT, config.NEW_SECRET_PREFIX, config.DRY_RUN, config.TARGET))); } } yield Promise.all(calls); diff --git a/src/config.ts b/src/config.ts index a512d08..a366db7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -28,6 +28,7 @@ export interface Config { RUN_DELETE: boolean; ENVIRONMENT: string; TARGET: string; + NEW_SECRET_PREFIX: string; } export function getConfig(): Config { @@ -54,6 +55,7 @@ export function getConfig(): Config { ), ENVIRONMENT: core.getInput("ENVIRONMENT", { required: false }), TARGET: core.getInput("TARGET", { required: false }), + NEW_SECRET_PREFIX: core.getInput("NEW_SECRET_PREFIX", { required: false }), }; if (config.DRY_RUN) { diff --git a/src/github.ts b/src/github.ts index 11bf991..4f7b51d 100644 --- a/src/github.ts +++ b/src/github.ts @@ -231,6 +231,7 @@ export async function setSecretForRepo( secret: string, repo: Repository, environment: string, + new_secret_prefix: string, dry_run: boolean, target: string ): Promise { @@ -238,8 +239,9 @@ export async function setSecretForRepo( const publicKey = await getPublicKey(octokit, repo, environment, target); const encrypted_value = encrypt(secret, publicKey.key); + const final_name = new_secret_prefix ? new_secret_prefix + name : name; - core.info(`Set \`${name} = ***\` on ${repo.full_name}`); + core.info(`Set \`${final_name} = ***\` on ${repo.full_name}`); if (!dry_run) { switch (target) { @@ -288,10 +290,13 @@ export async function deleteSecretForRepo( secret: string, repo: Repository, environment: string, + new_secret_prefix: string, dry_run: boolean, target: string ): Promise { - core.info(`Remove ${name} from ${repo.full_name}`); + const final_name = new_secret_prefix ? new_secret_prefix + name : name; + + core.info(`Remove ${final_name} from ${repo.full_name}`); try { if (!dry_run) { diff --git a/src/main.ts b/src/main.ts index ba4dcea..b7c68ac 100644 --- a/src/main.ts +++ b/src/main.ts @@ -88,6 +88,7 @@ export async function run(): Promise { FOUND_SECRETS: Object.keys(secrets), ENVIRONMENT: config.ENVIRONMENT, TARGET: config.TARGET, + NEW_SECRET_PREFIX: config.NEW_SECRET_PREFIX, }, null, 2 @@ -110,6 +111,7 @@ export async function run(): Promise { secrets[k], repo, config.ENVIRONMENT, + config.NEW_SECRET_PREFIX, config.DRY_RUN, config.TARGET )