@catladder/pipeline 2.9.3 → 2.10.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.
Files changed (36) hide show
  1. package/dist/constants.js +1 -1
  2. package/dist/deploy/cloudRun/createJobs/cloudRunJobs.d.ts +5 -3
  3. package/dist/deploy/cloudRun/createJobs/cloudRunJobs.js +17 -101
  4. package/dist/deploy/cloudRun/createJobs/execute/onDeploy.d.ts +4 -0
  5. package/dist/deploy/cloudRun/createJobs/execute/onDeploy.js +120 -0
  6. package/dist/deploy/cloudRun/createJobs/execute/schedules.d.ts +3 -0
  7. package/dist/deploy/cloudRun/createJobs/execute/schedules.js +173 -0
  8. package/dist/deploy/cloudRun/createJobs/getCloudRunDeployScripts.js +4 -2
  9. package/dist/deploy/cloudRun/createJobs/getCloudRunStopScripts.js +4 -2
  10. package/dist/deploy/cloudRun/index.js +6 -2
  11. package/dist/deploy/cloudRun/utils/cloudRunExecutionUrl.d.ts +10 -0
  12. package/dist/deploy/cloudRun/utils/cloudRunExecutionUrl.js +16 -0
  13. package/dist/deploy/cloudRun/utils/jobName.d.ts +5 -1
  14. package/dist/deploy/cloudRun/utils/jobName.js +14 -2
  15. package/dist/deploy/types/googleCloudRun.d.ts +95 -1
  16. package/dist/tsconfig.tsbuildinfo +1 -1
  17. package/examples/__snapshots__/cloud-run-with-sql-legacy-jobs.test.ts.snap +1697 -0
  18. package/examples/__snapshots__/cloud-run-with-sql.test.ts.snap +133 -70
  19. package/examples/cloud-run-no-service.ts +6 -0
  20. package/examples/cloud-run-post-stop-job.ts +7 -0
  21. package/examples/cloud-run-service-custom-vpc-connector.ts +0 -1
  22. package/examples/cloud-run-service-custom-vpc.ts +0 -1
  23. package/examples/cloud-run-service-with-volumes.ts +9 -1
  24. package/examples/cloud-run-with-sql-legacy-jobs.test.ts +11 -0
  25. package/examples/cloud-run-with-sql-legacy-jobs.ts +49 -0
  26. package/examples/cloud-run-with-sql.ts +38 -4
  27. package/package.json +1 -1
  28. package/src/deploy/cloudRun/createJobs/cloudRunJobs.ts +16 -155
  29. package/src/deploy/cloudRun/createJobs/execute/onDeploy.ts +112 -0
  30. package/src/deploy/cloudRun/createJobs/execute/schedules.ts +186 -0
  31. package/src/deploy/cloudRun/createJobs/getCloudRunDeployScripts.ts +6 -8
  32. package/src/deploy/cloudRun/createJobs/getCloudRunStopScripts.ts +6 -8
  33. package/src/deploy/cloudRun/index.ts +6 -6
  34. package/src/deploy/cloudRun/utils/cloudRunExecutionUrl.ts +20 -0
  35. package/src/deploy/cloudRun/utils/jobName.ts +16 -1
  36. package/src/deploy/types/googleCloudRun.ts +123 -1
@@ -0,0 +1,186 @@
1
+ import { bashEscape } from "../../../../bash";
2
+ import type {
3
+ BashExpression,
4
+ StringOrBashExpression,
5
+ } from "../../../../bash/BashExpression";
6
+ import type { ComponentContext } from "../../../../types/context";
7
+ import { notNil } from "../../../../utils";
8
+ import type {
9
+ DeployConfigCloudRunExecuteWithSchedule,
10
+ DeployConfigCloudRunJobWithSchedule,
11
+ } from "../../../types";
12
+ import { getCloudRunJobExecuteUrl } from "../../utils/cloudRunExecutionUrl";
13
+ import { createArgsString } from "../../utils/createArgsString";
14
+ import { getFullSchedulerName } from "../../utils/jobName";
15
+ import { getCloudRunJobsWithNames } from "../cloudRunJobs";
16
+ import { gcloudSchedulerCmd, getCloudRunDeployConfig } from "../common";
17
+
18
+ export const getDeleteSchedulesScript = (context: ComponentContext) => {
19
+ const deployConfig = getCloudRunDeployConfig(context);
20
+ const schedules = getSchedules(context);
21
+ const argsString = createArgsString({
22
+ project: deployConfig.projectId,
23
+ location: deployConfig.region,
24
+ });
25
+
26
+ return schedules
27
+ .map(({ fullName }) => {
28
+ return [`${gcloudSchedulerCmd()} jobs delete ${fullName} ${argsString}`];
29
+ })
30
+ .flat();
31
+ };
32
+
33
+ export const getCreateScheduleScript = (
34
+ context: ComponentContext,
35
+ ): string[] => {
36
+ const schedules = getSchedules(context);
37
+ const { region: location, projectId: project } =
38
+ getCloudRunDeployConfig(context);
39
+
40
+ return schedules.map(({ fullName, config }, jobIndex): string => {
41
+ const { uri, ...args } = getSchedulerArgs(config, context);
42
+
43
+ const argsString = createArgsString({
44
+ project,
45
+ location,
46
+ uri: `"$current_job_uri"`,
47
+ ...args,
48
+
49
+ schedule: `"${config.schedule}"`,
50
+ "max-retry-attempts": config.maxRetryAttempts ?? 0,
51
+ });
52
+ return [
53
+ jobIndex === 0
54
+ ? `exist_scheduler_names="$(\n ${gcloudSchedulerCmd()} jobs list --filter='httpTarget.uri ~ ${context.env}.*${context.name}' --format='value(name)' --limit=999 --location='${location}' --project='${project}'\n)"`
55
+ : null,
56
+ `current_job_uri="${uri}"`,
57
+ `current_scheduler_name="${fullName}"`,
58
+ `if grep "$current_scheduler_name" <<<"$exist_scheduler_names" >/dev/null; then`,
59
+ ` ${gcloudSchedulerCmd()} jobs update http "$current_scheduler_name" ${argsString}`,
60
+ `else`,
61
+ ` ${gcloudSchedulerCmd()} jobs create http "$current_scheduler_name" ${argsString}`,
62
+ `fi`,
63
+ ]
64
+ .filter(notNil)
65
+ .join("\n");
66
+ });
67
+ };
68
+
69
+ const getSchedulerArgs = (
70
+ scheduler: DeployConfigCloudRunExecuteWithSchedule,
71
+ context: ComponentContext,
72
+ ): {
73
+ uri: string;
74
+ "http-method"?: string;
75
+ "message-body"?: string;
76
+ "oauth-service-account-email"?: string;
77
+ "oidc-service-account-email"?: string;
78
+ } => {
79
+ if (scheduler.type === "job") {
80
+ const { projectId, region } = getCloudRunDeployConfig(context);
81
+ const body =
82
+ scheduler.args !== undefined
83
+ ? {
84
+ overrides: {
85
+ containerOverrides: [
86
+ scheduler.args?.length > 0
87
+ ? {
88
+ args: scheduler.args,
89
+ }
90
+ : {
91
+ args: [],
92
+ clearArgs: true, // not sure why this is neeeded, but it is
93
+ },
94
+ ],
95
+ },
96
+ }
97
+ : null;
98
+ return {
99
+ uri: getCloudRunJobExecuteUrl(scheduler.job, {
100
+ appFullName: context.environment.fullName,
101
+ projectId,
102
+ region,
103
+ }),
104
+ "message-body": body
105
+ ? '"' + bashEscape(JSON.stringify(body)) + '"'
106
+ : undefined,
107
+ "http-method": "POST",
108
+ "oauth-service-account-email": `"$GCLOUD_PROJECT_NUMBER-compute@developer.gserviceaccount.com"`,
109
+ };
110
+ }
111
+ if (scheduler.type === "http") {
112
+ return {
113
+ uri: scheduler.url,
114
+ "message-body": scheduler.body,
115
+ "http-method": scheduler.method,
116
+ "oidc-service-account-email": `"$GCLOUD_PROJECT_NUMBER-compute@developer.gserviceaccount.com"`,
117
+ };
118
+ }
119
+ throw new Error(`Unknown scheduler type: ${(scheduler as any).type}`);
120
+ };
121
+
122
+ const getSchedules = (
123
+ context: ComponentContext,
124
+ ): {
125
+ name: string;
126
+ fullName: StringOrBashExpression;
127
+ config: DeployConfigCloudRunExecuteWithSchedule;
128
+ }[] => {
129
+ const legacyScheduleJobs = getLegacyJobSchedules(context);
130
+ const schedules = getScheduledExecutes(context);
131
+
132
+ return Object.entries({ ...legacyScheduleJobs, ...schedules }).map(
133
+ ([name, config]) => ({
134
+ name,
135
+ fullName: getFullSchedulerName(context, name),
136
+ config,
137
+ }),
138
+ );
139
+ };
140
+
141
+ const getLegacyJobSchedules = (
142
+ context: ComponentContext,
143
+ ): Record<string, DeployConfigCloudRunExecuteWithSchedule> => {
144
+ const jobsWithNames = getCloudRunJobsWithNames(context);
145
+
146
+ return Object.fromEntries(
147
+ jobsWithNames
148
+ .filter(
149
+ (
150
+ entry,
151
+ ): entry is {
152
+ fullJobName: BashExpression;
153
+ job: DeployConfigCloudRunJobWithSchedule;
154
+ jobName: string;
155
+ } => entry.job.when === "schedule",
156
+ )
157
+ .map(({ job: { maxRetryAttempts, schedule }, jobName }) => {
158
+ const schedulerName = jobName.concat("-scheduler");
159
+ return [
160
+ schedulerName,
161
+ {
162
+ type: "job",
163
+ job: jobName,
164
+ maxRetryAttempts,
165
+ schedule,
166
+ when: "schedule",
167
+ },
168
+ ];
169
+ }),
170
+ );
171
+ };
172
+
173
+ const getScheduledExecutes = (
174
+ context: ComponentContext,
175
+ ): Record<string, DeployConfigCloudRunExecuteWithSchedule> => {
176
+ const deployConfig = getCloudRunDeployConfig(context);
177
+
178
+ return Object.fromEntries(
179
+ Object.entries(deployConfig.execute ?? {}).flatMap(([key, value]) => {
180
+ if (!value || value.when !== "schedule") {
181
+ return [];
182
+ }
183
+ return [[key, value]];
184
+ }),
185
+ );
186
+ };
@@ -8,17 +8,15 @@ import { writeBashYamlToFileScript } from "../../../bash/bashYaml";
8
8
  import { getRemoveOldRevisionsAndImagesCommand } from "../cleanup";
9
9
  import { getDatabaseCreateScript } from "../utils/database";
10
10
  import { gcloudServiceAccountLoginCommands } from "../utils/gcloudServiceAccountLoginCommands";
11
- import {
12
- getCreateScheduleScripts,
13
- getJobCreateScripts,
14
- getJobRunScripts,
15
- } from "./cloudRunJobs";
11
+ import { getJobCreateScripts } from "./cloudRunJobs";
12
+ import { getOnDeployExecuteScript } from "./execute/onDeploy";
16
13
  import { getServiceDeployScript } from "./cloudRunServices";
17
14
  import {
18
15
  getCloudRunDeployConfig,
19
16
  setGoogleProjectNumberScript,
20
17
  } from "./common";
21
18
  import { ENV_VARS_FILENAME } from "./constants";
19
+ import { getCreateScheduleScript } from "./execute/schedules";
22
20
 
23
21
  export function getCloudRunDeployScripts(context: ComponentContext) {
24
22
  const deployConfig = getCloudRunDeployConfig(context);
@@ -47,9 +45,9 @@ export function getCloudRunDeployScripts(context: ComponentContext) {
47
45
  ...(deployConfig.cloudSql
48
46
  ? getDatabaseCreateScript(context, deployConfig) // we create the db, so that we can also delete it afterwards
49
47
  : []),
50
- ...getCreateScheduleScripts(context),
48
+ ...getCreateScheduleScript(context),
51
49
  ...getJobCreateScripts(context),
52
- ...getJobRunScripts(context, "preDeploy"),
50
+ ...getOnDeployExecuteScript(context, "preDeploy"),
53
51
 
54
52
  ...(deployConfig.service !== false
55
53
  ? [getServiceDeployScript(context, deployConfig.service)]
@@ -58,7 +56,7 @@ export function getCloudRunDeployScripts(context: ComponentContext) {
58
56
  ([name, service]) =>
59
57
  getServiceDeployScript(context, service, "-" + name),
60
58
  ),
61
- ...getJobRunScripts(context, "postDeploy"),
59
+ ...getOnDeployExecuteScript(context, "postDeploy"),
62
60
  ]),
63
61
  ...collapseableSection(
64
62
  "cleanup",
@@ -4,11 +4,9 @@ import { getDependencyTrackDeleteScript } from "../../sbom";
4
4
  import { getRemoveOldRevisionsAndImagesCommand } from "../cleanup";
5
5
  import { getDatabaseDeleteScript } from "../utils/database";
6
6
  import { gcloudServiceAccountLoginCommands } from "../utils/gcloudServiceAccountLoginCommands";
7
- import {
8
- getDeleteJobsScripts,
9
- getDeleteSchedulesScripts,
10
- getJobRunScripts,
11
- } from "./cloudRunJobs";
7
+ import { getDeleteJobsScripts } from "./cloudRunJobs";
8
+ import { getOnDeployExecuteScript } from "./execute/onDeploy";
9
+ import { getDeleteSchedulesScript } from "./execute/schedules";
12
10
  import { getServiceDeleteScript } from "./cloudRunServices";
13
11
  import { getCloudRunDeployConfig } from "./common";
14
12
 
@@ -16,13 +14,13 @@ export function getCloudRunStopScripts(context: ComponentContext) {
16
14
  const deployConfig = getCloudRunDeployConfig(context);
17
15
  return [
18
16
  ...gcloudServiceAccountLoginCommands(context),
19
- ...getJobRunScripts(context, "preStop"),
17
+ ...getOnDeployExecuteScript(context, "preStop"),
20
18
  ...(deployConfig.service !== false ? getServiceDeleteScript(context) : []),
21
19
  ...Object.entries(deployConfig.additionalServices ?? {}).flatMap(([name]) =>
22
20
  getServiceDeleteScript(context, name),
23
21
  ),
24
- ...getJobRunScripts(context, "postStop"),
25
- ...getDeleteSchedulesScripts(context),
22
+ ...getOnDeployExecuteScript(context, "postStop"),
23
+ ...getDeleteSchedulesScript(context),
26
24
  ...getDeleteJobsScripts(context),
27
25
  ...(deployConfig.cloudSql && deployConfig.cloudSql.deleteDatabaseOnStop
28
26
  ? getDatabaseDeleteScript(context, deployConfig)
@@ -9,11 +9,11 @@ import type { EnvironmentContext } from "../../types/environmentContext";
9
9
  import { sanitizeForBashVariable } from "../../utils/gitlab";
10
10
  import { getFullDbName } from "../cloudSql/utils";
11
11
  import { createGoogleCloudRunDeployJobs } from "./createJobs";
12
+ import { getCloudRunJobExecuteUrl } from "./utils/cloudRunExecutionUrl";
12
13
  import {
13
14
  DATABASE_JDBC_URL,
14
15
  getDatabaseConnectionString,
15
16
  } from "./utils/database";
16
- import { getCloudRunJobName } from "./utils/jobName";
17
17
 
18
18
  export const GCLOUD_DEPLOY_CREDENTIALS_KEY = "GCLOUD_DEPLOY_credentialsKey";
19
19
 
@@ -103,11 +103,11 @@ export const GCLOUD_RUN_DEPLOY_TYPE: DeployTypeDefinition<DeployConfigCloudRun>
103
103
  ? Object.fromEntries(
104
104
  Object.entries(deployConfigRaw.jobs).map(([name, job]) => [
105
105
  "CLOUD_RUN_JOB_TRIGGER_URL_" + sanitizeForBashVariable(name),
106
- `https://${
107
- deployConfigRaw.region
108
- }-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${
109
- deployConfigRaw.projectId
110
- }/jobs/${getCloudRunJobName(fullName, name)}:run`,
106
+ getCloudRunJobExecuteUrl(name, {
107
+ appFullName: fullName,
108
+ projectId: deployConfigRaw.projectId,
109
+ region: deployConfigRaw.region,
110
+ }),
111
111
  ]),
112
112
  )
113
113
  : {};
@@ -0,0 +1,20 @@
1
+ import type { StringOrBashExpression } from "../../../bash";
2
+ import { getCloudRunJobName } from "./jobName";
3
+
4
+ export function getCloudRunJobExecuteUrl(
5
+ jobName: string,
6
+ {
7
+ region,
8
+ projectId,
9
+ appFullName,
10
+ }: {
11
+ appFullName: StringOrBashExpression;
12
+ region: string;
13
+ projectId: string;
14
+ },
15
+ ): string {
16
+ const uriBase = `https://${region}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${projectId}/jobs`;
17
+ const fullJobName = getCloudRunJobName(appFullName, jobName);
18
+
19
+ return `${uriBase}/${fullJobName}:run`;
20
+ }
@@ -1,7 +1,22 @@
1
1
  import type { StringOrBashExpression } from "../../../bash/BashExpression";
2
+ import type { ComponentContext } from "../../../types";
2
3
 
3
4
  export const getCloudRunJobName = (
4
5
  fullAppName: StringOrBashExpression,
5
- jobName: string,
6
+ jobName: StringOrBashExpression,
6
7
  ): StringOrBashExpression =>
7
8
  fullAppName.toLowerCase().concat("-" + jobName.toLowerCase());
9
+
10
+ export const getFullJobName = (
11
+ context: ComponentContext,
12
+ name: StringOrBashExpression,
13
+ ) => getCloudRunJobName(context.environment.fullName, name);
14
+
15
+ export const getCloudRunSchedulerName = (
16
+ fullAppName: StringOrBashExpression,
17
+ schedulerName: string,
18
+ ): StringOrBashExpression =>
19
+ fullAppName.toLowerCase().concat("-" + schedulerName.toLowerCase());
20
+
21
+ export const getFullSchedulerName = (context: ComponentContext, name: string) =>
22
+ getCloudRunSchedulerName(context.environment.fullName, name);
@@ -196,6 +196,11 @@ export type DeployConfigCloudRunJobBase = {
196
196
  */
197
197
  command: string | string[];
198
198
 
199
+ /**
200
+ * optional, specify args. Those get overwritten by job executions if specified there
201
+ */
202
+ args?: string[];
203
+
199
204
  /**
200
205
  * custom image to use. Defaults to the image from the build
201
206
  */
@@ -232,25 +237,39 @@ type DayOfWeek = string;
232
237
  type Month = string;
233
238
  export type DeployConfigCloudRunJobWithSchedule =
234
239
  DeployConfigCloudRunJobBase & {
240
+ /**
241
+ * @deprecated use `execute` instead
242
+ */
235
243
  when: "schedule";
244
+ /**
245
+ * @deprecated use `run` instead
246
+ */
236
247
  schedule: `${Minute} ${Hour} ${DayOfMonth} ${Month} ${DayOfWeek}`;
237
248
  /**
238
249
  * Max number of retries of the cloud scheduler.
239
250
  * Note: the task itself is never retried.
240
251
  *
241
252
  * defaults to 0 (no retries)
253
+ *
254
+ * @deprecated use `run` instead
242
255
  */
243
256
  maxRetryAttempts?: 0 | 1 | 2 | 3 | 4 | 5;
244
257
  };
245
258
 
246
259
  export type DeployConfigCloudRunJobNormal = DeployConfigCloudRunJobBase & {
247
- when: "manual" | "preDeploy" | "postDeploy" | "preStop" | "postStop";
248
260
  /**
249
261
  * wait for completion of the job on preDeploy and postDeploy
250
262
  *
251
263
  * has no effect on preStop and postStop (which always wait for completion)
264
+ *
265
+ *
266
+ * @deprecated use `run` instead
252
267
  */
253
268
  waitForCompletion?: boolean;
269
+ /**
270
+ * @deprecated use `run` instead
271
+ */
272
+ when?: "manual" | "preDeploy" | "postDeploy" | "preStop" | "postStop";
254
273
  };
255
274
 
256
275
  export type DeployConfigCloudRunWithVolumes = {
@@ -304,6 +323,11 @@ export type DeployConfigCloudRun = {
304
323
  */
305
324
  cloudSql?: DeployConfigCloudRunCloudSql | false;
306
325
 
326
+ /**
327
+ * execute jobs on deploy or execute jobs and http requests on a schedule
328
+ */
329
+ execute?: Record<string, DeployConfigCloudRunExecute | null>;
330
+
307
331
  debug?: boolean;
308
332
  } & DeployConfigBase;
309
333
 
@@ -318,3 +342,101 @@ export type DeployConfigCloudRunVolume = {
318
342
  mountPath: string;
319
343
  readonly?: boolean;
320
344
  };
345
+
346
+ export type DeployConfigCloudRunExecuteJobBase = {
347
+ /**
348
+ * set to "job" to run a cloud run job
349
+ */
350
+ type: "job";
351
+ /**
352
+ * the cloud run job to run
353
+ */
354
+ job: string;
355
+
356
+ /**
357
+ * additional args to pass to the job
358
+ */
359
+ args?: string[];
360
+ };
361
+
362
+ type WithSchedule = {
363
+ /**
364
+ * run the job on a schedule
365
+ */
366
+ when: "schedule";
367
+ /**
368
+ * the cron schedule. See https://crontab.guru/
369
+ */
370
+ schedule: `${Minute} ${Hour} ${DayOfMonth} ${Month} ${DayOfWeek}`;
371
+
372
+ /**
373
+ * Max number of retries of the cloud scheduler.
374
+ * Note: the task itself is never retried.
375
+ *
376
+ * defaults to 0 (no retries)
377
+ */
378
+ maxRetryAttempts?: 0 | 1 | 2 | 3 | 4 | 5;
379
+ };
380
+
381
+ export type DeployConfigCloudRunExecuteJobScheduled =
382
+ DeployConfigCloudRunExecuteJobBase & WithSchedule;
383
+
384
+ export type DeployConfigCloudRunExecuteOnDeploy =
385
+ DeployConfigCloudRunExecuteJobBase &
386
+ (
387
+ | {
388
+ /**
389
+ * run the job before or after the deploy
390
+ */
391
+ when: "preDeploy" | "postDeploy";
392
+ /**
393
+ * whether to wait for completion of the job
394
+ */
395
+ waitForCompletion?: boolean;
396
+ }
397
+ | {
398
+ /**
399
+ * run the job before or after the environment is stopped
400
+ */
401
+ when: "preStop" | "postStop";
402
+ }
403
+ );
404
+
405
+ export type DeployConfigCloudRunExecuteJob =
406
+ | DeployConfigCloudRunExecuteJobScheduled
407
+ | DeployConfigCloudRunExecuteOnDeploy;
408
+
409
+ export type DeployConfigCloudRunExecuteHttp = {
410
+ /**
411
+ * set to "http" to run a http request
412
+ */
413
+ type: "http";
414
+ /**
415
+ * the url to call
416
+ */
417
+ url: string;
418
+
419
+ /**
420
+ * the http-method to use. Defaults to "POST" (as specified by google cloud scheduler)
421
+ */
422
+ method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
423
+
424
+ /**
425
+ * the body to send
426
+ */
427
+ body?: string;
428
+
429
+ /**
430
+ * the headers to send
431
+ */
432
+ headers?: Record<string, string>;
433
+ } & WithSchedule;
434
+
435
+ export type DeployConfigCloudRunExecute =
436
+ | DeployConfigCloudRunExecuteJob
437
+ | DeployConfigCloudRunExecuteHttp;
438
+
439
+ export type DeployConfigCloudRunExecuteWithSchedule = Extract<
440
+ DeployConfigCloudRunExecute,
441
+ WithSchedule
442
+ >;