firebase-tools 13.5.2 → 13.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/accountExporter.js +1 -1
- package/lib/accountImporter.js +1 -1
- package/lib/api.js +112 -58
- package/lib/apiv2.js +3 -1
- package/lib/appdistribution/client.js +4 -4
- package/lib/apphosting/config.js +31 -0
- package/lib/apphosting/githubConnections.js +261 -0
- package/lib/{init/features/apphosting → apphosting}/index.js +26 -22
- package/lib/{init/features/apphosting → apphosting}/repo.js +25 -13
- package/lib/apphosting/secrets/dialogs.js +169 -0
- package/lib/apphosting/secrets/index.js +98 -0
- package/lib/auth.js +17 -17
- package/lib/commands/apphosting-backends-create.js +4 -2
- package/lib/commands/apphosting-backends-delete.js +1 -1
- package/lib/commands/apphosting-secrets-describe.js +29 -0
- package/lib/commands/apphosting-secrets-grantaccess.js +45 -0
- package/lib/commands/apphosting-secrets-set.js +102 -0
- package/lib/commands/functions-secrets-access.js +2 -2
- package/lib/commands/functions-secrets-describe.js +14 -0
- package/lib/commands/functions-secrets-destroy.js +2 -2
- package/lib/commands/functions-secrets-get.js +3 -17
- package/lib/commands/functions-secrets-prune.js +2 -1
- package/lib/commands/functions-secrets-set.js +2 -2
- package/lib/commands/hosting-disable.js +1 -1
- package/lib/commands/index.js +5 -0
- package/lib/commands/open.js +1 -1
- package/lib/database/metadata.js +3 -3
- package/lib/defaultCredentials.js +2 -2
- package/lib/deploy/extensions/v2FunctionHelper.js +1 -1
- package/lib/deploy/functions/build.js +1 -1
- package/lib/deploy/functions/checkIam.js +3 -6
- package/lib/deploy/functions/containerCleaner.js +5 -15
- package/lib/deploy/functions/deploy.js +8 -2
- package/lib/deploy/functions/ensure.js +1 -1
- package/lib/deploy/functions/params.js +2 -2
- package/lib/deploy/functions/prepare.js +16 -7
- package/lib/deploy/functions/release/fabricator.js +8 -8
- package/lib/deploy/functions/runtimes/discovery/index.js +2 -2
- package/lib/deploy/functions/runtimes/index.js +6 -43
- package/lib/deploy/functions/runtimes/node/index.js +3 -2
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +15 -34
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +2 -2
- package/lib/deploy/functions/runtimes/python/index.js +11 -7
- package/lib/deploy/functions/runtimes/supported.js +135 -0
- package/lib/deploy/hosting/uploader.js +1 -1
- package/lib/deploy/index.js +1 -1
- package/lib/deploy/remoteconfig/functions.js +1 -1
- package/lib/emulator/adminSdkConfig.js +1 -1
- package/lib/emulator/controller.js +8 -1
- package/lib/emulator/downloadableEmulators.js +6 -6
- package/lib/emulator/functionsEmulator.js +2 -2
- package/lib/emulator/hub.js +5 -0
- package/lib/ensureApiEnabled.js +1 -1
- package/lib/extensions/emulator/specHelper.js +4 -3
- package/lib/extensions/extensionsApi.js +5 -5
- package/lib/extensions/extensionsHelper.js +4 -4
- package/lib/extensions/provisioningHelper.js +2 -2
- package/lib/extensions/publishHelpers.js +1 -1
- package/lib/extensions/publisherApi.js +3 -3
- package/lib/extensions/resolveSource.js +1 -1
- package/lib/extensions/secretsUtils.js +1 -1
- package/lib/extensions/tos.js +1 -1
- package/lib/fetchMOTD.js +1 -1
- package/lib/fetchWebSetup.js +2 -2
- package/lib/firestore/api-sort.js +17 -0
- package/lib/firestore/api.js +9 -2
- package/lib/firestore/checkDatabaseType.js +1 -1
- package/lib/firestore/delete.js +1 -1
- package/lib/firestore/pretty-print.js +11 -2
- package/lib/functional.js +2 -2
- package/lib/functions/secrets.js +42 -24
- package/lib/functionsConfig.js +1 -1
- package/lib/gcp/apphosting.js +17 -4
- package/lib/gcp/artifactregistry.js +1 -1
- package/lib/gcp/auth.js +1 -1
- package/lib/gcp/cloudbilling.js +1 -1
- package/lib/gcp/cloudbuild.js +8 -4
- package/lib/gcp/cloudfunctions.js +6 -6
- package/lib/gcp/cloudfunctionsv2.js +4 -4
- package/lib/gcp/cloudlogging.js +1 -1
- package/lib/gcp/cloudmonitoring.js +1 -1
- package/lib/gcp/cloudscheduler.js +3 -3
- package/lib/gcp/cloudtasks.js +1 -1
- package/lib/gcp/computeEngine.js +7 -0
- package/lib/gcp/devConnect.js +45 -22
- package/lib/gcp/eventarc.js +1 -1
- package/lib/gcp/firestore.js +2 -2
- package/lib/gcp/iam.js +11 -3
- package/lib/gcp/identityPlatform.js +1 -1
- package/lib/gcp/pubsub.js +1 -1
- package/lib/gcp/resourceManager.js +2 -1
- package/lib/gcp/rules.js +1 -1
- package/lib/gcp/run.js +1 -1
- package/lib/gcp/runtimeconfig.js +1 -1
- package/lib/gcp/secretManager.js +54 -14
- package/lib/gcp/serviceusage.js +21 -5
- package/lib/gcp/storage.js +12 -8
- package/lib/hosting/api.js +2 -2
- package/lib/hosting/cloudRunProxy.js +1 -1
- package/lib/init/features/database.js +1 -1
- package/lib/init/features/extensions/index.js +1 -1
- package/lib/init/features/functions/index.js +2 -2
- package/lib/init/features/functions/python.js +4 -3
- package/lib/init/features/hosting/github.js +4 -3
- package/lib/init/features/index.js +1 -1
- package/lib/management/apps.js +4 -4
- package/lib/management/database.js +1 -1
- package/lib/management/projects.js +4 -4
- package/lib/remoteconfig/get.js +1 -1
- package/lib/remoteconfig/rollback.js +1 -1
- package/lib/remoteconfig/versionslist.js +1 -1
- package/lib/shortenUrl.js +2 -2
- package/lib/utils.js +1 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +12 -2
- /package/lib/{init/features/apphosting → apphosting}/constants.js +0 -0
|
@@ -3,31 +3,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.orchestrateRollout = exports.setDefaultTrafficPolicy = exports.createBackend = exports.doSetup = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const repo = require("./repo");
|
|
6
|
-
const poller = require("
|
|
7
|
-
const apphosting = require("
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
6
|
+
const poller = require("../operation-poller");
|
|
7
|
+
const apphosting = require("../gcp/apphosting");
|
|
8
|
+
const githubConnections = require("./githubConnections");
|
|
9
|
+
const utils_1 = require("../utils");
|
|
10
|
+
const api_1 = require("../api");
|
|
11
|
+
const apphosting_1 = require("../gcp/apphosting");
|
|
12
|
+
const resourceManager_1 = require("../gcp/resourceManager");
|
|
13
|
+
const iam = require("../gcp/iam");
|
|
14
|
+
const error_1 = require("../error");
|
|
15
|
+
const prompt_1 = require("../prompt");
|
|
15
16
|
const constants_1 = require("./constants");
|
|
16
|
-
const ensureApiEnabled_1 = require("
|
|
17
|
-
const deploymentTool = require("
|
|
17
|
+
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
18
|
+
const deploymentTool = require("../deploymentTool");
|
|
18
19
|
const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
|
|
19
20
|
const apphostingPollerOptions = {
|
|
20
|
-
apiOrigin: api_1.apphostingOrigin,
|
|
21
|
+
apiOrigin: (0, api_1.apphostingOrigin)(),
|
|
21
22
|
apiVersion: apphosting_1.API_VERSION,
|
|
22
23
|
masterTimeout: 25 * 60 * 1000,
|
|
23
24
|
maxBackoff: 10000,
|
|
24
25
|
};
|
|
25
|
-
async function doSetup(projectId, location, serviceAccount) {
|
|
26
|
+
async function doSetup(projectId, location, serviceAccount, withDevConnect) {
|
|
26
27
|
await Promise.all([
|
|
27
|
-
(0, ensureApiEnabled_1.ensure)(projectId, api_1.
|
|
28
|
-
(0, ensureApiEnabled_1.ensure)(projectId, api_1.
|
|
29
|
-
(0, ensureApiEnabled_1.ensure)(projectId, api_1.
|
|
30
|
-
(0, ensureApiEnabled_1.ensure)(projectId, api_1.
|
|
28
|
+
...(withDevConnect ? [(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.developerConnectOrigin)(), "apphosting", true)] : []),
|
|
29
|
+
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudbuildOrigin)(), "apphosting", true),
|
|
30
|
+
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.secretManagerOrigin)(), "apphosting", true),
|
|
31
|
+
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudRunApiOrigin)(), "apphosting", true),
|
|
32
|
+
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.artifactRegistryDomain)(), "apphosting", true),
|
|
31
33
|
]);
|
|
32
34
|
const allowedLocations = (await apphosting.listLocations(projectId)).map((loc) => loc.locationId);
|
|
33
35
|
if (location) {
|
|
@@ -53,8 +55,10 @@ async function doSetup(projectId, location, serviceAccount) {
|
|
|
53
55
|
default: "my-web-app",
|
|
54
56
|
message: "Create a name for your backend [1-30 characters]",
|
|
55
57
|
});
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
+
const gitRepositoryConnection = withDevConnect
|
|
59
|
+
? await githubConnections.linkGitHubRepository(projectId, location)
|
|
60
|
+
: await repo.linkGitHubRepository(projectId, location);
|
|
61
|
+
const backend = await createBackend(projectId, location, backendId, gitRepositoryConnection, serviceAccount);
|
|
58
62
|
const branch = await (0, prompt_1.promptOnce)({
|
|
59
63
|
name: "branch",
|
|
60
64
|
type: "input",
|
|
@@ -111,9 +115,9 @@ async function createBackend(projectId, location, backendId, repository, service
|
|
|
111
115
|
rootDirectory: "/",
|
|
112
116
|
},
|
|
113
117
|
labels: deploymentTool.labels(),
|
|
114
|
-
|
|
118
|
+
serviceAccount: serviceAccount || defaultServiceAccount,
|
|
115
119
|
};
|
|
116
|
-
delete backendReqBody.
|
|
120
|
+
delete backendReqBody.serviceAccount;
|
|
117
121
|
async function createBackendAndPoll() {
|
|
118
122
|
const op = await apphosting.createBackend(projectId, location, backendReqBody, backendId);
|
|
119
123
|
return await poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-${backendId}`, operationResourceName: op.name }));
|
|
@@ -137,7 +141,7 @@ async function createBackend(projectId, location, backendId, repository, service
|
|
|
137
141
|
exports.createBackend = createBackend;
|
|
138
142
|
async function provisionDefaultComputeServiceAccount(projectId) {
|
|
139
143
|
try {
|
|
140
|
-
await
|
|
144
|
+
await iam.createServiceAccount(projectId, DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME, "Firebase App Hosting compute service account", "Default service account used to run builds and deploys for Firebase App Hosting");
|
|
141
145
|
}
|
|
142
146
|
catch (err) {
|
|
143
147
|
if (err.status !== 409) {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fetchAllRepositories = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.linkGitHubRepository = exports.parseConnectionName = void 0;
|
|
3
|
+
exports.fetchAllRepositories = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.getOrCreateOauthConnection = exports.linkGitHubRepository = exports.parseConnectionName = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
|
-
const gcb = require("
|
|
6
|
-
const rm = require("
|
|
7
|
-
const poller = require("
|
|
8
|
-
const utils = require("
|
|
9
|
-
const api_1 = require("
|
|
10
|
-
const error_1 = require("
|
|
11
|
-
const prompt_1 = require("
|
|
12
|
-
const getProjectNumber_1 = require("
|
|
5
|
+
const gcb = require("../gcp/cloudbuild");
|
|
6
|
+
const rm = require("../gcp/resourceManager");
|
|
7
|
+
const poller = require("../operation-poller");
|
|
8
|
+
const utils = require("../utils");
|
|
9
|
+
const api_1 = require("../api");
|
|
10
|
+
const error_1 = require("../error");
|
|
11
|
+
const prompt_1 = require("../prompt");
|
|
12
|
+
const getProjectNumber_1 = require("../getProjectNumber");
|
|
13
13
|
const fuzzy = require("fuzzy");
|
|
14
14
|
const inquirer = require("inquirer");
|
|
15
15
|
const APPHOSTING_CONN_PATTERN = /.+\/apphosting-github-conn-.+$/;
|
|
@@ -29,7 +29,7 @@ function parseConnectionName(name) {
|
|
|
29
29
|
}
|
|
30
30
|
exports.parseConnectionName = parseConnectionName;
|
|
31
31
|
const gcbPollerOptions = {
|
|
32
|
-
apiOrigin: api_1.cloudbuildOrigin,
|
|
32
|
+
apiOrigin: (0, api_1.cloudbuildOrigin)(),
|
|
33
33
|
apiVersion: "v2",
|
|
34
34
|
masterTimeout: 25 * 60 * 1000,
|
|
35
35
|
maxBackoff: 10000,
|
|
@@ -56,7 +56,6 @@ async function linkGitHubRepository(projectId, location) {
|
|
|
56
56
|
const oauthConn = await getOrCreateOauthConnection(projectId, location);
|
|
57
57
|
const existingConns = await listAppHostingConnections(projectId);
|
|
58
58
|
if (existingConns.length === 0) {
|
|
59
|
-
await ensureSecretManagerAdminGrant(projectId);
|
|
60
59
|
existingConns.push(await createFullyInstalledConnection(projectId, location, generateConnectionId(), oauthConn));
|
|
61
60
|
}
|
|
62
61
|
let repoRemoteUri;
|
|
@@ -99,7 +98,19 @@ async function createFullyInstalledConnection(projectId, location, connectionId,
|
|
|
99
98
|
return conn;
|
|
100
99
|
}
|
|
101
100
|
async function getOrCreateOauthConnection(projectId, location) {
|
|
102
|
-
let conn
|
|
101
|
+
let conn;
|
|
102
|
+
try {
|
|
103
|
+
conn = await gcb.getConnection(projectId, location, APPHOSTING_OAUTH_CONN_NAME);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if (err.status === 404) {
|
|
107
|
+
await ensureSecretManagerAdminGrant(projectId);
|
|
108
|
+
conn = await createConnection(projectId, location, APPHOSTING_OAUTH_CONN_NAME);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
103
114
|
while (conn.installationState.stage === "PENDING_USER_OAUTH") {
|
|
104
115
|
utils.logBullet("You must authorize the Cloud Build GitHub app.");
|
|
105
116
|
utils.logBullet("Sign in to GitHub and authorize Cloud Build GitHub app:");
|
|
@@ -115,6 +126,7 @@ async function getOrCreateOauthConnection(projectId, location) {
|
|
|
115
126
|
}
|
|
116
127
|
return conn;
|
|
117
128
|
}
|
|
129
|
+
exports.getOrCreateOauthConnection = getOrCreateOauthConnection;
|
|
118
130
|
async function promptRepositoryUri(projectId, connections) {
|
|
119
131
|
const { repos, remoteUriToConnection } = await fetchAllRepositories(projectId, connections);
|
|
120
132
|
const remoteUri = await (0, prompt_1.promptOnce)({
|
|
@@ -146,7 +158,7 @@ async function promptRepositoryUri(projectId, connections) {
|
|
|
146
158
|
}
|
|
147
159
|
async function ensureSecretManagerAdminGrant(projectId) {
|
|
148
160
|
const projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
|
|
149
|
-
const cbsaEmail = gcb.
|
|
161
|
+
const cbsaEmail = gcb.getDefaultServiceAgent(projectNumber);
|
|
150
162
|
const alreadyGranted = await rm.serviceAccountHasRoles(projectId, cbsaEmail, ["roles/secretmanager.admin"], true);
|
|
151
163
|
if (alreadyGranted) {
|
|
152
164
|
return;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.envVarForSecret = exports.selectBackendServiceAccounts = exports.GRANT_ACCESS_IN_FUTURE = exports.WARN_NO_BACKENDS = exports.selectFromMetadata = exports.tableForBackends = exports.serviceAccountDisplay = exports.toMetadata = void 0;
|
|
4
|
+
const clc = require("colorette");
|
|
5
|
+
const Table = require("cli-table");
|
|
6
|
+
const _1 = require(".");
|
|
7
|
+
const apphosting = require("../../gcp/apphosting");
|
|
8
|
+
const prompt = require("../../prompt");
|
|
9
|
+
const utils = require("../../utils");
|
|
10
|
+
const logger_1 = require("../../logger");
|
|
11
|
+
const env = require("../../functions/env");
|
|
12
|
+
function toMetadata(projectNumber, backends) {
|
|
13
|
+
const metadata = [];
|
|
14
|
+
for (const backend of backends) {
|
|
15
|
+
const [, , , location, , id] = backend.name.split("/");
|
|
16
|
+
metadata.push(Object.assign({ location, id }, (0, _1.serviceAccountsForBackend)(projectNumber, backend)));
|
|
17
|
+
}
|
|
18
|
+
return metadata.sort((left, right) => {
|
|
19
|
+
const cmplocation = left.location.localeCompare(right.location);
|
|
20
|
+
if (cmplocation) {
|
|
21
|
+
return cmplocation;
|
|
22
|
+
}
|
|
23
|
+
return left.id.localeCompare(right.id);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
exports.toMetadata = toMetadata;
|
|
27
|
+
function serviceAccountDisplay(metadata) {
|
|
28
|
+
if (sameServiceAccount(metadata)) {
|
|
29
|
+
return metadata.runServiceAccount;
|
|
30
|
+
}
|
|
31
|
+
return `${metadata.buildServiceAccount}, ${metadata.runServiceAccount}`;
|
|
32
|
+
}
|
|
33
|
+
exports.serviceAccountDisplay = serviceAccountDisplay;
|
|
34
|
+
function sameServiceAccount(metadata) {
|
|
35
|
+
return metadata.buildServiceAccount === metadata.runServiceAccount;
|
|
36
|
+
}
|
|
37
|
+
const matchesServiceAccounts = (target) => (test) => {
|
|
38
|
+
return (target.buildServiceAccount === test.buildServiceAccount &&
|
|
39
|
+
target.runServiceAccount === test.runServiceAccount);
|
|
40
|
+
};
|
|
41
|
+
function tableForBackends(metadata) {
|
|
42
|
+
const headers = [
|
|
43
|
+
"location",
|
|
44
|
+
"backend",
|
|
45
|
+
metadata.every(sameServiceAccount) ? "service account" : "service accounts",
|
|
46
|
+
];
|
|
47
|
+
const rows = metadata.map((m) => [m.location, m.id, serviceAccountDisplay(m)]);
|
|
48
|
+
return [headers, rows];
|
|
49
|
+
}
|
|
50
|
+
exports.tableForBackends = tableForBackends;
|
|
51
|
+
function selectFromMetadata(input, selected) {
|
|
52
|
+
const buildAccounts = new Set();
|
|
53
|
+
const runAccounts = new Set();
|
|
54
|
+
for (const sa of selected) {
|
|
55
|
+
if (input.find((m) => m.buildServiceAccount === sa)) {
|
|
56
|
+
buildAccounts.add(sa);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
runAccounts.add(sa);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
buildServiceAccounts: [...buildAccounts],
|
|
64
|
+
runServiceAccounts: [...runAccounts],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
exports.selectFromMetadata = selectFromMetadata;
|
|
68
|
+
exports.WARN_NO_BACKENDS = "To use this secret, your backend's service account must have secret accessor permission. " +
|
|
69
|
+
"It does not look like you have a backend yet. After creating a backend, grant access with " +
|
|
70
|
+
clc.bold("firebase apphosting:secrets:grantAccess");
|
|
71
|
+
exports.GRANT_ACCESS_IN_FUTURE = `To grant access in the future, run ${clc.bold("firebase apphosting:secrets:grantaccess")}`;
|
|
72
|
+
async function selectBackendServiceAccounts(projectNumber, projectId, options) {
|
|
73
|
+
const listBackends = await apphosting.listBackends(projectId, "-");
|
|
74
|
+
if (listBackends.unreachable.length) {
|
|
75
|
+
utils.logLabeledWarning("apphosting", `Could not reach location(s) ${listBackends.unreachable.join(", ")}. You may need to run ` +
|
|
76
|
+
`${clc.bold("firebase apphosting:secrets:grantAccess")} at a later time if you have backends in these locations`);
|
|
77
|
+
}
|
|
78
|
+
if (!listBackends.backends.length) {
|
|
79
|
+
utils.logLabeledWarning("apphosting", exports.WARN_NO_BACKENDS);
|
|
80
|
+
return { buildServiceAccounts: [], runServiceAccounts: [] };
|
|
81
|
+
}
|
|
82
|
+
if (listBackends.backends.length === 1) {
|
|
83
|
+
const grant = await prompt.confirm({
|
|
84
|
+
nonInteractive: options.nonInteractive,
|
|
85
|
+
default: true,
|
|
86
|
+
message: "To use this secret, your backend's service account must have secret accessor permission. Would you like to grant it now?",
|
|
87
|
+
});
|
|
88
|
+
if (grant) {
|
|
89
|
+
return (0, _1.toMulti)((0, _1.serviceAccountsForBackend)(projectNumber, listBackends.backends[0]));
|
|
90
|
+
}
|
|
91
|
+
utils.logLabeledBullet("apphosting", exports.GRANT_ACCESS_IN_FUTURE);
|
|
92
|
+
return { buildServiceAccounts: [], runServiceAccounts: [] };
|
|
93
|
+
}
|
|
94
|
+
const metadata = toMetadata(projectNumber, listBackends.backends);
|
|
95
|
+
if (metadata.every(matchesServiceAccounts(metadata[0]))) {
|
|
96
|
+
utils.logLabeledBullet("apphosting", "To use this secret, your backend's service account must have secret accessor permission. All of your backends use " +
|
|
97
|
+
(sameServiceAccount(metadata[0]) ? "service account " : "service accounts ") +
|
|
98
|
+
serviceAccountDisplay(metadata[0]) +
|
|
99
|
+
". Granting access to one backend will grant access to all backends.");
|
|
100
|
+
const grant = await prompt.confirm({
|
|
101
|
+
nonInteractive: options.nonInteractive,
|
|
102
|
+
default: true,
|
|
103
|
+
message: "Would you like to grant it now?",
|
|
104
|
+
});
|
|
105
|
+
if (grant) {
|
|
106
|
+
return selectFromMetadata(metadata, [
|
|
107
|
+
metadata[0].buildServiceAccount,
|
|
108
|
+
metadata[0].runServiceAccount,
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
utils.logLabeledBullet("apphosting", exports.GRANT_ACCESS_IN_FUTURE);
|
|
112
|
+
return { buildServiceAccounts: [], runServiceAccounts: [] };
|
|
113
|
+
}
|
|
114
|
+
utils.logLabeledBullet("apphosting", "To use this secret, your backend's service account must have secret accessor permission. Your backends use the following service accounts:");
|
|
115
|
+
const tableData = tableForBackends(metadata);
|
|
116
|
+
const table = new Table({
|
|
117
|
+
head: tableData[0],
|
|
118
|
+
style: { head: ["green"] },
|
|
119
|
+
rows: tableData[1],
|
|
120
|
+
});
|
|
121
|
+
logger_1.logger.info(table.toString());
|
|
122
|
+
const allAccounts = metadata.reduce((accum, row) => {
|
|
123
|
+
accum.add(row.buildServiceAccount);
|
|
124
|
+
accum.add(row.runServiceAccount);
|
|
125
|
+
return accum;
|
|
126
|
+
}, new Set());
|
|
127
|
+
const chosen = await prompt.promptOnce({
|
|
128
|
+
type: "checkbox",
|
|
129
|
+
message: "Which service accounts would you like to grant access? " +
|
|
130
|
+
"Press Space to select accounts, then Enter to confirm your choices.",
|
|
131
|
+
choices: [...allAccounts.values()].sort(),
|
|
132
|
+
});
|
|
133
|
+
if (!chosen.length) {
|
|
134
|
+
utils.logLabeledBullet("apphosting", exports.GRANT_ACCESS_IN_FUTURE);
|
|
135
|
+
}
|
|
136
|
+
return selectFromMetadata(metadata, chosen);
|
|
137
|
+
}
|
|
138
|
+
exports.selectBackendServiceAccounts = selectBackendServiceAccounts;
|
|
139
|
+
function toUpperSnakeCase(key) {
|
|
140
|
+
return key
|
|
141
|
+
.replace(/[.-]/g, "_")
|
|
142
|
+
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
143
|
+
.toUpperCase();
|
|
144
|
+
}
|
|
145
|
+
async function envVarForSecret(secret) {
|
|
146
|
+
const upper = toUpperSnakeCase(secret);
|
|
147
|
+
if (upper === secret) {
|
|
148
|
+
try {
|
|
149
|
+
env.validateKey(secret);
|
|
150
|
+
return secret;
|
|
151
|
+
}
|
|
152
|
+
catch (_a) {
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
do {
|
|
156
|
+
const test = await prompt.promptOnce({
|
|
157
|
+
message: "What environment variable name would you like to use?",
|
|
158
|
+
default: upper,
|
|
159
|
+
});
|
|
160
|
+
try {
|
|
161
|
+
env.validateKey(test);
|
|
162
|
+
return test;
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
utils.logLabeledError("apphosting", err.message);
|
|
166
|
+
}
|
|
167
|
+
} while (true);
|
|
168
|
+
}
|
|
169
|
+
exports.envVarForSecret = envVarForSecret;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.upsertSecret = exports.grantSecretAccess = exports.serviceAccountsForBackend = exports.toMulti = void 0;
|
|
4
|
+
const error_1 = require("../../error");
|
|
5
|
+
const gcsm = require("../../gcp/secretManager");
|
|
6
|
+
const gcb = require("../../gcp/cloudbuild");
|
|
7
|
+
const gce = require("../../gcp/computeEngine");
|
|
8
|
+
const secretManager_1 = require("../../gcp/secretManager");
|
|
9
|
+
const secretManager_2 = require("../../gcp/secretManager");
|
|
10
|
+
const utils = require("../../utils");
|
|
11
|
+
const prompt = require("../../prompt");
|
|
12
|
+
function toMulti(accounts) {
|
|
13
|
+
const m = {
|
|
14
|
+
buildServiceAccounts: [accounts.buildServiceAccount],
|
|
15
|
+
runServiceAccounts: [],
|
|
16
|
+
};
|
|
17
|
+
if (accounts.buildServiceAccount !== accounts.runServiceAccount) {
|
|
18
|
+
m.runServiceAccounts.push(accounts.runServiceAccount);
|
|
19
|
+
}
|
|
20
|
+
return m;
|
|
21
|
+
}
|
|
22
|
+
exports.toMulti = toMulti;
|
|
23
|
+
function serviceAccountsForBackend(projectNumber, backend) {
|
|
24
|
+
if (backend.serviceAccount) {
|
|
25
|
+
return {
|
|
26
|
+
buildServiceAccount: backend.serviceAccount,
|
|
27
|
+
runServiceAccount: backend.serviceAccount,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
buildServiceAccount: gcb.getDefaultServiceAccount(projectNumber),
|
|
32
|
+
runServiceAccount: gce.getDefaultServiceAccount(projectNumber),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
exports.serviceAccountsForBackend = serviceAccountsForBackend;
|
|
36
|
+
async function grantSecretAccess(projectId, secretName, accounts) {
|
|
37
|
+
const newBindings = [
|
|
38
|
+
{
|
|
39
|
+
role: "roles/secretmanager.secretAccessor",
|
|
40
|
+
members: [...accounts.buildServiceAccounts, ...accounts.runServiceAccounts].map((sa) => `serviceAccount:${sa}`),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
role: "roles/secretmanager.viewer",
|
|
44
|
+
members: accounts.buildServiceAccounts.map((sa) => `serviceAccount:${sa}`),
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
let existingBindings;
|
|
48
|
+
try {
|
|
49
|
+
existingBindings = (await gcsm.getIamPolicy({ projectId, name: secretName })).bindings || [];
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
throw new error_1.FirebaseError(`Failed to get IAM bindings on secret: ${secretName}. Ensure you have the permissions to do so and try again.`, { original: err });
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const updatedBindings = existingBindings.concat(newBindings);
|
|
56
|
+
await gcsm.setIamPolicy({ projectId, name: secretName }, updatedBindings);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
throw new error_1.FirebaseError(`Failed to set IAM bindings ${JSON.stringify(newBindings)} on secret: ${secretName}. Ensure you have the permissions to do so and try again.`, { original: err });
|
|
60
|
+
}
|
|
61
|
+
utils.logSuccess(`Successfully set IAM bindings on secret ${secretName}.\n`);
|
|
62
|
+
}
|
|
63
|
+
exports.grantSecretAccess = grantSecretAccess;
|
|
64
|
+
async function upsertSecret(project, secret, location) {
|
|
65
|
+
var _a, _b, _c, _d;
|
|
66
|
+
let existing;
|
|
67
|
+
try {
|
|
68
|
+
existing = await gcsm.getSecret(project, secret);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (err.status !== 404) {
|
|
72
|
+
throw new error_1.FirebaseError("Unexpected error loading secret", { original: err });
|
|
73
|
+
}
|
|
74
|
+
await gcsm.createSecret(project, secret, gcsm.labels("apphosting"), location);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
const replication = (_a = existing.replication) === null || _a === void 0 ? void 0 : _a.userManaged;
|
|
78
|
+
if (location &&
|
|
79
|
+
(((_b = replication === null || replication === void 0 ? void 0 : replication.replicas) === null || _b === void 0 ? void 0 : _b.length) !== 1 || ((_d = (_c = replication === null || replication === void 0 ? void 0 : replication.replicas) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.location) !== location)) {
|
|
80
|
+
utils.logLabeledError("apphosting", "Secret replication policies cannot be changed after creation");
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if ((0, secretManager_2.isFunctionsManaged)(existing)) {
|
|
84
|
+
utils.logLabeledWarning("apphosting", `Cloud Functions for Firebase currently manages versions of ${secret}. Continuing will disable ` +
|
|
85
|
+
"automatic deletion of old versions.");
|
|
86
|
+
const stopTracking = await prompt.confirm({
|
|
87
|
+
message: "Do you wish to continue?",
|
|
88
|
+
default: false,
|
|
89
|
+
});
|
|
90
|
+
if (!stopTracking) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
delete existing.labels[secretManager_1.FIREBASE_MANAGED];
|
|
94
|
+
await gcsm.patchSecret(project, secret, existing.labels);
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
exports.upsertSecret = upsertSecret;
|
package/lib/auth.js
CHANGED
|
@@ -172,10 +172,10 @@ function queryParamString(args) {
|
|
|
172
172
|
return tokens.join("&");
|
|
173
173
|
}
|
|
174
174
|
function getLoginUrl(callbackUrl, userHint) {
|
|
175
|
-
return (api_1.authOrigin +
|
|
175
|
+
return ((0, api_1.authOrigin)() +
|
|
176
176
|
"/o/oauth2/auth?" +
|
|
177
177
|
queryParamString({
|
|
178
|
-
client_id: api_1.clientId,
|
|
178
|
+
client_id: (0, api_1.clientId)(),
|
|
179
179
|
scope: SCOPES.join(" "),
|
|
180
180
|
response_type: "code",
|
|
181
181
|
state: _nonce,
|
|
@@ -186,8 +186,8 @@ function getLoginUrl(callbackUrl, userHint) {
|
|
|
186
186
|
async function getTokensFromAuthorizationCode(code, callbackUrl, verifier) {
|
|
187
187
|
const params = {
|
|
188
188
|
code: code,
|
|
189
|
-
client_id: api_1.clientId,
|
|
190
|
-
client_secret: api_1.clientSecret,
|
|
189
|
+
client_id: (0, api_1.clientId)(),
|
|
190
|
+
client_secret: (0, api_1.clientSecret)(),
|
|
191
191
|
redirect_uri: callbackUrl,
|
|
192
192
|
grant_type: "authorization_code",
|
|
193
193
|
};
|
|
@@ -196,7 +196,7 @@ async function getTokensFromAuthorizationCode(code, callbackUrl, verifier) {
|
|
|
196
196
|
}
|
|
197
197
|
let res;
|
|
198
198
|
try {
|
|
199
|
-
const client = new apiv2.Client({ urlPrefix: api_1.authOrigin, auth: false });
|
|
199
|
+
const client = new apiv2.Client({ urlPrefix: (0, api_1.authOrigin)(), auth: false });
|
|
200
200
|
const form = new FormData();
|
|
201
201
|
for (const [k, v] of Object.entries(params)) {
|
|
202
202
|
form.append(k, v);
|
|
@@ -229,20 +229,20 @@ async function getTokensFromAuthorizationCode(code, callbackUrl, verifier) {
|
|
|
229
229
|
}
|
|
230
230
|
const GITHUB_SCOPES = ["read:user", "repo", "public_repo"];
|
|
231
231
|
function getGithubLoginUrl(callbackUrl) {
|
|
232
|
-
return (api_1.githubOrigin +
|
|
232
|
+
return ((0, api_1.githubOrigin)() +
|
|
233
233
|
"/login/oauth/authorize?" +
|
|
234
234
|
queryParamString({
|
|
235
|
-
client_id: api_1.githubClientId,
|
|
235
|
+
client_id: (0, api_1.githubClientId)(),
|
|
236
236
|
state: _nonce,
|
|
237
237
|
redirect_uri: callbackUrl,
|
|
238
238
|
scope: GITHUB_SCOPES.join(" "),
|
|
239
239
|
}));
|
|
240
240
|
}
|
|
241
241
|
async function getGithubTokensFromAuthorizationCode(code, callbackUrl) {
|
|
242
|
-
const client = new apiv2.Client({ urlPrefix: api_1.githubOrigin, auth: false });
|
|
242
|
+
const client = new apiv2.Client({ urlPrefix: (0, api_1.githubOrigin)(), auth: false });
|
|
243
243
|
const data = {
|
|
244
|
-
client_id: api_1.githubClientId,
|
|
245
|
-
client_secret: api_1.githubClientSecret,
|
|
244
|
+
client_id: (0, api_1.githubClientId)(),
|
|
245
|
+
client_secret: (0, api_1.githubClientSecret)(),
|
|
246
246
|
code,
|
|
247
247
|
redirect_uri: callbackUrl,
|
|
248
248
|
state: _nonce,
|
|
@@ -276,7 +276,7 @@ function urlsafeBase64(base64string) {
|
|
|
276
276
|
async function loginRemotely() {
|
|
277
277
|
var _a;
|
|
278
278
|
const authProxyClient = new apiv2.Client({
|
|
279
|
-
urlPrefix: api_1.authProxyOrigin,
|
|
279
|
+
urlPrefix: (0, api_1.authProxyOrigin)(),
|
|
280
280
|
auth: false,
|
|
281
281
|
});
|
|
282
282
|
const sessionId = (0, uuid_1.v4)();
|
|
@@ -285,7 +285,7 @@ async function loginRemotely() {
|
|
|
285
285
|
const attestToken = (_a = (await authProxyClient.post("/attest", {
|
|
286
286
|
session_id: sessionId,
|
|
287
287
|
})).body) === null || _a === void 0 ? void 0 : _a.token;
|
|
288
|
-
const loginUrl = `${api_1.authProxyOrigin}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}`;
|
|
288
|
+
const loginUrl = `${(0, api_1.authProxyOrigin)()}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}`;
|
|
289
289
|
logger_1.logger.info();
|
|
290
290
|
logger_1.logger.info("To sign in to the Firebase CLI:");
|
|
291
291
|
logger_1.logger.info();
|
|
@@ -304,7 +304,7 @@ async function loginRemotely() {
|
|
|
304
304
|
message: "Enter authorization code:",
|
|
305
305
|
});
|
|
306
306
|
try {
|
|
307
|
-
const tokens = await getTokensFromAuthorizationCode(code, `${api_1.authProxyOrigin}/complete`, codeVerifier);
|
|
307
|
+
const tokens = await getTokensFromAuthorizationCode(code, `${(0, api_1.authProxyOrigin)()}/complete`, codeVerifier);
|
|
308
308
|
void (0, track_1.trackGA4)("login", { method: "google_remote" });
|
|
309
309
|
return {
|
|
310
310
|
user: jwt.decode(tokens.id_token, { json: true }),
|
|
@@ -457,11 +457,11 @@ async function refreshTokens(refreshToken, authScopes) {
|
|
|
457
457
|
var _a, _b;
|
|
458
458
|
logger_1.logger.debug("> refreshing access token with scopes:", JSON.stringify(authScopes));
|
|
459
459
|
try {
|
|
460
|
-
const client = new apiv2.Client({ urlPrefix: api_1.googleOrigin, auth: false });
|
|
460
|
+
const client = new apiv2.Client({ urlPrefix: (0, api_1.googleOrigin)(), auth: false });
|
|
461
461
|
const data = {
|
|
462
462
|
refresh_token: refreshToken,
|
|
463
|
-
client_id: api_1.clientId,
|
|
464
|
-
client_secret: api_1.clientSecret,
|
|
463
|
+
client_id: (0, api_1.clientId)(),
|
|
464
|
+
client_secret: (0, api_1.clientSecret)(),
|
|
465
465
|
grant_type: "refresh_token",
|
|
466
466
|
scope: (authScopes || []).join(" "),
|
|
467
467
|
};
|
|
@@ -528,7 +528,7 @@ async function logout(refreshToken) {
|
|
|
528
528
|
}
|
|
529
529
|
logoutCurrentSession(refreshToken);
|
|
530
530
|
try {
|
|
531
|
-
const client = new apiv2.Client({ urlPrefix: api_1.authOrigin, auth: false });
|
|
531
|
+
const client = new apiv2.Client({ urlPrefix: (0, api_1.authOrigin)(), auth: false });
|
|
532
532
|
await client.get("/o/oauth2/revoke", { queryParams: { token: refreshToken } });
|
|
533
533
|
}
|
|
534
534
|
catch (thrown) {
|
|
@@ -4,17 +4,19 @@ exports.command = void 0;
|
|
|
4
4
|
const command_1 = require("../command");
|
|
5
5
|
const projectUtils_1 = require("../projectUtils");
|
|
6
6
|
const requireInteractive_1 = require("../requireInteractive");
|
|
7
|
-
const apphosting_1 = require("../
|
|
7
|
+
const apphosting_1 = require("../apphosting");
|
|
8
8
|
const apphosting_2 = require("../gcp/apphosting");
|
|
9
9
|
exports.command = new command_1.Command("apphosting:backends:create")
|
|
10
10
|
.description("create a backend in a Firebase project")
|
|
11
11
|
.option("-l, --location <location>", "specify the region of the backend", "")
|
|
12
12
|
.option("-s, --service-account <serviceAccount>", "specify the service account used to run the server", "")
|
|
13
|
+
.option("-w, --with-dev-connect", "use the Developer Connect flow insetad of Cloud Build Repositories (testing)", false)
|
|
13
14
|
.before(apphosting_2.ensureApiEnabled)
|
|
14
15
|
.before(requireInteractive_1.default)
|
|
15
16
|
.action(async (options) => {
|
|
16
17
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
17
18
|
const location = options.location;
|
|
18
19
|
const serviceAccount = options.serviceAccount;
|
|
19
|
-
|
|
20
|
+
const withDevConnect = options.withDevConnect;
|
|
21
|
+
await (0, apphosting_1.doSetup)(projectId, location, serviceAccount, withDevConnect);
|
|
20
22
|
});
|
|
@@ -5,7 +5,7 @@ const command_1 = require("../command");
|
|
|
5
5
|
const projectUtils_1 = require("../projectUtils");
|
|
6
6
|
const error_1 = require("../error");
|
|
7
7
|
const prompt_1 = require("../prompt");
|
|
8
|
-
const constants_1 = require("../
|
|
8
|
+
const constants_1 = require("../apphosting/constants");
|
|
9
9
|
const utils = require("../utils");
|
|
10
10
|
const apphosting = require("../gcp/apphosting");
|
|
11
11
|
const apphosting_backends_list_1 = require("./apphosting-backends-list");
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const projectUtils_1 = require("../projectUtils");
|
|
6
|
+
const logger_1 = require("../logger");
|
|
7
|
+
const requireAuth_1 = require("../requireAuth");
|
|
8
|
+
const secretManager_1 = require("../gcp/secretManager");
|
|
9
|
+
const secretManager = require("../gcp/secretManager");
|
|
10
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
11
|
+
const Table = require("cli-table");
|
|
12
|
+
exports.command = new command_1.Command("apphosting:secrets:describe <secretName>")
|
|
13
|
+
.description("Get metadata for secret and its versions.")
|
|
14
|
+
.before(requireAuth_1.requireAuth)
|
|
15
|
+
.before(secretManager.ensureApi)
|
|
16
|
+
.before(requirePermissions_1.requirePermissions, ["secretmanager.secrets.get"])
|
|
17
|
+
.action(async (secretName, options) => {
|
|
18
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
19
|
+
const versions = await (0, secretManager_1.listSecretVersions)(projectId, secretName);
|
|
20
|
+
const table = new Table({
|
|
21
|
+
head: ["Name", "Version", "Status", "Create Time"],
|
|
22
|
+
style: { head: ["yellow"] },
|
|
23
|
+
});
|
|
24
|
+
for (const version of versions) {
|
|
25
|
+
table.push([secretName, version.versionId, version.state, version.createTime]);
|
|
26
|
+
}
|
|
27
|
+
logger_1.logger.info(table.toString());
|
|
28
|
+
return { secrets: versions };
|
|
29
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const projectUtils_1 = require("../projectUtils");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
|
+
const requireAuth_1 = require("../requireAuth");
|
|
8
|
+
const secretManager = require("../gcp/secretManager");
|
|
9
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
10
|
+
const apphosting = require("../gcp/apphosting");
|
|
11
|
+
const secrets = require("../apphosting/secrets");
|
|
12
|
+
exports.command = new command_1.Command("apphosting:secrets:grantaccess <secretName>")
|
|
13
|
+
.description("grant service accounts permissions to the provided secret")
|
|
14
|
+
.option("-l, --location <location>", "app backend location")
|
|
15
|
+
.option("-b, --backend <backend>", "app backend name")
|
|
16
|
+
.before(requireAuth_1.requireAuth)
|
|
17
|
+
.before(secretManager.ensureApi)
|
|
18
|
+
.before(apphosting.ensureApiEnabled)
|
|
19
|
+
.before(requirePermissions_1.requirePermissions, [
|
|
20
|
+
"secretmanager.secrets.create",
|
|
21
|
+
"secretmanager.secrets.get",
|
|
22
|
+
"secretmanager.secrets.update",
|
|
23
|
+
"secretmanager.versions.add",
|
|
24
|
+
"secretmanager.secrets.getIamPolicy",
|
|
25
|
+
"secretmanager.secrets.setIamPolicy",
|
|
26
|
+
])
|
|
27
|
+
.action(async (secretName, options) => {
|
|
28
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
29
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
30
|
+
if (!options.location) {
|
|
31
|
+
throw new error_1.FirebaseError("Missing required flag --location. See firebase apphosting:secrets:grantaccess --help for more info");
|
|
32
|
+
}
|
|
33
|
+
const location = options.location;
|
|
34
|
+
if (!options.backend) {
|
|
35
|
+
throw new error_1.FirebaseError("Missing required flag --backend. See firebase apphosting:secrets:grantaccess --help for more info");
|
|
36
|
+
}
|
|
37
|
+
const exists = await secretManager.secretExists(projectId, secretName);
|
|
38
|
+
if (!exists) {
|
|
39
|
+
throw new error_1.FirebaseError(`Cannot find secret ${secretName}`);
|
|
40
|
+
}
|
|
41
|
+
const backendId = options.backend;
|
|
42
|
+
const backend = await apphosting.getBackend(projectId, location, backendId);
|
|
43
|
+
const accounts = secrets.toMulti(secrets.serviceAccountsForBackend(projectNumber, backend));
|
|
44
|
+
await secrets.grantSecretAccess(projectId, secretName, accounts);
|
|
45
|
+
});
|