firebase-tools 9.21.0 → 9.22.0
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/CHANGELOG.md +3 -3
- package/lib/api.js +2 -0
- package/lib/apiv2.js +3 -2
- package/lib/commands/crashlytics-symbols-upload.js +1 -1
- package/lib/commands/deploy.js +9 -1
- package/lib/commands/ext-configure.js +1 -1
- package/lib/commands/ext-dev-deprecate.js +63 -0
- package/lib/commands/ext-dev-undeprecate.js +56 -0
- package/lib/commands/ext-export.js +44 -0
- package/lib/commands/ext-install.js +1 -1
- package/lib/commands/ext-update.js +1 -1
- package/lib/commands/functions-delete.js +2 -0
- package/lib/commands/index.js +6 -5
- package/lib/commands/init.js +3 -0
- package/lib/config.js +3 -2
- package/lib/deploy/extensions/args.js +2 -0
- package/lib/deploy/extensions/deploy.js +49 -0
- package/lib/deploy/extensions/deploymentSummary.js +52 -0
- package/lib/deploy/extensions/errors.js +31 -0
- package/lib/deploy/extensions/index.js +8 -0
- package/lib/deploy/extensions/planner.js +95 -0
- package/lib/deploy/extensions/prepare.js +103 -0
- package/lib/deploy/extensions/release.js +43 -0
- package/lib/deploy/extensions/secrets.js +150 -0
- package/lib/deploy/extensions/tasks.js +98 -0
- package/lib/deploy/extensions/validate.js +17 -0
- package/lib/deploy/functions/backend.js +8 -1
- package/lib/deploy/functions/containerCleaner.js +77 -21
- package/lib/deploy/functions/release/fabricator.js +69 -9
- package/lib/deploy/functions/release/index.js +5 -1
- package/lib/deploy/functions/release/planner.js +3 -0
- package/lib/deploy/functions/release/reporter.js +4 -1
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +7 -2
- package/lib/deploy/index.js +1 -0
- package/lib/emulator/functionsEmulator.js +3 -1
- package/lib/extensions/askUserForParam.js +14 -6
- package/lib/extensions/checkProjectBilling.js +7 -7
- package/lib/extensions/export.js +107 -0
- package/lib/extensions/extensionsApi.js +103 -21
- package/lib/extensions/extensionsHelper.js +4 -1
- package/lib/extensions/listExtensions.js +16 -11
- package/lib/extensions/paramHelper.js +6 -4
- package/lib/extensions/provisioningHelper.js +16 -3
- package/lib/extensions/refs.js +9 -1
- package/lib/extensions/secretsUtils.js +10 -9
- package/lib/extensions/updateHelper.js +12 -2
- package/lib/extensions/versionHelper.js +14 -0
- package/lib/extensions/warnings.js +33 -1
- package/lib/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +25 -7
- package/lib/gcp/cloudfunctionsv2.js +10 -2
- package/lib/gcp/cloudtasks.js +143 -0
- package/lib/gcp/docker.js +7 -1
- package/lib/gcp/proto.js +2 -2
- package/lib/gcp/secretManager.js +27 -6
- package/lib/previews.js +1 -1
- package/package.json +2 -1
- package/schema/firebase-config.json +9 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepare = void 0;
|
|
4
|
+
const planner = require("./planner");
|
|
5
|
+
const deploymentSummary = require("./deploymentSummary");
|
|
6
|
+
const prompt = require("../../prompt");
|
|
7
|
+
const refs = require("../../extensions/refs");
|
|
8
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
9
|
+
const logger_1 = require("../../logger");
|
|
10
|
+
const error_1 = require("../../error");
|
|
11
|
+
const requirePermissions_1 = require("../../requirePermissions");
|
|
12
|
+
const extensionsHelper_1 = require("../../extensions/extensionsHelper");
|
|
13
|
+
const secretsUtils_1 = require("../../extensions/secretsUtils");
|
|
14
|
+
const secrets_1 = require("./secrets");
|
|
15
|
+
const warnings_1 = require("../../extensions/warnings");
|
|
16
|
+
async function prepare(context, options, payload) {
|
|
17
|
+
var _a;
|
|
18
|
+
const projectId = projectUtils_1.needProjectId(options);
|
|
19
|
+
await extensionsHelper_1.ensureExtensionsApiEnabled(options);
|
|
20
|
+
await requirePermissions_1.requirePermissions(options, ["firebaseextensions.instances.list"]);
|
|
21
|
+
context.have = await planner.have(projectId);
|
|
22
|
+
context.want = await planner.want(projectId, options.config.projectDir, options.config.get("extensions"));
|
|
23
|
+
const usingSecrets = await Promise.all((_a = context.have) === null || _a === void 0 ? void 0 : _a.map(secrets_1.checkSpecForSecrets));
|
|
24
|
+
if (usingSecrets.some((i) => i)) {
|
|
25
|
+
await secretsUtils_1.ensureSecretManagerApiEnabled(options);
|
|
26
|
+
}
|
|
27
|
+
payload.instancesToCreate = context.want.filter((i) => { var _a; return !((_a = context.have) === null || _a === void 0 ? void 0 : _a.some(matchesInstanceId(i))); });
|
|
28
|
+
payload.instancesToConfigure = context.want.filter((i) => { var _a; return (_a = context.have) === null || _a === void 0 ? void 0 : _a.some(isConfigure(i)); });
|
|
29
|
+
payload.instancesToUpdate = context.want.filter((i) => { var _a; return (_a = context.have) === null || _a === void 0 ? void 0 : _a.some(isUpdate(i)); });
|
|
30
|
+
payload.instancesToDelete = context.have.filter((i) => { var _a; return !((_a = context.want) === null || _a === void 0 ? void 0 : _a.some(matchesInstanceId(i))); });
|
|
31
|
+
if (await warnings_1.displayWarningsForDeploy(payload.instancesToCreate)) {
|
|
32
|
+
if (!options.force && options.nonInteractive) {
|
|
33
|
+
throw new error_1.FirebaseError("Pass the --force flag to acknowledge these terms in non-interactive mode");
|
|
34
|
+
}
|
|
35
|
+
else if (!options.force &&
|
|
36
|
+
!options.nonInteractive &&
|
|
37
|
+
!(await prompt.promptOnce({
|
|
38
|
+
type: "confirm",
|
|
39
|
+
message: `Do you wish to continue deploying these extensions?`,
|
|
40
|
+
default: true,
|
|
41
|
+
}))) {
|
|
42
|
+
throw new error_1.FirebaseError("Deployment cancelled");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (await warnings_1.displayWarningsForDeploy(payload.instancesToCreate)) {
|
|
46
|
+
if (!options.force && options.nonInteractive) {
|
|
47
|
+
throw new error_1.FirebaseError("Pass the --force flag to acknowledge these terms in non-interactive mode");
|
|
48
|
+
}
|
|
49
|
+
else if (!options.force &&
|
|
50
|
+
!options.nonInteractive &&
|
|
51
|
+
!(await prompt.promptOnce({
|
|
52
|
+
type: "confirm",
|
|
53
|
+
message: `Do you wish to continue deploying these extensions?`,
|
|
54
|
+
default: true,
|
|
55
|
+
}))) {
|
|
56
|
+
throw new error_1.FirebaseError("Deployment cancelled");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const permissionsNeeded = [];
|
|
60
|
+
if (payload.instancesToCreate.length) {
|
|
61
|
+
permissionsNeeded.push("firebaseextensions.instances.create");
|
|
62
|
+
logger_1.logger.info(deploymentSummary.createsSummary(payload.instancesToCreate));
|
|
63
|
+
}
|
|
64
|
+
if (payload.instancesToUpdate.length) {
|
|
65
|
+
permissionsNeeded.push("firebaseextensions.instances.update");
|
|
66
|
+
logger_1.logger.info(deploymentSummary.updatesSummary(payload.instancesToUpdate, context.have));
|
|
67
|
+
}
|
|
68
|
+
if (payload.instancesToConfigure.length) {
|
|
69
|
+
permissionsNeeded.push("firebaseextensions.instances.update");
|
|
70
|
+
logger_1.logger.info(deploymentSummary.configuresSummary(payload.instancesToConfigure));
|
|
71
|
+
}
|
|
72
|
+
if (payload.instancesToDelete.length) {
|
|
73
|
+
logger_1.logger.info(deploymentSummary.deletesSummary(payload.instancesToDelete));
|
|
74
|
+
if (!options.force && options.nonInteractive) {
|
|
75
|
+
throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
|
|
76
|
+
}
|
|
77
|
+
else if (!options.force &&
|
|
78
|
+
!options.nonInteractive &&
|
|
79
|
+
!(await prompt.promptOnce({
|
|
80
|
+
type: "confirm",
|
|
81
|
+
message: `Would you like to delete ${payload.instancesToDelete
|
|
82
|
+
.map((i) => i.instanceId)
|
|
83
|
+
.join(", ")}?`,
|
|
84
|
+
default: false,
|
|
85
|
+
}))) {
|
|
86
|
+
payload.instancesToDelete = [];
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
permissionsNeeded.push("firebaseextensions.instances.delete");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
await requirePermissions_1.requirePermissions(options, permissionsNeeded);
|
|
93
|
+
}
|
|
94
|
+
exports.prepare = prepare;
|
|
95
|
+
const matchesInstanceId = (dep) => (test) => {
|
|
96
|
+
return dep.instanceId === test.instanceId;
|
|
97
|
+
};
|
|
98
|
+
const isUpdate = (dep) => (test) => {
|
|
99
|
+
return dep.instanceId === test.instanceId && !refs.equal(dep.ref, test.ref);
|
|
100
|
+
};
|
|
101
|
+
const isConfigure = (dep) => (test) => {
|
|
102
|
+
return dep.instanceId === test.instanceId && refs.equal(dep.ref, test.ref);
|
|
103
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.release = void 0;
|
|
4
|
+
const queue_1 = require("../../throttler/queue");
|
|
5
|
+
const tasks = require("./tasks");
|
|
6
|
+
const error_1 = require("../../error");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
9
|
+
async function release(context, options, payload) {
|
|
10
|
+
var _a, _b, _c, _d;
|
|
11
|
+
const projectId = projectUtils_1.needProjectId(options);
|
|
12
|
+
const errorHandler = new errors_1.ErrorHandler();
|
|
13
|
+
const deploymentQueue = new queue_1.default({
|
|
14
|
+
retries: 5,
|
|
15
|
+
concurrency: 5,
|
|
16
|
+
handler: tasks.extensionsDeploymentHandler(errorHandler),
|
|
17
|
+
});
|
|
18
|
+
for (const creation of (_a = payload.instancesToCreate) !== null && _a !== void 0 ? _a : []) {
|
|
19
|
+
const task = tasks.createExtensionInstanceTask(projectId, creation);
|
|
20
|
+
void deploymentQueue.run(task);
|
|
21
|
+
}
|
|
22
|
+
for (const update of (_b = payload.instancesToUpdate) !== null && _b !== void 0 ? _b : []) {
|
|
23
|
+
const task = tasks.updateExtensionInstanceTask(projectId, update);
|
|
24
|
+
void deploymentQueue.run(task);
|
|
25
|
+
}
|
|
26
|
+
for (const update of (_c = payload.instancesToConfigure) !== null && _c !== void 0 ? _c : []) {
|
|
27
|
+
const task = tasks.configureExtensionInstanceTask(projectId, update);
|
|
28
|
+
void deploymentQueue.run(task);
|
|
29
|
+
}
|
|
30
|
+
for (const deletion of (_d = payload.instancesToDelete) !== null && _d !== void 0 ? _d : []) {
|
|
31
|
+
const task = tasks.deleteExtensionInstanceTask(projectId, deletion);
|
|
32
|
+
void deploymentQueue.run(task);
|
|
33
|
+
}
|
|
34
|
+
const deploymentPromise = deploymentQueue.wait();
|
|
35
|
+
deploymentQueue.process();
|
|
36
|
+
deploymentQueue.close();
|
|
37
|
+
await deploymentPromise;
|
|
38
|
+
if (errorHandler.hasErrors()) {
|
|
39
|
+
errorHandler.print();
|
|
40
|
+
throw new error_1.FirebaseError(`Extensions deployment failed.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.release = release;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkSpecForSecrets = exports.handleSecretParams = void 0;
|
|
4
|
+
const clc = require("cli-color");
|
|
5
|
+
const secretUtils = require("../../extensions/secretsUtils");
|
|
6
|
+
const secretManager = require("../../gcp/secretManager");
|
|
7
|
+
const planner_1 = require("./planner");
|
|
8
|
+
const askUserForParam_1 = require("../../extensions/askUserForParam");
|
|
9
|
+
const extensionsApi_1 = require("../../extensions/extensionsApi");
|
|
10
|
+
const error_1 = require("../../error");
|
|
11
|
+
const logger_1 = require("../../logger");
|
|
12
|
+
const utils_1 = require("../../utils");
|
|
13
|
+
async function handleSecretParams(payload, have, nonInteractive) {
|
|
14
|
+
var _a, _b, _c;
|
|
15
|
+
for (const i of (_a = payload.instancesToCreate) !== null && _a !== void 0 ? _a : []) {
|
|
16
|
+
if (await checkSpecForSecrets(i)) {
|
|
17
|
+
utils_1.logLabeledBullet("extensions", `Verifying secret params for ${clc.bold(i.instanceId)}`);
|
|
18
|
+
await handleSecretsCreateInstance(i, nonInteractive);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const updates = [...((_b = payload.instancesToUpdate) !== null && _b !== void 0 ? _b : []), ...((_c = payload.instancesToConfigure) !== null && _c !== void 0 ? _c : [])];
|
|
22
|
+
for (const i of updates) {
|
|
23
|
+
if (await checkSpecForSecrets(i)) {
|
|
24
|
+
utils_1.logLabeledBullet("extensions", `Verifying secret params for ${clc.bold(i.instanceId)}`);
|
|
25
|
+
const previousSpec = have.find((h) => h.instanceId === i.instanceId);
|
|
26
|
+
await handleSecretsUpdateInstance(i, previousSpec, nonInteractive);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.handleSecretParams = handleSecretParams;
|
|
31
|
+
async function checkSpecForSecrets(i) {
|
|
32
|
+
const extensionVersion = await planner_1.getExtensionVersion(i);
|
|
33
|
+
return secretUtils.usesSecrets(extensionVersion.spec);
|
|
34
|
+
}
|
|
35
|
+
exports.checkSpecForSecrets = checkSpecForSecrets;
|
|
36
|
+
const secretsInSpec = (spec) => {
|
|
37
|
+
return spec.params.filter((p) => p.type === extensionsApi_1.ParamType.SECRET);
|
|
38
|
+
};
|
|
39
|
+
async function handleSecretsCreateInstance(i, nonInteractive) {
|
|
40
|
+
const extensionVersion = await planner_1.getExtensionVersion(i);
|
|
41
|
+
const secretParams = secretsInSpec(extensionVersion.spec);
|
|
42
|
+
for (const s of secretParams) {
|
|
43
|
+
await handleSecretParamForCreate(s, i, nonInteractive);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function handleSecretsUpdateInstance(i, prevSpec, nonInteractive) {
|
|
47
|
+
const extensionVersion = await planner_1.getExtensionVersion(i);
|
|
48
|
+
const prevExtensionVersion = await planner_1.getExtensionVersion(prevSpec);
|
|
49
|
+
const secretParams = secretsInSpec(extensionVersion.spec);
|
|
50
|
+
for (const s of secretParams) {
|
|
51
|
+
const prevParam = prevExtensionVersion.spec.params.find((p) => p.param === s.param);
|
|
52
|
+
if ((prevParam === null || prevParam === void 0 ? void 0 : prevParam.type) === extensionsApi_1.ParamType.SECRET && prevSpec.params[prevParam === null || prevParam === void 0 ? void 0 : prevParam.param]) {
|
|
53
|
+
await handleSecretParamForUpdate(s, i, prevSpec.params[prevParam === null || prevParam === void 0 ? void 0 : prevParam.param], nonInteractive);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
await handleSecretParamForCreate(s, i, nonInteractive);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function handleSecretParamForCreate(secretParam, i, nonInteractive) {
|
|
61
|
+
var _a;
|
|
62
|
+
const providedValue = i.params[secretParam.param];
|
|
63
|
+
if (!providedValue) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const [, projectId, , secretName, , version] = providedValue.split("/");
|
|
67
|
+
if (!projectId || !secretName || !version) {
|
|
68
|
+
throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}, but expected a secret version.`);
|
|
69
|
+
}
|
|
70
|
+
const secretInfo = await getSecretInfo(projectId, secretName, version);
|
|
71
|
+
if (!secretInfo.secret) {
|
|
72
|
+
await promptForCreateSecret({
|
|
73
|
+
projectId,
|
|
74
|
+
secretName,
|
|
75
|
+
instanceId: i.instanceId,
|
|
76
|
+
secretParam,
|
|
77
|
+
nonInteractive,
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
else if (!secretInfo.secretVersion) {
|
|
82
|
+
throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}. ` +
|
|
83
|
+
`projects/${projectId}/secrets/${secretName} exists, but version ${version} does not. ` +
|
|
84
|
+
`See more information about this secret at ${secretManager.secretManagerConsoleUri(projectId)}`);
|
|
85
|
+
}
|
|
86
|
+
if (!!((_a = secretInfo === null || secretInfo === void 0 ? void 0 : secretInfo.secret) === null || _a === void 0 ? void 0 : _a.labels) &&
|
|
87
|
+
!!(secretInfo === null || secretInfo === void 0 ? void 0 : secretInfo.secret.labels[secretUtils.SECRET_LABEL]) &&
|
|
88
|
+
secretInfo.secret.labels[secretUtils.SECRET_LABEL] !== i.instanceId) {
|
|
89
|
+
throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}. ` +
|
|
90
|
+
`projects/${projectId}/secrets/${secretName} is managed by a different extension instance (${secretInfo.secret.labels[secretUtils.SECRET_LABEL]}), so reusing it here can lead to unexpected behavior. ` +
|
|
91
|
+
"Please choose a different name for this secret, and rerun this command.");
|
|
92
|
+
}
|
|
93
|
+
await secretUtils.grantFirexServiceAgentSecretAdminRole(secretInfo.secret);
|
|
94
|
+
}
|
|
95
|
+
async function handleSecretParamForUpdate(secretParam, i, prevValue, nonInteractive) {
|
|
96
|
+
const providedValue = i.params[secretParam.param];
|
|
97
|
+
if (!providedValue) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const [, projectId, , secretName, , version] = providedValue.split("/");
|
|
101
|
+
if (!projectId || !secretName || !version) {
|
|
102
|
+
throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}, but expected a secret version.`);
|
|
103
|
+
}
|
|
104
|
+
const [, prevProjectId, , prevSecretName] = prevValue.split("/");
|
|
105
|
+
if (prevSecretName !== secretName) {
|
|
106
|
+
throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}, ` +
|
|
107
|
+
`but this instance was previously using a different secret projects/${prevProjectId}/secrets/${prevSecretName}.\n` +
|
|
108
|
+
`Changing secrets is not supported. If you want to change the value of this secret, ` +
|
|
109
|
+
`use a new version of projects/${prevProjectId}/secrets/${prevSecretName}.` +
|
|
110
|
+
`You can create a new version at ${secretManager.secretManagerConsoleUri(projectId)}`);
|
|
111
|
+
}
|
|
112
|
+
const secretInfo = await getSecretInfo(projectId, secretName, version);
|
|
113
|
+
if (!secretInfo.secret) {
|
|
114
|
+
i.params[secretParam.param] = await promptForCreateSecret({
|
|
115
|
+
projectId,
|
|
116
|
+
secretName,
|
|
117
|
+
instanceId: i.instanceId,
|
|
118
|
+
secretParam,
|
|
119
|
+
nonInteractive,
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
else if (!secretInfo.secretVersion) {
|
|
124
|
+
throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}. ` +
|
|
125
|
+
`projects/${projectId}/secrets/${secretName} exists, but version ${version} does not. ` +
|
|
126
|
+
`See more information about this secret at ${secretManager.secretManagerConsoleUri(projectId)}`);
|
|
127
|
+
}
|
|
128
|
+
i.params[secretParam.param] = secretManager.toSecretVersionResourceName(secretInfo.secretVersion);
|
|
129
|
+
await secretUtils.grantFirexServiceAgentSecretAdminRole(secretInfo.secret);
|
|
130
|
+
}
|
|
131
|
+
async function getSecretInfo(projectId, secretName, version) {
|
|
132
|
+
const secretInfo = {};
|
|
133
|
+
try {
|
|
134
|
+
secretInfo.secret = await secretManager.getSecret(projectId, secretName);
|
|
135
|
+
secretInfo.secretVersion = await secretManager.getSecretVersion(projectId, secretName, version);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (err.status !== 404) {
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return secretInfo;
|
|
143
|
+
}
|
|
144
|
+
async function promptForCreateSecret(args) {
|
|
145
|
+
logger_1.logger.info(`${clc.bold(args.instanceId)}: Secret ${args.projectId}/${args.secretName} doesn't exist yet.`);
|
|
146
|
+
if (args.nonInteractive) {
|
|
147
|
+
throw new error_1.FirebaseError(`To create this secret, run this command in interactive mode, or go to ${secretManager.secretManagerConsoleUri(args.projectId)}`);
|
|
148
|
+
}
|
|
149
|
+
return askUserForParam_1.promptCreateSecret(args.projectId, args.instanceId, args.secretParam, args.secretName);
|
|
150
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deleteExtensionInstanceTask = exports.configureExtensionInstanceTask = exports.updateExtensionInstanceTask = exports.createExtensionInstanceTask = exports.extensionsDeploymentHandler = void 0;
|
|
4
|
+
const clc = require("cli-color");
|
|
5
|
+
const extensionsApi = require("../../extensions/extensionsApi");
|
|
6
|
+
const refs = require("../../extensions/refs");
|
|
7
|
+
const utils = require("../../utils");
|
|
8
|
+
const isRetryable = (err) => err.status == 429 || err.status == 409;
|
|
9
|
+
function extensionsDeploymentHandler(errorHandler) {
|
|
10
|
+
return async (task) => {
|
|
11
|
+
var _a, _b, _c, _d;
|
|
12
|
+
let result;
|
|
13
|
+
try {
|
|
14
|
+
result = await task.run();
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
if (isRetryable(err)) {
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
errorHandler.record(task.spec.instanceId, task.type, (_d = (_c = (_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.message) !== null && _d !== void 0 ? _d : err);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
exports.extensionsDeploymentHandler = extensionsDeploymentHandler;
|
|
26
|
+
function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = false) {
|
|
27
|
+
const run = async () => {
|
|
28
|
+
const res = await extensionsApi.createInstance({
|
|
29
|
+
projectId,
|
|
30
|
+
instanceId: instanceSpec.instanceId,
|
|
31
|
+
params: instanceSpec.params,
|
|
32
|
+
extensionVersionRef: refs.toExtensionVersionRef(instanceSpec.ref),
|
|
33
|
+
validateOnly,
|
|
34
|
+
});
|
|
35
|
+
printSuccess(instanceSpec.instanceId, "create", validateOnly);
|
|
36
|
+
return;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
run,
|
|
40
|
+
spec: instanceSpec,
|
|
41
|
+
type: "create",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
exports.createExtensionInstanceTask = createExtensionInstanceTask;
|
|
45
|
+
function updateExtensionInstanceTask(projectId, instanceSpec, validateOnly = false) {
|
|
46
|
+
const run = async () => {
|
|
47
|
+
const res = await extensionsApi.updateInstanceFromRegistry({
|
|
48
|
+
projectId,
|
|
49
|
+
instanceId: instanceSpec.instanceId,
|
|
50
|
+
extRef: refs.toExtensionVersionRef(instanceSpec.ref),
|
|
51
|
+
params: instanceSpec.params,
|
|
52
|
+
validateOnly,
|
|
53
|
+
});
|
|
54
|
+
printSuccess(instanceSpec.instanceId, "update", validateOnly);
|
|
55
|
+
return;
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
run,
|
|
59
|
+
spec: instanceSpec,
|
|
60
|
+
type: "update",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
exports.updateExtensionInstanceTask = updateExtensionInstanceTask;
|
|
64
|
+
function configureExtensionInstanceTask(projectId, instanceSpec, validateOnly = false) {
|
|
65
|
+
const run = async () => {
|
|
66
|
+
const res = await extensionsApi.configureInstance({
|
|
67
|
+
projectId,
|
|
68
|
+
instanceId: instanceSpec.instanceId,
|
|
69
|
+
params: instanceSpec.params,
|
|
70
|
+
validateOnly,
|
|
71
|
+
});
|
|
72
|
+
printSuccess(instanceSpec.instanceId, "configure", validateOnly);
|
|
73
|
+
return;
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
run,
|
|
77
|
+
spec: instanceSpec,
|
|
78
|
+
type: "configure",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
exports.configureExtensionInstanceTask = configureExtensionInstanceTask;
|
|
82
|
+
function deleteExtensionInstanceTask(projectId, instanceSpec) {
|
|
83
|
+
const run = async () => {
|
|
84
|
+
const res = await extensionsApi.deleteInstance(projectId, instanceSpec.instanceId);
|
|
85
|
+
printSuccess(instanceSpec.instanceId, "delete", false);
|
|
86
|
+
return;
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
run,
|
|
90
|
+
spec: instanceSpec,
|
|
91
|
+
type: "delete",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
exports.deleteExtensionInstanceTask = deleteExtensionInstanceTask;
|
|
95
|
+
function printSuccess(instanceId, type, validateOnly) {
|
|
96
|
+
const action = validateOnly ? `validated ${type} for` : `${type}d`;
|
|
97
|
+
utils.logSuccess(clc.bold.green("extensions") + ` Successfully ${action} ${instanceId}`);
|
|
98
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkBilling = void 0;
|
|
4
|
+
const cloudbilling_1 = require("../../gcp/cloudbilling");
|
|
5
|
+
const checkProjectBilling_1 = require("../../extensions/checkProjectBilling");
|
|
6
|
+
const error_1 = require("../../error");
|
|
7
|
+
async function checkBilling(projectId, nonInteractive) {
|
|
8
|
+
const enabled = await cloudbilling_1.checkBillingEnabled(projectId);
|
|
9
|
+
if (!enabled && nonInteractive) {
|
|
10
|
+
throw new error_1.FirebaseError(`Extensions require the Blaze plan, but project ${projectId} is not on the Blaze plan. ` +
|
|
11
|
+
`Please visit https://console.cloud.google.com/billing/linkedaccount?project=${projectId} to upgrade your project.`);
|
|
12
|
+
}
|
|
13
|
+
else if (!enabled) {
|
|
14
|
+
await checkProjectBilling_1.enableBilling(projectId);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.checkBilling = checkBilling;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
|
|
3
|
+
exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
|
|
4
4
|
const gcf = require("../../gcp/cloudfunctions");
|
|
5
5
|
const gcfV2 = require("../../gcp/cloudfunctionsv2");
|
|
6
6
|
const utils = require("../../utils");
|
|
@@ -16,6 +16,9 @@ function endpointTriggerType(endpoint) {
|
|
|
16
16
|
else if (isEventTriggered(endpoint)) {
|
|
17
17
|
return endpoint.eventTrigger.eventType;
|
|
18
18
|
}
|
|
19
|
+
else if (isTaskQueueTriggered(endpoint)) {
|
|
20
|
+
return "taskQueue";
|
|
21
|
+
}
|
|
19
22
|
else {
|
|
20
23
|
throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
|
|
21
24
|
}
|
|
@@ -46,6 +49,10 @@ function isScheduleTriggered(triggered) {
|
|
|
46
49
|
return {}.hasOwnProperty.call(triggered, "scheduleTrigger");
|
|
47
50
|
}
|
|
48
51
|
exports.isScheduleTriggered = isScheduleTriggered;
|
|
52
|
+
function isTaskQueueTriggered(triggered) {
|
|
53
|
+
return {}.hasOwnProperty.call(triggered, "taskQueueTrigger");
|
|
54
|
+
}
|
|
55
|
+
exports.isTaskQueueTriggered = isTaskQueueTriggered;
|
|
49
56
|
function empty() {
|
|
50
57
|
return {
|
|
51
58
|
requiredAPIs: {},
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DockerHelper = exports.deleteGcfArtifacts = exports.listGcfPaths = exports.ContainerRegistryCleaner = exports.cleanupBuildImages = exports.SUBDOMAIN_MAPPING = void 0;
|
|
3
|
+
exports.DockerHelper = exports.deleteGcfArtifacts = exports.listGcfPaths = exports.ContainerRegistryCleaner = exports.ArtifactRegistryCleaner = exports.cleanupBuildImages = exports.SUBDOMAIN_MAPPING = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
|
+
const error_1 = require("../../error");
|
|
6
|
+
const previews_1 = require("../../previews");
|
|
5
7
|
const api_1 = require("../../api");
|
|
6
8
|
const logger_1 = require("../../logger");
|
|
7
|
-
const
|
|
9
|
+
const artifactregistry = require("../../gcp/artifactregistry");
|
|
8
10
|
const backend = require("./backend");
|
|
11
|
+
const docker = require("../../gcp/docker");
|
|
9
12
|
const utils = require("../../utils");
|
|
10
|
-
const
|
|
13
|
+
const poller = require("../../operation-poller");
|
|
11
14
|
exports.SUBDOMAIN_MAPPING = {
|
|
12
15
|
"us-west2": "us",
|
|
13
16
|
"us-west3": "us",
|
|
@@ -47,7 +50,7 @@ async function retry(func) {
|
|
|
47
50
|
return await Promise.race([func(), timeout]);
|
|
48
51
|
}
|
|
49
52
|
catch (error) {
|
|
50
|
-
logger_1.logger.debug("Failed docker command with error", error);
|
|
53
|
+
logger_1.logger.debug("Failed docker command with error ", error);
|
|
51
54
|
retry += 1;
|
|
52
55
|
if (retry >= MAX_RETRIES) {
|
|
53
56
|
throw new error_1.FirebaseError("Failed to clean up artifacts", { original: error });
|
|
@@ -56,19 +59,47 @@ async function retry(func) {
|
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
|
-
async function cleanupBuildImages(
|
|
62
|
+
async function cleanupBuildImages(haveFunctions, deletedFunctions, cleaners = {}) {
|
|
60
63
|
utils.logBullet(clc.bold.cyan("functions: ") + "cleaning up build files...");
|
|
61
|
-
const gcrCleaner = new ContainerRegistryCleaner();
|
|
62
64
|
const failedDomains = new Set();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
if (previews_1.previews.artifactregistry) {
|
|
66
|
+
const arCleaner = cleaners.ar || new ArtifactRegistryCleaner();
|
|
67
|
+
await Promise.all([
|
|
68
|
+
...haveFunctions.map(async (func) => {
|
|
69
|
+
try {
|
|
70
|
+
await arCleaner.cleanupFunction(func);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
const path = `${func.project}/${func.region}/gcf-artifacts`;
|
|
74
|
+
failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`);
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
...deletedFunctions.map(async (func) => {
|
|
78
|
+
try {
|
|
79
|
+
await Promise.all([
|
|
80
|
+
arCleaner.cleanupFunction(func),
|
|
81
|
+
arCleaner.cleanupFunctionCache(func),
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const path = `${func.project}/${func.region}/gcf-artifacts`;
|
|
86
|
+
failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`);
|
|
87
|
+
}
|
|
88
|
+
}),
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const gcrCleaner = cleaners.gcr || new ContainerRegistryCleaner();
|
|
93
|
+
await Promise.all([...haveFunctions, ...deletedFunctions].map(async (func) => {
|
|
94
|
+
try {
|
|
95
|
+
await gcrCleaner.cleanupFunction(func);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const path = `${func.project}/${exports.SUBDOMAIN_MAPPING[func.region]}/gcf`;
|
|
99
|
+
failedDomains.add(`https://console.cloud.google.com/gcr/images/${path}`);
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
72
103
|
if (failedDomains.size) {
|
|
73
104
|
let message = "Unhandled error cleaning up build images. This could result in a small monthly bill if not corrected. ";
|
|
74
105
|
message +=
|
|
@@ -83,6 +114,31 @@ async function cleanupBuildImages(functions) {
|
|
|
83
114
|
}
|
|
84
115
|
}
|
|
85
116
|
exports.cleanupBuildImages = cleanupBuildImages;
|
|
117
|
+
class ArtifactRegistryCleaner {
|
|
118
|
+
static packagePath(func) {
|
|
119
|
+
return `projects/${func.project}/locations/${func.region}/repositories/gcf-artifacts/packages/${func.id}`;
|
|
120
|
+
}
|
|
121
|
+
async cleanupFunction(func) {
|
|
122
|
+
const op = await artifactregistry.deletePackage(ArtifactRegistryCleaner.packagePath(func));
|
|
123
|
+
if (op.done) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
await poller.pollOperation(Object.assign(Object.assign({}, ArtifactRegistryCleaner.POLLER_OPTIONS), { pollerName: `cleanup-${func.region}-${func.id}`, operationResourceName: op.name }));
|
|
127
|
+
}
|
|
128
|
+
async cleanupFunctionCache(func) {
|
|
129
|
+
const op = await artifactregistry.deletePackage(`${ArtifactRegistryCleaner.packagePath(func)}%2Fcache`);
|
|
130
|
+
if (op.done) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
await poller.pollOperation(Object.assign(Object.assign({}, ArtifactRegistryCleaner.POLLER_OPTIONS), { pollerName: `cleanup-cache-${func.region}-${func.id}`, operationResourceName: op.name }));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.ArtifactRegistryCleaner = ArtifactRegistryCleaner;
|
|
137
|
+
ArtifactRegistryCleaner.POLLER_OPTIONS = {
|
|
138
|
+
apiOrigin: api_1.artifactRegistryDomain,
|
|
139
|
+
apiVersion: artifactregistry.API_VERSION,
|
|
140
|
+
masterTimeout: 5 * 60 * 1000,
|
|
141
|
+
};
|
|
86
142
|
class ContainerRegistryCleaner {
|
|
87
143
|
constructor() {
|
|
88
144
|
this.helpers = {};
|
|
@@ -217,7 +273,7 @@ class DockerHelper {
|
|
|
217
273
|
async rm(path) {
|
|
218
274
|
let toThrowLater = undefined;
|
|
219
275
|
const stat = await this.ls(path);
|
|
220
|
-
const recursive = stat.children.map(
|
|
276
|
+
const recursive = stat.children.map(async (child) => {
|
|
221
277
|
try {
|
|
222
278
|
await this.rm(`${path}/${child}`);
|
|
223
279
|
stat.children.splice(stat.children.indexOf(child), 1);
|
|
@@ -225,8 +281,8 @@ class DockerHelper {
|
|
|
225
281
|
catch (err) {
|
|
226
282
|
toThrowLater = err;
|
|
227
283
|
}
|
|
228
|
-
})
|
|
229
|
-
const deleteTags = stat.tags.map(
|
|
284
|
+
});
|
|
285
|
+
const deleteTags = stat.tags.map(async (tag) => {
|
|
230
286
|
try {
|
|
231
287
|
await retry(() => this.client.deleteTag(path, tag));
|
|
232
288
|
stat.tags.splice(stat.tags.indexOf(tag), 1);
|
|
@@ -235,9 +291,9 @@ class DockerHelper {
|
|
|
235
291
|
logger_1.logger.debug("Got error trying to remove docker tag:", err);
|
|
236
292
|
toThrowLater = err;
|
|
237
293
|
}
|
|
238
|
-
})
|
|
294
|
+
});
|
|
239
295
|
await Promise.all(deleteTags);
|
|
240
|
-
const deleteImages = stat.digests.map(
|
|
296
|
+
const deleteImages = stat.digests.map(async (digest) => {
|
|
241
297
|
try {
|
|
242
298
|
await retry(() => this.client.deleteImage(path, digest));
|
|
243
299
|
stat.digests.splice(stat.digests.indexOf(digest), 1);
|
|
@@ -246,7 +302,7 @@ class DockerHelper {
|
|
|
246
302
|
logger_1.logger.debug("Got error trying to remove docker image:", err);
|
|
247
303
|
toThrowLater = err;
|
|
248
304
|
}
|
|
249
|
-
})
|
|
305
|
+
});
|
|
250
306
|
await Promise.all(deleteImages);
|
|
251
307
|
await Promise.all(recursive);
|
|
252
308
|
if (toThrowLater) {
|