@google-cloud/cloud-run-mcp 1.9.0 → 1.11.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.
- package/README.md +24 -0
- package/SECURITY.md +7 -0
- package/cloudbuild.yaml +37 -0
- package/example-sources-to-deploy/java/pom.xml +41 -0
- package/example-sources-to-deploy/java/src/main/java/com/example/helloworld/HelloworldApplication.java +21 -0
- package/example-sources-to-deploy/java/src/main/resources/application.properties +1 -0
- package/example-sources-to-deploy/nodejs/index.js +15 -0
- package/example-sources-to-deploy/nodejs/package.json +12 -0
- package/example-sources-to-deploy/python/pip-project/main.py +11 -0
- package/example-sources-to-deploy/python/pip-project/requirements.txt +2 -0
- package/example-sources-to-deploy/python/pyproject-project/main.py +11 -0
- package/example-sources-to-deploy/python/pyproject-project/pyproject.toml +10 -0
- package/gemini-extension.json +12 -0
- package/lib/clients.js +104 -3
- package/lib/cloud-api/build.js +192 -28
- package/lib/cloud-api/run.js +62 -0
- package/lib/cloud-api/secrets.js +196 -0
- package/lib/cloud-api/storage.js +164 -16
- package/lib/deployment/compose.js +517 -0
- package/lib/deployment/constants.js +11 -0
- package/lib/deployment/deployer.js +334 -30
- package/lib/deployment/helpers.js +99 -49
- package/lib/deployment/source-processor.js +0 -48
- package/lib/deployment/universal-maker.js +147 -0
- package/lib/util/archive.js +3 -3
- package/lib/util/artifacts.js +189 -0
- package/lib/util/helpers.js +109 -0
- package/mcp-server.js +4 -0
- package/package.json +6 -3
- package/skills/cloud-run/SKILL.md +109 -0
- package/test/local/archive.test.js +88 -0
- package/test/local/clients.test.js +224 -2
- package/test/local/cloud-api/build.test.js +330 -25
- package/test/local/cloud-api/run.test.js +129 -0
- package/test/local/cloud-api/secrets.test.js +291 -0
- package/test/local/cloud-api/storage.test.js +405 -0
- package/test/local/compose.test.js +618 -0
- package/test/local/deploy.test.js +408 -0
- package/test/local/deployment-helpers.test.js +126 -79
- package/test/local/source-processor.test.js +0 -165
- package/test/local/test-utils.js +2 -18
- package/test/local/tools.test.js +43 -1
- package/test/local/universal-maker.test.js +157 -0
- package/test/local/util.test.js +361 -0
- package/test/need-gcp/deployer.test.js +1 -1
- package/test/need-gcp/workflows/deployment-workflows.test.js +172 -6
- package/tools/register-tools.js +19 -3
- /package/example-sources-to-deploy/{Dockerfile → golang/Dockerfile} +0 -0
- /package/example-sources-to-deploy/{go.mod → golang/go.mod} +0 -0
- /package/example-sources-to-deploy/{main.go → golang/main.go} +0 -0
|
@@ -22,9 +22,15 @@ import {
|
|
|
22
22
|
uploadToStorageBucket,
|
|
23
23
|
} from '../cloud-api/storage.js';
|
|
24
24
|
import { ensureArtifactRegistryRepoExists } from '../cloud-api/registry.js';
|
|
25
|
-
import { triggerCloudBuild } from '../cloud-api/build.js';
|
|
26
|
-
import {
|
|
27
|
-
|
|
25
|
+
import { triggerCloudBuild, composeBuild } from '../cloud-api/build.js';
|
|
26
|
+
import {
|
|
27
|
+
logAndProgress,
|
|
28
|
+
sanitizeCloudRunServiceName,
|
|
29
|
+
} from '../util/helpers.js';
|
|
30
|
+
import {
|
|
31
|
+
checkCloudRunServiceExists,
|
|
32
|
+
setServicePublicAccess,
|
|
33
|
+
} from '../cloud-api/run.js';
|
|
28
34
|
import {
|
|
29
35
|
canDeployWithoutBuild,
|
|
30
36
|
createDirectSourceDeploymentContainer,
|
|
@@ -32,16 +38,29 @@ import {
|
|
|
32
38
|
} from './helpers.js';
|
|
33
39
|
import {
|
|
34
40
|
prepareSourceDirectory,
|
|
35
|
-
installDependencies,
|
|
36
41
|
cleanupTempDirectory,
|
|
37
42
|
} from './source-processor.js';
|
|
43
|
+
import { runUniversalMaker } from './universal-maker.js';
|
|
38
44
|
|
|
39
45
|
import {
|
|
40
46
|
DEPLOYMENT_CONFIG,
|
|
41
47
|
DEPLOYMENT_TYPES,
|
|
42
48
|
MAX_ALLOWED_DIRECT_SOURCE_SIZE_BYTES,
|
|
43
49
|
REQUIRED_APIS,
|
|
50
|
+
RUNTIMES,
|
|
44
51
|
} from './constants.js';
|
|
52
|
+
import path from 'path';
|
|
53
|
+
import fs from 'fs';
|
|
54
|
+
import yaml from 'js-yaml';
|
|
55
|
+
import { getRunV1Client } from '../clients.js';
|
|
56
|
+
import {
|
|
57
|
+
runCompose as downloadRunCompose,
|
|
58
|
+
resourceCompose,
|
|
59
|
+
translateCompose,
|
|
60
|
+
composeVolumes,
|
|
61
|
+
composeSecrets,
|
|
62
|
+
} from './compose.js';
|
|
63
|
+
import { getProjectNumber } from '../util/helpers.js';
|
|
45
64
|
|
|
46
65
|
/**
|
|
47
66
|
* Deploys or updates a Cloud Run service with the specified container image.
|
|
@@ -68,7 +87,8 @@ async function deployToCloudRun(
|
|
|
68
87
|
deploymentType,
|
|
69
88
|
accessToken,
|
|
70
89
|
zippedSourceContainer,
|
|
71
|
-
runtime
|
|
90
|
+
runtime,
|
|
91
|
+
ingress
|
|
72
92
|
) {
|
|
73
93
|
const runClient = await getRunClient(projectId, accessToken);
|
|
74
94
|
const parent = runClient.locationPath(projectId, location);
|
|
@@ -90,7 +110,11 @@ async function deployToCloudRun(
|
|
|
90
110
|
'deployment-type': deploymentType,
|
|
91
111
|
runtime: runtime,
|
|
92
112
|
},
|
|
113
|
+
client: DEPLOYMENT_CONFIG.LABEL_CREATED_BY,
|
|
93
114
|
};
|
|
115
|
+
if (ingress) {
|
|
116
|
+
service.ingress = ingress;
|
|
117
|
+
}
|
|
94
118
|
|
|
95
119
|
// Conditionally set invokerIamDisabled based on the skipIamCheck flag
|
|
96
120
|
if (skipIamCheck) {
|
|
@@ -248,7 +272,8 @@ async function deployWithZip(
|
|
|
248
272
|
deploymentAttrs,
|
|
249
273
|
progressCallback,
|
|
250
274
|
skipIamCheck,
|
|
251
|
-
accessToken
|
|
275
|
+
accessToken,
|
|
276
|
+
ingress
|
|
252
277
|
) {
|
|
253
278
|
await logAndProgress(
|
|
254
279
|
`Attempting direct source deployment...`,
|
|
@@ -259,7 +284,18 @@ async function deployWithZip(
|
|
|
259
284
|
|
|
260
285
|
try {
|
|
261
286
|
tempDir = await prepareSourceDirectory(files);
|
|
262
|
-
|
|
287
|
+
|
|
288
|
+
if (
|
|
289
|
+
deploymentAttrs.runtime === RUNTIMES.NODEJS ||
|
|
290
|
+
deploymentAttrs.runtime === RUNTIMES.PYTHON
|
|
291
|
+
) {
|
|
292
|
+
deploymentAttrs = await updateDeploymentAttrsWithUniversalMaker(
|
|
293
|
+
tempDir,
|
|
294
|
+
deploymentAttrs,
|
|
295
|
+
accessToken,
|
|
296
|
+
progressCallback
|
|
297
|
+
);
|
|
298
|
+
}
|
|
263
299
|
|
|
264
300
|
const archiveBuffer = await zipFiles([tempDir], true, progressCallback);
|
|
265
301
|
const archiveName = DEPLOYMENT_CONFIG.TARGZ_FILE_NAME;
|
|
@@ -280,10 +316,12 @@ async function deployWithZip(
|
|
|
280
316
|
bucketName,
|
|
281
317
|
region,
|
|
282
318
|
accessToken,
|
|
319
|
+
undefined,
|
|
320
|
+
false,
|
|
283
321
|
progressCallback
|
|
284
322
|
);
|
|
285
323
|
|
|
286
|
-
await uploadToStorageBucket(
|
|
324
|
+
const file = await uploadToStorageBucket(
|
|
287
325
|
bucket,
|
|
288
326
|
archiveBuffer,
|
|
289
327
|
archiveName,
|
|
@@ -292,7 +330,7 @@ async function deployWithZip(
|
|
|
292
330
|
await logAndProgress('Source code uploaded successfully', progressCallback);
|
|
293
331
|
|
|
294
332
|
const container = createDirectSourceDeploymentContainer({
|
|
295
|
-
bucketName,
|
|
333
|
+
bucketName: bucket.name,
|
|
296
334
|
fileName: archiveName,
|
|
297
335
|
deploymentAttrs,
|
|
298
336
|
});
|
|
@@ -307,15 +345,57 @@ async function deployWithZip(
|
|
|
307
345
|
DEPLOYMENT_TYPES.NO_BUILD,
|
|
308
346
|
accessToken,
|
|
309
347
|
container,
|
|
310
|
-
deploymentAttrs.runtime
|
|
348
|
+
deploymentAttrs.runtime,
|
|
349
|
+
ingress
|
|
311
350
|
);
|
|
312
351
|
|
|
352
|
+
await logAndProgress(
|
|
353
|
+
`Deployment completed successfully with zip deploy`,
|
|
354
|
+
progressCallback
|
|
355
|
+
);
|
|
313
356
|
return service;
|
|
314
357
|
} finally {
|
|
315
358
|
await cleanupTempDirectory(tempDir);
|
|
316
359
|
}
|
|
317
360
|
}
|
|
318
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Updates deployment attributes using result from Universal Maker.
|
|
364
|
+
*
|
|
365
|
+
* @async
|
|
366
|
+
* @param {string} tempDir - Path to the temporary source directory.
|
|
367
|
+
* @param {object} deploymentAttrs - The initial deployment attributes.
|
|
368
|
+
* @param {function(object): void} [progressCallback] - Optional callback for progress updates.
|
|
369
|
+
* @returns {Promise<object>} A promise that resolves with the updated deployment attributes.
|
|
370
|
+
*/
|
|
371
|
+
async function updateDeploymentAttrsWithUniversalMaker(
|
|
372
|
+
tempDir,
|
|
373
|
+
deploymentAttrs,
|
|
374
|
+
accessToken,
|
|
375
|
+
progressCallback
|
|
376
|
+
) {
|
|
377
|
+
const umResult = await runUniversalMaker(
|
|
378
|
+
tempDir,
|
|
379
|
+
accessToken,
|
|
380
|
+
progressCallback
|
|
381
|
+
);
|
|
382
|
+
if (umResult) {
|
|
383
|
+
await logAndProgress(
|
|
384
|
+
'Updating deployment attributes using Universal Maker results.',
|
|
385
|
+
progressCallback
|
|
386
|
+
);
|
|
387
|
+
return {
|
|
388
|
+
...deploymentAttrs,
|
|
389
|
+
baseImage: umResult.runtime || deploymentAttrs.baseImage,
|
|
390
|
+
runtime: umResult.runtime || deploymentAttrs.runtime,
|
|
391
|
+
cmd: umResult.command ? [umResult.command] : deploymentAttrs.cmd,
|
|
392
|
+
args: umResult.args || deploymentAttrs.args,
|
|
393
|
+
envVars: umResult.envVars || {},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
return deploymentAttrs;
|
|
397
|
+
}
|
|
398
|
+
|
|
319
399
|
/**
|
|
320
400
|
* Handles deployment using Google Cloud Build.
|
|
321
401
|
* This function zips the source code, uploads it to Google Cloud Storage,
|
|
@@ -343,7 +423,8 @@ async function deployWithBuild(
|
|
|
343
423
|
bucketName,
|
|
344
424
|
progressCallback,
|
|
345
425
|
skipIamCheck,
|
|
346
|
-
accessToken
|
|
426
|
+
accessToken,
|
|
427
|
+
ingress
|
|
347
428
|
) {
|
|
348
429
|
const imageUrl = `${region}-docker.pkg.dev/${projectId}/${DEPLOYMENT_CONFIG.REPO_NAME}/${serviceName}:${DEPLOYMENT_CONFIG.IMAGE_TAG}`;
|
|
349
430
|
|
|
@@ -352,13 +433,15 @@ async function deployWithBuild(
|
|
|
352
433
|
bucketName,
|
|
353
434
|
region,
|
|
354
435
|
accessToken,
|
|
436
|
+
undefined,
|
|
437
|
+
false,
|
|
355
438
|
progressCallback
|
|
356
439
|
);
|
|
357
440
|
|
|
358
441
|
const archiveBuffer = await zipFiles(files, false, progressCallback);
|
|
359
442
|
const archiveName = DEPLOYMENT_CONFIG.ZIP_FILE_NAME;
|
|
360
443
|
|
|
361
|
-
await uploadToStorageBucket(
|
|
444
|
+
const file = await uploadToStorageBucket(
|
|
362
445
|
bucket,
|
|
363
446
|
archiveBuffer,
|
|
364
447
|
archiveName,
|
|
@@ -380,13 +463,14 @@ async function deployWithBuild(
|
|
|
380
463
|
const buildResult = await triggerCloudBuild(
|
|
381
464
|
projectId,
|
|
382
465
|
region,
|
|
383
|
-
|
|
466
|
+
bucket.name,
|
|
384
467
|
archiveName,
|
|
385
468
|
DEPLOYMENT_CONFIG.REPO_NAME,
|
|
386
469
|
imageUrl,
|
|
387
470
|
hasDockerfile,
|
|
388
471
|
accessToken,
|
|
389
|
-
progressCallback
|
|
472
|
+
progressCallback,
|
|
473
|
+
ingress
|
|
390
474
|
);
|
|
391
475
|
|
|
392
476
|
const builtImageUrl = buildResult.results.images[0].name;
|
|
@@ -399,10 +483,16 @@ async function deployWithBuild(
|
|
|
399
483
|
progressCallback,
|
|
400
484
|
skipIamCheck,
|
|
401
485
|
DEPLOYMENT_TYPES.WITH_BUILD,
|
|
402
|
-
accessToken
|
|
486
|
+
accessToken,
|
|
487
|
+
undefined,
|
|
488
|
+
undefined,
|
|
489
|
+
ingress
|
|
403
490
|
);
|
|
404
491
|
|
|
405
|
-
await logAndProgress(
|
|
492
|
+
await logAndProgress(
|
|
493
|
+
`Deployment completed successfully with source build deploy`,
|
|
494
|
+
progressCallback
|
|
495
|
+
);
|
|
406
496
|
return service;
|
|
407
497
|
}
|
|
408
498
|
|
|
@@ -425,6 +515,7 @@ export async function deploy({
|
|
|
425
515
|
progressCallback,
|
|
426
516
|
skipIamCheck,
|
|
427
517
|
accessToken,
|
|
518
|
+
ingress,
|
|
428
519
|
}) {
|
|
429
520
|
if (!projectId) {
|
|
430
521
|
const errorMsg =
|
|
@@ -433,13 +524,6 @@ export async function deploy({
|
|
|
433
524
|
throw new Error(errorMsg);
|
|
434
525
|
}
|
|
435
526
|
|
|
436
|
-
if (!serviceName) {
|
|
437
|
-
const errorMsg =
|
|
438
|
-
'Error: serviceName is required in the configuration object.';
|
|
439
|
-
await logAndProgress(errorMsg, progressCallback, 'error');
|
|
440
|
-
throw new Error(errorMsg);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
527
|
if (!files || !Array.isArray(files) || files.length === 0) {
|
|
444
528
|
const errorMsg =
|
|
445
529
|
'Error: files array is required in the configuration object.';
|
|
@@ -451,6 +535,30 @@ export async function deploy({
|
|
|
451
535
|
}
|
|
452
536
|
}
|
|
453
537
|
|
|
538
|
+
// Determines if we can use the direct source deployment (no Cloud Build) or compose deployment
|
|
539
|
+
const { hasDockerfile, composeFilePath, deploymentAttrs } =
|
|
540
|
+
makeFileDeploymentMetadata(files);
|
|
541
|
+
|
|
542
|
+
if (composeFilePath) {
|
|
543
|
+
return await deployCompose({
|
|
544
|
+
projectId,
|
|
545
|
+
serviceName,
|
|
546
|
+
region,
|
|
547
|
+
files,
|
|
548
|
+
composeFilePath,
|
|
549
|
+
progressCallback,
|
|
550
|
+
skipIamCheck,
|
|
551
|
+
accessToken,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!serviceName) {
|
|
556
|
+
const errorMsg =
|
|
557
|
+
'Error: serviceName is required in the configuration object.';
|
|
558
|
+
await logAndProgress(errorMsg, progressCallback, 'error');
|
|
559
|
+
throw new Error(errorMsg);
|
|
560
|
+
}
|
|
561
|
+
|
|
454
562
|
try {
|
|
455
563
|
await ensureApisEnabled(
|
|
456
564
|
projectId,
|
|
@@ -466,10 +574,6 @@ export async function deploy({
|
|
|
466
574
|
await logAndProgress(`Service Name: ${serviceName}`, progressCallback);
|
|
467
575
|
await logAndProgress(`Files to deploy: ${files.length}`, progressCallback);
|
|
468
576
|
|
|
469
|
-
// Determines if we can use the direct source deployment (no Cloud Build)
|
|
470
|
-
const { hasDockerfile, deploymentAttrs } =
|
|
471
|
-
makeFileDeploymentMetadata(files);
|
|
472
|
-
|
|
473
577
|
await logAndProgress(`Dockerfile: ${hasDockerfile}`, progressCallback);
|
|
474
578
|
|
|
475
579
|
if (canDeployWithoutBuild({ hasDockerfile, deploymentAttrs })) {
|
|
@@ -483,7 +587,8 @@ export async function deploy({
|
|
|
483
587
|
deploymentAttrs,
|
|
484
588
|
progressCallback,
|
|
485
589
|
skipIamCheck,
|
|
486
|
-
accessToken
|
|
590
|
+
accessToken,
|
|
591
|
+
ingress
|
|
487
592
|
);
|
|
488
593
|
} catch (error) {
|
|
489
594
|
await logAndProgress(
|
|
@@ -503,7 +608,8 @@ export async function deploy({
|
|
|
503
608
|
bucketName,
|
|
504
609
|
progressCallback,
|
|
505
610
|
skipIamCheck,
|
|
506
|
-
accessToken
|
|
611
|
+
accessToken,
|
|
612
|
+
ingress
|
|
507
613
|
);
|
|
508
614
|
} catch (error) {
|
|
509
615
|
const deployFailedMessage = `Deployment Failed: ${error.message}`;
|
|
@@ -533,6 +639,7 @@ export async function deployImage({
|
|
|
533
639
|
progressCallback,
|
|
534
640
|
skipIamCheck,
|
|
535
641
|
accessToken,
|
|
642
|
+
ingress,
|
|
536
643
|
}) {
|
|
537
644
|
if (!projectId) {
|
|
538
645
|
const errorMsg =
|
|
@@ -579,7 +686,10 @@ export async function deployImage({
|
|
|
579
686
|
progressCallback,
|
|
580
687
|
skipIamCheck,
|
|
581
688
|
DEPLOYMENT_TYPES.IMAGE,
|
|
582
|
-
accessToken
|
|
689
|
+
accessToken,
|
|
690
|
+
undefined,
|
|
691
|
+
undefined,
|
|
692
|
+
ingress
|
|
583
693
|
);
|
|
584
694
|
|
|
585
695
|
await logAndProgress(`Deployment completed successfully`, progressCallback);
|
|
@@ -591,3 +701,197 @@ export async function deployImage({
|
|
|
591
701
|
throw error;
|
|
592
702
|
}
|
|
593
703
|
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Deploys services defined in a compose.yaml file to Cloud Run.
|
|
707
|
+
*
|
|
708
|
+
* @async
|
|
709
|
+
* @param {object} config - The deployment configuration.
|
|
710
|
+
* @param {string} config.projectId - The Google Cloud project ID.
|
|
711
|
+
* @param {string} [config.serviceName] - The name of the Cloud Run service.
|
|
712
|
+
* @param {string} [config.region] - The Google Cloud region for deployment.
|
|
713
|
+
* @param {Array<string>} config.files - An array of paths to include in the source bundle.
|
|
714
|
+
* @param {string} config.composeFilePath - The absolute path to the compose configuration file.
|
|
715
|
+
* @param {function(object): void} [config.progressCallback] - Optional callback for progress updates.
|
|
716
|
+
* @param {boolean} [config.skipIamCheck=false] - Whether to skip the IAM check (if true, will attempt to set public access).
|
|
717
|
+
* @param {string} [config.accessToken] - Access token for authentication.
|
|
718
|
+
* @returns {Promise<object>} A promise that resolves with the deployment result.
|
|
719
|
+
* @throws {Error} If deployment fails or required configuration is missing.
|
|
720
|
+
*/
|
|
721
|
+
export async function deployCompose({
|
|
722
|
+
projectId,
|
|
723
|
+
serviceName,
|
|
724
|
+
region,
|
|
725
|
+
files,
|
|
726
|
+
composeFilePath,
|
|
727
|
+
progressCallback,
|
|
728
|
+
skipIamCheck,
|
|
729
|
+
accessToken,
|
|
730
|
+
}) {
|
|
731
|
+
let tempDir;
|
|
732
|
+
try {
|
|
733
|
+
tempDir = await prepareSourceDirectory(files, progressCallback);
|
|
734
|
+
const tempComposeFilePath = path.join(
|
|
735
|
+
tempDir,
|
|
736
|
+
path.basename(composeFilePath)
|
|
737
|
+
);
|
|
738
|
+
const folderPath = tempDir;
|
|
739
|
+
const projectNumber = await getProjectNumber(projectId, accessToken);
|
|
740
|
+
const binPath = await downloadRunCompose(accessToken, progressCallback);
|
|
741
|
+
|
|
742
|
+
// Get resource configuration
|
|
743
|
+
const resourceOutput = await resourceCompose(
|
|
744
|
+
binPath,
|
|
745
|
+
tempComposeFilePath,
|
|
746
|
+
region,
|
|
747
|
+
progressCallback
|
|
748
|
+
);
|
|
749
|
+
let resourcesConfig = JSON.parse(resourceOutput);
|
|
750
|
+
if (!serviceName) {
|
|
751
|
+
serviceName = resourcesConfig.project;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Handle source builds if any
|
|
755
|
+
if (resourcesConfig.source_builds) {
|
|
756
|
+
resourcesConfig = await composeBuild(
|
|
757
|
+
resourcesConfig,
|
|
758
|
+
accessToken,
|
|
759
|
+
projectId,
|
|
760
|
+
region,
|
|
761
|
+
folderPath,
|
|
762
|
+
progressCallback
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Handle volumes (bind and named)
|
|
767
|
+
resourcesConfig = await composeVolumes(
|
|
768
|
+
resourcesConfig,
|
|
769
|
+
accessToken,
|
|
770
|
+
projectId,
|
|
771
|
+
region,
|
|
772
|
+
folderPath,
|
|
773
|
+
progressCallback
|
|
774
|
+
);
|
|
775
|
+
|
|
776
|
+
// Handle secrets
|
|
777
|
+
resourcesConfig = await composeSecrets(
|
|
778
|
+
resourcesConfig,
|
|
779
|
+
accessToken,
|
|
780
|
+
projectId,
|
|
781
|
+
folderPath,
|
|
782
|
+
progressCallback
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
const result = await translateCompose(
|
|
786
|
+
binPath,
|
|
787
|
+
tempComposeFilePath,
|
|
788
|
+
region,
|
|
789
|
+
projectNumber,
|
|
790
|
+
progressCallback,
|
|
791
|
+
resourcesConfig
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
const translation = JSON.parse(result);
|
|
795
|
+
const deploymentItems = [];
|
|
796
|
+
|
|
797
|
+
if (translation.models) {
|
|
798
|
+
for (const [name, yamlPath] of Object.entries(translation.models)) {
|
|
799
|
+
deploymentItems.push({ name, yamlPath, type: 'model' });
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (translation.services) {
|
|
803
|
+
for (const [name, yamlPath] of Object.entries(translation.services)) {
|
|
804
|
+
deploymentItems.push({ name, yamlPath, type: 'service' });
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const deploymentResults = {};
|
|
809
|
+
const runV1Client = await getRunV1Client(projectId, accessToken, region);
|
|
810
|
+
|
|
811
|
+
for (const { name: rawName, yamlPath, type } of deploymentItems) {
|
|
812
|
+
const serviceName = sanitizeCloudRunServiceName(rawName);
|
|
813
|
+
const fullYamlPath = path.join(folderPath, yamlPath);
|
|
814
|
+
await logAndProgress(
|
|
815
|
+
`Deploying ${type} ${serviceName} using configuration from ${fullYamlPath}...`,
|
|
816
|
+
progressCallback
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
const serviceConfig = yaml.load(
|
|
820
|
+
await fs.promises.readFile(fullYamlPath, 'utf8')
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
// Ensure the metadata name is sanitized to match
|
|
824
|
+
serviceConfig.metadata = serviceConfig.metadata || {};
|
|
825
|
+
serviceConfig.metadata.name = serviceName;
|
|
826
|
+
|
|
827
|
+
// The name for replaceService in v1 is namespaces/{project}/services/{service}
|
|
828
|
+
const name = `namespaces/${projectId}/services/${serviceName}`;
|
|
829
|
+
const parent = `namespaces/${projectId}`;
|
|
830
|
+
|
|
831
|
+
let response;
|
|
832
|
+
try {
|
|
833
|
+
response = await runV1Client.namespaces.services.replaceService({
|
|
834
|
+
name,
|
|
835
|
+
requestBody: serviceConfig,
|
|
836
|
+
});
|
|
837
|
+
} catch (error) {
|
|
838
|
+
if (error.code === 404) {
|
|
839
|
+
await logAndProgress(
|
|
840
|
+
`${type.charAt(0).toUpperCase() + type.slice(1)} ${serviceName} does not exist. Creating it...`,
|
|
841
|
+
progressCallback
|
|
842
|
+
);
|
|
843
|
+
response = await runV1Client.namespaces.services.create({
|
|
844
|
+
parent,
|
|
845
|
+
requestBody: serviceConfig,
|
|
846
|
+
});
|
|
847
|
+
} else {
|
|
848
|
+
throw error;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
await logAndProgress(
|
|
853
|
+
`${type.charAt(0).toUpperCase() + type.slice(1)} ${serviceName} deployed successfully.`,
|
|
854
|
+
progressCallback
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
// Set public access if skipIamCheck is enabled
|
|
858
|
+
try {
|
|
859
|
+
if (skipIamCheck) {
|
|
860
|
+
await setServicePublicAccess(
|
|
861
|
+
projectId,
|
|
862
|
+
region,
|
|
863
|
+
serviceName,
|
|
864
|
+
accessToken,
|
|
865
|
+
progressCallback
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
} catch (error) {
|
|
869
|
+
await logAndProgress(
|
|
870
|
+
`Failed to set public access for ${type} ${serviceName}: ${error.message}`,
|
|
871
|
+
progressCallback,
|
|
872
|
+
'warn'
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
if (type === 'service') {
|
|
876
|
+
const deployedService = response.data || response;
|
|
877
|
+
const serviceUrl = deployedService.status?.url;
|
|
878
|
+
|
|
879
|
+
deploymentResults['service'] = serviceName;
|
|
880
|
+
deploymentResults['uri'] = serviceUrl;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
await logAndProgress(
|
|
884
|
+
`Deployment (compose) completed successfully. Deployment results: ${JSON.stringify(deploymentResults)}`,
|
|
885
|
+
progressCallback
|
|
886
|
+
);
|
|
887
|
+
return deploymentResults;
|
|
888
|
+
} catch (error) {
|
|
889
|
+
const errorMsg = `Deployment (compose) failed: ${error.message}`;
|
|
890
|
+
await logAndProgress(errorMsg, progressCallback, 'error');
|
|
891
|
+
throw error;
|
|
892
|
+
} finally {
|
|
893
|
+
if (tempDir) {
|
|
894
|
+
await cleanupTempDirectory(tempDir);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|