generated from salesforcecli/plugin-template-sf
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: repo configuration and secrets check
- Loading branch information
Showing
8 changed files
with
269 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# summary | ||
|
||
Ensures a repo has correct access to secrets based on its workflows | ||
|
||
# description | ||
|
||
Inspects a repo's yaml files and verifies that secrets required are available for the repo (either set at the repo level or shared via organization-level secrets). | ||
|
||
This command requires scope:admin permissions to inspect the org secrets and admin access to the repo to inspect the repo secrets | ||
|
||
# flags.repository.summary | ||
|
||
The github owner/repo | ||
|
||
# flags.dryRun.summary | ||
|
||
Make no changes | ||
|
||
# examples | ||
|
||
- <%= config.bin %> <%= command.id %> -r salesforcecli/testPackageRelease |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule plugin-env
added at
acfc63
Submodule sfdx-core
added at
3ee7b2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
//// secret stuff--there's not a good way to ask, "what organizational secrets are shared with this repo?" | ||
// has secrets for dependabot | ||
// has secrets for npm publish | ||
// if sign, has aws secrets | ||
// if nuts, has nuts auth secrets | ||
|
||
/* | ||
* Copyright (c) 2022, salesforce.com, inc. | ||
* All rights reserved. | ||
* Licensed under the BSD 3-Clause license. | ||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
|
||
import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; | ||
import { Messages } from '@salesforce/core'; | ||
|
||
import { Octokit } from 'octokit'; | ||
import { exec } from 'shelljs'; | ||
import * as yaml from 'js-yaml'; | ||
import * as fs from 'fs'; | ||
|
||
Messages.importMessagesDirectory(__dirname); | ||
const messages = Messages.load('@salesforce/plugin-dev', 'configure.secrets', [ | ||
'summary', | ||
'description', | ||
'examples', | ||
'flags.repository.summary', | ||
'flags.dryRun.summary', | ||
]); | ||
|
||
type SecretClassification = | ||
| 'not needed' | ||
| 'overridden by repo' | ||
| 'shared to repo by org' | ||
| 'exists, but not shared with repo' | ||
| 'does not exist in org' | ||
| 'unable to check'; | ||
|
||
type GhaJob = { | ||
uses: string; | ||
with: { | ||
ctc: boolean; | ||
sign: boolean; | ||
}; | ||
}; | ||
export type SecretsResult = { | ||
AWS_ACCESS_KEY_ID: SecretClassification; | ||
AWS_SECRET_ACCESS_KEY: SecretClassification; | ||
NPM_TOKEN: SecretClassification; | ||
TESTKIT_AUTH_URL: SecretClassification; | ||
SF_CLI_BOT_GITHUB_TOKEN: SecretClassification; | ||
SF_CHANGE_CASE_SFDX_AUTH_URL: SecretClassification; | ||
}; | ||
|
||
export default class ConfigureSecrets extends SfCommand<SecretsResult> { | ||
public static summary = messages.getMessage('summary'); | ||
public static description = messages.getMessage('description'); | ||
public static examples = messages.getMessages('examples'); | ||
|
||
public static flags = { | ||
repository: Flags.string({ | ||
summary: messages.getMessage('flags.repository.summary'), | ||
char: 'r', | ||
required: true, | ||
}), | ||
'dry-run': Flags.boolean({ | ||
summary: messages.getMessage('flags.dryRun.summary'), | ||
char: 'c', | ||
}), | ||
}; | ||
|
||
public async run(): Promise<SecretsResult> { | ||
const { flags } = await this.parse(ConfigureSecrets); | ||
const output: SecretsResult = { | ||
AWS_ACCESS_KEY_ID: 'not needed', | ||
AWS_SECRET_ACCESS_KEY: 'not needed', | ||
NPM_TOKEN: 'not needed', | ||
TESTKIT_AUTH_URL: 'not needed', | ||
SF_CLI_BOT_GITHUB_TOKEN: 'not needed', | ||
SF_CHANGE_CASE_SFDX_AUTH_URL: 'not needed', | ||
}; | ||
|
||
// TODO: nice error if no token exists | ||
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); | ||
|
||
const repoBase = { | ||
owner: flags.repository.split('/')[0], | ||
repo: flags.repository.split('/')[1], | ||
}; | ||
|
||
// clone repo so we can review its yaml | ||
exec(`git clone https://github.com/${flags.repository}`); | ||
|
||
// part 1: what secrets does this repo need? | ||
const publish = yaml.load( | ||
await fs.promises.readFile(`${repoBase.repo}/.github/workflows/onRelease.yml`, 'utf8') | ||
) as { | ||
jobs: { | ||
[key: string]: GhaJob; | ||
}; | ||
}; | ||
|
||
const publishJob = Object.values(publish.jobs).find((job) => | ||
job.uses.includes('salesforcecli/github-workflows/.github/workflows/npmPublish') | ||
); | ||
|
||
if (!publishJob) { | ||
return output; | ||
} else { | ||
// NPM: if uses npm publish | ||
output.NPM_TOKEN = await secretCheck(octokit, repoBase, 'NPM_TOKEN'); | ||
|
||
// AWS: if sign:true on publish | ||
if (publishJob.with.sign) { | ||
output.AWS_ACCESS_KEY_ID = await secretCheck(octokit, repoBase, 'AWS_ACCESS_KEY_ID'); | ||
output.AWS_SECRET_ACCESS_KEY = await secretCheck(octokit, repoBase, 'AWS_SECRET_ACCESS_KEY'); | ||
} | ||
|
||
if (publishJob.with.ctc) { | ||
output.SF_CHANGE_CASE_SFDX_AUTH_URL = await secretCheck(octokit, repoBase, 'SF_CHANGE_CASE_SFDX_AUTH_URL'); | ||
} | ||
} | ||
|
||
const release = yaml.load( | ||
await fs.promises.readFile(`${repoBase.repo}/.github/workflows/onPushToMain.yml`, 'utf8') | ||
) as { | ||
jobs: { | ||
[key: string]: GhaJob; | ||
}; | ||
}; | ||
|
||
const releaseJob = Object.values(release.jobs).find((job) => | ||
job.uses.includes('salesforcecli/github-workflows/.github/workflows/githubRelease') | ||
); | ||
|
||
if (releaseJob) { | ||
output.SF_CLI_BOT_GITHUB_TOKEN = await secretCheck(octokit, repoBase, 'SF_CLI_BOT_GITHUB_TOKEN'); | ||
} | ||
|
||
const test = yaml.load(await fs.promises.readFile(`${repoBase.repo}/.github/workflows/test.yml`, 'utf8')) as { | ||
jobs: { | ||
[key: string]: GhaJob; | ||
}; | ||
}; | ||
const testJob = Object.values(test.jobs).find( | ||
(job) => | ||
job.uses.includes('salesforcecli/github-workflows/.github/workflows/nut') || | ||
job.uses.includes('salesforcecli/github-workflows/.github/workflows/externalNut') | ||
); | ||
if (testJob) { | ||
output.TESTKIT_AUTH_URL = await secretCheck(octokit, repoBase, 'TESTKIT_AUTH_URL'); | ||
} | ||
|
||
this.styledJSON(output); | ||
|
||
exec(`rm -rf ${repoBase.repo}`); | ||
return output; | ||
} | ||
} | ||
|
||
const secretCheck = async ( | ||
octokit: Octokit, | ||
repoBase: { owner: string; repo: string }, | ||
secretName: string | ||
): Promise<SecretClassification> => { | ||
// is it overridden locally? | ||
try { | ||
const { data: localSecret } = await octokit.rest.actions.getRepoSecret({ | ||
...repoBase, | ||
secret_name: secretName, | ||
}); | ||
|
||
if (localSecret) { | ||
return 'overridden by repo'; | ||
} | ||
} catch (e) { | ||
// if (e.response.data) { | ||
// console.log(`check repo secrets for ${secretName}: ${e.response.data.message}`); | ||
// } | ||
// secret doesn't exist locally, keep looking. | ||
} | ||
|
||
// is it in the org? | ||
try { | ||
const { data: secret } = await octokit.rest.actions.getOrgSecret({ | ||
org: repoBase.owner, | ||
secret_name: secretName, | ||
}); | ||
if (secret.visibility === 'all') { | ||
return 'shared to repo by org'; | ||
} | ||
|
||
const { data: repositoriesForSecret } = await octokit.rest.actions.listSelectedReposForOrgSecret({ | ||
org: repoBase.owner, | ||
secret_name: secretName, | ||
per_page: 100, | ||
}); | ||
|
||
if (repositoriesForSecret) { | ||
if (repositoriesForSecret.repositories.some((r) => r.name === repoBase.repo)) { | ||
// if so, is it shared with this repo? | ||
return 'shared to repo by org'; | ||
} else { | ||
return 'exists, but not shared with repo'; | ||
} | ||
} | ||
} catch (e) { | ||
if (e.response.data) { | ||
console.log(`check org secrets for ${secretName}: ${e.response.data.message}`); | ||
} | ||
return 'does not exist in org'; | ||
} | ||
|
||
return 'does not exist in org'; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1622,6 +1622,11 @@ | |
dependencies: | ||
"@types/through" "*" | ||
|
||
"@types/js-yaml@^4.0.5": | ||
version "4.0.5" | ||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" | ||
integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== | ||
|
||
"@types/jsforce@^1.9.41": | ||
version "1.9.43" | ||
resolved "https://registry.yarnpkg.com/@types/jsforce/-/jsforce-1.9.43.tgz#407907aac838b1828133958ef326ce649d03138f" | ||
|
@@ -5207,7 +5212,7 @@ js-tokens@^4.0.0: | |
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" | ||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== | ||
|
||
[email protected]: | ||
[email protected], js-yaml@^4.1.0: | ||
version "4.1.0" | ||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" | ||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== | ||
|