@google-cloud/cloud-run-mcp 1.3.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/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/lint-checker.yml +17 -0
- package/.github/workflows/local-tests.yml +1 -1
- package/.kokoro/kokoro_build.sh +22 -0
- package/.kokoro/presubmit.cfg +21 -0
- package/.kokoro/run_tests.sh +31 -0
- package/CONTRIBUTING.md +29 -8
- package/README.md +65 -19
- package/REVIEWING.md +20 -0
- package/gemini-extension/GEMINI.md +3 -3
- 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 +29 -11
- package/package.json +9 -6
- 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/mcp-server.test.js +0 -1
- package/test/local/notifications.test.js +48 -0
- package/test/local/tools.test.js +143 -124
- package/test/need-gcp/cloud-run-services.test.js +57 -77
- package/test/need-gcp/deployer.test.js +71 -0
- package/test/need-gcp/gcp-auth-check.test.js +1 -1
- package/test/need-gcp/gcp-projects.test.js +6 -60
- 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 +612 -0
- package/tools/tools.js +48 -0
- package/lib/cloud-run-deploy.js +0 -917
- package/test/need-gcp/cloud-run-deploy.test.js +0 -158
- package/tools.js +0 -969
- /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
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,12 +37,16 @@ describe('registerTools', () => {
|
|
|
38
37
|
registerTool: mock.fn(),
|
|
39
38
|
};
|
|
40
39
|
|
|
41
|
-
const { registerTools } = await esmock(
|
|
42
|
-
'../../
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
const { registerTools } = await esmock(
|
|
41
|
+
'../../tools/tools.js',
|
|
42
|
+
{},
|
|
43
|
+
{
|
|
44
|
+
'../../lib/cloud-api/projects.js': {
|
|
45
|
+
listProjects: () =>
|
|
46
|
+
Promise.resolve([{ id: 'project1' }, { id: 'project2' }]),
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
);
|
|
47
50
|
|
|
48
51
|
registerTools(server, { gcpCredentialsAvailable: true });
|
|
49
52
|
|
|
@@ -69,15 +72,19 @@ describe('registerTools', () => {
|
|
|
69
72
|
registerTool: mock.fn(),
|
|
70
73
|
};
|
|
71
74
|
|
|
72
|
-
const { registerTools } = await esmock(
|
|
73
|
-
'../../
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
const { registerTools } = await esmock(
|
|
76
|
+
'../../tools/tools.js',
|
|
77
|
+
{},
|
|
78
|
+
{
|
|
79
|
+
'../../lib/cloud-api/projects.js': {
|
|
80
|
+
createProjectAndAttachBilling: (projectId) =>
|
|
81
|
+
Promise.resolve({
|
|
82
|
+
projectId: projectId,
|
|
83
|
+
billingMessage: `Project ${projectId} created successfully. Billing attached.`,
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
);
|
|
81
88
|
|
|
82
89
|
registerTools(server, { gcpCredentialsAvailable: true });
|
|
83
90
|
|
|
@@ -90,7 +97,7 @@ describe('registerTools', () => {
|
|
|
90
97
|
content: [
|
|
91
98
|
{
|
|
92
99
|
type: 'text',
|
|
93
|
-
text: '
|
|
100
|
+
text: 'Project my-project created successfully. Billing attached.',
|
|
94
101
|
},
|
|
95
102
|
],
|
|
96
103
|
});
|
|
@@ -101,15 +108,20 @@ describe('registerTools', () => {
|
|
|
101
108
|
registerTool: mock.fn(),
|
|
102
109
|
};
|
|
103
110
|
|
|
104
|
-
const { registerTools } = await esmock(
|
|
105
|
-
'../../
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
const { registerTools } = await esmock(
|
|
112
|
+
'../../tools/tools.js',
|
|
113
|
+
{},
|
|
114
|
+
{
|
|
115
|
+
'../../lib/cloud-api/projects.js': {
|
|
116
|
+
createProjectAndAttachBilling: () =>
|
|
117
|
+
Promise.resolve({
|
|
118
|
+
projectId: 'generated-project',
|
|
119
|
+
billingMessage:
|
|
120
|
+
'Project generated-project created successfully. Billing attached.',
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
);
|
|
113
125
|
|
|
114
126
|
registerTools(server, { gcpCredentialsAvailable: true });
|
|
115
127
|
|
|
@@ -122,7 +134,7 @@ describe('registerTools', () => {
|
|
|
122
134
|
content: [
|
|
123
135
|
{
|
|
124
136
|
type: 'text',
|
|
125
|
-
text: '
|
|
137
|
+
text: 'Project generated-project created successfully. Billing attached.',
|
|
126
138
|
},
|
|
127
139
|
],
|
|
128
140
|
});
|
|
@@ -135,15 +147,25 @@ describe('registerTools', () => {
|
|
|
135
147
|
registerTool: mock.fn(),
|
|
136
148
|
};
|
|
137
149
|
|
|
138
|
-
const { registerTools } = await esmock(
|
|
139
|
-
'../../
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
150
|
+
const { registerTools } = await esmock(
|
|
151
|
+
'../../tools/tools.js',
|
|
152
|
+
{},
|
|
153
|
+
{
|
|
154
|
+
'../../lib/cloud-api/run.js': {
|
|
155
|
+
listServices: () =>
|
|
156
|
+
Promise.resolve({
|
|
157
|
+
'my-region1': [
|
|
158
|
+
{ name: 'service1', uri: 'uri1' },
|
|
159
|
+
{ name: 'service2', uri: 'uri2' },
|
|
160
|
+
],
|
|
161
|
+
'my-region2': [
|
|
162
|
+
{ name: 'service3', uri: 'uri3' },
|
|
163
|
+
{ name: 'service4', uri: 'uri4' },
|
|
164
|
+
],
|
|
165
|
+
}),
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
);
|
|
147
169
|
|
|
148
170
|
registerTools(server, { gcpCredentialsAvailable: true });
|
|
149
171
|
|
|
@@ -152,14 +174,17 @@ describe('registerTools', () => {
|
|
|
152
174
|
).arguments[2];
|
|
153
175
|
const result = await handler({
|
|
154
176
|
project: 'my-project',
|
|
155
|
-
region: 'my-region',
|
|
156
177
|
});
|
|
157
178
|
|
|
158
179
|
assert.deepStrictEqual(result, {
|
|
159
180
|
content: [
|
|
160
181
|
{
|
|
161
182
|
type: 'text',
|
|
162
|
-
text: 'Services in project my-project (location my-
|
|
183
|
+
text: 'Services in project my-project (location my-region1):\n- service1 (URL: uri1)\n- service2 (URL: uri2)',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
type: 'text',
|
|
187
|
+
text: 'Services in project my-project (location my-region2):\n- service3 (URL: uri3)\n- service4 (URL: uri4)',
|
|
163
188
|
},
|
|
164
189
|
],
|
|
165
190
|
});
|
|
@@ -172,16 +197,20 @@ describe('registerTools', () => {
|
|
|
172
197
|
registerTool: mock.fn(),
|
|
173
198
|
};
|
|
174
199
|
|
|
175
|
-
const { registerTools } = await esmock(
|
|
176
|
-
'../../
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
200
|
+
const { registerTools } = await esmock(
|
|
201
|
+
'../../tools/tools.js',
|
|
202
|
+
{},
|
|
203
|
+
{
|
|
204
|
+
'../../lib/cloud-api/run.js': {
|
|
205
|
+
getService: () =>
|
|
206
|
+
Promise.resolve({
|
|
207
|
+
name: 'my-service',
|
|
208
|
+
uri: 'my-uri',
|
|
209
|
+
lastModifier: 'me',
|
|
210
|
+
}),
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
);
|
|
185
214
|
|
|
186
215
|
registerTools(server, { gcpCredentialsAvailable: true });
|
|
187
216
|
|
|
@@ -223,11 +252,15 @@ describe('registerTools', () => {
|
|
|
223
252
|
return Promise.resolve({ logs: 'log3\nlog4', requestOptions: null });
|
|
224
253
|
};
|
|
225
254
|
|
|
226
|
-
const { registerTools } = await esmock(
|
|
227
|
-
'../../
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
255
|
+
const { registerTools } = await esmock(
|
|
256
|
+
'../../tools/tools.js',
|
|
257
|
+
{},
|
|
258
|
+
{
|
|
259
|
+
'../../lib/cloud-api/run.js': {
|
|
260
|
+
getServiceLogs: getServiceLogs,
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
);
|
|
231
264
|
|
|
232
265
|
registerTools(server, { gcpCredentialsAvailable: true });
|
|
233
266
|
|
|
@@ -251,64 +284,36 @@ describe('registerTools', () => {
|
|
|
251
284
|
});
|
|
252
285
|
});
|
|
253
286
|
|
|
254
|
-
describe('deploy_local_files', () => {
|
|
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
|
-
});
|
|
288
|
-
|
|
289
287
|
describe('deploy_local_folder', () => {
|
|
290
288
|
it('should deploy local folder', async () => {
|
|
291
289
|
const server = {
|
|
292
290
|
registerTool: mock.fn(),
|
|
293
291
|
};
|
|
294
292
|
|
|
295
|
-
const { registerTools } = await esmock(
|
|
296
|
-
'../../
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
293
|
+
const { registerTools } = await esmock(
|
|
294
|
+
'../../tools/tools.js',
|
|
295
|
+
{},
|
|
296
|
+
{
|
|
297
|
+
'../../lib/deployment/deployer.js': {
|
|
298
|
+
deploy: () => Promise.resolve({ uri: 'my-uri' }),
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
);
|
|
300
302
|
|
|
301
303
|
registerTools(server, { gcpCredentialsAvailable: true });
|
|
302
304
|
|
|
303
305
|
const handler = server.registerTool.mock.calls.find(
|
|
304
306
|
(call) => call.arguments[0] === 'deploy_local_folder'
|
|
305
307
|
).arguments[2];
|
|
306
|
-
const result = await handler(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
308
|
+
const result = await handler(
|
|
309
|
+
{
|
|
310
|
+
project: 'my-project',
|
|
311
|
+
region: 'my-region',
|
|
312
|
+
service: 'my-service',
|
|
313
|
+
folderPath: '/my/folder',
|
|
314
|
+
},
|
|
315
|
+
{ sendNotification: mock.fn() }
|
|
316
|
+
);
|
|
312
317
|
|
|
313
318
|
assert.deepStrictEqual(result, {
|
|
314
319
|
content: [
|
|
@@ -327,23 +332,30 @@ describe('registerTools', () => {
|
|
|
327
332
|
registerTool: mock.fn(),
|
|
328
333
|
};
|
|
329
334
|
|
|
330
|
-
const { registerTools } = await esmock(
|
|
331
|
-
'../../
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
+
const { registerTools } = await esmock(
|
|
336
|
+
'../../tools/tools.js',
|
|
337
|
+
{},
|
|
338
|
+
{
|
|
339
|
+
'../../lib/deployment/deployer.js': {
|
|
340
|
+
deploy: () => Promise.resolve({ uri: 'my-uri' }),
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
);
|
|
335
344
|
|
|
336
345
|
registerTools(server, { gcpCredentialsAvailable: true });
|
|
337
346
|
|
|
338
347
|
const handler = server.registerTool.mock.calls.find(
|
|
339
348
|
(call) => call.arguments[0] === 'deploy_file_contents'
|
|
340
349
|
).arguments[2];
|
|
341
|
-
const result = await handler(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
350
|
+
const result = await handler(
|
|
351
|
+
{
|
|
352
|
+
project: 'my-project',
|
|
353
|
+
region: 'my-region',
|
|
354
|
+
service: 'my-service',
|
|
355
|
+
files: [{ filename: 'file1', content: 'content1' }],
|
|
356
|
+
},
|
|
357
|
+
{ sendNotification: mock.fn() }
|
|
358
|
+
);
|
|
347
359
|
|
|
348
360
|
assert.deepStrictEqual(result, {
|
|
349
361
|
content: [
|
|
@@ -362,23 +374,30 @@ describe('registerTools', () => {
|
|
|
362
374
|
registerTool: mock.fn(),
|
|
363
375
|
};
|
|
364
376
|
|
|
365
|
-
const { registerTools } = await esmock(
|
|
366
|
-
'../../
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
377
|
+
const { registerTools } = await esmock(
|
|
378
|
+
'../../tools/tools.js',
|
|
379
|
+
{},
|
|
380
|
+
{
|
|
381
|
+
'../../lib/deployment/deployer.js': {
|
|
382
|
+
deployImage: () => Promise.resolve({ uri: 'my-uri' }),
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
);
|
|
370
386
|
|
|
371
387
|
registerTools(server, { gcpCredentialsAvailable: true });
|
|
372
388
|
|
|
373
389
|
const handler = server.registerTool.mock.calls.find(
|
|
374
390
|
(call) => call.arguments[0] === 'deploy_container_image'
|
|
375
391
|
).arguments[2];
|
|
376
|
-
const result = await handler(
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
392
|
+
const result = await handler(
|
|
393
|
+
{
|
|
394
|
+
project: 'my-project',
|
|
395
|
+
region: 'my-region',
|
|
396
|
+
service: 'my-service',
|
|
397
|
+
imageUrl: 'gcr.io/my-project/my-image',
|
|
398
|
+
},
|
|
399
|
+
{ sendNotification: mock.fn() }
|
|
400
|
+
);
|
|
382
401
|
|
|
383
402
|
assert.deepStrictEqual(result, {
|
|
384
403
|
content: [
|
|
@@ -397,7 +416,7 @@ describe('registerTools', () => {
|
|
|
397
416
|
registerTool: mock.fn(),
|
|
398
417
|
};
|
|
399
418
|
|
|
400
|
-
const { registerTools } = await esmock('../../tools.js', {});
|
|
419
|
+
const { registerTools } = await esmock('../../tools/tools.js', {});
|
|
401
420
|
|
|
402
421
|
registerTools(server, { gcpCredentialsAvailable: false });
|
|
403
422
|
|
|
@@ -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 { getServiceLogs, listServices } from '../../lib/cloud-api/run.js';
|
|
19
|
+
import assert from 'node:assert';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @returns {
|
|
22
|
+
* Get the GCP project ID from GOOGLE_CLOUD_PROJECT or command line argument.
|
|
23
|
+
* @returns {string} The GCP project ID
|
|
24
24
|
*/
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
console.log(`Using command line arguments:
|
|
33
|
-
Project ID: ${projectId}
|
|
34
|
-
Region: ${region}
|
|
35
|
-
Service ID: ${serviceId}`);
|
|
36
|
-
rl.close();
|
|
37
|
-
return { projectId, region, serviceId };
|
|
25
|
+
function getProjectId() {
|
|
26
|
+
const projectId = process.env.GOOGLE_CLOUD_PROJECT || process.argv[2];
|
|
27
|
+
if (!projectId) {
|
|
28
|
+
console.error(
|
|
29
|
+
'Usage: node <script> <projectId> or set GOOGLE_CLOUD_PROJECT'
|
|
30
|
+
);
|
|
31
|
+
process.exit(1);
|
|
38
32
|
}
|
|
33
|
+
console.log(`Using Project ID: ${projectId}`);
|
|
34
|
+
return projectId;
|
|
35
|
+
}
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
37
|
+
/**
|
|
38
|
+
* Gets service details from GOOGLE_CLOUD_PROJECT or command line arguments.
|
|
39
|
+
* @returns {{projectId: string, region: string, serviceId: string}} The service details
|
|
40
|
+
*/
|
|
41
|
+
async function getServiceDetails() {
|
|
42
|
+
const projectId = getProjectId();
|
|
43
|
+
let region, serviceId;
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
try {
|
|
46
|
+
const allServices = await listServices(projectId);
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
serviceId: serviceIdInput.trim(),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
48
|
+
if (!allServices || Object.keys(allServices).length === 0) {
|
|
49
|
+
assert.fail('No services found for the given project.');
|
|
50
|
+
}
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const { projectId, region, serviceId } = await getServiceDetails();
|
|
52
|
+
const regions = Object.keys(allServices);
|
|
53
|
+
region = regions[0];
|
|
59
54
|
|
|
60
|
-
if (!
|
|
61
|
-
|
|
62
|
-
process.exit(1);
|
|
55
|
+
if (!allServices[region] || allServices[region].length === 0) {
|
|
56
|
+
assert.fail(`No services found in region: ${region}`);
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
);
|
|
59
|
+
const serviceName = allServices[region][0].name;
|
|
60
|
+
serviceId = serviceName.split('/').pop();
|
|
61
|
+
console.log(`Using region - ${region} and service ID - ${serviceId}`);
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
let pageCount = 0;
|
|
71
|
-
const MAX_PAGES = 3; // Limit the number of pages to avoid too much output
|
|
72
|
-
|
|
73
|
-
do {
|
|
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.');
|
|
63
|
+
return { projectId, region, serviceId };
|
|
103
64
|
} catch (error) {
|
|
104
|
-
|
|
105
|
-
|
|
65
|
+
// Better error handling for the API call itself
|
|
66
|
+
console.error('Error fetching services:', error.message);
|
|
67
|
+
throw error; // Re-throw the error to fail the test
|
|
106
68
|
}
|
|
107
69
|
}
|
|
108
70
|
|
|
109
|
-
|
|
71
|
+
test('should list services', async () => {
|
|
72
|
+
const projectId = getProjectId();
|
|
73
|
+
const services = await listServices(projectId);
|
|
74
|
+
console.log('Services found:', services ? Object.keys(services) : 'None');
|
|
75
|
+
console.log('All Services', services);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should fetch service logs', async () => {
|
|
79
|
+
const { projectId, region, serviceId } = await getServiceDetails();
|
|
80
|
+
|
|
81
|
+
const result = await getServiceLogs(projectId, region, serviceId);
|
|
82
|
+
|
|
83
|
+
if (result.logs) {
|
|
84
|
+
console.log('\nLog entries:');
|
|
85
|
+
console.log(result.logs);
|
|
86
|
+
} else {
|
|
87
|
+
console.log('No logs found for this service.');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
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 { describe, test } from 'node:test';
|
|
18
|
+
import assert from 'node:assert';
|
|
19
|
+
import { deploy, deployImage } from '../../lib/deployment/deployer.js';
|
|
20
|
+
|
|
21
|
+
const projectId = process.env.GOOGLE_CLOUD_PROJECT || process.argv[2];
|
|
22
|
+
if (!projectId) {
|
|
23
|
+
console.error('Usage: node <script> <projectId> or set GOOGLE_CLOUD_PROJECT');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('Cloud Run Deployments', () => {
|
|
28
|
+
test('should fail to deploy without a service name', async () => {
|
|
29
|
+
const config = {
|
|
30
|
+
projectId: projectId,
|
|
31
|
+
region: 'europe-west1',
|
|
32
|
+
files: ['example-sources-to-deploy/main.go'],
|
|
33
|
+
};
|
|
34
|
+
await assert.rejects(deploy(config), {
|
|
35
|
+
message: 'Error: serviceName is required in the configuration object.',
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should fail to deploy image without a service name', async () => {
|
|
40
|
+
const config = {
|
|
41
|
+
projectId: projectId,
|
|
42
|
+
region: 'europe-west1',
|
|
43
|
+
imageUrl: 'gcr.io/cloudrun/hello',
|
|
44
|
+
};
|
|
45
|
+
await assert.rejects(deployImage(config), {
|
|
46
|
+
message: 'Error: serviceName is required in the configuration object.',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should fail to deploy without a project id', async () => {
|
|
51
|
+
const config = {
|
|
52
|
+
serviceName: 'hello-from-image',
|
|
53
|
+
region: 'europe-west1',
|
|
54
|
+
files: ['example-sources-to-deploy/main.go'],
|
|
55
|
+
};
|
|
56
|
+
await assert.rejects(deploy(config), {
|
|
57
|
+
message: 'Error: projectId is required in the configuration object.',
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('should fail to deploy image without a project id', async () => {
|
|
62
|
+
const config = {
|
|
63
|
+
serviceName: 'hello-from-image',
|
|
64
|
+
region: 'europe-west1',
|
|
65
|
+
imageUrl: 'gcr.io/cloudrun/hello',
|
|
66
|
+
};
|
|
67
|
+
await assert.rejects(deployImage(config), {
|
|
68
|
+
message: 'Error: projectId is required in the configuration object.',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -17,7 +17,7 @@ limitations under the License.
|
|
|
17
17
|
import assert from 'node:assert/strict';
|
|
18
18
|
import { describe, it, mock, beforeEach, afterEach } from 'node:test';
|
|
19
19
|
import { GoogleAuth } from 'google-auth-library';
|
|
20
|
-
import { ensureGCPCredentials } from '../../lib/
|
|
20
|
+
import { ensureGCPCredentials } from '../../lib/cloud-api/auth.js';
|
|
21
21
|
|
|
22
22
|
describe('ensureGCPCredentials', () => {
|
|
23
23
|
let originalConsoleLog;
|