firebase-tools 12.3.0 → 12.3.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/commands/functions-secrets-access.js +2 -0
- package/lib/commands/functions-secrets-destroy.js +1 -0
- package/lib/commands/functions-secrets-get.js +2 -0
- package/lib/commands/functions-secrets-prune.js +3 -2
- package/lib/commands/functions-secrets-set.js +8 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/internaltesting-frameworks-compose.js +20 -0
- package/lib/deploy/functions/release/executor.js +19 -10
- package/lib/deploy/functions/release/fabricator.js +22 -8
- package/lib/frameworks/compose/discover/index.js +23 -0
- package/lib/frameworks/compose/driver/docker.js +177 -0
- package/lib/frameworks/compose/driver/hooks.js +22 -0
- package/lib/frameworks/compose/driver/index.js +16 -0
- package/lib/frameworks/compose/driver/local.js +42 -0
- package/lib/frameworks/compose/index.js +30 -0
- package/lib/frameworks/compose/interfaces.js +21 -0
- package/lib/functions/secrets.js +8 -1
- package/package.json +1 -1
|
@@ -5,8 +5,10 @@ const command_1 = require("../command");
|
|
|
5
5
|
const logger_1 = require("../logger");
|
|
6
6
|
const projectUtils_1 = require("../projectUtils");
|
|
7
7
|
const secretManager_1 = require("../gcp/secretManager");
|
|
8
|
+
const secrets = require("../functions/secrets");
|
|
8
9
|
exports.command = new command_1.Command("functions:secrets:access <KEY>[@version]")
|
|
9
10
|
.description("Access secret value given secret and its version. Defaults to accessing the latest version.")
|
|
11
|
+
.before(secrets.ensureApi)
|
|
10
12
|
.action(async (key, options) => {
|
|
11
13
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
12
14
|
let [name, version] = key.split("@");
|
|
@@ -11,6 +11,7 @@ const backend = require("../deploy/functions/backend");
|
|
|
11
11
|
exports.command = new command_1.Command("functions:secrets:destroy <KEY>[@version]")
|
|
12
12
|
.description("Destroy a secret. Defaults to destroying the latest version.")
|
|
13
13
|
.withForce("Destroys a secret without confirmation.")
|
|
14
|
+
.before(secrets.ensureApi)
|
|
14
15
|
.action(async (key, options) => {
|
|
15
16
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
16
17
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
@@ -7,8 +7,10 @@ const logger_1 = require("../logger");
|
|
|
7
7
|
const projectUtils_1 = require("../projectUtils");
|
|
8
8
|
const secretManager_1 = require("../gcp/secretManager");
|
|
9
9
|
const requirePermissions_1 = require("../requirePermissions");
|
|
10
|
+
const secrets = require("../functions/secrets");
|
|
10
11
|
exports.command = new command_1.Command("functions:secrets:get <KEY>")
|
|
11
12
|
.description("Get metadata for secret and its versions")
|
|
13
|
+
.before(secrets.ensureApi)
|
|
12
14
|
.before(requirePermissions_1.requirePermissions, ["secretmanager.secrets.get"])
|
|
13
15
|
.action(async (key, options) => {
|
|
14
16
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
@@ -4,7 +4,7 @@ exports.command = void 0;
|
|
|
4
4
|
const backend = require("../deploy/functions/backend");
|
|
5
5
|
const command_1 = require("../command");
|
|
6
6
|
const projectUtils_1 = require("../projectUtils");
|
|
7
|
-
const
|
|
7
|
+
const secrets = require("../functions/secrets");
|
|
8
8
|
const requirePermissions_1 = require("../requirePermissions");
|
|
9
9
|
const deploymentTool_1 = require("../deploymentTool");
|
|
10
10
|
const utils_1 = require("../utils");
|
|
@@ -13,6 +13,7 @@ const secretManager_1 = require("../gcp/secretManager");
|
|
|
13
13
|
exports.command = new command_1.Command("functions:secrets:prune")
|
|
14
14
|
.withForce("Destroys unused secrets without prompt")
|
|
15
15
|
.description("Destroys unused secrets")
|
|
16
|
+
.before(secrets.ensureApi)
|
|
16
17
|
.before(requirePermissions_1.requirePermissions, [
|
|
17
18
|
"cloudfunctions.functions.list",
|
|
18
19
|
"secretmanager.secrets.list",
|
|
@@ -27,7 +28,7 @@ exports.command = new command_1.Command("functions:secrets:prune")
|
|
|
27
28
|
const haveEndpoints = backend
|
|
28
29
|
.allEndpoints(haveBackend)
|
|
29
30
|
.filter((e) => (0, deploymentTool_1.isFirebaseManaged)(e.labels || []));
|
|
30
|
-
const pruned = await
|
|
31
|
+
const pruned = await secrets.pruneSecrets({ projectNumber, projectId }, haveEndpoints);
|
|
31
32
|
if (pruned.length === 0) {
|
|
32
33
|
(0, utils_1.logBullet)("All secrets are in use. Nothing to prune today.");
|
|
33
34
|
return;
|
|
@@ -4,6 +4,7 @@ exports.command = void 0;
|
|
|
4
4
|
const tty = require("tty");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const clc = require("colorette");
|
|
7
|
+
const logger_1 = require("../logger");
|
|
7
8
|
const secrets_1 = require("../functions/secrets");
|
|
8
9
|
const command_1 = require("../command");
|
|
9
10
|
const requirePermissions_1 = require("../requirePermissions");
|
|
@@ -13,9 +14,11 @@ const projectUtils_1 = require("../projectUtils");
|
|
|
13
14
|
const secretManager_1 = require("../gcp/secretManager");
|
|
14
15
|
const secrets = require("../functions/secrets");
|
|
15
16
|
const backend = require("../deploy/functions/backend");
|
|
17
|
+
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
16
18
|
exports.command = new command_1.Command("functions:secrets:set <KEY>")
|
|
17
19
|
.description("Create or update a secret for use in Cloud Functions for Firebase.")
|
|
18
20
|
.withForce("Automatically updates functions to use the new secret.")
|
|
21
|
+
.before(secrets.ensureApi)
|
|
19
22
|
.before(requirePermissions_1.requirePermissions, [
|
|
20
23
|
"secretmanager.secrets.create",
|
|
21
24
|
"secretmanager.secrets.get",
|
|
@@ -50,6 +53,11 @@ exports.command = new command_1.Command("functions:secrets:set <KEY>")
|
|
|
50
53
|
clc.bold("firebase deploy --only functions"));
|
|
51
54
|
return;
|
|
52
55
|
}
|
|
56
|
+
const functionsEnabled = await (0, ensureApiEnabled_1.check)(projectId, "cloudfunctions.googleapis.com", "functions", true);
|
|
57
|
+
if (!functionsEnabled) {
|
|
58
|
+
logger_1.logger.debug("Customer set secrets before enabling functions. Exiting");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
53
61
|
const haveBackend = await backend.existingBackend({ projectId });
|
|
54
62
|
const endpointsToUpdate = backend
|
|
55
63
|
.allEndpoints(haveBackend)
|
package/lib/commands/index.js
CHANGED
|
@@ -137,6 +137,8 @@ function load(client) {
|
|
|
137
137
|
client.init = loadCommand("init");
|
|
138
138
|
if (experiments.isEnabled("internaltesting")) {
|
|
139
139
|
client.internaltesting = {};
|
|
140
|
+
client.internaltesting.frameworks = {};
|
|
141
|
+
client.internaltesting.frameworks.compose = loadCommand("internaltesting-frameworks-compose");
|
|
140
142
|
client.internaltesting.functions = {};
|
|
141
143
|
client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover");
|
|
142
144
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const logger_1 = require("../logger");
|
|
6
|
+
const driver_1 = require("../frameworks/compose/driver");
|
|
7
|
+
const compose_1 = require("../frameworks/compose");
|
|
8
|
+
const error_1 = require("../error");
|
|
9
|
+
exports.command = new command_1.Command("internaltesting:frameworks:compose")
|
|
10
|
+
.option("-m, --mode <mode>", "Composer mode (local or docker)", "local")
|
|
11
|
+
.description("compose framework in current directory")
|
|
12
|
+
.action((options) => {
|
|
13
|
+
const mode = options.mode;
|
|
14
|
+
if (!driver_1.SUPPORTED_MODES.includes(mode)) {
|
|
15
|
+
throw new error_1.FirebaseError(`Unsupported mode ${mode}. Supported modes are [${driver_1.SUPPORTED_MODES.join(", ")}]`);
|
|
16
|
+
}
|
|
17
|
+
const bundle = (0, compose_1.compose)(mode);
|
|
18
|
+
logger_1.logger.info(JSON.stringify(bundle, null, 2));
|
|
19
|
+
return {};
|
|
20
|
+
});
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InlineExecutor = exports.QueueExecutor = void 0;
|
|
3
|
+
exports.InlineExecutor = exports.QueueExecutor = exports.DEFAULT_RETRY_CODES = void 0;
|
|
4
4
|
const queue_1 = require("../../../throttler/queue");
|
|
5
|
-
|
|
5
|
+
exports.DEFAULT_RETRY_CODES = [429, 409, 503];
|
|
6
|
+
function parseErrorCode(err) {
|
|
6
7
|
var _a, _b, _c, _d, _e, _f;
|
|
8
|
+
return (err.status ||
|
|
9
|
+
err.code ||
|
|
10
|
+
((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) ||
|
|
11
|
+
((_c = err.original) === null || _c === void 0 ? void 0 : _c.code) ||
|
|
12
|
+
((_f = (_e = (_d = err.original) === null || _d === void 0 ? void 0 : _d.context) === null || _e === void 0 ? void 0 : _e.response) === null || _f === void 0 ? void 0 : _f.statusCode));
|
|
13
|
+
}
|
|
14
|
+
async function handler(op) {
|
|
7
15
|
try {
|
|
8
16
|
op.result = await op.func();
|
|
9
17
|
}
|
|
10
18
|
catch (err) {
|
|
11
|
-
const code = err
|
|
12
|
-
|
|
13
|
-
((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) ||
|
|
14
|
-
((_c = err.original) === null || _c === void 0 ? void 0 : _c.code) ||
|
|
15
|
-
((_f = (_e = (_d = err.original) === null || _d === void 0 ? void 0 : _d.context) === null || _e === void 0 ? void 0 : _e.response) === null || _f === void 0 ? void 0 : _f.statusCode);
|
|
16
|
-
if (code === 429 || code === 409 || code === 503) {
|
|
19
|
+
const code = parseErrorCode(err);
|
|
20
|
+
if (op.retryCodes.includes(code)) {
|
|
17
21
|
throw err;
|
|
18
22
|
}
|
|
23
|
+
err.code = code;
|
|
19
24
|
op.error = err;
|
|
20
25
|
}
|
|
21
26
|
return;
|
|
@@ -24,8 +29,12 @@ class QueueExecutor {
|
|
|
24
29
|
constructor(options) {
|
|
25
30
|
this.queue = new queue_1.Queue(Object.assign(Object.assign({}, options), { handler }));
|
|
26
31
|
}
|
|
27
|
-
async run(func) {
|
|
28
|
-
const
|
|
32
|
+
async run(func, opts) {
|
|
33
|
+
const retryCodes = (opts === null || opts === void 0 ? void 0 : opts.retryCodes) || exports.DEFAULT_RETRY_CODES;
|
|
34
|
+
const op = {
|
|
35
|
+
func,
|
|
36
|
+
retryCodes,
|
|
37
|
+
};
|
|
29
38
|
await this.queue.run(op);
|
|
30
39
|
if (op.error) {
|
|
31
40
|
throw op.error;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Fabricator = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
|
+
const executor_1 = require("./executor");
|
|
5
6
|
const error_1 = require("../../../error");
|
|
6
7
|
const sourceTokenScraper_1 = require("./sourceTokenScraper");
|
|
7
8
|
const timer_1 = require("./timer");
|
|
@@ -44,6 +45,7 @@ const eventarcPollerOptions = {
|
|
|
44
45
|
masterTimeout: 25 * 60 * 1000,
|
|
45
46
|
maxBackoff: 10000,
|
|
46
47
|
};
|
|
48
|
+
const CLOUD_RUN_RESOURCE_EXHAUSTED_CODE = 8;
|
|
47
49
|
const rethrowAs = (endpoint, op) => (err) => {
|
|
48
50
|
logger_1.logger.error(err.message);
|
|
49
51
|
throw new reporter.DeploymentError(endpoint, op, err);
|
|
@@ -269,12 +271,24 @@ class Fabricator {
|
|
|
269
271
|
})
|
|
270
272
|
.catch(rethrowAs(endpoint, "upsert eventarc channel"));
|
|
271
273
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
let resultFunction = null;
|
|
275
|
+
while (!resultFunction) {
|
|
276
|
+
resultFunction = await this.functionExecutor
|
|
277
|
+
.run(async () => {
|
|
278
|
+
const op = await gcfV2.createFunction(apiFunction);
|
|
279
|
+
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
|
|
280
|
+
})
|
|
281
|
+
.catch(async (err) => {
|
|
282
|
+
if (err.code === CLOUD_RUN_RESOURCE_EXHAUSTED_CODE) {
|
|
283
|
+
await this.deleteV2Function(endpoint);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
logger_1.logger.error(err.message);
|
|
288
|
+
throw new reporter.DeploymentError(endpoint, "create", err);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
278
292
|
endpoint.uri = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.uri;
|
|
279
293
|
const serviceName = (_e = resultFunction.serviceConfig) === null || _e === void 0 ? void 0 : _e.service;
|
|
280
294
|
endpoint.runServiceId = utils.last(serviceName === null || serviceName === void 0 ? void 0 : serviceName.split("/"));
|
|
@@ -367,7 +381,7 @@ class Fabricator {
|
|
|
367
381
|
.run(async () => {
|
|
368
382
|
const op = await gcfV2.updateFunction(apiFunction);
|
|
369
383
|
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
|
|
370
|
-
})
|
|
384
|
+
}, { retryCodes: [...executor_1.DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] })
|
|
371
385
|
.catch(rethrowAs(endpoint, "update"));
|
|
372
386
|
endpoint.uri = (_c = resultFunction.serviceConfig) === null || _c === void 0 ? void 0 : _c.uri;
|
|
373
387
|
const serviceName = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.service;
|
|
@@ -414,7 +428,7 @@ class Fabricator {
|
|
|
414
428
|
const op = await gcfV2.deleteFunction(fnName);
|
|
415
429
|
const pollerOptions = Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
|
|
416
430
|
await poller.pollOperation(pollerOptions);
|
|
417
|
-
})
|
|
431
|
+
}, { retryCodes: [...executor_1.DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] })
|
|
418
432
|
.catch(rethrowAs(endpoint, "delete"));
|
|
419
433
|
}
|
|
420
434
|
async setRunTraits(serviceName, endpoint) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.discover = void 0;
|
|
4
|
+
function discover() {
|
|
5
|
+
return {
|
|
6
|
+
baseImage: "us-docker.pkg.dev/firestack-build/test/run:latest",
|
|
7
|
+
environmentVariables: {
|
|
8
|
+
NODE_ENV: "PRODUCTION",
|
|
9
|
+
},
|
|
10
|
+
installCommand: "npm install",
|
|
11
|
+
buildCommand: "npm run build",
|
|
12
|
+
startCommand: "npm run start",
|
|
13
|
+
afterInstall: (b) => {
|
|
14
|
+
console.log("HOOK: AFTER INSTALL");
|
|
15
|
+
return Object.assign(Object.assign({}, b), { version: "v1alpha", notes: "afterInstall" });
|
|
16
|
+
},
|
|
17
|
+
afterBuild(b) {
|
|
18
|
+
console.log("HOOK: AFTER BUILD");
|
|
19
|
+
return Object.assign(Object.assign({}, b), { version: "v1alpha", notes: "afterBuild" });
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
exports.discover = discover;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DockerDriver = exports.DockerfileBuilder = void 0;
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const spawn = require("cross-spawn");
|
|
7
|
+
const hooks_1 = require("./hooks");
|
|
8
|
+
const ADAPTER_SCRIPTS_PATH = "./.firebase/adapters";
|
|
9
|
+
const DOCKER_STAGE_INSTALL = "installer";
|
|
10
|
+
const DOCKER_STAGE_BUILD = "builder";
|
|
11
|
+
class DockerfileBuilder {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.dockerfile = "";
|
|
14
|
+
this.lastStage = "";
|
|
15
|
+
}
|
|
16
|
+
from(image, name) {
|
|
17
|
+
this.dockerfile += `FROM ${image}`;
|
|
18
|
+
if (name) {
|
|
19
|
+
this.dockerfile += ` AS ${name}`;
|
|
20
|
+
this.lastStage = name;
|
|
21
|
+
}
|
|
22
|
+
this.dockerfile += "\n";
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
fromLastStage(name) {
|
|
26
|
+
return this.from(this.lastStage, name);
|
|
27
|
+
}
|
|
28
|
+
tempFrom(image, name) {
|
|
29
|
+
this.dockerfile += `FROM ${image}`;
|
|
30
|
+
if (name) {
|
|
31
|
+
this.dockerfile += ` AS ${name}`;
|
|
32
|
+
}
|
|
33
|
+
this.dockerfile += "\n";
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
workdir(dir) {
|
|
37
|
+
this.dockerfile += `WORKDIR ${dir}\n`;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
copyForFirebase(src, dest, from) {
|
|
41
|
+
if (from) {
|
|
42
|
+
this.dockerfile += `COPY --chown=firebase:firebase --from=${from} ${src} ${dest}\n`;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
this.dockerfile += `COPY --chown=firebase:firebase ${src} ${dest}\n`;
|
|
46
|
+
}
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
copyFrom(src, dest, from) {
|
|
50
|
+
this.dockerfile += `COPY --from=${from} ${src} ${dest}\n`;
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
run(cmd, mount) {
|
|
54
|
+
if (mount) {
|
|
55
|
+
this.dockerfile += `RUN --mount=${mount} ${cmd}\n`;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this.dockerfile += `RUN ${cmd}\n`;
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
env(key, value) {
|
|
63
|
+
this.dockerfile += `ENV ${key}="${value}"\n`;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
envs(envs) {
|
|
67
|
+
for (const [key, value] of Object.entries(envs)) {
|
|
68
|
+
this.env(key, value);
|
|
69
|
+
}
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
cmd(cmds) {
|
|
73
|
+
this.dockerfile += `CMD [${cmds.map((c) => `"${c}"`).join(", ")}]\n`;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
user(user) {
|
|
77
|
+
this.dockerfile += `USER ${user}\n`;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
toString() {
|
|
81
|
+
return this.dockerfile;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.DockerfileBuilder = DockerfileBuilder;
|
|
85
|
+
class DockerDriver {
|
|
86
|
+
constructor(spec) {
|
|
87
|
+
this.spec = spec;
|
|
88
|
+
this.dockerfileBuilder = new DockerfileBuilder();
|
|
89
|
+
this.dockerfileBuilder.from(spec.baseImage, "base").user("firebase");
|
|
90
|
+
}
|
|
91
|
+
execDockerPush(args) {
|
|
92
|
+
console.log(`executing docker build: ${args.join(" ")}`);
|
|
93
|
+
return spawn.sync("docker", ["push", ...args], {
|
|
94
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
execDockerBuild(args, contextDir) {
|
|
98
|
+
console.log(`executing docker build: ${args.join(" ")} ${contextDir}`);
|
|
99
|
+
console.log(this.dockerfileBuilder.toString());
|
|
100
|
+
return spawn.sync("docker", ["buildx", "build", ...args, "-f", "-", contextDir], {
|
|
101
|
+
env: Object.assign(Object.assign({}, process.env), this.spec.environmentVariables),
|
|
102
|
+
input: this.dockerfileBuilder.toString(),
|
|
103
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
buildStage(stage, contextDir, tag) {
|
|
107
|
+
console.log(`Building stage: ${stage}`);
|
|
108
|
+
const args = ["--target", stage];
|
|
109
|
+
if (tag) {
|
|
110
|
+
args.push("--tag", tag);
|
|
111
|
+
}
|
|
112
|
+
const ret = this.execDockerBuild(args, contextDir);
|
|
113
|
+
if (ret.error || ret.status !== 0) {
|
|
114
|
+
throw new Error(`Failed to execute stage ${stage}: error=${ret.error} status=${ret.status}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exportBundle(stage, contextDir) {
|
|
118
|
+
const exportStage = `${stage}-export`;
|
|
119
|
+
this.dockerfileBuilder
|
|
120
|
+
.tempFrom("scratch", exportStage)
|
|
121
|
+
.copyFrom(hooks_1.BUNDLE_PATH, "/bundle.json", stage);
|
|
122
|
+
const ret = this.execDockerBuild(["--target", exportStage, "--output", ".firebase/.output"], contextDir);
|
|
123
|
+
if (ret.error || ret.status !== 0) {
|
|
124
|
+
throw new Error(`Failed to export bundle ${stage}: error=${ret.error} status=${ret.status}`);
|
|
125
|
+
}
|
|
126
|
+
return JSON.parse(fs.readFileSync("./.firebase/.output/bundle.json", "utf8"));
|
|
127
|
+
}
|
|
128
|
+
install() {
|
|
129
|
+
this.dockerfileBuilder
|
|
130
|
+
.fromLastStage(DOCKER_STAGE_INSTALL)
|
|
131
|
+
.workdir("/home/firebase/app")
|
|
132
|
+
.envs(this.spec.environmentVariables || {})
|
|
133
|
+
.copyForFirebase("package.json", ".")
|
|
134
|
+
.run(this.spec.installCommand);
|
|
135
|
+
this.buildStage(DOCKER_STAGE_INSTALL, ".");
|
|
136
|
+
}
|
|
137
|
+
build() {
|
|
138
|
+
this.dockerfileBuilder
|
|
139
|
+
.fromLastStage(DOCKER_STAGE_BUILD)
|
|
140
|
+
.copyForFirebase(".", ".")
|
|
141
|
+
.run(this.spec.buildCommand);
|
|
142
|
+
this.buildStage(DOCKER_STAGE_BUILD, ".");
|
|
143
|
+
}
|
|
144
|
+
export(bundle) {
|
|
145
|
+
var _a;
|
|
146
|
+
const startCmd = (_a = bundle.server) === null || _a === void 0 ? void 0 : _a.start.cmd;
|
|
147
|
+
if (startCmd) {
|
|
148
|
+
const exportStage = "exporter";
|
|
149
|
+
this.dockerfileBuilder
|
|
150
|
+
.from(this.spec.baseImage, exportStage)
|
|
151
|
+
.workdir("/home/firebase/app")
|
|
152
|
+
.copyForFirebase("/home/firebase/app", ".", DOCKER_STAGE_BUILD)
|
|
153
|
+
.cmd(startCmd);
|
|
154
|
+
const imageName = `us-docker.pkg.dev/${process.env.PROJECT_ID}/test/demo-nodappe`;
|
|
155
|
+
this.buildStage(exportStage, ".", imageName);
|
|
156
|
+
const ret = this.execDockerPush([imageName]);
|
|
157
|
+
if (ret.error || ret.status !== 0) {
|
|
158
|
+
throw new Error(`Failed to push image ${imageName}: error=${ret.error} status=${ret.status}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
execHook(bundle, hook) {
|
|
163
|
+
const hookScript = `hook-${Date.now()}.js`;
|
|
164
|
+
const hookScriptSrc = (0, hooks_1.genHookScript)(bundle, hook);
|
|
165
|
+
if (!fs.existsSync(ADAPTER_SCRIPTS_PATH)) {
|
|
166
|
+
fs.mkdirSync(ADAPTER_SCRIPTS_PATH, { recursive: true });
|
|
167
|
+
}
|
|
168
|
+
fs.writeFileSync(path.join(ADAPTER_SCRIPTS_PATH, hookScript), hookScriptSrc);
|
|
169
|
+
const hookStage = path.basename(hookScript, ".js");
|
|
170
|
+
this.dockerfileBuilder
|
|
171
|
+
.fromLastStage(hookStage)
|
|
172
|
+
.run(`NODE_PATH=./node_modules node /framework/adapters/${hookScript}`, `source=${ADAPTER_SCRIPTS_PATH},target=/framework/adapters`);
|
|
173
|
+
this.buildStage(hookStage, ".");
|
|
174
|
+
return this.exportBundle(hookStage, ".");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.DockerDriver = DockerDriver;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.genHookScript = exports.BUNDLE_PATH = void 0;
|
|
4
|
+
exports.BUNDLE_PATH = "/home/firebase/app/.firebase/.output/bundle.json";
|
|
5
|
+
function genHookScript(bundle, hook) {
|
|
6
|
+
let hookSrc = hook.toString().trimLeft();
|
|
7
|
+
if (!hookSrc.startsWith("(") && !hookSrc.startsWith("function ")) {
|
|
8
|
+
hookSrc = `function ${hookSrc}`;
|
|
9
|
+
}
|
|
10
|
+
return `
|
|
11
|
+
const fs = require("node:fs");
|
|
12
|
+
const path = require("node:path");
|
|
13
|
+
|
|
14
|
+
const bundleDir = path.dirname("${exports.BUNDLE_PATH}");
|
|
15
|
+
if (!fs.existsSync(bundleDir)) {
|
|
16
|
+
fs.mkdirSync(bundleDir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
const bundle = (${hookSrc})(${JSON.stringify(bundle)});
|
|
19
|
+
fs.writeFileSync("${exports.BUNDLE_PATH}", JSON.stringify(bundle));
|
|
20
|
+
`;
|
|
21
|
+
}
|
|
22
|
+
exports.genHookScript = genHookScript;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDriver = exports.SUPPORTED_MODES = void 0;
|
|
4
|
+
const local_1 = require("./local");
|
|
5
|
+
const docker_1 = require("./docker");
|
|
6
|
+
exports.SUPPORTED_MODES = ["local", "docker"];
|
|
7
|
+
function getDriver(mode, app) {
|
|
8
|
+
if (mode === "local") {
|
|
9
|
+
return new local_1.LocalDriver(app);
|
|
10
|
+
}
|
|
11
|
+
else if (mode === "docker") {
|
|
12
|
+
return new docker_1.DockerDriver(app);
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`Unsupported mode ${mode}`);
|
|
15
|
+
}
|
|
16
|
+
exports.getDriver = getDriver;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalDriver = void 0;
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const spawn = require("cross-spawn");
|
|
6
|
+
const hooks_1 = require("./hooks");
|
|
7
|
+
class LocalDriver {
|
|
8
|
+
constructor(spec) {
|
|
9
|
+
this.spec = spec;
|
|
10
|
+
}
|
|
11
|
+
execCmd(cmd, args) {
|
|
12
|
+
const ret = spawn.sync(cmd, args, {
|
|
13
|
+
env: Object.assign(Object.assign({}, process.env), this.spec.environmentVariables),
|
|
14
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
15
|
+
});
|
|
16
|
+
if (ret.error) {
|
|
17
|
+
throw ret.error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
install() {
|
|
21
|
+
const [cmd, ...args] = this.spec.installCommand.split(" ");
|
|
22
|
+
this.execCmd(cmd, args);
|
|
23
|
+
}
|
|
24
|
+
build() {
|
|
25
|
+
const [cmd, ...args] = this.spec.buildCommand.split(" ");
|
|
26
|
+
this.execCmd(cmd, args);
|
|
27
|
+
}
|
|
28
|
+
export(bundle) {
|
|
29
|
+
}
|
|
30
|
+
execHook(bundle, hook) {
|
|
31
|
+
const script = (0, hooks_1.genHookScript)(bundle, hook);
|
|
32
|
+
this.execCmd("node", ["-e", script]);
|
|
33
|
+
if (!fs.existsSync(hooks_1.BUNDLE_PATH)) {
|
|
34
|
+
console.warn(`Expected hook to generate app bundle at ${hooks_1.BUNDLE_PATH} but got nothing.`);
|
|
35
|
+
console.warn("Returning original bundle.");
|
|
36
|
+
return bundle;
|
|
37
|
+
}
|
|
38
|
+
const newBundle = JSON.parse(fs.readFileSync(hooks_1.BUNDLE_PATH, "utf8"));
|
|
39
|
+
return newBundle;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.LocalDriver = LocalDriver;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compose = void 0;
|
|
4
|
+
const driver_1 = require("./driver");
|
|
5
|
+
const discover_1 = require("./discover");
|
|
6
|
+
function compose(mode) {
|
|
7
|
+
let bundle = { version: "v1alpha" };
|
|
8
|
+
const spec = (0, discover_1.discover)();
|
|
9
|
+
const driver = (0, driver_1.getDriver)(mode, spec);
|
|
10
|
+
if (spec.startCommand) {
|
|
11
|
+
bundle.server = {
|
|
12
|
+
start: {
|
|
13
|
+
cmd: spec.startCommand.split(" "),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
driver.install();
|
|
18
|
+
if (spec.afterInstall) {
|
|
19
|
+
bundle = driver.execHook(bundle, spec.afterInstall);
|
|
20
|
+
}
|
|
21
|
+
driver.build();
|
|
22
|
+
if (spec.afterBuild) {
|
|
23
|
+
bundle = driver.execHook(bundle, spec.afterBuild);
|
|
24
|
+
}
|
|
25
|
+
if (bundle.server) {
|
|
26
|
+
driver.export(bundle);
|
|
27
|
+
}
|
|
28
|
+
return bundle;
|
|
29
|
+
}
|
|
30
|
+
exports.compose = compose;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Driver = void 0;
|
|
4
|
+
class Driver {
|
|
5
|
+
constructor(spec) {
|
|
6
|
+
this.spec = spec;
|
|
7
|
+
}
|
|
8
|
+
install() {
|
|
9
|
+
throw new Error("install() not implemented");
|
|
10
|
+
}
|
|
11
|
+
build() {
|
|
12
|
+
throw new Error("build() not implemented");
|
|
13
|
+
}
|
|
14
|
+
export(bundle) {
|
|
15
|
+
throw new Error("export() not implemented");
|
|
16
|
+
}
|
|
17
|
+
execHook(bundle, hook) {
|
|
18
|
+
throw new Error("execHook() not implemented");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.Driver = Driver;
|
package/lib/functions/secrets.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
|
|
3
|
+
exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.ensureApi = exports.labels = exports.isFirebaseManaged = void 0;
|
|
4
4
|
const utils = require("../utils");
|
|
5
5
|
const poller = require("../operation-poller");
|
|
6
6
|
const gcf = require("../gcp/cloudfunctions");
|
|
7
|
+
const ensureApiEnabled = require("../ensureApiEnabled");
|
|
7
8
|
const secretManager_1 = require("../gcp/secretManager");
|
|
8
9
|
const error_1 = require("../error");
|
|
9
10
|
const utils_1 = require("../utils");
|
|
@@ -12,6 +13,7 @@ const env_1 = require("./env");
|
|
|
12
13
|
const logger_1 = require("../logger");
|
|
13
14
|
const api_1 = require("../api");
|
|
14
15
|
const functional_1 = require("../functional");
|
|
16
|
+
const projectUtils_1 = require("../projectUtils");
|
|
15
17
|
const FIREBASE_MANAGED = "firebase-managed";
|
|
16
18
|
function isFirebaseManaged(secret) {
|
|
17
19
|
return Object.keys(secret.labels || []).includes(FIREBASE_MANAGED);
|
|
@@ -27,6 +29,11 @@ function toUpperSnakeCase(key) {
|
|
|
27
29
|
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
28
30
|
.toUpperCase();
|
|
29
31
|
}
|
|
32
|
+
function ensureApi(options) {
|
|
33
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
34
|
+
return ensureApiEnabled.ensure(projectId, "secretmanager.googleapis.com", "runtimeconfig", true);
|
|
35
|
+
}
|
|
36
|
+
exports.ensureApi = ensureApi;
|
|
30
37
|
async function ensureValidKey(key, options) {
|
|
31
38
|
const transformedKey = toUpperSnakeCase(key);
|
|
32
39
|
if (transformedKey !== key) {
|