@google-cloud/cloud-run-mcp 1.3.0 → 1.5.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/.github/images/deploy_from_apps.gif +0 -0
- package/.github/images/deploy_from_ide.gif +0 -0
- package/.github/images/deploycli.gif +0 -0
- package/.github/workflows/local-tests.yml +1 -1
- package/CONTRIBUTING.md +6 -0
- package/README.md +19 -14
- package/gemini-extension/GEMINI.md +2 -2
- package/lib/cloud-run-deploy.js +78 -58
- package/mcp-server.js +8 -1
- package/package.json +3 -3
- package/test/local/mcp-server.test.js +0 -1
- package/test/local/notifications.test.js +45 -0
- package/test/local/tools.test.js +40 -65
- package/test/need-gcp/cloud-run-deploy.test.js +101 -135
- package/test/need-gcp/cloud-run-services.test.js +57 -77
- package/test/need-gcp/gcp-projects.test.js +14 -56
- package/{tools.js → tools/register-tools.js} +95 -461
- package/tools/tools.js +55 -0
package/test/local/tools.test.js
CHANGED
|
@@ -8,11 +8,11 @@ describe('registerTools', () => {
|
|
|
8
8
|
registerTool: mock.fn(),
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
const { registerTools } = await esmock('../../tools.js', {});
|
|
11
|
+
const { registerTools } = await esmock('../../tools/tools.js', {});
|
|
12
12
|
|
|
13
13
|
registerTools(server);
|
|
14
14
|
|
|
15
|
-
assert.strictEqual(server.registerTool.mock.callCount(),
|
|
15
|
+
assert.strictEqual(server.registerTool.mock.callCount(), 8);
|
|
16
16
|
const toolNames = server.registerTool.mock.calls.map(
|
|
17
17
|
(call) => call.arguments[0]
|
|
18
18
|
);
|
|
@@ -22,7 +22,6 @@ describe('registerTools', () => {
|
|
|
22
22
|
'create_project',
|
|
23
23
|
'deploy_container_image',
|
|
24
24
|
'deploy_file_contents',
|
|
25
|
-
'deploy_local_files',
|
|
26
25
|
'deploy_local_folder',
|
|
27
26
|
'get_service',
|
|
28
27
|
'get_service_log',
|
|
@@ -38,7 +37,7 @@ describe('registerTools', () => {
|
|
|
38
37
|
registerTool: mock.fn(),
|
|
39
38
|
};
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
const { registerTools } = await esmock('../../tools/tools.js', {}, {
|
|
42
41
|
'../../lib/gcp-projects.js': {
|
|
43
42
|
listProjects: () =>
|
|
44
43
|
Promise.resolve([{ id: 'project1' }, { id: 'project2' }]),
|
|
@@ -69,7 +68,7 @@ describe('registerTools', () => {
|
|
|
69
68
|
registerTool: mock.fn(),
|
|
70
69
|
};
|
|
71
70
|
|
|
72
|
-
|
|
71
|
+
const { registerTools } = await esmock('../../tools/tools.js', {}, {
|
|
73
72
|
'../../lib/gcp-projects.js': {
|
|
74
73
|
createProjectAndAttachBilling: (projectId) =>
|
|
75
74
|
Promise.resolve({
|
|
@@ -101,7 +100,7 @@ describe('registerTools', () => {
|
|
|
101
100
|
registerTool: mock.fn(),
|
|
102
101
|
};
|
|
103
102
|
|
|
104
|
-
|
|
103
|
+
const { registerTools } = await esmock('../../tools/tools.js', {}, {
|
|
105
104
|
'../../lib/gcp-projects.js': {
|
|
106
105
|
createProjectAndAttachBilling: () =>
|
|
107
106
|
Promise.resolve({
|
|
@@ -135,7 +134,7 @@ describe('registerTools', () => {
|
|
|
135
134
|
registerTool: mock.fn(),
|
|
136
135
|
};
|
|
137
136
|
|
|
138
|
-
|
|
137
|
+
const { registerTools } = await esmock('../../tools/tools.js', {}, {
|
|
139
138
|
'../../lib/cloud-run-services.js': {
|
|
140
139
|
listServices: () =>
|
|
141
140
|
Promise.resolve([
|
|
@@ -172,7 +171,7 @@ describe('registerTools', () => {
|
|
|
172
171
|
registerTool: mock.fn(),
|
|
173
172
|
};
|
|
174
173
|
|
|
175
|
-
|
|
174
|
+
const { registerTools } = await esmock('../../tools/tools.js', {}, {
|
|
176
175
|
'../../lib/cloud-run-services.js': {
|
|
177
176
|
getService: () =>
|
|
178
177
|
Promise.resolve({
|
|
@@ -223,7 +222,7 @@ describe('registerTools', () => {
|
|
|
223
222
|
return Promise.resolve({ logs: 'log3\nlog4', requestOptions: null });
|
|
224
223
|
};
|
|
225
224
|
|
|
226
|
-
|
|
225
|
+
const { registerTools } = await esmock('../../tools/tools.js', {}, {
|
|
227
226
|
'../../lib/cloud-run-services.js': {
|
|
228
227
|
getServiceLogs: getServiceLogs,
|
|
229
228
|
},
|
|
@@ -251,40 +250,7 @@ describe('registerTools', () => {
|
|
|
251
250
|
});
|
|
252
251
|
});
|
|
253
252
|
|
|
254
|
-
|
|
255
|
-
it('should deploy local files', async () => {
|
|
256
|
-
const server = {
|
|
257
|
-
registerTool: mock.fn(),
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const { registerTools } = await esmock('../../tools.js', {
|
|
261
|
-
'../../lib/cloud-run-deploy.js': {
|
|
262
|
-
deploy: () => Promise.resolve({ uri: 'my-uri' }),
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
registerTools(server, { gcpCredentialsAvailable: true });
|
|
267
|
-
|
|
268
|
-
const handler = server.registerTool.mock.calls.find(
|
|
269
|
-
(call) => call.arguments[0] === 'deploy_local_files'
|
|
270
|
-
).arguments[2];
|
|
271
|
-
const result = await handler({
|
|
272
|
-
project: 'my-project',
|
|
273
|
-
region: 'my-region',
|
|
274
|
-
service: 'my-service',
|
|
275
|
-
files: ['file1', 'file2'],
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
assert.deepStrictEqual(result, {
|
|
279
|
-
content: [
|
|
280
|
-
{
|
|
281
|
-
type: 'text',
|
|
282
|
-
text: 'Cloud Run service my-service deployed in project my-project\nCloud Console: https://console.cloud.google.com/run/detail/my-region/my-service?project=my-project\nService URL: my-uri',
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
});
|
|
253
|
+
|
|
288
254
|
|
|
289
255
|
describe('deploy_local_folder', () => {
|
|
290
256
|
it('should deploy local folder', async () => {
|
|
@@ -292,7 +258,7 @@ describe('registerTools', () => {
|
|
|
292
258
|
registerTool: mock.fn(),
|
|
293
259
|
};
|
|
294
260
|
|
|
295
|
-
|
|
261
|
+
const { registerTools } = await esmock('../../tools/tools.js', {}, {
|
|
296
262
|
'../../lib/cloud-run-deploy.js': {
|
|
297
263
|
deploy: () => Promise.resolve({ uri: 'my-uri' }),
|
|
298
264
|
},
|
|
@@ -303,12 +269,15 @@ describe('registerTools', () => {
|
|
|
303
269
|
const handler = server.registerTool.mock.calls.find(
|
|
304
270
|
(call) => call.arguments[0] === 'deploy_local_folder'
|
|
305
271
|
).arguments[2];
|
|
306
|
-
const result = await handler(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
272
|
+
const result = await handler(
|
|
273
|
+
{
|
|
274
|
+
project: 'my-project',
|
|
275
|
+
region: 'my-region',
|
|
276
|
+
service: 'my-service',
|
|
277
|
+
folderPath: '/my/folder',
|
|
278
|
+
},
|
|
279
|
+
{ sendNotification: mock.fn() }
|
|
280
|
+
);
|
|
312
281
|
|
|
313
282
|
assert.deepStrictEqual(result, {
|
|
314
283
|
content: [
|
|
@@ -327,7 +296,7 @@ describe('registerTools', () => {
|
|
|
327
296
|
registerTool: mock.fn(),
|
|
328
297
|
};
|
|
329
298
|
|
|
330
|
-
|
|
299
|
+
const { registerTools } = await esmock('../../tools/tools.js', {}, {
|
|
331
300
|
'../../lib/cloud-run-deploy.js': {
|
|
332
301
|
deploy: () => Promise.resolve({ uri: 'my-uri' }),
|
|
333
302
|
},
|
|
@@ -338,12 +307,15 @@ describe('registerTools', () => {
|
|
|
338
307
|
const handler = server.registerTool.mock.calls.find(
|
|
339
308
|
(call) => call.arguments[0] === 'deploy_file_contents'
|
|
340
309
|
).arguments[2];
|
|
341
|
-
const result = await handler(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
310
|
+
const result = await handler(
|
|
311
|
+
{
|
|
312
|
+
project: 'my-project',
|
|
313
|
+
region: 'my-region',
|
|
314
|
+
service: 'my-service',
|
|
315
|
+
files: [{ filename: 'file1', content: 'content1' }],
|
|
316
|
+
},
|
|
317
|
+
{ sendNotification: mock.fn() }
|
|
318
|
+
);
|
|
347
319
|
|
|
348
320
|
assert.deepStrictEqual(result, {
|
|
349
321
|
content: [
|
|
@@ -362,7 +334,7 @@ describe('registerTools', () => {
|
|
|
362
334
|
registerTool: mock.fn(),
|
|
363
335
|
};
|
|
364
336
|
|
|
365
|
-
|
|
337
|
+
const { registerTools } = await esmock('../../tools/tools.js', {}, {
|
|
366
338
|
'../../lib/cloud-run-deploy.js': {
|
|
367
339
|
deployImage: () => Promise.resolve({ uri: 'my-uri' }),
|
|
368
340
|
},
|
|
@@ -373,12 +345,15 @@ describe('registerTools', () => {
|
|
|
373
345
|
const handler = server.registerTool.mock.calls.find(
|
|
374
346
|
(call) => call.arguments[0] === 'deploy_container_image'
|
|
375
347
|
).arguments[2];
|
|
376
|
-
const result = await handler(
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
348
|
+
const result = await handler(
|
|
349
|
+
{
|
|
350
|
+
project: 'my-project',
|
|
351
|
+
region: 'my-region',
|
|
352
|
+
service: 'my-service',
|
|
353
|
+
imageUrl: 'gcr.io/my-project/my-image',
|
|
354
|
+
},
|
|
355
|
+
{ sendNotification: mock.fn() }
|
|
356
|
+
);
|
|
382
357
|
|
|
383
358
|
assert.deepStrictEqual(result, {
|
|
384
359
|
content: [
|
|
@@ -397,7 +372,7 @@ describe('registerTools', () => {
|
|
|
397
372
|
registerTool: mock.fn(),
|
|
398
373
|
};
|
|
399
374
|
|
|
400
|
-
|
|
375
|
+
const { registerTools } = await esmock('../../tools/tools.js', {});
|
|
401
376
|
|
|
402
377
|
registerTools(server, { gcpCredentialsAvailable: false });
|
|
403
378
|
|
|
@@ -14,145 +14,111 @@ See the License for the specific language governing permissions and
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { describe, test } from 'node:test';
|
|
18
|
+
import assert from 'node:assert';
|
|
17
19
|
import { deploy, deployImage } from '../../lib/cloud-run-deploy.js';
|
|
18
|
-
import readline from 'readline/promises';
|
|
19
|
-
import { stdin as input, stdout as output } from 'process';
|
|
20
20
|
import fs from 'fs/promises';
|
|
21
21
|
import path from 'path';
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*/
|
|
28
|
-
async function getProjectId() {
|
|
29
|
-
let projectIdInput = process.argv[2]; // Get the third element (index 2) which is the first argument
|
|
30
|
-
|
|
31
|
-
if (!projectIdInput) {
|
|
32
|
-
const rl = readline.createInterface({ input, output });
|
|
33
|
-
projectIdInput = await rl.question(
|
|
34
|
-
'Please enter your Google Cloud project ID: '
|
|
35
|
-
);
|
|
36
|
-
rl.close();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (!projectIdInput || projectIdInput.trim() === '') {
|
|
40
|
-
console.error('No project ID provided. Exiting.');
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
if (projectIdInput.toLowerCase() === 'new') {
|
|
44
|
-
console.error(
|
|
45
|
-
'Project creation ("new") is now handled by test-create-project.js. Please provide an existing project ID. Exiting.'
|
|
46
|
-
);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
console.log(`Using Project ID: ${projectIdInput}`);
|
|
50
|
-
return projectIdInput;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const projectIdToUse = await getProjectId();
|
|
54
|
-
|
|
55
|
-
// Configuration for Go deployment with Dockerfile
|
|
56
|
-
const configGoWithDockerfile = {
|
|
57
|
-
projectId: projectIdToUse, // Use the obtained project ID
|
|
58
|
-
serviceName: 'example-go-app-docker', // Name of the Cloud Run service
|
|
59
|
-
region: 'europe-west1', // Google Cloud region
|
|
60
|
-
files: [
|
|
61
|
-
'example-sources-to-deploy/main.go',
|
|
62
|
-
'example-sources-to-deploy/go.mod',
|
|
63
|
-
'example-sources-to-deploy/Dockerfile',
|
|
64
|
-
],
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Configuration for Go deployment without Dockerfile (using buildpacks)
|
|
68
|
-
const configGoWithoutDockerfile = {
|
|
69
|
-
projectId: projectIdToUse,
|
|
70
|
-
serviceName: 'example-go-app-buildpack',
|
|
71
|
-
region: 'europe-west1',
|
|
72
|
-
files: [
|
|
73
|
-
'example-sources-to-deploy/main.go',
|
|
74
|
-
'example-sources-to-deploy/go.mod',
|
|
75
|
-
// Dockerfile is intentionally omitted here
|
|
76
|
-
],
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// Configuration for Go deployment with file content (using buildpacks)
|
|
80
|
-
const configGoWithContent = {
|
|
81
|
-
projectId: projectIdToUse,
|
|
82
|
-
serviceName: 'example-go-app-content',
|
|
83
|
-
region: 'europe-west1',
|
|
84
|
-
files: [], // To be populated with file content
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Configuration for a deployment that is expected to fail
|
|
88
|
-
const configFailingBuild = {
|
|
89
|
-
projectId: projectIdToUse,
|
|
90
|
-
serviceName: 'example-failing-app',
|
|
91
|
-
region: 'europe-west1',
|
|
92
|
-
files: [
|
|
93
|
-
{
|
|
94
|
-
filename: 'main.txt',
|
|
95
|
-
content:
|
|
96
|
-
'This is not a valid application source file and should cause a build failure.',
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// Configuration for deploying a container image
|
|
102
|
-
const configImageDeploy = {
|
|
103
|
-
projectId: projectIdToUse,
|
|
104
|
-
serviceName: 'hello-from-image',
|
|
105
|
-
region: 'europe-west1',
|
|
106
|
-
imageUrl: 'gcr.io/cloudrun/hello',
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
console.log('\n--- Testing container image deployment ---');
|
|
111
|
-
await deployImage(configImageDeploy);
|
|
112
|
-
console.log('--- Container image deployment test completed ---');
|
|
113
|
-
|
|
114
|
-
console.log('\n--- Testing intentionally failing build ---');
|
|
115
|
-
try {
|
|
116
|
-
await deploy(configFailingBuild);
|
|
117
|
-
// If deploy doesn't throw an error, then the test has failed because it was expected to fail.
|
|
118
|
-
console.error(
|
|
119
|
-
'Deployment test failed: The build with invalid files succeeded when it was expected to fail.'
|
|
120
|
-
);
|
|
121
|
-
process.exit(1);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.log('Intentionally failing build test completed as expected.');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
console.log('--- Testing Go deployment with Dockerfile ---');
|
|
127
|
-
await deploy(configGoWithDockerfile);
|
|
128
|
-
console.log('--- Go deployment with Dockerfile test completed ---');
|
|
129
|
-
|
|
130
|
-
console.log(
|
|
131
|
-
'\n--- Testing Go deployment without Dockerfile (Buildpacks) ---'
|
|
132
|
-
);
|
|
133
|
-
await deploy(configGoWithoutDockerfile);
|
|
134
|
-
console.log(
|
|
135
|
-
'--- Go deployment without Dockerfile (Buildpacks) test completed ---'
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
console.log('\n--- Testing Go deployment with file content (Buildpacks) ---');
|
|
139
|
-
const mainGoContent = await fs.readFile(
|
|
140
|
-
path.resolve('example-sources-to-deploy/main.go'),
|
|
141
|
-
'utf-8'
|
|
142
|
-
);
|
|
143
|
-
const goModContent = await fs.readFile(
|
|
144
|
-
path.resolve('example-sources-to-deploy/go.mod'),
|
|
145
|
-
'utf-8'
|
|
146
|
-
);
|
|
147
|
-
configGoWithContent.files = [
|
|
148
|
-
{ filename: 'main.go', content: mainGoContent },
|
|
149
|
-
{ filename: 'go.mod', content: goModContent },
|
|
150
|
-
];
|
|
151
|
-
await deploy(configGoWithContent);
|
|
152
|
-
console.log(
|
|
153
|
-
'--- Go deployment with file content (Buildpacks) test completed ---'
|
|
23
|
+
const projectId = process.env.GOOGLE_CLOUD_PROJECT || process.argv[2];
|
|
24
|
+
if (!projectId) {
|
|
25
|
+
console.error(
|
|
26
|
+
'Usage: node <script> <projectId> or set GOOGLE_CLOUD_PROJECT'
|
|
154
27
|
);
|
|
155
|
-
} catch (error) {
|
|
156
|
-
console.error('An unexpected error occurred in the test script:', error);
|
|
157
28
|
process.exit(1);
|
|
158
29
|
}
|
|
30
|
+
|
|
31
|
+
describe('Cloud Run Deployments', () => {
|
|
32
|
+
test('should deploy a container image', async () => {
|
|
33
|
+
const configImageDeploy = {
|
|
34
|
+
projectId: projectId,
|
|
35
|
+
serviceName: 'hello-from-image',
|
|
36
|
+
region: 'europe-west1',
|
|
37
|
+
imageUrl: 'gcr.io/cloudrun/hello',
|
|
38
|
+
};
|
|
39
|
+
await deployImage(configImageDeploy);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('should fail to deploy with invalid files', async () => {
|
|
43
|
+
const configFailingBuild = {
|
|
44
|
+
projectId: projectId,
|
|
45
|
+
serviceName: 'example-failing-app',
|
|
46
|
+
region: 'europe-west1',
|
|
47
|
+
files: [
|
|
48
|
+
{
|
|
49
|
+
filename: 'main.txt',
|
|
50
|
+
content:
|
|
51
|
+
'This is not a valid application source file and should cause a build failure.',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
await assert.rejects(deploy(configFailingBuild));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
test('should deploy a Go app with file content (Buildpacks)', async () => {
|
|
61
|
+
const mainGoContent = await fs.readFile(
|
|
62
|
+
path.resolve('example-sources-to-deploy/main.go'),
|
|
63
|
+
'utf-8'
|
|
64
|
+
);
|
|
65
|
+
const goModContent = await fs.readFile(
|
|
66
|
+
path.resolve('example-sources-to-deploy/go.mod'),
|
|
67
|
+
'utf-8'
|
|
68
|
+
);
|
|
69
|
+
const configGoWithContent = {
|
|
70
|
+
projectId: projectId,
|
|
71
|
+
serviceName: 'example-go-app-content',
|
|
72
|
+
region: 'europe-west1',
|
|
73
|
+
files: [
|
|
74
|
+
{ filename: 'main.go', content: mainGoContent },
|
|
75
|
+
{ filename: 'go.mod', content: goModContent },
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
await deploy(configGoWithContent);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('should fail to deploy without a service name', async () => {
|
|
82
|
+
const config = {
|
|
83
|
+
projectId: projectId,
|
|
84
|
+
region: 'europe-west1',
|
|
85
|
+
files: ['example-sources-to-deploy/main.go'],
|
|
86
|
+
};
|
|
87
|
+
await assert.rejects(deploy(config), {
|
|
88
|
+
message: 'Error: serviceName is required in the configuration object.',
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should fail to deploy image without a service name', async () => {
|
|
93
|
+
const config = {
|
|
94
|
+
projectId: projectId,
|
|
95
|
+
region: 'europe-west1',
|
|
96
|
+
imageUrl: 'gcr.io/cloudrun/hello',
|
|
97
|
+
};
|
|
98
|
+
await assert.rejects(deployImage(config), {
|
|
99
|
+
message: 'Error: serviceName is required in the configuration object.',
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('should fail to deploy without a project id', async () => {
|
|
104
|
+
const config = {
|
|
105
|
+
serviceName: 'hello-from-image',
|
|
106
|
+
region: 'europe-west1',
|
|
107
|
+
files: ['example-sources-to-deploy/main.go'],
|
|
108
|
+
};
|
|
109
|
+
await assert.rejects(deploy(config), {
|
|
110
|
+
message: 'Error: projectId is required in the configuration object.',
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('should fail to deploy image without a project id', async () => {
|
|
115
|
+
const config = {
|
|
116
|
+
serviceName: 'hello-from-image',
|
|
117
|
+
region: 'europe-west1',
|
|
118
|
+
imageUrl: 'gcr.io/cloudrun/hello',
|
|
119
|
+
};
|
|
120
|
+
await assert.rejects(deployImage(config), {
|
|
121
|
+
message: 'Error: projectId is required in the configuration object.',
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -14,96 +14,76 @@ See the License for the specific language governing permissions and
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
17
|
+
import { test } from 'node:test';
|
|
18
|
+
import assert from 'node:assert';
|
|
19
|
+
import {
|
|
20
|
+
getServiceLogs,
|
|
21
|
+
listServices,
|
|
22
|
+
} from '../../lib/cloud-run-services.js';
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @returns {
|
|
25
|
+
* Gets service details from GOOGLE_CLOUD_PROJECT or command line arguments.
|
|
26
|
+
* @returns {{projectId: string, region: string, serviceId: string}} The service details
|
|
24
27
|
*/
|
|
25
|
-
|
|
26
|
-
const
|
|
28
|
+
function getServiceDetails() {
|
|
29
|
+
const projectId = process.env.GOOGLE_CLOUD_PROJECT || process.argv[2];
|
|
30
|
+
const [, , arg1, arg2, arg3] = process.argv;
|
|
31
|
+
|
|
32
|
+
let region = 'europe-west1';
|
|
33
|
+
let serviceId;
|
|
34
|
+
|
|
35
|
+
if (process.env.GOOGLE_CLOUD_PROJECT) {
|
|
36
|
+
region = arg1 || 'europe-west1';
|
|
37
|
+
serviceId = arg2;
|
|
38
|
+
} else {
|
|
39
|
+
region = arg2 || 'europe-west1';
|
|
40
|
+
serviceId = arg3;
|
|
41
|
+
}
|
|
27
42
|
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
if (!projectId) {
|
|
44
|
+
console.error(
|
|
45
|
+
'Usage: node <script> <projectId> [region] [serviceId] or set GOOGLE_CLOUD_PROJECT'
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
30
49
|
|
|
31
|
-
|
|
32
|
-
console.log(`Using command line arguments:
|
|
50
|
+
console.log(`Using:
|
|
33
51
|
Project ID: ${projectId}
|
|
34
|
-
Region: ${region}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return { projectId, region, serviceId };
|
|
52
|
+
Region: ${region}`);
|
|
53
|
+
if (serviceId) {
|
|
54
|
+
console.log(`Service ID: ${serviceId}`);
|
|
38
55
|
}
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const regionInput = await rl.question(
|
|
43
|
-
'Enter the region (press Enter for europe-west1): '
|
|
44
|
-
);
|
|
45
|
-
const serviceIdInput = await rl.question('Enter the Service ID: ');
|
|
57
|
+
return { projectId, region, serviceId };
|
|
58
|
+
}
|
|
46
59
|
|
|
47
|
-
|
|
60
|
+
const { projectId, region, serviceId } = getServiceDetails();
|
|
48
61
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
62
|
+
test('should list services', async () => {
|
|
63
|
+
const services = await listServices(projectId, region);
|
|
64
|
+
assert(Array.isArray(services), 'services should be an array');
|
|
65
|
+
console.log('Services found:', services.length);
|
|
66
|
+
});
|
|
55
67
|
|
|
56
|
-
async
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
test('should fetch service logs', async () => {
|
|
69
|
+
if (!serviceId) {
|
|
70
|
+
console.log('Skipping service log test: no serviceId provided.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
59
73
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
console.log(
|
|
75
|
+
`
|
|
76
|
+
Fetching logs for service "${serviceId}" in project "${projectId}" (region: ${region})...`
|
|
77
|
+
);
|
|
64
78
|
|
|
65
|
-
|
|
66
|
-
`\nFetching logs for service "${serviceId}" in project "${projectId}" (region: ${region})...`
|
|
67
|
-
);
|
|
79
|
+
const result = await getServiceLogs(projectId, region, serviceId);
|
|
68
80
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const result = await getServiceLogs(
|
|
75
|
-
projectId,
|
|
76
|
-
region,
|
|
77
|
-
serviceId,
|
|
78
|
-
requestOptions
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
if (result.logs) {
|
|
82
|
-
console.log('\nLog entries:');
|
|
83
|
-
console.log(result.logs);
|
|
84
|
-
} else {
|
|
85
|
-
console.log('No logs found for this service.');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
requestOptions = result.requestOptions;
|
|
89
|
-
pageCount++;
|
|
90
|
-
|
|
91
|
-
if (requestOptions && pageCount < MAX_PAGES) {
|
|
92
|
-
const rl = readline.createInterface({ input, output });
|
|
93
|
-
const answer = await rl.question('\nFetch more logs? (y/N): ');
|
|
94
|
-
rl.close();
|
|
95
|
-
|
|
96
|
-
if (answer.toLowerCase() !== 'y') {
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
} while (requestOptions && pageCount < MAX_PAGES);
|
|
101
|
-
|
|
102
|
-
console.log('\nService logs test completed successfully.');
|
|
103
|
-
} catch (error) {
|
|
104
|
-
console.error('Error during service logs test:', error.message);
|
|
105
|
-
process.exit(1);
|
|
81
|
+
if (result.logs) {
|
|
82
|
+
console.log('\nLog entries:');
|
|
83
|
+
console.log(result.logs);
|
|
84
|
+
} else {
|
|
85
|
+
console.log('No logs found for this service.');
|
|
106
86
|
}
|
|
107
|
-
}
|
|
87
|
+
});
|
|
88
|
+
|
|
108
89
|
|
|
109
|
-
main();
|