@google-cloud/cloud-run-mcp 1.8.0 → 1.9.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/.env.example +13 -0
- package/.env.gcloud-sdk-oauth +13 -0
- package/README.md +38 -0
- package/constants.js +8 -0
- package/lib/clients.js +72 -2
- package/lib/cloud-api/auth.js +1 -0
- package/lib/cloud-api/billing.js +16 -10
- package/lib/cloud-api/build.js +6 -4
- package/lib/cloud-api/helpers.js +19 -6
- package/lib/cloud-api/projects.js +15 -10
- package/lib/cloud-api/registry.js +5 -1
- package/lib/cloud-api/run.js +12 -11
- package/lib/cloud-api/storage.js +2 -1
- package/lib/deployment/deployer.js +24 -7
- package/lib/middleware/oauth.js +92 -0
- package/lib/util/helpers.js +12 -0
- package/mcp-server.js +51 -7
- package/package.json +2 -2
- package/test/local/clients.test.js +33 -3
- package/test/local/mcp-server-stdio.test.js +0 -1
- package/test/local/mcp-server-streamable-http.test.js +69 -0
- package/test/local/mcp-server.test.js +1 -0
- package/test/local/middleware/oauth.test.js +156 -0
- package/test/local/tools.test.js +2 -2
- package/tools/register-tools.js +18 -5
- package/.kokoro/kokoro_build.sh +0 -22
- package/.kokoro/presubmit.cfg +0 -21
- package/.kokoro/run_tests.sh +0 -31
package/.env.example
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
PORT = 3000
|
|
2
|
+
GCP_STDIO = false
|
|
3
|
+
|
|
4
|
+
OAUTH_ENABLED = true
|
|
5
|
+
GOOGLE_OAUTH_CLIENT_ID = YOUR_CLIENT_ID
|
|
6
|
+
GOOGLE_OAUTH_CLIENT_SECRET = YOUR_CLIENT_SECRET
|
|
7
|
+
GOOGLE_OAUTH_AUDIENCE = YOUR_AUDIENCE # generally, same as your client id
|
|
8
|
+
GOOGLE_OAUTH_REDIRECT_URI = http://localhost:7777/oauth/callback
|
|
9
|
+
|
|
10
|
+
OAUTH_PROTECTED_RESOURCE = http://localhost:${PORT}/mcp
|
|
11
|
+
OAUTH_AUTHORIZATION_SERVER = http://localhost:${PORT}/auth/google
|
|
12
|
+
OAUTH_AUTHORIZATION_ENDPOINT = https://accounts.google.com/o/oauth2/v2/auth
|
|
13
|
+
OAUTH_TOKEN_ENDPOINT = https://oauth2.googleapis.com/token
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
PORT = 3000
|
|
2
|
+
GCP_STDIO = false
|
|
3
|
+
|
|
4
|
+
OAUTH_ENABLED = true
|
|
5
|
+
GOOGLE_OAUTH_CLIENT_ID = 32555940559.apps.googleusercontent.com
|
|
6
|
+
GOOGLE_OAUTH_CLIENT_SECRET = ZmssLNjJy2998hD4CTg2ejr2
|
|
7
|
+
GOOGLE_OAUTH_AUDIENCE = 32555940559.apps.googleusercontent.com
|
|
8
|
+
GOOGLE_OAUTH_REDIRECT_URI = http://localhost:7777/oauth/callback
|
|
9
|
+
|
|
10
|
+
OAUTH_PROTECTED_RESOURCE = http://localhost:${PORT}/mcp
|
|
11
|
+
OAUTH_AUTHORIZATION_SERVER = http://localhost:${PORT}/auth/google
|
|
12
|
+
OAUTH_AUTHORIZATION_ENDPOINT = https://accounts.google.com/o/oauth2/v2/auth
|
|
13
|
+
OAUTH_TOKEN_ENDPOINT = https://oauth2.googleapis.com/token
|
package/README.md
CHANGED
|
@@ -235,4 +235,42 @@ With this option, you will only be able to deploy code to the same Google Cloud
|
|
|
235
235
|
}
|
|
236
236
|
```
|
|
237
237
|
|
|
238
|
+
## Using MCP Server with OAuth
|
|
239
|
+
|
|
240
|
+
Cloud Run MCP server supports OAuth as an authentication mechanism. In order to use OAuth, create the OAuth client, and configure a `.env` file with the appropriate values pertaining to your OAuth client. A `.env.example` is provided for reference.
|
|
241
|
+
|
|
242
|
+
The Cloud Run MCP server works seamlessly with Google Cloud SDK OAuth client. In order to leverage the Google Cloud SDK OAuth client, you can use the `.env.gcloud-sdk-oauth` file as your `.env` file as follows:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
cp .env.gcloud-sdk-oauth .env
|
|
246
|
+
node mcp-server.js
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Configure MCP Server on Gemini CLI to use OAuth
|
|
250
|
+
|
|
251
|
+
When the Cloud Run MCP server is started in the OAuth mode, the MCP client should also be configured to use OAuth. You can setup the MCP server in OAuth mode in the Gemini CLI by using the following JSON in the `~/.gemini/settings.json` file:
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"mcpServers": {
|
|
256
|
+
"cloud-run": {
|
|
257
|
+
"httpUrl": "http://localhost:3000/mcp",
|
|
258
|
+
"oauth": {
|
|
259
|
+
"enabled": true,
|
|
260
|
+
"clientId": "<OAUTH_CLIENT_ID>",
|
|
261
|
+
"clientSecret": "<OAUTH_CLIENT_SECRET>"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Post the configuration changes as shown above, start the Gemini CLI. You should authenticate the Cloud Run MCP server using the following prompt in the Gemini CLI:
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
/mcp auth cloud-run
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
This will take you to the authentication page on your browser, wherein you need to sign in using the appropriate gmail id, and accept the terms and conditions. Once the authentication is succcessful, you can come back to the Gemini CLI, and the Cloud Run MCP server will be ready to use.
|
|
275
|
+
|
|
238
276
|
The Google Cloud Platform Terms of Service (available at https://cloud.google.com/terms/) and the Data Processing and Security Terms (available at https://cloud.google.com/terms/data-processing-terms) do not apply to any component of the Cloud Run MCP Server software.
|
package/constants.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const SCOPES = {
|
|
2
|
+
OPENID: 'openid',
|
|
3
|
+
EMAIL: 'https://www.googleapis.com/auth/userinfo.email',
|
|
4
|
+
CLOUD_PLATFORM: 'https://www.googleapis.com/auth/cloud-platform',
|
|
5
|
+
};
|
|
6
|
+
export const BEARER_METHODS_SUPPORTED = ['header'];
|
|
7
|
+
export const RESPONSE_TYPES_SUPPORTED = ['code'];
|
|
8
|
+
export const GCLOUD_AUTH = 'gcloud_auth';
|
package/lib/clients.js
CHANGED
|
@@ -14,9 +14,14 @@ See the License for the specific language governing permissions and
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
18
|
+
import { GCLOUD_AUTH } from '../constants.js';
|
|
19
|
+
|
|
20
|
+
const CLIENT_ID = process.env.GOOGLE_OAUTH_CLIENT_ID;
|
|
21
|
+
const CLIENT_SECRET = process.env.GOOGLE_OAUTH_CLIENT_SECRET;
|
|
22
|
+
const REDIRECT_URI = process.env.GOOGLE_OAUTH_REDIRECT_URI;
|
|
17
23
|
const AUTHORIZATION_HEADER = 'Authorization';
|
|
18
24
|
const BEARER_PREFIX = 'Bearer';
|
|
19
|
-
const GCLOUD_AUTH = 'gcloud_auth';
|
|
20
25
|
|
|
21
26
|
function keyGenerator(projectId, accessToken) {
|
|
22
27
|
return accessToken !== GCLOUD_AUTH ? projectId + accessToken : projectId;
|
|
@@ -32,6 +37,7 @@ const clients = {
|
|
|
32
37
|
logging: new Map(),
|
|
33
38
|
billing: new Map(),
|
|
34
39
|
projects: new Map(),
|
|
40
|
+
oauth: new Map(),
|
|
35
41
|
};
|
|
36
42
|
|
|
37
43
|
function getAuthClient(accessToken) {
|
|
@@ -44,6 +50,47 @@ function getAuthClient(accessToken) {
|
|
|
44
50
|
};
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Wraps an OAuth2Client to be compatible with gRPC-based Google Cloud clients.
|
|
55
|
+
*
|
|
56
|
+
* Some Google Cloud SDK clients (like Cloud Run, Service Usage, etc.) use gRPC
|
|
57
|
+
* and expect the `getRequestHeaders()` method of the auth client to return a
|
|
58
|
+
* `Map` of headers. However, the standard `OAuth2Client` from `google-auth-library`
|
|
59
|
+
* returns a plain Javascript object (e.g., `{ Authorization: 'Bearer ...' }`).
|
|
60
|
+
*
|
|
61
|
+
* This wrapper uses a Proxy to intercept calls to `getRequestHeaders`. It calls
|
|
62
|
+
* the original method and converts the result from a plain object to a `Map`
|
|
63
|
+
* if necessary, ensuring compatibility with gRPC-based clients while maintaining
|
|
64
|
+
* the original behavior for other properties.
|
|
65
|
+
*
|
|
66
|
+
* @param {OAuth2Client} authClient - The original OAuth2Client instance.
|
|
67
|
+
* @returns {Proxy<OAuth2Client>} A proxy compatible with gRPC clients.
|
|
68
|
+
*/
|
|
69
|
+
function wrapForGrpc(authClient) {
|
|
70
|
+
return new Proxy(authClient, {
|
|
71
|
+
get(target, prop, receiver) {
|
|
72
|
+
if (prop === 'getRequestHeaders') {
|
|
73
|
+
return async (...args) => {
|
|
74
|
+
const headers = await target.getRequestHeaders(...args);
|
|
75
|
+
if (headers instanceof Map) {
|
|
76
|
+
return headers;
|
|
77
|
+
}
|
|
78
|
+
// Convert plain object (from OAuth2Client) to Map (expected by grpc-js)
|
|
79
|
+
const headerMap = new Map();
|
|
80
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
81
|
+
headerMap.set(k, v);
|
|
82
|
+
}
|
|
83
|
+
return headerMap;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return Reflect.get(target, prop, receiver);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Services that use HTTP/REST (or prefer Object headers) instead of gRPC (Map headers)
|
|
92
|
+
const HTTP_SERVICES = ['storage', 'logging'];
|
|
93
|
+
|
|
47
94
|
export async function getClient(
|
|
48
95
|
service,
|
|
49
96
|
key,
|
|
@@ -55,7 +102,14 @@ export async function getClient(
|
|
|
55
102
|
const ClientClass = await loadClient();
|
|
56
103
|
const finalOptions = { ...options };
|
|
57
104
|
if (accessToken && accessToken !== GCLOUD_AUTH) {
|
|
58
|
-
|
|
105
|
+
const oauthClient = await getOAuthClient(accessToken);
|
|
106
|
+
// Storage and Logging use HTTP and expect the native OAuth2Client (headers as Object).
|
|
107
|
+
// Other services (Run, ServiceUsage, etc.) use gRPC and expect headers as Map.
|
|
108
|
+
if (HTTP_SERVICES.includes(service)) {
|
|
109
|
+
finalOptions.authClient = oauthClient;
|
|
110
|
+
} else {
|
|
111
|
+
finalOptions.authClient = wrapForGrpc(oauthClient);
|
|
112
|
+
}
|
|
59
113
|
}
|
|
60
114
|
clients[service].set(key, new ClientClass(finalOptions));
|
|
61
115
|
}
|
|
@@ -220,3 +274,19 @@ export async function getProjectsClient(accessToken = GCLOUD_AUTH) {
|
|
|
220
274
|
accessToken
|
|
221
275
|
);
|
|
222
276
|
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Gets an OAuth2 Client for the specified access token.
|
|
280
|
+
* @param {string} accessToken - The access token.
|
|
281
|
+
* @returns {Promise<OAuth2Client>}
|
|
282
|
+
*/
|
|
283
|
+
export async function getOAuthClient(accessToken) {
|
|
284
|
+
// Use the access token itself as the key since it's unique per session/user
|
|
285
|
+
if (!clients.oauth.has(accessToken)) {
|
|
286
|
+
const client = new OAuth2Client(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
|
|
287
|
+
client.setCredentials({ access_token: accessToken });
|
|
288
|
+
clients.oauth.set(accessToken, client);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return clients.oauth.get(accessToken);
|
|
292
|
+
}
|
package/lib/cloud-api/auth.js
CHANGED
|
@@ -23,6 +23,7 @@ import { GoogleAuth } from 'google-auth-library';
|
|
|
23
23
|
* @async
|
|
24
24
|
* @returns {Promise<boolean>} A promise that resolves to true if GCP auth are found, and false otherwise.
|
|
25
25
|
*/
|
|
26
|
+
//TODO: Rename ensureGCPCredentials to ensureADCCredentials
|
|
26
27
|
export async function ensureGCPCredentials() {
|
|
27
28
|
console.error('Checking for Google Cloud Application Default Credentials...');
|
|
28
29
|
try {
|
package/lib/cloud-api/billing.js
CHANGED
|
@@ -30,8 +30,8 @@ import { getBillingClient } from '../clients.js';
|
|
|
30
30
|
* @returns {Promise<Array<{name: string, displayName: string, open: boolean}>>} A promise that resolves to an array of billing account objects,
|
|
31
31
|
* each with 'name', 'displayName', and 'open' status. Returns an empty array on error.
|
|
32
32
|
*/
|
|
33
|
-
export async function listBillingAccounts() {
|
|
34
|
-
const client = await getBillingClient();
|
|
33
|
+
export async function listBillingAccounts(accessToken) {
|
|
34
|
+
const client = await getBillingClient('global', accessToken);
|
|
35
35
|
try {
|
|
36
36
|
const [accounts] = await client.listBillingAccounts();
|
|
37
37
|
if (!accounts || accounts.length === 0) {
|
|
@@ -59,9 +59,10 @@ export async function listBillingAccounts() {
|
|
|
59
59
|
*/
|
|
60
60
|
export async function attachProjectToBillingAccount(
|
|
61
61
|
projectId,
|
|
62
|
-
billingAccountName
|
|
62
|
+
billingAccountName,
|
|
63
|
+
accessToken
|
|
63
64
|
) {
|
|
64
|
-
const client = await getBillingClient();
|
|
65
|
+
const client = await getBillingClient(projectId, accessToken);
|
|
65
66
|
const projectName = `projects/${projectId}`;
|
|
66
67
|
|
|
67
68
|
if (!projectId) {
|
|
@@ -111,8 +112,8 @@ export async function attachProjectToBillingAccount(
|
|
|
111
112
|
* @param {string} projectId - The ID of the project to check.
|
|
112
113
|
* @returns {Promise<boolean>} A promise that resolves to true if billing is enabled, false otherwise.
|
|
113
114
|
*/
|
|
114
|
-
export async function isBillingEnabled(projectId) {
|
|
115
|
-
const client = await getBillingClient();
|
|
115
|
+
export async function isBillingEnabled(projectId, accessToken) {
|
|
116
|
+
const client = await getBillingClient(projectId, accessToken);
|
|
116
117
|
const projectName = `projects/${projectId}`;
|
|
117
118
|
try {
|
|
118
119
|
// getProjectBillingInfo requires cloudbilling.googleapis.com API to check billing status
|
|
@@ -136,10 +137,14 @@ export async function isBillingEnabled(projectId) {
|
|
|
136
137
|
* @param {string} projectId The project ID to check billing for.
|
|
137
138
|
* @param {function} progressCallback A callback function for progress updates.
|
|
138
139
|
*/
|
|
139
|
-
export async function ensureBillingEnabled(
|
|
140
|
-
|
|
140
|
+
export async function ensureBillingEnabled(
|
|
141
|
+
projectId,
|
|
142
|
+
accessToken,
|
|
143
|
+
progressCallback
|
|
144
|
+
) {
|
|
145
|
+
if (!(await isBillingEnabled(projectId, accessToken))) {
|
|
141
146
|
// Billing is disabled, try to fix it.
|
|
142
|
-
const accounts = await listBillingAccounts();
|
|
147
|
+
const accounts = await listBillingAccounts(accessToken);
|
|
143
148
|
|
|
144
149
|
if (accounts && accounts.length === 1 && accounts[0].open) {
|
|
145
150
|
// Exactly one open account found, try to attach it.
|
|
@@ -151,7 +156,8 @@ export async function ensureBillingEnabled(projectId, progressCallback) {
|
|
|
151
156
|
|
|
152
157
|
const attachmentResult = await attachProjectToBillingAccount(
|
|
153
158
|
projectId,
|
|
154
|
-
account.name
|
|
159
|
+
account.name,
|
|
160
|
+
accessToken
|
|
155
161
|
);
|
|
156
162
|
|
|
157
163
|
if (!attachmentResult || !attachmentResult.billingEnabled) {
|
package/lib/cloud-api/build.js
CHANGED
|
@@ -53,21 +53,23 @@ export async function triggerCloudBuild(
|
|
|
53
53
|
targetRepoName,
|
|
54
54
|
targetImageUrl,
|
|
55
55
|
hasDockerfile,
|
|
56
|
+
accessToken,
|
|
56
57
|
progressCallback
|
|
57
58
|
) {
|
|
58
59
|
const serviceName = targetImageUrl.split('/').pop().split(/[:@]/)[0];
|
|
59
60
|
const parent = `projects/${projectId}/locations/${location}`;
|
|
60
61
|
const serviceFullName = `${parent}/services/${serviceName}`;
|
|
61
|
-
const buildsClient = await getBuildsClient(projectId);
|
|
62
|
-
const runClient = await getRunClient(projectId);
|
|
62
|
+
const buildsClient = await getBuildsClient(projectId, accessToken);
|
|
63
|
+
const runClient = await getRunClient(projectId, accessToken);
|
|
63
64
|
const serviceExists = await checkCloudRunServiceExists(
|
|
64
65
|
projectId,
|
|
65
66
|
location,
|
|
66
67
|
serviceName,
|
|
68
|
+
accessToken,
|
|
67
69
|
progressCallback
|
|
68
70
|
);
|
|
69
|
-
const cloudBuildClient = await getCloudBuildClient(projectId);
|
|
70
|
-
const loggingClient = await getLoggingClient(projectId);
|
|
71
|
+
const cloudBuildClient = await getCloudBuildClient(projectId, accessToken);
|
|
72
|
+
const loggingClient = await getLoggingClient(projectId, accessToken);
|
|
71
73
|
let buildSteps;
|
|
72
74
|
|
|
73
75
|
const servicePatch = {
|
package/lib/cloud-api/helpers.js
CHANGED
|
@@ -100,8 +100,16 @@ async function checkAndEnableApi(
|
|
|
100
100
|
* @returns {Promise<void>} A promise that resolves when the API is enabled.
|
|
101
101
|
* @throws {Error} If the API fails to enable or if there's an issue checking its status.
|
|
102
102
|
*/
|
|
103
|
-
export async function enableApiWithRetry(
|
|
104
|
-
|
|
103
|
+
export async function enableApiWithRetry(
|
|
104
|
+
projectId,
|
|
105
|
+
api,
|
|
106
|
+
accessToken,
|
|
107
|
+
progressCallback
|
|
108
|
+
) {
|
|
109
|
+
const serviceUsageClient = await getServiceUsageClient(
|
|
110
|
+
projectId,
|
|
111
|
+
accessToken
|
|
112
|
+
);
|
|
105
113
|
const serviceName = `projects/${projectId}/services/${api}`;
|
|
106
114
|
try {
|
|
107
115
|
await checkAndEnableApi(
|
|
@@ -148,20 +156,25 @@ export async function enableApiWithRetry(projectId, api, progressCallback) {
|
|
|
148
156
|
* @throws {Error} If an API fails to enable or if there's an issue checking its status.
|
|
149
157
|
* @returns {Promise<void>} A promise that resolves when all specified APIs are enabled.
|
|
150
158
|
*/
|
|
151
|
-
export async function ensureApisEnabled(
|
|
159
|
+
export async function ensureApisEnabled(
|
|
160
|
+
projectId,
|
|
161
|
+
apis,
|
|
162
|
+
accessToken,
|
|
163
|
+
progressCallback
|
|
164
|
+
) {
|
|
152
165
|
for (const api of PREREQUISITE_APIS) {
|
|
153
|
-
await enableApiWithRetry(projectId, api, progressCallback);
|
|
166
|
+
await enableApiWithRetry(projectId, api, accessToken, progressCallback);
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
// Before enabling other APIs, ensure billing is enabled.
|
|
157
|
-
await ensureBillingEnabled(projectId, progressCallback);
|
|
170
|
+
await ensureBillingEnabled(projectId, accessToken, progressCallback);
|
|
158
171
|
|
|
159
172
|
const message = 'Checking and enabling required APIs...';
|
|
160
173
|
console.log(message);
|
|
161
174
|
if (progressCallback) progressCallback({ level: 'info', data: message });
|
|
162
175
|
|
|
163
176
|
for (const api of apis) {
|
|
164
|
-
await enableApiWithRetry(projectId, api, progressCallback);
|
|
177
|
+
await enableApiWithRetry(projectId, api, accessToken, progressCallback);
|
|
165
178
|
}
|
|
166
179
|
const successMsg = 'All required APIs are enabled.';
|
|
167
180
|
console.log(successMsg);
|
|
@@ -26,8 +26,8 @@ import { getProjectsClient } from '../clients.js';
|
|
|
26
26
|
* @function listProjects
|
|
27
27
|
* @returns {Promise<Array<{id: string}>>} A promise that resolves to an array of project objects, each with an 'id' property. Returns an empty array on error.
|
|
28
28
|
*/
|
|
29
|
-
export async function listProjects() {
|
|
30
|
-
const client = await getProjectsClient();
|
|
29
|
+
export async function listProjects(accessToken) {
|
|
30
|
+
const client = await getProjectsClient(accessToken);
|
|
31
31
|
try {
|
|
32
32
|
const [projects] = await client.searchProjects();
|
|
33
33
|
return projects.map((project) => ({
|
|
@@ -71,8 +71,8 @@ export function generateProjectId() {
|
|
|
71
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".
|
|
72
72
|
* @returns {Promise<{projectId: string}|null>} A promise that resolves to an object containing the new project's ID.
|
|
73
73
|
*/
|
|
74
|
-
export async function createProject(projectId, parent) {
|
|
75
|
-
const client = await getProjectsClient();
|
|
74
|
+
export async function createProject(projectId, parent, accessToken) {
|
|
75
|
+
const client = await getProjectsClient(accessToken);
|
|
76
76
|
let projectIdToUse = projectId;
|
|
77
77
|
|
|
78
78
|
if (!projectIdToUse) {
|
|
@@ -112,10 +112,14 @@ export async function createProject(projectId, parent) {
|
|
|
112
112
|
* @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".
|
|
113
113
|
* @returns {Promise<{projectId: string, billingMessage: string}>} A promise that resolves to an object containing the project ID and a billing status message.
|
|
114
114
|
*/
|
|
115
|
-
export async function createProjectAndAttachBilling(
|
|
115
|
+
export async function createProjectAndAttachBilling(
|
|
116
|
+
projectIdParam,
|
|
117
|
+
parent,
|
|
118
|
+
accessToken
|
|
119
|
+
) {
|
|
116
120
|
let newProject;
|
|
117
121
|
try {
|
|
118
|
-
newProject = await createProject(projectIdParam, parent);
|
|
122
|
+
newProject = await createProject(projectIdParam, parent, accessToken);
|
|
119
123
|
} catch (error) {
|
|
120
124
|
throw new Error(`Failed to create project: ${error.message}`);
|
|
121
125
|
}
|
|
@@ -128,7 +132,7 @@ export async function createProjectAndAttachBilling(projectIdParam, parent) {
|
|
|
128
132
|
let billingMessage = `Project ${projectId} created successfully.`;
|
|
129
133
|
|
|
130
134
|
try {
|
|
131
|
-
const billingAccounts = await listBillingAccounts();
|
|
135
|
+
const billingAccounts = await listBillingAccounts(accessToken);
|
|
132
136
|
if (billingAccounts && billingAccounts.length > 0) {
|
|
133
137
|
const firstBillingAccount = billingAccounts.find((acc) => acc.open); // Prefer an open account
|
|
134
138
|
if (firstBillingAccount) {
|
|
@@ -137,7 +141,8 @@ export async function createProjectAndAttachBilling(projectIdParam, parent) {
|
|
|
137
141
|
);
|
|
138
142
|
const billingInfo = await attachProjectToBillingAccount(
|
|
139
143
|
projectId,
|
|
140
|
-
firstBillingAccount.name
|
|
144
|
+
firstBillingAccount.name,
|
|
145
|
+
accessToken
|
|
141
146
|
);
|
|
142
147
|
if (billingInfo && billingInfo.billingEnabled) {
|
|
143
148
|
billingMessage += ` It has been attached to billing account ${firstBillingAccount.displayName}.`;
|
|
@@ -171,8 +176,8 @@ export async function createProjectAndAttachBilling(projectIdParam, parent) {
|
|
|
171
176
|
* @param {string} projectId - The ID of the project to delete.
|
|
172
177
|
* @returns {Promise<void>} A promise that resolves when the delete operation is initiated.
|
|
173
178
|
*/
|
|
174
|
-
export async function deleteProject(projectId) {
|
|
175
|
-
const client = await getProjectsClient();
|
|
179
|
+
export async function deleteProject(projectId, accessToken) {
|
|
180
|
+
const client = await getProjectsClient(accessToken);
|
|
176
181
|
try {
|
|
177
182
|
console.log(`Attempting to delete project with ID: ${projectId}`);
|
|
178
183
|
await client.deleteProject({ name: `projects/${projectId}` });
|
|
@@ -33,12 +33,16 @@ import { logAndProgress } from '../util/helpers.js';
|
|
|
33
33
|
*/
|
|
34
34
|
export async function ensureArtifactRegistryRepoExists(
|
|
35
35
|
projectId,
|
|
36
|
+
accessToken,
|
|
36
37
|
location,
|
|
37
38
|
repositoryId,
|
|
38
39
|
format = 'DOCKER',
|
|
39
40
|
progressCallback
|
|
40
41
|
) {
|
|
41
|
-
const artifactRegistryClient = await getArtifactRegistryClient(
|
|
42
|
+
const artifactRegistryClient = await getArtifactRegistryClient(
|
|
43
|
+
projectId,
|
|
44
|
+
accessToken
|
|
45
|
+
);
|
|
42
46
|
const parent = `projects/${projectId}/locations/${location}`;
|
|
43
47
|
const repoPath = artifactRegistryClient.repositoryPath(
|
|
44
48
|
projectId,
|
package/lib/cloud-api/run.js
CHANGED
|
@@ -23,14 +23,14 @@ import {
|
|
|
23
23
|
import { callWithRetry, ensureApisEnabled } from './helpers.js';
|
|
24
24
|
import { logAndProgress } from '../util/helpers.js';
|
|
25
25
|
|
|
26
|
-
async function listCloudRunLocations(projectId) {
|
|
26
|
+
async function listCloudRunLocations(projectId, accessToken) {
|
|
27
27
|
const listLocationsRequest = {
|
|
28
28
|
name: `projects/${projectId}`,
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
const availableLocations = [];
|
|
32
32
|
try {
|
|
33
|
-
const runClient = await getRunClient(projectId);
|
|
33
|
+
const runClient = await getRunClient(projectId, accessToken);
|
|
34
34
|
console.log('Listing Cloud Run supported locations:');
|
|
35
35
|
const iterable = runClient.listLocationsAsync(listLocationsRequest);
|
|
36
36
|
for await (const location of iterable) {
|
|
@@ -51,11 +51,11 @@ async function listCloudRunLocations(projectId) {
|
|
|
51
51
|
* @param {string} projectId - The Google Cloud project ID.
|
|
52
52
|
* @returns {Promise<object>} - A promise that resolves to an object mapping region to list of service objects in that region.
|
|
53
53
|
*/
|
|
54
|
-
export async function listServices(projectId) {
|
|
55
|
-
const runClient = await getRunClient(projectId);
|
|
54
|
+
export async function listServices(projectId, accessToken) {
|
|
55
|
+
const runClient = await getRunClient(projectId, accessToken);
|
|
56
56
|
|
|
57
|
-
await ensureApisEnabled(projectId, ['run.googleapis.com']);
|
|
58
|
-
const locations = await listCloudRunLocations(projectId);
|
|
57
|
+
await ensureApisEnabled(projectId, ['run.googleapis.com'], accessToken);
|
|
58
|
+
const locations = await listCloudRunLocations(projectId, accessToken);
|
|
59
59
|
|
|
60
60
|
const allServices = {};
|
|
61
61
|
for (const location of locations) {
|
|
@@ -85,8 +85,8 @@ export async function listServices(projectId) {
|
|
|
85
85
|
* @param {string} serviceId - The ID of the Cloud Run service.
|
|
86
86
|
* @returns {Promise<object>} - A promise that resolves to the service object.
|
|
87
87
|
*/
|
|
88
|
-
export async function getService(projectId, location, serviceId) {
|
|
89
|
-
const runClient = await getRunClient(projectId);
|
|
88
|
+
export async function getService(projectId, location, serviceId, accessToken) {
|
|
89
|
+
const runClient = await getRunClient(projectId, accessToken);
|
|
90
90
|
|
|
91
91
|
const servicePath = runClient.servicePath(projectId, location, serviceId);
|
|
92
92
|
|
|
@@ -125,10 +125,10 @@ export async function getServiceLogs(
|
|
|
125
125
|
projectId,
|
|
126
126
|
location,
|
|
127
127
|
serviceId,
|
|
128
|
+
accessToken,
|
|
128
129
|
requestOptions
|
|
129
130
|
) {
|
|
130
|
-
const loggingClient = await getLoggingClient(projectId);
|
|
131
|
-
|
|
131
|
+
const loggingClient = await getLoggingClient(projectId, accessToken);
|
|
132
132
|
try {
|
|
133
133
|
const LOG_SEVERITY = 'DEFAULT'; // e.g., 'DEFAULT', 'INFO', 'WARNING', 'ERROR'
|
|
134
134
|
const PAGE_SIZE = 100; // Number of log entries to retrieve per page
|
|
@@ -226,9 +226,10 @@ export async function checkCloudRunServiceExists(
|
|
|
226
226
|
projectId,
|
|
227
227
|
location,
|
|
228
228
|
serviceId,
|
|
229
|
+
accessToken,
|
|
229
230
|
progressCallback
|
|
230
231
|
) {
|
|
231
|
-
const runClient = await getRunClient(projectId);
|
|
232
|
+
const runClient = await getRunClient(projectId, accessToken);
|
|
232
233
|
const servicePath = runClient.servicePath(projectId, location, serviceId);
|
|
233
234
|
try {
|
|
234
235
|
await callWithRetry(
|
package/lib/cloud-api/storage.js
CHANGED
|
@@ -34,9 +34,10 @@ export async function ensureStorageBucketExists(
|
|
|
34
34
|
projectId,
|
|
35
35
|
bucketName,
|
|
36
36
|
location = 'us',
|
|
37
|
+
accessToken,
|
|
37
38
|
progressCallback
|
|
38
39
|
) {
|
|
39
|
-
const storage = await getStorageClient(projectId);
|
|
40
|
+
const storage = await getStorageClient(projectId, accessToken);
|
|
40
41
|
const bucket = storage.bucket(bucketName);
|
|
41
42
|
try {
|
|
42
43
|
const [exists] = await callWithRetry(
|
|
@@ -66,10 +66,11 @@ async function deployToCloudRun(
|
|
|
66
66
|
progressCallback,
|
|
67
67
|
skipIamCheck,
|
|
68
68
|
deploymentType,
|
|
69
|
+
accessToken,
|
|
69
70
|
zippedSourceContainer,
|
|
70
71
|
runtime
|
|
71
72
|
) {
|
|
72
|
-
const runClient = await getRunClient(projectId);
|
|
73
|
+
const runClient = await getRunClient(projectId, accessToken);
|
|
73
74
|
const parent = runClient.locationPath(projectId, location);
|
|
74
75
|
const servicePath = runClient.servicePath(projectId, location, serviceId);
|
|
75
76
|
const revisionName = `${serviceId}-${Date.now()}`; // Generate a unique revision name
|
|
@@ -101,6 +102,7 @@ async function deployToCloudRun(
|
|
|
101
102
|
projectId,
|
|
102
103
|
location,
|
|
103
104
|
serviceId,
|
|
105
|
+
accessToken,
|
|
104
106
|
progressCallback
|
|
105
107
|
);
|
|
106
108
|
|
|
@@ -245,7 +247,8 @@ async function deployWithZip(
|
|
|
245
247
|
bucketName,
|
|
246
248
|
deploymentAttrs,
|
|
247
249
|
progressCallback,
|
|
248
|
-
skipIamCheck
|
|
250
|
+
skipIamCheck,
|
|
251
|
+
accessToken
|
|
249
252
|
) {
|
|
250
253
|
await logAndProgress(
|
|
251
254
|
`Attempting direct source deployment...`,
|
|
@@ -276,6 +279,7 @@ async function deployWithZip(
|
|
|
276
279
|
projectId,
|
|
277
280
|
bucketName,
|
|
278
281
|
region,
|
|
282
|
+
accessToken,
|
|
279
283
|
progressCallback
|
|
280
284
|
);
|
|
281
285
|
|
|
@@ -301,6 +305,7 @@ async function deployWithZip(
|
|
|
301
305
|
progressCallback,
|
|
302
306
|
skipIamCheck,
|
|
303
307
|
DEPLOYMENT_TYPES.NO_BUILD,
|
|
308
|
+
accessToken,
|
|
304
309
|
container,
|
|
305
310
|
deploymentAttrs.runtime
|
|
306
311
|
);
|
|
@@ -337,7 +342,8 @@ async function deployWithBuild(
|
|
|
337
342
|
hasDockerfile,
|
|
338
343
|
bucketName,
|
|
339
344
|
progressCallback,
|
|
340
|
-
skipIamCheck
|
|
345
|
+
skipIamCheck,
|
|
346
|
+
accessToken
|
|
341
347
|
) {
|
|
342
348
|
const imageUrl = `${region}-docker.pkg.dev/${projectId}/${DEPLOYMENT_CONFIG.REPO_NAME}/${serviceName}:${DEPLOYMENT_CONFIG.IMAGE_TAG}`;
|
|
343
349
|
|
|
@@ -345,6 +351,7 @@ async function deployWithBuild(
|
|
|
345
351
|
projectId,
|
|
346
352
|
bucketName,
|
|
347
353
|
region,
|
|
354
|
+
accessToken,
|
|
348
355
|
progressCallback
|
|
349
356
|
);
|
|
350
357
|
|
|
@@ -361,6 +368,7 @@ async function deployWithBuild(
|
|
|
361
368
|
|
|
362
369
|
await ensureArtifactRegistryRepoExists(
|
|
363
370
|
projectId,
|
|
371
|
+
accessToken,
|
|
364
372
|
region,
|
|
365
373
|
DEPLOYMENT_CONFIG.REPO_NAME,
|
|
366
374
|
'DOCKER',
|
|
@@ -377,6 +385,7 @@ async function deployWithBuild(
|
|
|
377
385
|
DEPLOYMENT_CONFIG.REPO_NAME,
|
|
378
386
|
imageUrl,
|
|
379
387
|
hasDockerfile,
|
|
388
|
+
accessToken,
|
|
380
389
|
progressCallback
|
|
381
390
|
);
|
|
382
391
|
|
|
@@ -389,7 +398,8 @@ async function deployWithBuild(
|
|
|
389
398
|
builtImageUrl,
|
|
390
399
|
progressCallback,
|
|
391
400
|
skipIamCheck,
|
|
392
|
-
DEPLOYMENT_TYPES.WITH_BUILD
|
|
401
|
+
DEPLOYMENT_TYPES.WITH_BUILD,
|
|
402
|
+
accessToken
|
|
393
403
|
);
|
|
394
404
|
|
|
395
405
|
await logAndProgress(`Deployment completed successfully`, progressCallback);
|
|
@@ -414,6 +424,7 @@ export async function deploy({
|
|
|
414
424
|
files,
|
|
415
425
|
progressCallback,
|
|
416
426
|
skipIamCheck,
|
|
427
|
+
accessToken,
|
|
417
428
|
}) {
|
|
418
429
|
if (!projectId) {
|
|
419
430
|
const errorMsg =
|
|
@@ -444,6 +455,7 @@ export async function deploy({
|
|
|
444
455
|
await ensureApisEnabled(
|
|
445
456
|
projectId,
|
|
446
457
|
REQUIRED_APIS.SOURCE_DEPLOY,
|
|
458
|
+
accessToken,
|
|
447
459
|
progressCallback
|
|
448
460
|
);
|
|
449
461
|
|
|
@@ -470,7 +482,8 @@ export async function deploy({
|
|
|
470
482
|
bucketName,
|
|
471
483
|
deploymentAttrs,
|
|
472
484
|
progressCallback,
|
|
473
|
-
skipIamCheck
|
|
485
|
+
skipIamCheck,
|
|
486
|
+
accessToken
|
|
474
487
|
);
|
|
475
488
|
} catch (error) {
|
|
476
489
|
await logAndProgress(
|
|
@@ -489,7 +502,8 @@ export async function deploy({
|
|
|
489
502
|
hasDockerfile,
|
|
490
503
|
bucketName,
|
|
491
504
|
progressCallback,
|
|
492
|
-
skipIamCheck
|
|
505
|
+
skipIamCheck,
|
|
506
|
+
accessToken
|
|
493
507
|
);
|
|
494
508
|
} catch (error) {
|
|
495
509
|
const deployFailedMessage = `Deployment Failed: ${error.message}`;
|
|
@@ -518,6 +532,7 @@ export async function deployImage({
|
|
|
518
532
|
imageUrl,
|
|
519
533
|
progressCallback,
|
|
520
534
|
skipIamCheck,
|
|
535
|
+
accessToken,
|
|
521
536
|
}) {
|
|
522
537
|
if (!projectId) {
|
|
523
538
|
const errorMsg =
|
|
@@ -547,6 +562,7 @@ export async function deployImage({
|
|
|
547
562
|
await ensureApisEnabled(
|
|
548
563
|
projectId,
|
|
549
564
|
REQUIRED_APIS.IMAGE_DEPLOY,
|
|
565
|
+
accessToken,
|
|
550
566
|
progressCallback
|
|
551
567
|
);
|
|
552
568
|
|
|
@@ -562,7 +578,8 @@ export async function deployImage({
|
|
|
562
578
|
imageUrl,
|
|
563
579
|
progressCallback,
|
|
564
580
|
skipIamCheck,
|
|
565
|
-
DEPLOYMENT_TYPES.IMAGE
|
|
581
|
+
DEPLOYMENT_TYPES.IMAGE,
|
|
582
|
+
accessToken
|
|
566
583
|
);
|
|
567
584
|
|
|
568
585
|
await logAndProgress(`Deployment completed successfully`, progressCallback);
|