-
Notifications
You must be signed in to change notification settings - Fork 903
/
deploy.ts
151 lines (137 loc) · 4.65 KB
/
deploy.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
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
144
145
146
147
148
149
150
151
import { setGracefulCleanup } from "tmp";
import * as clc from "colorette";
import * as fs from "fs";
import { checkHttpIam } from "./checkIam";
import { logSuccess, logWarning } from "../../utils";
import { Options } from "../../options";
import { configForCodebase } from "../../functions/projectConfig";
import * as args from "./args";
import * as gcs from "../../gcp/storage";
import * as gcf from "../../gcp/cloudfunctions";
import * as gcfv2 from "../../gcp/cloudfunctionsv2";
import * as backend from "./backend";
import { findEndpoint } from "./backend";
setGracefulCleanup();
async function uploadSourceV1(
projectId: string,
source: args.Source,
wantBackend: backend.Backend
): Promise<string | undefined> {
const v1Endpoints = backend.allEndpoints(wantBackend).filter((e) => e.platform === "gcfv1");
if (v1Endpoints.length === 0) {
return;
}
const region = v1Endpoints[0].region; // Just pick a region to upload the source.
const uploadUrl = await gcf.generateUploadUrl(projectId, region);
const uploadOpts = {
file: source.functionsSourceV1!,
stream: fs.createReadStream(source.functionsSourceV1!),
};
await gcs.upload(uploadOpts, uploadUrl, {
"x-goog-content-length-range": "0,104857600",
});
return uploadUrl;
}
async function uploadSourceV2(
projectId: string,
source: args.Source,
wantBackend: backend.Backend
): Promise<gcfv2.StorageSource | undefined> {
const v2Endpoints = backend.allEndpoints(wantBackend).filter((e) => e.platform === "gcfv2");
if (v2Endpoints.length === 0) {
return;
}
const region = v2Endpoints[0].region; // Just pick a region to upload the source.
const res = await gcfv2.generateUploadUrl(projectId, region);
const uploadOpts = {
file: source.functionsSourceV2!,
stream: fs.createReadStream(source.functionsSourceV2!),
};
await gcs.upload(uploadOpts, res.uploadUrl);
return res.storageSource;
}
async function uploadCodebase(
context: args.Context,
codebase: string,
wantBackend: backend.Backend
): Promise<void> {
const source = context.sources?.[codebase];
if (!source || (!source.functionsSourceV1 && !source.functionsSourceV2)) {
return;
}
const uploads: Promise<unknown>[] = [];
try {
uploads.push(uploadSourceV1(context.projectId, source, wantBackend));
uploads.push(uploadSourceV2(context.projectId, source, wantBackend));
const [sourceUrl, storage] = await Promise.all(uploads);
if (sourceUrl) {
source.sourceUrl = sourceUrl as string;
}
if (storage) {
source.storage = storage as gcfv2.StorageSource;
}
const sourceDir = configForCodebase(context.config!, codebase).source;
if (uploads.length) {
logSuccess(
`${clc.green(clc.bold("functions:"))} ${clc.bold(sourceDir)} folder uploaded successfully`
);
}
} catch (err: any) {
logWarning(clc.yellow("functions:") + " Upload Error: " + err.message);
throw err;
}
}
/**
* The "deploy" stage for Cloud Functions -- uploads source code to a generated URL.
* @param context The deploy context.
* @param options The command-wide options object.
* @param payload The deploy payload.
*/
export async function deploy(
context: args.Context,
options: Options,
payload: args.Payload
): Promise<void> {
if (!context.config) {
return;
}
if (!payload.functions) {
return;
}
await checkHttpIam(context, options, payload);
const uploads: Promise<void>[] = [];
for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) {
if (shouldUploadBeSkipped(context, wantBackend, haveBackend)) {
continue;
}
uploads.push(uploadCodebase(context, codebase, wantBackend));
}
await Promise.all(uploads);
}
/**
* @return True IFF wantBackend + haveBackend are the same
*/
export function shouldUploadBeSkipped(
context: args.Context,
wantBackend: backend.Backend,
haveBackend: backend.Backend
): boolean {
// If function targets are specified by --only flag, assume that function will be deployed
// and go ahead and upload the source.
if (context.filters && context.filters.length > 0) {
return false;
}
const wantEndpoints = backend.allEndpoints(wantBackend);
const haveEndpoints = backend.allEndpoints(haveBackend);
// Mismatching length immediately tells us they are different, and we should not skip.
if (wantEndpoints.length !== haveEndpoints.length) {
return false;
}
return wantEndpoints.every((wantEndpoint) => {
const haveEndpoint = findEndpoint(haveBackend, (endpoint) => endpoint.id === wantEndpoint.id);
if (!haveEndpoint) {
return false;
}
return haveEndpoint.hash && wantEndpoint.hash && haveEndpoint.hash === wantEndpoint.hash;
});
}