@catladder/pipeline 2.9.3 → 2.10.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.
Files changed (39) hide show
  1. package/dist/bash/bashEscape.d.ts +4 -1
  2. package/dist/bash/bashEscape.js +7 -2
  3. package/dist/constants.js +1 -1
  4. package/dist/deploy/cloudRun/createJobs/cloudRunJobs.d.ts +5 -3
  5. package/dist/deploy/cloudRun/createJobs/cloudRunJobs.js +17 -101
  6. package/dist/deploy/cloudRun/createJobs/execute/onDeploy.d.ts +4 -0
  7. package/dist/deploy/cloudRun/createJobs/execute/onDeploy.js +120 -0
  8. package/dist/deploy/cloudRun/createJobs/execute/schedules.d.ts +3 -0
  9. package/dist/deploy/cloudRun/createJobs/execute/schedules.js +173 -0
  10. package/dist/deploy/cloudRun/createJobs/getCloudRunDeployScripts.js +4 -2
  11. package/dist/deploy/cloudRun/createJobs/getCloudRunStopScripts.js +4 -2
  12. package/dist/deploy/cloudRun/index.js +6 -2
  13. package/dist/deploy/cloudRun/utils/cloudRunExecutionUrl.d.ts +10 -0
  14. package/dist/deploy/cloudRun/utils/cloudRunExecutionUrl.js +16 -0
  15. package/dist/deploy/cloudRun/utils/jobName.d.ts +5 -1
  16. package/dist/deploy/cloudRun/utils/jobName.js +14 -2
  17. package/dist/deploy/types/googleCloudRun.d.ts +95 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/examples/__snapshots__/cloud-run-with-sql-legacy-jobs.test.ts.snap +1697 -0
  20. package/examples/__snapshots__/cloud-run-with-sql.test.ts.snap +133 -70
  21. package/examples/cloud-run-no-service.ts +6 -0
  22. package/examples/cloud-run-post-stop-job.ts +7 -0
  23. package/examples/cloud-run-service-custom-vpc-connector.ts +0 -1
  24. package/examples/cloud-run-service-custom-vpc.ts +0 -1
  25. package/examples/cloud-run-service-with-volumes.ts +9 -1
  26. package/examples/cloud-run-with-sql-legacy-jobs.test.ts +11 -0
  27. package/examples/cloud-run-with-sql-legacy-jobs.ts +49 -0
  28. package/examples/cloud-run-with-sql.ts +38 -4
  29. package/package.json +1 -1
  30. package/src/bash/bashEscape.ts +7 -1
  31. package/src/deploy/cloudRun/createJobs/cloudRunJobs.ts +16 -155
  32. package/src/deploy/cloudRun/createJobs/execute/onDeploy.ts +112 -0
  33. package/src/deploy/cloudRun/createJobs/execute/schedules.ts +186 -0
  34. package/src/deploy/cloudRun/createJobs/getCloudRunDeployScripts.ts +6 -8
  35. package/src/deploy/cloudRun/createJobs/getCloudRunStopScripts.ts +6 -8
  36. package/src/deploy/cloudRun/index.ts +6 -6
  37. package/src/deploy/cloudRun/utils/cloudRunExecutionUrl.ts +20 -0
  38. package/src/deploy/cloudRun/utils/jobName.ts +16 -1
  39. package/src/deploy/types/googleCloudRun.ts +123 -1
@@ -17,6 +17,12 @@ const config: Config = {
17
17
  jobs: {
18
18
  "alarm-clock": {
19
19
  command: "./wake-up-call",
20
+ },
21
+ },
22
+ execute: {
23
+ "alarm-clock-scheduler": {
24
+ type: "job",
25
+ job: "alarm-clock",
20
26
  when: "schedule",
21
27
  schedule: "0 7 0 0 1-5",
22
28
  },
@@ -18,6 +18,7 @@ const config: Config = {
18
18
  review: {
19
19
  deploy: {
20
20
  jobs: {
21
+ // in this example we only declare the job on the review environment.
21
22
  ["drop-db"]: {
22
23
  command: [
23
24
  "/bin/sh",
@@ -25,6 +26,12 @@ const config: Config = {
25
26
  "mongosh \\$MONGO_URL --eval 'db.dropDatabase()'",
26
27
  ],
27
28
  image: "rtsp/mongosh:latest",
29
+ },
30
+ },
31
+ execute: {
32
+ ["drop-db"]: {
33
+ type: "job",
34
+ job: "drop-db",
28
35
  when: "postStop",
29
36
  },
30
37
  },
@@ -20,7 +20,6 @@ const config: Config = {
20
20
  },
21
21
  jobs: {
22
22
  myjob: {
23
- when: "manual",
24
23
  command: "echo hello",
25
24
  vpcConnector: "my-first-vpc-connector",
26
25
  vpcEgress: "all-traffic",
@@ -21,7 +21,6 @@ const config: Config = {
21
21
  },
22
22
  jobs: {
23
23
  myjob: {
24
- when: "manual",
25
24
  command: "echo hello",
26
25
  network: "my-network",
27
26
  subnet: "my-subnet",
@@ -34,7 +34,7 @@ const config: Config = {
34
34
  jobs: {
35
35
  migrate: {
36
36
  command: "migrate",
37
- when: "postDeploy",
37
+
38
38
  volumes: {
39
39
  myMount: {
40
40
  type: "cloud-storage",
@@ -44,6 +44,14 @@ const config: Config = {
44
44
  },
45
45
  },
46
46
  },
47
+
48
+ execute: {
49
+ migrate: {
50
+ type: "job",
51
+ job: "migrate",
52
+ when: "postDeploy",
53
+ },
54
+ },
47
55
  },
48
56
  },
49
57
  },
@@ -0,0 +1,11 @@
1
+ import { createYamlLocalPipeline } from "./__utils__/helpers";
2
+ import config from "./cloud-run-with-sql-legacy-jobs";
3
+
4
+ /**
5
+ * This test is auto-generated.
6
+ * Modifications will be overwritten on every `yarn test` run!
7
+ */
8
+
9
+ it("matches snapshot for cloud-run-with-sql-legacy-jobs local pipeline YAML", async () => {
10
+ expect(await createYamlLocalPipeline(config)).toMatchSnapshot();
11
+ });
@@ -0,0 +1,49 @@
1
+ import type { Config } from "../src";
2
+
3
+ const config: Config = {
4
+ appName: "test-app",
5
+ customerName: "pan",
6
+ components: {
7
+ api: {
8
+ dir: "api",
9
+ build: {
10
+ type: "node",
11
+ },
12
+ deploy: {
13
+ type: "google-cloudrun",
14
+ projectId: "google-project-id",
15
+ region: "europe-west6",
16
+ // optional, set min and max instances
17
+ // defaults to 0-100
18
+ service: {
19
+ minInstances: 0,
20
+ maxInstances: 5,
21
+ },
22
+ cloudSql: {
23
+ type: "unmanaged",
24
+ instanceConnectionName: "projectId:region:instancename",
25
+ dbUser: "my-user",
26
+ },
27
+ jobs: {
28
+ migration: {
29
+ when: "preDeploy",
30
+ command: "yarn migrate",
31
+ waitForCompletion: true,
32
+ },
33
+ ["send-reminders"]: {
34
+ when: "schedule",
35
+ command: "yarn job:send-reminders",
36
+ schedule: "0 * * * *",
37
+ timeout: "15m",
38
+ },
39
+ },
40
+ },
41
+ },
42
+ },
43
+ };
44
+
45
+ export default config;
46
+
47
+ export const information = {
48
+ title: "Cloud Run: With SQL",
49
+ };
@@ -25,16 +25,50 @@ const config: Config = {
25
25
  dbUser: "my-user",
26
26
  },
27
27
  jobs: {
28
- migration: {
29
- when: "preDeploy",
28
+ migrate: {
30
29
  command: "yarn migrate",
30
+ },
31
+ ["send-reminders"]: {
32
+ command: "yarn job:send-reminders",
33
+ args: ["--verbose=false"],
34
+ },
35
+ },
36
+ execute: {
37
+ migrate: {
38
+ type: "job",
39
+ job: "migrate",
40
+ when: "preDeploy",
31
41
  waitForCompletion: true,
42
+ args: ["--verbose=true", "--create-db"],
43
+ },
44
+ ["send-reminders-admins"]: {
45
+ type: "job",
46
+ when: "schedule",
47
+ job: "send-reminders",
48
+ args: ["--only-admins", "--verbose=true"],
49
+ schedule: "0 * * * *",
32
50
  },
33
51
  ["send-reminders"]: {
52
+ type: "job",
53
+ when: "schedule",
54
+ job: "send-reminders",
55
+ schedule: "0 * * * *",
56
+ },
57
+ ["call-http-endpoint"]: {
58
+ type: "http",
59
+ url: "${ROOT_URL}/myEndpoint",
34
60
  when: "schedule",
35
- command: "yarn job:send-reminders",
36
61
  schedule: "0 * * * *",
37
- timeout: "15m",
62
+ method: "GET",
63
+ },
64
+ },
65
+ },
66
+ env: {
67
+ review: {
68
+ deploy: {
69
+ execute: {
70
+ "send-reminders": null, // disable on this env
71
+ },
38
72
  },
39
73
  },
40
74
  },
package/package.json CHANGED
@@ -53,7 +53,7 @@
53
53
  }
54
54
  ],
55
55
  "license": "MIT",
56
- "version": "2.9.3",
56
+ "version": "2.10.1",
57
57
  "scripts": {
58
58
  "build:tsc": "yarn tsc",
59
59
  "build": "yarn build:compile && yarn build:inline-variables",
@@ -57,6 +57,9 @@ export const escapeDoubleQuotes = (value: string | null | undefined) =>
57
57
  export const escapeSingleQuotes = (value: string | null | undefined) =>
58
58
  value?.toString().replace(/'/g, "\\'");
59
59
 
60
+ export type EscapeForDotEnvOptions = {
61
+ quoteMode: "auto" | "always";
62
+ };
60
63
  /**
61
64
  *
62
65
  * escape env vars for .env files.
@@ -75,6 +78,9 @@ export const escapeSingleQuotes = (value: string | null | undefined) =>
75
78
  */
76
79
  export const escapeForDotEnv = (
77
80
  value: VariableValue | undefined | null,
81
+ options: EscapeForDotEnvOptions = {
82
+ quoteMode: "auto",
83
+ },
78
84
  ): string => {
79
85
  if (value === undefined || value === null) {
80
86
  return "";
@@ -82,7 +88,7 @@ export const escapeForDotEnv = (
82
88
  if (typeof value === "string") {
83
89
  // if string contains newlines, we need to wrap it in quotes
84
90
  // we additionaly escape newlines, that give best compatibility
85
- if (value.includes("\n")) {
91
+ if (options.quoteMode === "always" || value.includes("\n")) {
86
92
  const newlinesReplaces = value.replace(/\n/g, "\\n");
87
93
 
88
94
  // default to ", but if this is not possible, we try to use ' or `
@@ -1,90 +1,36 @@
1
1
  import type { ComponentContext } from "../../../types/context";
2
2
 
3
- import type {
4
- DeployConfigCloudRunJob,
5
- DeployConfigCloudRunJobWithSchedule,
6
- } from "../../types/googleCloudRun";
3
+ import { getLabels } from "../../../context/getLabels";
4
+ import { notNil } from "../../../utils";
5
+ import type { DeployConfigCloudRunJob } from "../../types/googleCloudRun";
7
6
  import { createArgsString } from "../utils/createArgsString";
8
- import { getCloudRunJobName } from "../utils/jobName";
7
+ import { getFullJobName } from "../utils/jobName";
9
8
  import {
10
9
  gcloudRunCmd,
11
- gcloudSchedulerCmd,
12
10
  getCloudRunDeployConfig,
13
11
  getCommonCloudRunArgs,
14
12
  getCommonDeployArgs,
15
13
  makeLabelString,
16
14
  } from "./common";
17
- import { getLabels } from "../../../context/getLabels";
18
- import { createVolumeConfig } from "./volumes";
19
- import type {
20
- BashExpression,
21
- StringOrBashExpression,
22
- } from "../../../bash/BashExpression";
23
15
  import { ENV_VARS_FILENAME } from "./constants";
24
- import { notNil } from "../../../utils";
25
-
26
- const getJobRunScriptForJob = (
27
- context: ComponentContext,
28
- jobName: StringOrBashExpression,
29
- wait: boolean,
30
- ) => {
31
- const commonArgs = getCommonCloudRunArgs(context);
32
-
33
- const commonArgsString = createArgsString(commonArgs);
34
- return `${gcloudRunCmd()} jobs execute ${jobName.toString()} ${commonArgsString}${
35
- wait ? " --wait" : ""
36
- }`;
37
- };
38
-
39
- export const getDeleteSchedulesScripts = (context: ComponentContext) => {
40
- const deployConfig = getCloudRunDeployConfig(context);
41
- const schedules = getSchedules(context);
42
- const argsString = createArgsString({
43
- project: deployConfig.projectId,
44
- location: deployConfig.region,
45
- });
46
- return schedules
47
- .map(({ name }) => {
48
- return [`${gcloudSchedulerCmd()} jobs delete ${name} ${argsString}`];
49
- })
50
- .flat();
51
- };
16
+ import { createVolumeConfig } from "./volumes";
17
+ import { getCloudRunJobArgsArg } from "./execute/onDeploy";
52
18
 
53
19
  export const getDeleteJobsScripts = (context: ComponentContext) => {
54
20
  const commonArgs = getCommonCloudRunArgs(context);
55
21
  const commonArgsString = createArgsString(commonArgs);
56
22
  const jobsWithNames = getCloudRunJobsWithNames(context);
57
23
 
58
- return jobsWithNames.flatMap(({ jobName }) => [
24
+ return jobsWithNames.flatMap(({ fullJobName }) => [
59
25
  // first delete all job executions. Otherwise delete might fail if one of those is still running
60
- `${gcloudRunCmd()} jobs executions list ${commonArgsString} --job ${jobName} --format="value(name)" | xargs -I {} ${gcloudRunCmd()} jobs executions delete {} --quiet ${commonArgsString}`,
61
- `${gcloudRunCmd()} jobs delete ${jobName} ${commonArgsString}`,
26
+ `${gcloudRunCmd()} jobs executions list ${commonArgsString} --job ${fullJobName} --format="value(name)" | xargs -I {} ${gcloudRunCmd()} jobs executions delete {} --quiet ${commonArgsString}`,
27
+ `${gcloudRunCmd()} jobs delete ${fullJobName} ${commonArgsString}`,
62
28
  ]);
63
29
  };
64
30
 
65
- export const getJobRunScripts = (
66
- context: ComponentContext,
67
- when: DeployConfigCloudRunJob["when"],
68
- ) => {
69
- const jobsWithNames = getCloudRunJobsWithNames(context);
70
-
71
- return jobsWithNames
72
- .filter(({ job }) => job.when === when)
73
- .map(({ jobName, job }) => {
74
- // always wait for completion for preStop and postStop jobs
75
- // since stop will delete the jobs afterwards, so they will fail
76
- const waitForCompletion = ["preStop", "postStop"].includes(when)
77
- ? true
78
- : "waitForCompletion" in job
79
- ? (job.waitForCompletion ?? false)
80
- : false;
81
- return getJobRunScriptForJob(context, jobName, waitForCompletion);
82
- });
83
- };
84
-
85
31
  export const getJobCreateScripts = (context: ComponentContext): string[] =>
86
32
  getCloudRunJobsWithNames(context).map(
87
- ({ job, jobName }, jobIndex): string => {
33
+ ({ job, fullJobName }, jobIndex): string => {
88
34
  const commandArray = Array.isArray(job.command)
89
35
  ? job.command
90
36
  : job.command.split(" ");
@@ -98,6 +44,7 @@ export const getJobCreateScripts = (context: ComponentContext): string[] =>
98
44
  const commonDeployArgsString = createArgsString(
99
45
  {
100
46
  command: `"${commandArray.join(",")}"`,
47
+ args: getCloudRunJobArgsArg(job.args),
101
48
  labels: `"${makeLabelString(getLabels(context))},cloud-run-job-name=$current_job_name"`,
102
49
  image: `"${job.image ?? commonImage}"`,
103
50
  project,
@@ -125,7 +72,7 @@ export const getJobCreateScripts = (context: ComponentContext): string[] =>
125
72
  jobIndex === 0
126
73
  ? `exist_job_names="$(\n ${gcloudRunCmd()} jobs list --filter='metadata.name ~ ${context.env}.*${context.name}' --format='value(name)' --limit=999 --project='${project}' --region='${region}'\n)"`
127
74
  : null,
128
- `current_job_name="${jobName}"`,
75
+ `current_job_name="${fullJobName}"`,
129
76
  'if grep "$current_job_name" <<<"$exist_job_names" >/dev/null; then',
130
77
  ` ${gcloudRunCmd()} jobs update "$current_job_name" ${commonDeployArgsString}`,
131
78
  "else",
@@ -137,104 +84,18 @@ export const getJobCreateScripts = (context: ComponentContext): string[] =>
137
84
  },
138
85
  );
139
86
 
140
- export const getCreateScheduleScripts = (
141
- context: ComponentContext,
142
- ): string[] => {
143
- const schedules = getSchedules(context);
144
- const { region: location, projectId: project } =
145
- getCloudRunDeployConfig(context);
146
-
147
- return schedules.map((scheduler, jobIndex): string => {
148
- const uri = getSchedulerUrl(scheduler, context);
149
-
150
- const argsString = createArgsString({
151
- project,
152
- location,
153
- uri: `"$current_job_uri"`,
154
- "http-method": "POST",
155
- "oauth-service-account-email": `"$GCLOUD_PROJECT_NUMBER-compute@developer.gserviceaccount.com"`,
156
- schedule: `"${scheduler.schedule}"`,
157
- "max-retry-attempts": scheduler.maxRetryAttempts ?? 0,
158
- });
159
- return [
160
- jobIndex === 0
161
- ? `exist_scheduler_names="$(\n ${gcloudSchedulerCmd()} jobs list --filter='httpTarget.uri ~ ${context.env}.*${context.name}' --format='value(name)' --limit=999 --location='${location}' --project='${project}'\n)"`
162
- : null,
163
- `current_job_uri="${uri}"`,
164
- `current_scheduler_name="${scheduler.name}"`,
165
- `if grep "$current_scheduler_name" <<<"$exist_scheduler_names" >/dev/null; then`,
166
- ` ${gcloudSchedulerCmd()} jobs update http "$current_scheduler_name" ${argsString}`,
167
- `else`,
168
- ` ${gcloudSchedulerCmd()} jobs create http "$current_scheduler_name" ${argsString}`,
169
- `fi`,
170
- ]
171
- .filter(notNil)
172
- .join("\n");
173
- });
174
- };
175
-
176
- type Scheduler = {
177
- name: StringOrBashExpression;
178
- maxRetryAttempts?: number;
179
- schedule: string;
180
- } & {
181
- type: "cloudRunJob";
182
- jobName: StringOrBashExpression;
183
- };
184
-
185
- const getSchedulerUrl = (scheduler: Scheduler, context: ComponentContext) => {
186
- if (scheduler.type === "cloudRunJob") {
187
- const { region: location, projectId: project } =
188
- getCloudRunDeployConfig(context);
189
-
190
- const uriBase = `https://${location}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${project}/jobs`;
191
-
192
- return `${uriBase}/${scheduler.jobName}:run`;
193
- }
194
-
195
- throw new Error(`Unknown scheduler type: ${scheduler.type}`);
196
- };
197
-
198
- const getSchedules = (context: ComponentContext): Scheduler[] => {
199
- const jobsWithNames = getCloudRunJobsWithNames(context);
200
-
201
- return jobsWithNames
202
- .filter(
203
- (
204
- entry,
205
- ): entry is {
206
- jobName: BashExpression;
207
- job: DeployConfigCloudRunJobWithSchedule;
208
- jobKey: string;
209
- } => entry.job.when === "schedule",
210
- )
211
- .map(({ job: { maxRetryAttempts, schedule }, jobName }) => {
212
- const schedulerName = jobName.concat("-scheduler");
213
- return {
214
- name: schedulerName,
215
- maxRetryAttempts,
216
- schedule,
217
- type: "cloudRunJob",
218
- jobName,
219
- };
220
- });
221
- };
222
-
223
- const getCloudRunJobsWithNames = (context: ComponentContext) => {
87
+ export const getCloudRunJobsWithNames = (context: ComponentContext) => {
224
88
  const deployConfig = getCloudRunDeployConfig(context);
225
89
 
226
- const getFullJobName = (name: string) =>
227
- getCloudRunJobName(context.environment.fullName, name);
228
-
229
90
  const jobsWithNames = Object.entries(deployConfig.jobs ?? {})
230
91
  // filter out disabled jobs
231
92
  .filter((entry): entry is [string, DeployConfigCloudRunJob] =>
232
93
  Boolean(entry[1]),
233
94
  )
234
- .map(([jobKey, job]) => ({
235
- jobName: getFullJobName(jobKey),
95
+ .map(([jobName, job]) => ({
96
+ fullJobName: getFullJobName(context, jobName),
236
97
  job,
237
- jobKey,
98
+ jobName,
238
99
  }));
239
100
  return jobsWithNames;
240
101
  };
@@ -0,0 +1,112 @@
1
+ import type { StringOrBashExpression } from "../../../../bash";
2
+ import type { ComponentContext } from "../../../../types/context";
3
+ import type {
4
+ DeployConfigCloudRunExecuteOnDeploy,
5
+ DeployConfigCloudRunJob,
6
+ } from "../../../types/googleCloudRun";
7
+ import { createArgsString } from "../../utils/createArgsString";
8
+ import { getFullJobName } from "../../utils/jobName";
9
+ import { getCloudRunJobsWithNames } from "../cloudRunJobs";
10
+ import {
11
+ gcloudRunCmd,
12
+ getCloudRunDeployConfig,
13
+ getCommonCloudRunArgs,
14
+ } from "../common";
15
+
16
+ type Execute = {
17
+ jobName: StringOrBashExpression;
18
+ config: DeployConfigCloudRunExecuteOnDeploy;
19
+ };
20
+ export const getOnDeployExecuteScript = (
21
+ context: ComponentContext,
22
+ when: DeployConfigCloudRunExecuteOnDeploy["when"],
23
+ ) => {
24
+ const executes = getExecutes(context);
25
+
26
+ return executes
27
+ .filter(({ config }) => config.when === when)
28
+ .map((execute) => {
29
+ return getJobRunScriptForExecute(context, execute);
30
+ });
31
+ };
32
+
33
+ const getExecutes = (context: ComponentContext): Execute[] => {
34
+ const deployConfig = getCloudRunDeployConfig(context);
35
+ return [
36
+ ...getLegacyExecutes(context),
37
+ ...Object.entries(deployConfig.execute ?? {}).flatMap(([key, value]) => {
38
+ if (!value || (value.when !== "schedule" && value.type !== "job")) {
39
+ return [];
40
+ }
41
+ return [
42
+ {
43
+ jobName: key,
44
+ config: value,
45
+ } as Execute,
46
+ ];
47
+ }),
48
+ ];
49
+ };
50
+
51
+ const getLegacyExecutes = (context: ComponentContext): Execute[] => {
52
+ const jobsWithNames = getCloudRunJobsWithNames(context);
53
+
54
+ return jobsWithNames.flatMap(({ jobName, job }) => {
55
+ if (
56
+ !job.when ||
57
+ !["preDeploy", "postDeploy", "preStop", "postStop"].includes(job.when)
58
+ ) {
59
+ return [];
60
+ }
61
+ return [
62
+ {
63
+ jobName,
64
+ config: {
65
+ job: jobName,
66
+ type: "job",
67
+ when: job.when,
68
+ ...(job.when === "preDeploy" || job.when === "postDeploy"
69
+ ? {
70
+ waitForCompletion: job.waitForCompletion,
71
+ }
72
+ : {}),
73
+ } as Execute["config"],
74
+ },
75
+ ];
76
+ });
77
+ };
78
+
79
+ const getJobRunScriptForExecute = (
80
+ context: ComponentContext,
81
+ { jobName, config }: Execute,
82
+ ) => {
83
+ const commonArgs = getCommonCloudRunArgs(context);
84
+
85
+ // always wait for completion for preStop and postStop jobs
86
+ // since stop will delete the jobs afterwards, so they will fail
87
+ const waitForCompletion = ["preStop", "postStop"].includes(config.when)
88
+ ? true // always
89
+ : "waitForCompletion" in config
90
+ ? (config.waitForCompletion ?? false) // depends on config
91
+ : false;
92
+
93
+ const argString = createArgsString({
94
+ ...commonArgs,
95
+ wait: waitForCompletion === true ? true : undefined,
96
+ args: getCloudRunJobArgsArg(config.args),
97
+ });
98
+ const fullJobName = getFullJobName(context, jobName);
99
+ return `${gcloudRunCmd()} jobs execute ${fullJobName.toString()} ${argString}`;
100
+ };
101
+
102
+ export const getCloudRunJobArgsArg = (
103
+ args:
104
+ | DeployConfigCloudRunExecuteOnDeploy["args"]
105
+ | DeployConfigCloudRunJob["args"],
106
+ ) => {
107
+ return args !== undefined
108
+ ? args.length > 0
109
+ ? args?.map((arg) => `"${arg}"`).join(",")
110
+ : ""
111
+ : undefined;
112
+ };