@google-cloud/cloud-run-mcp 1.2.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 +8 -0
- package/README.md +22 -17
- package/gemini-extension/{Gemini.md → GEMINI.md} +2 -2
- package/gemini-extension.json +1 -1
- package/lib/cloud-run-deploy.js +162 -215
- package/lib/cloud-run-services.js +16 -4
- package/lib/gcp-api-helpers.js +143 -0
- package/lib/gcp-projects.js +1 -1
- package/mcp-server.js +8 -1
- package/package.json +3 -3
- package/test/local/gemini-extension.test.js +35 -0
- package/test/local/mcp-server.test.js +0 -1
- package/test/local/notifications.test.js +45 -0
- package/test/local/npx.test.js +36 -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
|
@@ -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();
|
|
@@ -14,64 +14,22 @@ See the License for the specific language governing permissions and
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import
|
|
18
|
-
import
|
|
17
|
+
import { test } from 'node:test';
|
|
18
|
+
import assert from 'node:assert';
|
|
19
19
|
import { createProjectAndAttachBilling } from '../../lib/gcp-projects.js';
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* @returns {Promise<string|undefined>} The Google Cloud project ID or undefined if auto-generation is chosen.
|
|
25
|
-
*/
|
|
26
|
-
async function getOptionalProjectId() {
|
|
27
|
-
let projectIdInput = process.argv[2]; // Get the third element (index 2) which is the first argument
|
|
21
|
+
test('should create a new project and attach billing', async () => {
|
|
22
|
+
console.log('Attempting to create a new project and attach billing...');
|
|
23
|
+
const newProjectResult = await createProjectAndAttachBilling();
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return projectIdInput.trim();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const rl = readline.createInterface({ input, output });
|
|
37
|
-
projectIdInput = await rl.question(
|
|
38
|
-
'Enter a specific Project ID to use (or press Enter to auto-generate): '
|
|
25
|
+
assert(newProjectResult, 'newProjectResult should not be null');
|
|
26
|
+
assert(
|
|
27
|
+
newProjectResult.projectId,
|
|
28
|
+
'newProjectResult.projectId should not be null'
|
|
39
29
|
);
|
|
40
|
-
rl.close();
|
|
41
|
-
|
|
42
|
-
if (projectIdInput && projectIdInput.trim() !== '') {
|
|
43
|
-
console.log(
|
|
44
|
-
`Attempting to create project with specified ID: ${projectIdInput.trim()}`
|
|
45
|
-
);
|
|
46
|
-
return projectIdInput.trim();
|
|
47
|
-
}
|
|
48
|
-
console.log('No Project ID specified. An ID will be auto-generated.');
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function main() {
|
|
53
|
-
try {
|
|
54
|
-
const optionalProjectId = await getOptionalProjectId();
|
|
55
|
-
console.log('Attempting to create a new project and attach billing...');
|
|
56
|
-
const newProjectResult =
|
|
57
|
-
await createProjectAndAttachBilling(optionalProjectId);
|
|
58
30
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.log('\nProject creation test completed successfully.');
|
|
65
|
-
} else {
|
|
66
|
-
console.error(
|
|
67
|
-
'Failed to create a new project or retrieve project details.'
|
|
68
|
-
);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
} catch (error) {
|
|
72
|
-
console.error('Error during project creation test:', error.message);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
main();
|
|
31
|
+
console.log(
|
|
32
|
+
`Successfully created project: ${newProjectResult.projectId}`
|
|
33
|
+
);
|
|
34
|
+
console.log(newProjectResult.billingMessage);
|
|
35
|
+
});
|