-
Notifications
You must be signed in to change notification settings - Fork 899
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
Improve function secrets ergonomics #4130
Merged
Merged
Changes from 19 commits
Commits
Show all changes
65 commits
Select commit
Hold shift + click to select a range
41696b3
Use runtime delegate to parse function triggers in the Functions Emul…
taeold ac72bf8
Slim down Functions Emulator Runtime (i.e. args sent over to emulated…
taeold dd8cef8
Merge remote-tracking branch 'origin/master' into cf3-secrets
taeold 8b1a590
Merge branch 'cf3-secrets' of https://github.com/firebase/firebase-to…
taeold 39f2ac7
CF3 Secrets Support (#3959)
taeold 3293186
Add new command (functions:secrets:set) for creating secrets to be us…
taeold 08f2236
Add functions:secrets:{access, destroy, get} commands. (#4026)
taeold 3310fdc
Add command to prune unused secrets (#4108)
taeold 5731022
Guard destroy command by checking to see if version is in use.
taeold ea194e1
Prune secrets on deploy, redeploy on new secret versions.
taeold ca8a09e
Update log messages.
taeold 87534f7
Use utility fn instead.
taeold 8144038
Only prune secrets on successful deploy.
taeold 3b45d53
Improve log messages.
taeold 7906346
Improve prune to find all non-destroyed secret versions.
taeold 1853e7f
Better types, better docs.
taeold 5bb029e
Eslints.
taeold bb51612
Add support for secrets in the Functions Emulator (#4106)
taeold 7e1d70d
Exit early if secret is not in use.
taeold b784fb5
Add test cases.
taeold 4c1dc1f
Make better code comments.
taeold c459b23
Fix bug where label wasn't included in the returned resource.
taeold 108beea
Merge branch 'master' of https://github.com/firebase/firebase-tools i…
taeold ef86cce
Merge branch 'cf3-secrets' of https://github.com/firebase/firebase-to…
taeold e799549
Remove preview flag, add option to disable dotenv support. (#4022)
taeold ad8be43
Reload all endpoints when pruning secrets post deploy.
taeold f271e69
Guard destroy command by checking to see if version is in use.
taeold 21ae5fb
Prune secrets on deploy, redeploy on new secret versions.
taeold c9e7a2e
Update log messages.
taeold 09670c1
Use utility fn instead.
taeold 704003e
Only prune secrets on successful deploy.
taeold 38d5b1a
Improve log messages.
taeold 91a358d
Improve prune to find all non-destroyed secret versions.
taeold 64bb335
Better types, better docs.
taeold 8cd70c6
Eslints.
taeold 3c6ddb4
Exit early if secret is not in use.
taeold a342ba3
Add test cases.
taeold f6a52d0
Make better code comments.
taeold 432159a
Fix bug where label wasn't included in the returned resource.
taeold 2ada4f3
Reload all endpoints when pruning secrets post deploy.
taeold fd16fb8
Merge branch 'dl-cf3-secret-cmds-ergonomics' of https://github.com/fi…
taeold fc0a888
Add -f option to prune.
taeold 31d0fe6
Only prune secrets if the deploy contained function w/ secrets.
taeold 6cd5c16
Merge branch 'master' of https://github.com/firebase/firebase-tools i…
taeold 31b9c9c
Nits.
taeold 2cfbcce
Add changelog.
taeold d0ca620
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold 207f202
Use === instead of ==.
taeold 50020b6
Remove unnecessary null check.
taeold 502af3d
Reuse existing haveEndpoints variable.
taeold a6cf8c7
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold f8a3b01
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold af43869
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold 2a66e7d
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold cf14e46
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold 7bf6214
Update CHANGELOG.md
taeold 09d93ae
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold 8f2f309
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold 28897a4
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold 74067b9
Prettier.
taeold 8c557c0
Merge branch 'master' into dl-cf3-secret-cmds-ergonomics
taeold 02fbd31
Merge conflict.
taeold 05068df
Fix merge gone wrong.
taeold 6abe03a
Pretty.
taeold d679d0d
Pretty.
taeold File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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,19 @@ | ||
import { Command } from "../command"; | ||
import { logger } from "../logger"; | ||
import { Options } from "../options"; | ||
import { needProjectId } from "../projectUtils"; | ||
import { accessSecretVersion } from "../gcp/secretManager"; | ||
|
||
export default new Command("functions:secrets:access <KEY>[@version]") | ||
.description( | ||
"Access secret value given secret and its version. Defaults to accessing the latest version." | ||
) | ||
.action(async (key: string, options: Options) => { | ||
const projectId = needProjectId(options); | ||
let [name, version] = key.split("@"); | ||
if (!version) { | ||
version = "latest"; | ||
} | ||
const value = await accessSecretVersion(projectId, name, version); | ||
logger.info(value); | ||
}); |
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,78 @@ | ||
import { Command } from "../command"; | ||
import { Options } from "../options"; | ||
import { needProjectId, needProjectNumber } from "../projectUtils"; | ||
import { | ||
deleteSecret, | ||
destroySecretVersion, | ||
getSecret, | ||
getSecretVersion, | ||
listSecretVersions, | ||
} from "../gcp/secretManager"; | ||
import { promptOnce } from "../prompt"; | ||
import { logBullet, logWarning } from "../utils"; | ||
import * as secrets from "../functions/secrets"; | ||
import * as backend from "../deploy/functions/backend"; | ||
import * as args from "../deploy/functions/args"; | ||
|
||
export default new Command("functions:secrets:destroy <KEY>[@version]") | ||
.description("Destroy a secret. Defaults to destroying the latest version.") | ||
.withForce("Destroys a secret without confirmation.") | ||
.action(async (key: string, options: Options) => { | ||
const projectId = needProjectId(options); | ||
const projectNumber = await needProjectNumber(options); | ||
const haveBackend = await backend.existingBackend({ projectId } as args.Context); | ||
|
||
let [name, version] = key.split("@"); | ||
if (!version) { | ||
version = "latest"; | ||
} | ||
const sv = await getSecretVersion(projectId, name, version); | ||
|
||
if (sv.state === "DESTROYED") { | ||
logBullet(`Secret ${sv.secret.name}@${version} is already destroyed. Nothing to do.`); | ||
return; | ||
} | ||
|
||
const boundEndpoints = backend | ||
.allEndpoints(haveBackend) | ||
.filter((e) => secrets.inUse({ projectId, projectNumber }, sv.secret, e)); | ||
if (boundEndpoints.length > 0) { | ||
const endpointsMsg = boundEndpoints | ||
.map((e) => `${e.id}[${e.platform}](${e.region})`) | ||
.join("\t\n"); | ||
logWarning( | ||
`Secret ${name}@${version} is currently in use by following functions:\n\t${endpointsMsg}` | ||
); | ||
if (!options.force) { | ||
logWarning("Refusing to destroy secret in use. Use -f to destroy the secret anyway."); | ||
return; | ||
} | ||
} | ||
|
||
if (!options.force) { | ||
const confirm = await promptOnce( | ||
{ | ||
name: "destroy", | ||
type: "confirm", | ||
default: true, | ||
message: `Are you sure you want to destroy ${sv.secret.name}@${sv.versionId}`, | ||
}, | ||
options | ||
); | ||
if (!confirm) { | ||
return; | ||
} | ||
} | ||
await destroySecretVersion(projectId, name, version); | ||
logBullet(`Destroyed secret version ${name}@${sv.versionId}`); | ||
|
||
const secret = await getSecret(projectId, name); | ||
if (secrets.isFirebaseManaged(secret)) { | ||
const versions = await listSecretVersions(projectId, name); | ||
if (versions.filter((v) => v.state === "ENABLED").length === 0) { | ||
logBullet(`No active secret versions left. Destroying secret ${name}`); | ||
// No active secret version. Remove secret resource. | ||
await deleteSecret(projectId, name); | ||
} | ||
} | ||
}); |
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,23 @@ | ||
import Table = require("cli-table"); | ||
|
||
import { Command } from "../command"; | ||
import { logger } from "../logger"; | ||
import { Options } from "../options"; | ||
import { needProjectId } from "../projectUtils"; | ||
import { listSecretVersions } from "../gcp/secretManager"; | ||
|
||
export default new Command("functions:secrets:get <KEY>") | ||
.description("Get metadata for secret and its versions") | ||
.action(async (key: string, options: Options) => { | ||
const projectId = needProjectId(options); | ||
const versions = await listSecretVersions(projectId, key); | ||
|
||
const table = new Table({ | ||
head: ["Version", "State"], | ||
style: { head: ["yellow"] }, | ||
}); | ||
for (const version of versions) { | ||
table.push([version.versionId, version.state]); | ||
} | ||
logger.info(table.toString()); | ||
}); |
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,68 @@ | ||
import * as args from "../deploy/functions/args"; | ||
import * as backend from "../deploy/functions/backend"; | ||
import { Command } from "../command"; | ||
import { Options } from "../options"; | ||
import { needProjectId, needProjectNumber } from "../projectUtils"; | ||
import { pruneSecrets } from "../functions/secrets"; | ||
import { requirePermissions } from "../requirePermissions"; | ||
import { isFirebaseManaged } from "../deploymentTool"; | ||
import { logBullet, logSuccess } from "../utils"; | ||
import { promptOnce } from "../prompt"; | ||
import { destroySecretVersion } from "../gcp/secretManager"; | ||
|
||
export default new Command("functions:secrets:prune") | ||
.description("Destroys unused secrets") | ||
.before(requirePermissions, [ | ||
"cloudfunctions.functions.list", | ||
"secretmanager.secrets.list", | ||
"secretmanager.versions.list", | ||
"secretmanager.versions.destroy", | ||
]) | ||
.action(async (options: Options) => { | ||
const projectNumber = await needProjectNumber(options); | ||
const projectId = needProjectId(options); | ||
|
||
logBullet("Loading secrets..."); | ||
|
||
const haveBackend = await backend.existingBackend({ projectId } as args.Context); | ||
const haveEndpoints = backend | ||
.allEndpoints(haveBackend) | ||
.filter((e) => isFirebaseManaged(e.labels || [])); | ||
|
||
const pruned = await pruneSecrets({ projectNumber, projectId }, haveEndpoints); | ||
|
||
if (pruned.length === 0) { | ||
logBullet("All secrets are in use. Nothing to prune today."); | ||
return; | ||
} | ||
|
||
// prompt to get them all deleted | ||
logBullet( | ||
`Found ${pruned.length} unused active secret versions:\n\t` + | ||
pruned.map((sv) => `${sv.secret}@${sv.version}`).join("\n\t") | ||
); | ||
|
||
const confirm = await promptOnce( | ||
{ | ||
name: "destroy", | ||
type: "confirm", | ||
default: true, | ||
message: `Do you want to destroy unused secret versions?`, | ||
}, | ||
options | ||
); | ||
|
||
if (!confirm) { | ||
logBullet( | ||
"Run the following commands to destroy each unused secret version:\n\t" + | ||
pruned | ||
.map((sv) => `firebase functions:secrets:destroy ${sv.secret}@${sv.version}`) | ||
.join("\n\t") | ||
); | ||
return; | ||
} | ||
|
||
await Promise.all(pruned.map((sv) => destroySecretVersion(projectId, sv.secret, sv.version))); | ||
|
||
logSuccess("Destroyed all unused secrets!"); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A function can only be gcf v1 or v2, so we can probably drop this disambiguation. It'll also help us avoid leaking gcfv2's existence/support when we release secrets support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, old comment. Disregard.