forked from firebase/firebase-tools
/
index.ts
109 lines (96 loc) · 3.69 KB
/
index.ts
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
import * as clc from "cli-color";
import { Options } from "../../../options";
import { logger } from "../../../logger";
import { reduceFlat } from "../../../functional";
import * as args from "../args";
import * as backend from "../backend";
import * as containerCleaner from "../containerCleaner";
import * as planner from "./planner";
import * as fabricator from "./fabricator";
import * as reporter from "./reporter";
import * as executor from "./executor";
import * as prompts from "../prompts";
import { getAppEngineLocation } from "../../../functionsConfig";
import { getFunctionLabel } from "../functionsDeployHelper";
import { FirebaseError } from "../../../error";
/** Releases new versions of functions to prod. */
export async function release(
context: args.Context,
options: Options,
payload: args.Payload
): Promise<void> {
if (!options.config.has("functions")) {
return;
}
const plan = planner.createDeploymentPlan(
payload.functions!.backend,
await backend.existingBackend(context),
{ filters: context.filters }
);
const fnsToDelete = Object.values(plan)
.map((regionalChanges) => regionalChanges.endpointsToDelete)
.reduce(reduceFlat, []);
const shouldDelete = await prompts.promptForFunctionDeletion(
fnsToDelete,
options.force,
options.nonInteractive
);
if (!shouldDelete) {
for (const change of Object.values(plan)) {
change.endpointsToDelete = [];
}
}
const functionExecutor: executor.QueueExecutor = new executor.QueueExecutor({
retries: 30,
backoff: 20000,
concurrency: 40,
maxBackoff: 40000,
});
const fab = new fabricator.Fabricator({
functionExecutor,
executor: new executor.QueueExecutor({}),
sourceUrl: context.uploadUrl!,
storage: context.storage!,
appEngineLocation: getAppEngineLocation(context.firebaseConfig),
});
const summary = await fab.applyPlan(plan);
await reporter.logAndTrackDeployStats(summary);
reporter.printErrors(summary);
// N.B. Fabricator::applyPlan updates the endpoints it deploys to include the
// uri field. createDeploymentPlan copies endpoints by reference. Both of these
// subtleties are so we can take out a round trip API call to get the latest
// trigger URLs by calling existingBackend again.
printTriggerUrls(payload.functions!.backend);
const haveEndpoints = backend.allEndpoints(payload.functions!.backend);
const deletedEndpoints = Object.values(plan)
.map((r) => r.endpointsToDelete)
.reduce(reduceFlat, []);
const opts: { ar?: containerCleaner.ArtifactRegistryCleaner } = {};
if (!context.artifactRegistryEnabled) {
opts.ar = new containerCleaner.NoopArtifactRegistryCleaner();
}
await containerCleaner.cleanupBuildImages(haveEndpoints, deletedEndpoints, opts);
const allErrors = summary.results.filter((r) => r.error).map((r) => r.error) as Error[];
if (allErrors.length) {
const opts = allErrors.length == 1 ? { original: allErrors[0] } : { children: allErrors };
throw new FirebaseError("There was an error deploying functions", { ...opts, exit: 2 });
}
}
/**
* Prints the URLs of HTTPS functions.
* Caller must eitehr force refresh the backend or assume the fabricator
* has updated the URI of endpoints after deploy.
*/
export function printTriggerUrls(results: backend.Backend): void {
const httpsFunctions = backend.allEndpoints(results).filter(backend.isHttpsTriggered);
if (httpsFunctions.length === 0) {
return;
}
for (const httpsFunc of httpsFunctions) {
if (!httpsFunc.uri) {
logger.debug("Missing URI for HTTPS function in printTriggerUrls. This shouldn't happen");
continue;
}
logger.info(clc.bold("Function URL"), `(${getFunctionLabel(httpsFunc)}):`, httpsFunc.uri);
}
}