-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathfunctions-config-export.ts
More file actions
143 lines (125 loc) · 5.49 KB
/
functions-config-export.ts
File metadata and controls
143 lines (125 loc) · 5.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import * as clc from "colorette";
import * as functionsConfig from "../functionsConfig";
import { Command } from "../command";
import { FirebaseError } from "../error";
import { input } from "../prompt";
import { requirePermissions } from "../requirePermissions";
import { logBullet, logWarning, logSuccess } from "../utils";
import { requireConfig } from "../requireConfig";
import { ensureValidKey, ensureSecret } from "../functions/secrets";
import { addVersion, toSecretVersionResourceName } from "../gcp/secretManager";
import { needProjectId } from "../projectUtils";
import { requireAuth } from "../requireAuth";
import { ensureApi } from "../gcp/secretManager";
import type { Options } from "../options";
const RUNTIME_CONFIG_PERMISSIONS = [
"runtimeconfig.configs.list",
"runtimeconfig.configs.get",
"runtimeconfig.variables.list",
"runtimeconfig.variables.get",
];
const SECRET_MANAGER_PERMISSIONS = [
"secretmanager.secrets.create",
"secretmanager.secrets.get",
"secretmanager.secrets.update",
"secretmanager.versions.add",
];
export const command = new Command("functions:config:export")
.description("export environment config as a JSON secret to store in Cloud Secret Manager")
.option("--secret <name>", "name of the secret to create (default: RUNTIME_CONFIG)")
.withForce("use default secret name without prompting")
.before(requireAuth)
.before(ensureApi)
.before(requirePermissions, [...RUNTIME_CONFIG_PERMISSIONS, ...SECRET_MANAGER_PERMISSIONS])
.before(requireConfig)
.action(async (options: Options) => {
const projectId = needProjectId(options);
logBullet(
"This command retrieves your Runtime Config values (accessed via " +
clc.bold("functions.config()") +
") and exports them as a Secret Manager secret.",
);
console.log("");
logBullet(`Fetching your existing functions.config() from ${clc.bold(projectId)}...`);
let configJson: Record<string, unknown>;
try {
configJson = await functionsConfig.materializeAll(projectId);
} catch (err: unknown) {
throw new FirebaseError(
`Failed to fetch runtime config for project ${projectId}. ` +
"Ensure you have the required permissions:\n\t" +
RUNTIME_CONFIG_PERMISSIONS.join("\n\t"),
{ original: err as Error },
);
}
if (Object.keys(configJson).length === 0) {
logSuccess("Your functions.config() is empty. Nothing to do.");
return;
}
logSuccess("Fetched your existing functions.config().");
console.log("");
// Display config in interactive mode
if (!options.nonInteractive) {
logBullet(clc.bold("Configuration to be exported:"));
logWarning("This may contain sensitive data. Do not share this output.");
console.log("");
console.log(JSON.stringify(configJson, null, 2));
console.log("");
}
const defaultSecretName = "RUNTIME_CONFIG";
let secretName = options.secret as string;
if (!secretName) {
secretName = await input({
message: "What would you like to name the new secret for your configuration?",
default: defaultSecretName,
nonInteractive: options.nonInteractive,
force: options.force,
});
}
const key = await ensureValidKey(secretName, options);
await ensureSecret(projectId, key, options);
const secretValue = JSON.stringify(configJson, null, 2);
// Check size limit (64KB)
const sizeInBytes = Buffer.byteLength(secretValue, "utf8");
const maxSize = 64 * 1024; // 64KB
if (sizeInBytes > maxSize) {
throw new FirebaseError(
`Configuration size (${sizeInBytes} bytes) exceeds the 64KB limit for JSON secrets. ` +
"Please reduce the size of your configuration or split it into multiple secrets.",
);
}
const secretVersion = await addVersion(projectId, key, secretValue);
console.log("");
logSuccess(`Created new secret version ${toSecretVersionResourceName(secretVersion)}`);
console.log("");
logBullet(clc.bold("To complete the migration, update your code:"));
console.log("");
console.log(clc.gray(" // Before:"));
console.log(clc.gray(` const functions = require('firebase-functions');`));
console.log(clc.gray(` `));
console.log(clc.gray(` exports.myFunction = functions.https.onRequest((req, res) => {`));
console.log(clc.gray(` const apiKey = functions.config().service.key;`));
console.log(clc.gray(` // ...`));
console.log(clc.gray(` });`));
console.log("");
console.log(clc.gray(" // After:"));
console.log(clc.gray(` const functions = require('firebase-functions');`));
console.log(clc.gray(` const { defineJsonSecret } = require('firebase-functions/params');`));
console.log(clc.gray(` `));
console.log(clc.gray(` const config = defineJsonSecret("${key}");`));
console.log(clc.gray(` `));
console.log(clc.gray(` exports.myFunction = functions`));
console.log(clc.gray(` .runWith({ secrets: [config] }) // Bind secret here`));
console.log(clc.gray(` .https.onRequest((req, res) => {`));
console.log(clc.gray(` const apiKey = config.value().service.key;`));
console.log(clc.gray(` // ...`));
console.log(clc.gray(` });`));
console.log("");
logBullet(
clc.bold("Note: ") +
"defineJsonSecret requires firebase-functions v6.6.0 or later. " +
"Update your package.json if needed.",
);
logBullet("Then deploy your functions:\n " + clc.bold("firebase deploy --only functions"));
return secretName;
});