@google-cloud/cloud-run-mcp 1.5.0 → 1.6.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/.c8rc.json +9 -0
- package/.github/workflows/lint-checker.yml +17 -0
- package/.kokoro/kokoro_build.sh +22 -0
- package/.kokoro/presubmit.cfg +21 -0
- package/.kokoro/run_tests.sh +31 -0
- package/CONTRIBUTING.md +23 -8
- package/README.md +52 -11
- package/REVIEWING.md +20 -0
- package/gemini-extension/GEMINI.md +1 -1
- package/lib/cloud-api/build.js +200 -0
- package/lib/{gcp-api-helpers.js → cloud-api/helpers.js} +12 -8
- package/lib/{gcp-projects.js → cloud-api/projects.js} +58 -28
- package/lib/cloud-api/registry.js +98 -0
- package/lib/{cloud-run-services.js → cloud-api/run.js} +103 -21
- package/lib/cloud-api/storage.js +123 -0
- package/lib/deployment/deployer.js +474 -0
- package/lib/util/archive.js +95 -0
- package/lib/util/helpers.js +42 -0
- package/mcp-server.js +21 -10
- package/package.json +7 -4
- package/test/local/cloud-api/build.test.js +436 -0
- package/test/local/cloud-api/projects.test.js +18 -0
- package/test/local/mcp-server-streamable-http.test.js +57 -0
- package/test/local/notifications.test.js +9 -6
- package/test/local/tools.test.js +114 -70
- package/test/need-gcp/cloud-run-services.test.js +44 -44
- package/test/need-gcp/{cloud-run-deploy.test.js → deployer.test.js} +3 -56
- package/test/need-gcp/gcp-auth-check.test.js +1 -1
- package/test/need-gcp/gcp-projects.test.js +3 -15
- package/test/need-gcp/test-helpers.js +175 -0
- package/test/need-gcp/workflows/deployment-workflows.test.js +100 -0
- package/tools/register-tools.js +106 -97
- package/tools/tools.js +2 -9
- package/lib/cloud-run-deploy.js +0 -937
- /package/lib/{gcp-auth-check.js → cloud-api/auth.js} +0 -0
- /package/lib/{gcp-billing-accounts.js → cloud-api/billing.js} +0 -0
- /package/lib/{gcp-metadata.js → cloud-api/metadata.js} +0 -0
|
@@ -17,7 +17,7 @@ limitations under the License.
|
|
|
17
17
|
import {
|
|
18
18
|
listBillingAccounts,
|
|
19
19
|
attachProjectToBillingAccount,
|
|
20
|
-
} from './
|
|
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
|
-
|
|
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 += `
|
|
144
|
+
billingMessage += ` It has been attached to billing account ${firstBillingAccount.displayName}.`;
|
|
134
145
|
} else {
|
|
135
|
-
billingMessage += `
|
|
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 += `
|
|
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 += `
|
|
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
|
+
}
|
|
@@ -15,42 +15,74 @@ limitations under the License.
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import protofiles from 'google-proto-files';
|
|
18
|
-
import { callWithRetry, ensureApisEnabled } from './
|
|
18
|
+
import { callWithRetry, ensureApisEnabled } from './helpers.js';
|
|
19
|
+
import { logAndProgress } from '../util/helpers.js';
|
|
19
20
|
|
|
20
21
|
let runClient;
|
|
22
|
+
let serviceUsageClient;
|
|
21
23
|
let loggingClient;
|
|
22
24
|
|
|
25
|
+
async function listCloudRunLocations(projectId) {
|
|
26
|
+
const listLocationsRequest = {
|
|
27
|
+
name: `projects/${projectId}`,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const availableLocations = [];
|
|
31
|
+
try {
|
|
32
|
+
console.log('Listing Cloud Run supported locations:');
|
|
33
|
+
const iterable = runClient.listLocationsAsync(listLocationsRequest);
|
|
34
|
+
for await (const location of iterable) {
|
|
35
|
+
if (location.labels.initialized) {
|
|
36
|
+
console.log(`${location.locationId}: ${location.name}`);
|
|
37
|
+
availableLocations.push(location.locationId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error('Error listing locations:', err);
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
return availableLocations;
|
|
45
|
+
}
|
|
46
|
+
|
|
23
47
|
/**
|
|
24
|
-
* Lists all Cloud Run services in a given project
|
|
48
|
+
* Lists all Cloud Run services in a given project.
|
|
25
49
|
* @param {string} projectId - The Google Cloud project ID.
|
|
26
|
-
* @
|
|
27
|
-
* @returns {Promise<Array<object>>} - A promise that resolves to an array of service objects.
|
|
50
|
+
* @returns {Promise<object>} - A promise that resolves to an object mapping region to list of service objects in that region.
|
|
28
51
|
*/
|
|
29
|
-
export async function listServices(projectId
|
|
30
|
-
// TODO make location optional, if not provided, list all services from all regions
|
|
52
|
+
export async function listServices(projectId) {
|
|
31
53
|
if (!runClient) {
|
|
32
54
|
const { v2 } = await import('@google-cloud/run');
|
|
33
55
|
const { ServicesClient } = v2;
|
|
56
|
+
const { ServiceUsageClient } = await import('@google-cloud/service-usage');
|
|
34
57
|
runClient = new ServicesClient({ projectId });
|
|
58
|
+
serviceUsageClient = new ServiceUsageClient({ projectId });
|
|
35
59
|
}
|
|
60
|
+
const context = {
|
|
61
|
+
runClient: runClient,
|
|
62
|
+
serviceUsageClient: serviceUsageClient,
|
|
63
|
+
};
|
|
64
|
+
await ensureApisEnabled(context, projectId, ['run.googleapis.com']);
|
|
65
|
+
const locations = await listCloudRunLocations(projectId);
|
|
36
66
|
|
|
37
|
-
|
|
67
|
+
const allServices = {};
|
|
68
|
+
for (const location of locations) {
|
|
69
|
+
const parent = runClient.locationPath(projectId, location);
|
|
38
70
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
throw error;
|
|
71
|
+
try {
|
|
72
|
+
console.log(
|
|
73
|
+
`Listing Cloud Run services in project ${projectId}, location ${location}...`
|
|
74
|
+
);
|
|
75
|
+
const [services] = await callWithRetry(
|
|
76
|
+
() => runClient.listServices({ parent }),
|
|
77
|
+
'listServices'
|
|
78
|
+
);
|
|
79
|
+
allServices[location] = services;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(`Error listing Cloud Run services:`, error);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
53
84
|
}
|
|
85
|
+
return allServices;
|
|
54
86
|
}
|
|
55
87
|
|
|
56
88
|
/**
|
|
@@ -191,3 +223,53 @@ function formatLogEntry(entry) {
|
|
|
191
223
|
}
|
|
192
224
|
return `[${timestampStr}] [${severity}] ${responseData} ${data}`;
|
|
193
225
|
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Checks if a Cloud Run service already exists.
|
|
229
|
+
*
|
|
230
|
+
* @async
|
|
231
|
+
* @param {object} context - The context object containing clients and other parameters.
|
|
232
|
+
* @param {string} projectId - The Google Cloud project ID.
|
|
233
|
+
* @param {string} location - The Google Cloud region where the service is located.
|
|
234
|
+
* @param {string} serviceId - The ID of the Cloud Run service.
|
|
235
|
+
* @param {function(object): void} [progressCallback] - Optional callback for progress updates.
|
|
236
|
+
* @returns {Promise<boolean>} A promise that resolves to true if the service exists, false otherwise.
|
|
237
|
+
* @throws {Error} If there's an error checking the service (other than not found).
|
|
238
|
+
*/
|
|
239
|
+
export async function checkCloudRunServiceExists(
|
|
240
|
+
context,
|
|
241
|
+
projectId,
|
|
242
|
+
location,
|
|
243
|
+
serviceId,
|
|
244
|
+
progressCallback
|
|
245
|
+
) {
|
|
246
|
+
const parent = context.runClient.locationPath(projectId, location);
|
|
247
|
+
const servicePath = context.runClient.servicePath(
|
|
248
|
+
projectId,
|
|
249
|
+
location,
|
|
250
|
+
serviceId
|
|
251
|
+
);
|
|
252
|
+
try {
|
|
253
|
+
await callWithRetry(
|
|
254
|
+
() => context.runClient.getService({ name: servicePath }),
|
|
255
|
+
`getService ${serviceId}`
|
|
256
|
+
);
|
|
257
|
+
await logAndProgress(
|
|
258
|
+
`Cloud Run service ${serviceId} already exists.`,
|
|
259
|
+
progressCallback
|
|
260
|
+
);
|
|
261
|
+
return true;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (error.code === 5) {
|
|
264
|
+
await logAndProgress(
|
|
265
|
+
`Cloud Run service ${serviceId} does not exist.`,
|
|
266
|
+
progressCallback
|
|
267
|
+
);
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
const errorMessage = `Error checking Cloud Run service ${serviceId}: ${error.message}`;
|
|
271
|
+
console.error(`Error checking Cloud Run service ${serviceId}:`, error);
|
|
272
|
+
await logAndProgress(errorMessage, progressCallback, 'error');
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
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 a Google Cloud Storage bucket exists.
|
|
22
|
+
* If the bucket does not exist, it attempts to create it in the specified location.
|
|
23
|
+
*
|
|
24
|
+
* @async
|
|
25
|
+
* @param {object} context - The context object containing clients and other parameters.
|
|
26
|
+
* @param {string} bucketName - The name of the storage bucket.
|
|
27
|
+
* @param {string} [location='us'] - The location to create the bucket in if it doesn't exist. Defaults to 'us'.
|
|
28
|
+
* @param {function(object): void} [progressCallback] - Optional callback for progress updates.
|
|
29
|
+
* @returns {Promise<import('@google-cloud/storage').Bucket>} A promise that resolves with the GCS Bucket object.
|
|
30
|
+
* @throws {Error} If there's an error checking or creating the bucket.
|
|
31
|
+
*/
|
|
32
|
+
export async function ensureStorageBucketExists(
|
|
33
|
+
context,
|
|
34
|
+
bucketName,
|
|
35
|
+
location = 'us',
|
|
36
|
+
progressCallback
|
|
37
|
+
) {
|
|
38
|
+
const bucket = context.storage.bucket(bucketName);
|
|
39
|
+
try {
|
|
40
|
+
const [exists] = await callWithRetry(
|
|
41
|
+
() => bucket.exists(),
|
|
42
|
+
`storage.bucket.exists ${bucketName}`
|
|
43
|
+
);
|
|
44
|
+
if (exists) {
|
|
45
|
+
await logAndProgress(
|
|
46
|
+
`Bucket ${bucketName} already exists.`,
|
|
47
|
+
progressCallback
|
|
48
|
+
);
|
|
49
|
+
return bucket;
|
|
50
|
+
} else {
|
|
51
|
+
await logAndProgress(
|
|
52
|
+
`Bucket ${bucketName} does not exist. Creating in location ${location}...`,
|
|
53
|
+
progressCallback
|
|
54
|
+
);
|
|
55
|
+
try {
|
|
56
|
+
const [createdBucket] = await callWithRetry(
|
|
57
|
+
() =>
|
|
58
|
+
context.storage.createBucket(bucketName, { location: location }),
|
|
59
|
+
`storage.createBucket ${bucketName}`
|
|
60
|
+
);
|
|
61
|
+
await logAndProgress(
|
|
62
|
+
`Storage bucket ${createdBucket.name} created successfully in ${location}.`,
|
|
63
|
+
progressCallback
|
|
64
|
+
);
|
|
65
|
+
return createdBucket;
|
|
66
|
+
} catch (createError) {
|
|
67
|
+
const errorMessage = `Failed to create storage bucket ${bucketName}. Error details: ${createError.message}`;
|
|
68
|
+
console.error(
|
|
69
|
+
`Failed to create storage bucket ${bucketName}. Error details:`,
|
|
70
|
+
createError
|
|
71
|
+
);
|
|
72
|
+
await logAndProgress(errorMessage, progressCallback, 'error');
|
|
73
|
+
throw createError;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const errorMessage = `Error checking/creating bucket ${bucketName}: ${error.message}`;
|
|
78
|
+
console.error(`Error checking/creating bucket ${bucketName}:`, error);
|
|
79
|
+
await logAndProgress(errorMessage, progressCallback, 'error');
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Uploads a buffer to a specified Google Cloud Storage bucket and blob name.
|
|
86
|
+
*
|
|
87
|
+
* @async
|
|
88
|
+
* @param {object} context - The context object containing clients and other parameters.
|
|
89
|
+
* @param {import('@google-cloud/storage').Bucket} bucket - The Google Cloud Storage bucket object.
|
|
90
|
+
* @param {Buffer} buffer - The buffer containing the data to upload.
|
|
91
|
+
* @param {string} destinationBlobName - The name for the blob in the bucket.
|
|
92
|
+
* @param {function(object): void} [progressCallback] - Optional callback for progress updates.
|
|
93
|
+
* @returns {Promise<import('@google-cloud/storage').File>} A promise that resolves with the GCS File object representing the uploaded blob.
|
|
94
|
+
* @throws {Error} If the upload fails.
|
|
95
|
+
*/
|
|
96
|
+
export async function uploadToStorageBucket(
|
|
97
|
+
context,
|
|
98
|
+
bucket,
|
|
99
|
+
buffer,
|
|
100
|
+
destinationBlobName,
|
|
101
|
+
progressCallback
|
|
102
|
+
) {
|
|
103
|
+
try {
|
|
104
|
+
await logAndProgress(
|
|
105
|
+
`Uploading buffer to gs://${bucket.name}/${destinationBlobName}...`,
|
|
106
|
+
progressCallback
|
|
107
|
+
);
|
|
108
|
+
await callWithRetry(
|
|
109
|
+
() => bucket.file(destinationBlobName).save(buffer),
|
|
110
|
+
`storage.bucket.file.save ${destinationBlobName}`
|
|
111
|
+
);
|
|
112
|
+
await logAndProgress(
|
|
113
|
+
`File ${destinationBlobName} uploaded successfully to gs://${bucket.name}/${destinationBlobName}.`,
|
|
114
|
+
progressCallback
|
|
115
|
+
);
|
|
116
|
+
return bucket.file(destinationBlobName);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const errorMessage = `Error uploading buffer: ${error.message}`;
|
|
119
|
+
console.error(`Error uploading buffer:`, error);
|
|
120
|
+
await logAndProgress(errorMessage, progressCallback, 'error');
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|