@google-cloud/cloud-run-mcp 1.5.0 → 1.7.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 (46) hide show
  1. package/.c8rc.json +9 -0
  2. package/.github/workflows/lint-checker.yml +17 -0
  3. package/.github/workflows/npm-publish.yml +1 -1
  4. package/.kokoro/kokoro_build.sh +22 -0
  5. package/.kokoro/presubmit.cfg +21 -0
  6. package/.kokoro/run_tests.sh +31 -0
  7. package/CONTRIBUTING.md +24 -9
  8. package/README.md +52 -11
  9. package/REVIEWING.md +20 -0
  10. package/gemini-extension/GEMINI.md +1 -1
  11. package/lib/cloud-api/billing.js +183 -0
  12. package/lib/cloud-api/build.js +200 -0
  13. package/lib/{gcp-api-helpers.js → cloud-api/helpers.js} +81 -35
  14. package/lib/{gcp-projects.js → cloud-api/projects.js} +58 -28
  15. package/lib/cloud-api/registry.js +98 -0
  16. package/lib/{cloud-run-services.js → cloud-api/run.js} +103 -21
  17. package/lib/cloud-api/storage.js +123 -0
  18. package/lib/deployment/constants.js +54 -0
  19. package/lib/deployment/deployer.js +621 -0
  20. package/lib/deployment/helpers.js +194 -0
  21. package/lib/deployment/source-processor.js +124 -0
  22. package/lib/util/archive.js +104 -0
  23. package/lib/util/helpers.js +42 -0
  24. package/mcp-server.js +21 -10
  25. package/package.json +8 -5
  26. package/test/local/cloud-api/build.test.js +436 -0
  27. package/test/local/cloud-api/projects.test.js +18 -0
  28. package/test/local/deployment-helpers.test.js +231 -0
  29. package/test/local/mcp-server-stdio.test.js +59 -0
  30. package/test/local/mcp-server-streamable-http.test.js +61 -0
  31. package/test/local/notifications.test.js +9 -6
  32. package/test/local/source-processor.test.js +290 -0
  33. package/test/local/test-utils.js +51 -0
  34. package/test/local/tools.test.js +114 -70
  35. package/test/need-gcp/cloud-run-services.test.js +44 -44
  36. package/test/need-gcp/{cloud-run-deploy.test.js → deployer.test.js} +3 -56
  37. package/test/need-gcp/gcp-auth-check.test.js +1 -1
  38. package/test/need-gcp/gcp-projects.test.js +3 -15
  39. package/test/need-gcp/test-helpers.js +175 -0
  40. package/test/need-gcp/workflows/deployment-workflows.test.js +100 -0
  41. package/tools/register-tools.js +106 -97
  42. package/tools/tools.js +2 -9
  43. package/lib/cloud-run-deploy.js +0 -937
  44. package/lib/gcp-billing-accounts.js +0 -98
  45. /package/lib/{gcp-auth-check.js → cloud-api/auth.js} +0 -0
  46. /package/lib/{gcp-metadata.js → cloud-api/metadata.js} +0 -0
@@ -0,0 +1,200 @@
1
+ /*
2
+ Copyright 2025 Google LLC
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { callWithRetry } from './helpers.js';
18
+ import { logAndProgress } from '../util/helpers.js';
19
+
20
+ const DELAY_WAIT_FOR_BUILD_LOGS = 10000; // 10 seconds delay to allow logs to propagate
21
+ const BUILD_LOGS_LINES_TO_FETCH = 100; // Number of log lines to fetch for build logs snippet
22
+
23
+ /**
24
+ * Triggers a Google Cloud Build job to build a container image from source code in a GCS bucket.
25
+ * It uses either a Dockerfile found in the source or Google Cloud Buildpacks if no Dockerfile is present.
26
+ * Waits for the build to complete and returns the build result.
27
+ *
28
+ * @async
29
+ * @param {object} context - The context object containing clients and other parameters.
30
+ * @param {string} projectId - The Google Cloud project ID.
31
+ * @param {string} location - The Google Cloud region for the build.
32
+ * @param {string} sourceBucketName - The GCS bucket name where the source code (zip) is stored.
33
+ * @param {string} sourceBlobName - The GCS blob name (the zip file) for the source code.
34
+ * @param {string} targetRepoName - The name of the target Artifact Registry repository (used for context, not directly in build steps).
35
+ * @param {string} targetImageUrl - The full Artifact Registry URL for the image to be built (e.g., `location-docker.pkg.dev/project/repo/image:tag`).
36
+ * @param {boolean} hasDockerfile - Indicates whether a Dockerfile is present in the source to guide the build process.
37
+ * @param {function(object): void} [progressCallback] - Optional callback for progress updates.
38
+ * @returns {Promise<object>} A promise that resolves with the completed Cloud Build object.
39
+ * @throws {Error} If the Cloud Build job fails, times out, or encounters an error during initiation or execution.
40
+ */
41
+ export async function triggerCloudBuild(
42
+ context,
43
+ projectId,
44
+ location,
45
+ sourceBucketName,
46
+ sourceBlobName,
47
+ targetRepoName,
48
+ targetImageUrl,
49
+ hasDockerfile,
50
+ progressCallback
51
+ ) {
52
+ let buildSteps;
53
+
54
+ if (hasDockerfile) {
55
+ buildSteps = [
56
+ {
57
+ name: 'gcr.io/cloud-builders/docker',
58
+ args: ['build', '-t', targetImageUrl, '.'],
59
+ dir: '/workspace',
60
+ },
61
+ ];
62
+ } else {
63
+ buildSteps = [
64
+ {
65
+ name: 'gcr.io/k8s-skaffold/pack',
66
+ entrypoint: 'pack',
67
+ args: [
68
+ 'build',
69
+ targetImageUrl,
70
+ '--builder',
71
+ 'gcr.io/buildpacks/builder:latest',
72
+ ],
73
+ dir: '/workspace',
74
+ },
75
+ ];
76
+ }
77
+
78
+ const build = {
79
+ source: {
80
+ storageSource: {
81
+ bucket: sourceBucketName,
82
+ object: sourceBlobName,
83
+ },
84
+ },
85
+ steps: buildSteps,
86
+ images: [targetImageUrl],
87
+ };
88
+
89
+ await logAndProgress(
90
+ `Initiating Cloud Build for gs://${sourceBucketName}/${sourceBlobName} in ${location}...`,
91
+ progressCallback
92
+ );
93
+ const [operation] = await callWithRetry(
94
+ () =>
95
+ context.cloudBuildClient.createBuild({
96
+ projectId: projectId,
97
+ build: build,
98
+ }),
99
+ 'cloudBuild.createBuild'
100
+ );
101
+
102
+ await logAndProgress(`Cloud Build job started...`, progressCallback);
103
+ const buildId = operation.metadata.build.id;
104
+ let completedBuild;
105
+ while (true) {
106
+ const [getBuildOperation] = await callWithRetry(
107
+ () =>
108
+ context.cloudBuildClient.getBuild({
109
+ projectId: projectId,
110
+ id: buildId,
111
+ }),
112
+ `cloudBuild.getBuild ${buildId}`
113
+ );
114
+ if (
115
+ ['SUCCESS', 'FAILURE', 'INTERNAL_ERROR', 'TIMEOUT', 'CANCELLED'].includes(
116
+ getBuildOperation.status
117
+ )
118
+ ) {
119
+ completedBuild = getBuildOperation;
120
+ break;
121
+ }
122
+ await logAndProgress(
123
+ `Build status: ${getBuildOperation.status}. Waiting...`,
124
+ progressCallback,
125
+ 'debug'
126
+ );
127
+ await new Promise((resolve) => setTimeout(resolve, 5000));
128
+ }
129
+
130
+ if (completedBuild.status === 'SUCCESS') {
131
+ await logAndProgress(
132
+ `Cloud Build job ${buildId} completed successfully.`,
133
+ progressCallback
134
+ );
135
+ await logAndProgress(
136
+ `Image built: ${completedBuild.results.images[0].name}`,
137
+ progressCallback
138
+ );
139
+ return completedBuild;
140
+ } else {
141
+ const failureMessage = `Cloud Build job ${buildId} failed with status: ${completedBuild.status}`;
142
+ await logAndProgress(failureMessage, progressCallback, 'error');
143
+ const logsMessage = `Build logs: ${completedBuild.logUrl}`;
144
+ await logAndProgress(logsMessage, progressCallback); // Log URL is info, failure is error
145
+
146
+ let buildLogsSnippet = `\n\nRefer to Log URL for full details: ${completedBuild.logUrl}`; // Default snippet
147
+ try {
148
+ const logFilter = `resource.type="build" AND resource.labels.build_id="${buildId}"`;
149
+ await logAndProgress(
150
+ `Attempting to fetch last ${BUILD_LOGS_LINES_TO_FETCH} log lines for build ${buildId}...`,
151
+ progressCallback,
152
+ 'debug'
153
+ );
154
+
155
+ // Wait for a short period to allow logs to propagate
156
+ await new Promise((resolve) =>
157
+ setTimeout(resolve, DELAY_WAIT_FOR_BUILD_LOGS)
158
+ );
159
+
160
+ // Fetch the most recent N log entries
161
+ const [entries] = await callWithRetry(
162
+ () =>
163
+ context.loggingClient.getEntries({
164
+ filter: logFilter,
165
+ orderBy: 'timestamp desc', // Get latest logs first
166
+ pageSize: BUILD_LOGS_LINES_TO_FETCH,
167
+ }),
168
+ `logging.getEntries for build ${buildId}`
169
+ );
170
+
171
+ if (entries && entries.length > 0) {
172
+ // Entries are newest first, reverse for chronological order of the snippet
173
+ const logLines = entries.reverse().map((entry) => entry.data || '');
174
+ if (logLines.length > 0) {
175
+ buildLogsSnippet = `\n\nLast ${logLines.length} log lines from build ${buildId}:\n${logLines.join('\n')}`;
176
+ await logAndProgress(
177
+ `Successfully fetched snippet of build logs for ${buildId}.`,
178
+ progressCallback,
179
+ 'info'
180
+ );
181
+ }
182
+ } else {
183
+ await logAndProgress(
184
+ `No specific log entries retrieved for build ${buildId}. ${buildLogsSnippet}`,
185
+ progressCallback,
186
+ 'warn'
187
+ );
188
+ }
189
+ } catch (logError) {
190
+ console.error(`Error fetching build logs for ${buildId}:`, logError);
191
+ await logAndProgress(
192
+ `Failed to fetch build logs snippet: ${logError.message}. ${buildLogsSnippet}`,
193
+ progressCallback,
194
+ 'warn'
195
+ );
196
+ // buildLogsSnippet already contains the Log URL as a fallback
197
+ }
198
+ throw new Error(`Build ${buildId} failed.${buildLogsSnippet}`);
199
+ }
200
+ }
@@ -14,6 +14,13 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ import { ensureBillingEnabled } from './billing.js';
18
+
19
+ const PREREQUISITE_APIS = [
20
+ 'serviceusage.googleapis.com',
21
+ 'cloudbilling.googleapis.com',
22
+ ];
23
+
17
24
  /**
18
25
  * Calls a function with retry logic for GCP API calls.
19
26
  * Retries on gRPC error code 7 (PERMISSION_DENIED).
@@ -84,12 +91,61 @@ async function checkAndEnableApi(
84
91
  }
85
92
  }
86
93
 
94
+ /**
95
+ * Enables a single Google Cloud API with retry logic.
96
+ * @param {object} serviceUsageClient - The Service Usage client.
97
+ * @param {string} projectId - The Google Cloud project ID.
98
+ * @param {string} api - The API identifier (e.g., 'run.googleapis.com').
99
+ * @param {function(string, string=): void} progressCallback - A function to call with progress updates.
100
+ * @returns {Promise<void>} A promise that resolves when the API is enabled.
101
+ * @throws {Error} If the API fails to enable or if there's an issue checking its status.
102
+ */
103
+ export async function enableApiWithRetry(
104
+ serviceUsageClient,
105
+ projectId,
106
+ api,
107
+ progressCallback
108
+ ) {
109
+ const serviceName = `projects/${projectId}/services/${api}`;
110
+ try {
111
+ await checkAndEnableApi(
112
+ serviceUsageClient,
113
+ serviceName,
114
+ api,
115
+ progressCallback
116
+ );
117
+ } catch (error) {
118
+ // First attempt failed, log a warning and retry once after a delay.
119
+ const warnMsg = `Failed to check/enable ${api}, retrying in 1s...`;
120
+ console.warn(warnMsg);
121
+ if (progressCallback) progressCallback({ level: 'warn', data: warnMsg });
122
+
123
+ await new Promise((resolve) => setTimeout(resolve, 1000));
124
+ try {
125
+ await checkAndEnableApi(
126
+ serviceUsageClient,
127
+ serviceName,
128
+ api,
129
+ progressCallback
130
+ );
131
+ } catch (retryError) {
132
+ // If the retry also fails, throw an error.
133
+ const errorMessage = `Failed to ensure API [${api}] is enabled after retry. Please check manually.`;
134
+ console.error(errorMessage, retryError);
135
+ if (progressCallback)
136
+ progressCallback({ level: 'error', data: errorMessage });
137
+ throw new Error(errorMessage);
138
+ }
139
+ }
140
+ }
141
+
87
142
  /**
88
143
  * Ensures that the specified Google Cloud APIs are enabled for the given project.
89
144
  * If an API is not enabled, it attempts to enable it. Retries any failure once after 1s.
90
145
  * Throws an error if an API cannot be enabled.
91
146
  *
92
147
  * @async
148
+ * @param {object} context - The context object containing clients and other parameters.
93
149
  * @param {string} projectId - The Google Cloud project ID.
94
150
  * @param {string[]} apis - An array of API identifiers to check and enable (e.g., 'run.googleapis.com').
95
151
  * @param {function(string, string=): void} progressCallback - A function to call with progress updates.
@@ -97,47 +153,37 @@ async function checkAndEnableApi(
97
153
  * @throws {Error} If an API fails to enable or if there's an issue checking its status.
98
154
  * @returns {Promise<void>} A promise that resolves when all specified APIs are enabled.
99
155
  */
100
- export async function ensureApisEnabled(projectId, apis, progressCallback) {
101
- const { ServiceUsageClient } = await import('@google-cloud/service-usage');
102
- const serviceUsageClient = new ServiceUsageClient({ projectId });
156
+ export async function ensureApisEnabled(
157
+ context,
158
+ projectId,
159
+ apis,
160
+ progressCallback
161
+ ) {
162
+ for (const api of PREREQUISITE_APIS) {
163
+ await enableApiWithRetry(
164
+ context.serviceUsageClient,
165
+ projectId,
166
+ api,
167
+ progressCallback
168
+ );
169
+ }
170
+
171
+ // Before enabling other APIs, ensure billing is enabled.
172
+ await ensureBillingEnabled(context, projectId, progressCallback);
173
+
103
174
  const message = 'Checking and enabling required APIs...';
104
175
  console.log(message);
105
176
  if (progressCallback) progressCallback({ level: 'info', data: message });
106
177
 
107
-
108
178
  for (const api of apis) {
109
- const serviceName = `projects/${projectId}/services/${api}`;
110
- try {
111
- await checkAndEnableApi(
112
- serviceUsageClient,
113
- serviceName,
114
- api,
115
- progressCallback
116
- );
117
- } catch (error) {
118
- // First attempt failed, log a warning and retry once after a delay.
119
- const warnMsg = `Failed to check/enable ${api}, retrying in 1s...`;
120
- console.warn(warnMsg);
121
- if (progressCallback) progressCallback({ level: 'warn', data: warnMsg });
122
-
123
- await new Promise((resolve) => setTimeout(resolve, 1000));
124
- try {
125
- await checkAndEnableApi(
126
- serviceUsageClient,
127
- serviceName,
128
- api,
129
- progressCallback
130
- );
131
- } catch (retryError) {
132
- // If the retry also fails, throw an error.
133
- const errorMessage = `Failed to ensure API [${api}] is enabled after retry. Please check manually.`;
134
- console.error(errorMessage, retryError);
135
- if (progressCallback) progressCallback({ level: 'error', data: errorMessage });
136
- throw new Error(errorMessage);
137
- }
138
- }
179
+ await enableApiWithRetry(
180
+ context.serviceUsageClient,
181
+ projectId,
182
+ api,
183
+ progressCallback
184
+ );
139
185
  }
140
186
  const successMsg = 'All required APIs are enabled.';
141
187
  console.log(successMsg);
142
188
  if (progressCallback) progressCallback({ level: 'info', data: successMsg });
143
- }
189
+ }
@@ -17,7 +17,7 @@ limitations under the License.
17
17
  import {
18
18
  listBillingAccounts,
19
19
  attachProjectToBillingAccount,
20
- } from './gcp-billing-accounts.js';
20
+ } from './billing.js';
21
21
 
22
22
  /**
23
23
  * Lists all accessible Google Cloud Platform projects.
@@ -39,40 +39,50 @@ export async function listProjects() {
39
39
  }
40
40
  }
41
41
 
42
+ /**
43
+ * Creates a compliant GCP project ID in the format 'mcp-cvc-cvc', where 'c' is a consonant and 'v' is a vowel.
44
+ * @function generateProjectId
45
+ * @returns {string} A randomly generated, compliant GCP project ID in the format 'mcp-cvc-cvc'.
46
+ */
47
+ export function generateProjectId() {
48
+ const consonants = 'bcdfghjklmnpqrstvwxyz';
49
+ const vowels = 'aeiou';
50
+
51
+ const getRandomChar = (source) =>
52
+ source.charAt(Math.floor(Math.random() * source.length));
53
+
54
+ const generateCVC = () => {
55
+ const c1 = getRandomChar(consonants);
56
+ const v = getRandomChar(vowels);
57
+ const c2 = getRandomChar(consonants);
58
+ return `${c1}${v}${c2}`;
59
+ };
60
+
61
+ const cvc1 = generateCVC();
62
+ const cvc2 = generateCVC();
63
+ return `mcp-${cvc1}-${cvc2}`;
64
+ }
65
+
42
66
  /**
43
67
  * Creates a new Google Cloud Platform project.
44
68
  * @async
45
69
  * @function createProject
46
70
  * @param {string} [projectId] - Optional. The desired ID for the new project. If not provided, a compliant ID will be generated automatically (e.g., app-cvc-cvc).
71
+ * @param {string} [parent] - Optional. The resource name of the parent under which the project is to be created. e.g., "organizations/123" or "folders/456".
47
72
  * @returns {Promise<{projectId: string}|null>} A promise that resolves to an object containing the new project's ID.
48
73
  */
49
- export async function createProject(projectId) {
74
+ export async function createProject(projectId, parent) {
50
75
  const { ProjectsClient } = await import('@google-cloud/resource-manager');
51
76
  const client = new ProjectsClient();
52
77
  let projectIdToUse = projectId;
53
78
 
54
79
  if (!projectIdToUse) {
55
- const consonants = 'bcdfghjklmnpqrstvwxyz';
56
- const vowels = 'aeiou';
57
-
58
- const getRandomChar = (source) =>
59
- source.charAt(Math.floor(Math.random() * source.length));
60
-
61
- const generateCVC = () => {
62
- const c1 = getRandomChar(consonants);
63
- const v = getRandomChar(vowels);
64
- const c2 = getRandomChar(consonants);
65
- return `${c1}${v}${c2}`;
66
- };
67
-
68
- const cvc1 = generateCVC();
69
- const cvc2 = generateCVC();
70
- projectIdToUse = `mcp-${cvc1}-${cvc2}`;
80
+ projectIdToUse = generateProjectId();
71
81
  console.log(`Project ID not provided, generated ID: ${projectIdToUse}`);
72
82
  }
73
83
 
74
84
  try {
75
- const projectPayload = { projectId: projectIdToUse };
85
+ const projectPayload = { projectId: projectIdToUse, parent };
76
86
 
77
87
  console.log(`Attempting to create project with ID: ${projectIdToUse}`);
78
88
 
@@ -100,12 +110,13 @@ export async function createProject(projectId) {
100
110
  * @async
101
111
  * @function createProjectAndAttachBilling
102
112
  * @param {string} [projectIdParam] - Optional. The desired ID for the new project.
113
+ * @param {string} [parent] - Optional. The resource name of the parent under which the project is to be created. e.g., "organizations/123" or "folders/456".
103
114
  * @returns {Promise<{projectId: string, billingMessage: string}>} A promise that resolves to an object containing the project ID and a billing status message.
104
115
  */
105
- export async function createProjectAndAttachBilling(projectIdParam) {
116
+ export async function createProjectAndAttachBilling(projectIdParam, parent) {
106
117
  let newProject;
107
118
  try {
108
- newProject = await createProject(projectIdParam);
119
+ newProject = await createProject(projectIdParam, parent);
109
120
  } catch (error) {
110
121
  throw new Error(`Failed to create project: ${error.message}`);
111
122
  }
@@ -115,7 +126,7 @@ export async function createProjectAndAttachBilling(projectIdParam) {
115
126
  }
116
127
 
117
128
  const { projectId } = newProject;
118
- let billingMessage = `Project ${projectId} created.`;
129
+ let billingMessage = `Project ${projectId} created successfully.`;
119
130
 
120
131
  try {
121
132
  const billingAccounts = await listBillingAccounts();
@@ -130,27 +141,46 @@ export async function createProjectAndAttachBilling(projectIdParam) {
130
141
  firstBillingAccount.name
131
142
  );
132
143
  if (billingInfo && billingInfo.billingEnabled) {
133
- billingMessage += ` Successfully attached to billing account ${firstBillingAccount.displayName}.`;
144
+ billingMessage += ` It has been attached to billing account ${firstBillingAccount.displayName}.`;
134
145
  } else {
135
- billingMessage += ` Could not attach to billing account ${firstBillingAccount.displayName} or billing not enabled. Please check manually. Console: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
146
+ billingMessage += ` However, it could not be attached to billing account ${firstBillingAccount.displayName} or billing not enabled. Please check manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
136
147
  }
137
148
  } else {
138
149
  const allBillingAccounts = billingAccounts
139
150
  .map((b) => `${b.displayName} (Open: ${b.open})`)
140
151
  .join(', ');
141
- billingMessage += ` No open billing accounts found. Available (may not be usable): ${allBillingAccounts || 'None'}. Please link billing manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
152
+ billingMessage += ` However, no open billing accounts were found. Available (may not be usable): ${allBillingAccounts || 'None'}. Please link billing manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
142
153
  }
143
154
  } else {
144
- billingMessage +=
145
- ' No billing accounts found. Please link billing manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}';
155
+ billingMessage += ` However, no billing accounts were found. Please link billing manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
146
156
  }
147
157
  } catch (billingError) {
148
158
  console.error(
149
159
  `Error during billing operations for project ${projectId}:`,
150
160
  billingError
151
161
  );
152
- billingMessage += ` Error during billing operations: ${billingError.message}. Please check manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
162
+ billingMessage += ` However, an error occurred during billing operations: ${billingError.message}. Please check manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
153
163
  }
154
164
 
155
165
  return { projectId, billingMessage };
156
166
  }
167
+
168
+ /**
169
+ * Deletes a Google Cloud Platform project.
170
+ * @async
171
+ * @function deleteProject
172
+ * @param {string} projectId - The ID of the project to delete.
173
+ * @returns {Promise<void>} A promise that resolves when the delete operation is initiated.
174
+ */
175
+ export async function deleteProject(projectId) {
176
+ const { ProjectsClient } = await import('@google-cloud/resource-manager');
177
+ const client = new ProjectsClient();
178
+ try {
179
+ console.log(`Attempting to delete project with ID: ${projectId}`);
180
+ await client.deleteProject({ name: `projects/${projectId}` });
181
+ console.log(`Project ${projectId} deletion initiated successfully.`);
182
+ } catch (error) {
183
+ console.error(`Error deleting GCP project ${projectId}:`, error.message);
184
+ throw error; // Re-throw to be caught by the caller
185
+ }
186
+ }
@@ -0,0 +1,98 @@
1
+ /*
2
+ Copyright 2025 Google LLC
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { callWithRetry } from './helpers.js';
18
+ import { logAndProgress } from '../util/helpers.js';
19
+
20
+ /**
21
+ * Ensures that an Artifact Registry repository exists.
22
+ * If the repository does not exist, it attempts to create it with the specified format.
23
+ *
24
+ * @async
25
+ * @param {object} context - The context object containing clients and other parameters.
26
+ * @param {string} projectId - The Google Cloud project ID.
27
+ * @param {string} location - The Google Cloud region for the repository.
28
+ * @param {string} repositoryId - The ID for the Artifact Registry repository.
29
+ * @param {string} [format='DOCKER'] - The format of the repository (e.g., 'DOCKER', 'NPM'). Defaults to 'DOCKER'.
30
+ * @param {function(object): void} [progressCallback] - Optional callback for progress updates.
31
+ * @returns {Promise<object>} A promise that resolves with the Artifact Registry repository object.
32
+ * @throws {Error} If there's an error checking or creating the repository.
33
+ */
34
+ export async function ensureArtifactRegistryRepoExists(
35
+ context,
36
+ projectId,
37
+ location,
38
+ repositoryId,
39
+ format = 'DOCKER',
40
+ progressCallback
41
+ ) {
42
+ const parent = `projects/${projectId}/locations/${location}`;
43
+ const repoPath = context.artifactRegistryClient.repositoryPath(
44
+ projectId,
45
+ location,
46
+ repositoryId
47
+ );
48
+
49
+ try {
50
+ const [repository] = await callWithRetry(
51
+ () => context.artifactRegistryClient.getRepository({ name: repoPath }),
52
+ `artifactRegistry.getRepository ${repositoryId}`
53
+ );
54
+ await logAndProgress(
55
+ `Repository ${repositoryId} already exists in ${location}.`,
56
+ progressCallback
57
+ );
58
+ return repository;
59
+ } catch (error) {
60
+ if (error.code === 5) {
61
+ await logAndProgress(
62
+ `Repository ${repositoryId} does not exist in ${location}. Creating...`,
63
+ progressCallback
64
+ );
65
+ const repositoryToCreate = {
66
+ format: format,
67
+ };
68
+
69
+ const [operation] = await callWithRetry(
70
+ () =>
71
+ context.artifactRegistryClient.createRepository({
72
+ parent: parent,
73
+ repository: repositoryToCreate,
74
+ repositoryId: repositoryId,
75
+ }),
76
+ `artifactRegistry.createRepository ${repositoryId}`
77
+ );
78
+ await logAndProgress(
79
+ `Creating Artifact Registry repository ${repositoryId}...`,
80
+ progressCallback
81
+ );
82
+ const [result] = await operation.promise();
83
+ await logAndProgress(
84
+ `Artifact Registry repository ${result.name} created successfully.`,
85
+ progressCallback
86
+ );
87
+ return result;
88
+ } else {
89
+ const errorMessage = `Error checking/creating repository ${repositoryId}: ${error.message}`;
90
+ console.error(
91
+ `Error checking/creating repository ${repositoryId}:`,
92
+ error
93
+ );
94
+ await logAndProgress(errorMessage, progressCallback, 'error');
95
+ throw error;
96
+ }
97
+ }
98
+ }