@google-cloud/cloud-run-mcp 1.6.0 → 1.8.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/workflows/npm-publish.yml +1 -1
- package/CONTRIBUTING.md +1 -1
- package/lib/clients.js +222 -0
- package/lib/cloud-api/billing.js +90 -4
- package/lib/cloud-api/build.js +96 -49
- package/lib/cloud-api/helpers.js +60 -38
- package/lib/cloud-api/projects.js +4 -6
- package/lib/cloud-api/registry.js +5 -5
- package/lib/cloud-api/run.js +14 -33
- package/lib/cloud-api/storage.js +6 -7
- package/lib/deployment/constants.js +54 -0
- package/lib/deployment/deployer.js +251 -149
- package/lib/deployment/helpers.js +194 -0
- package/lib/deployment/source-processor.js +124 -0
- package/lib/util/archive.js +15 -6
- package/package.json +3 -3
- package/test/local/clients.test.js +146 -0
- package/test/local/cloud-api/build.test.js +293 -309
- package/test/local/deployment-helpers.test.js +231 -0
- package/test/local/mcp-server-stdio.test.js +59 -0
- package/test/local/mcp-server-streamable-http.test.js +25 -21
- package/test/local/mock-clients.js +35 -0
- package/test/local/source-processor.test.js +290 -0
- package/test/local/test-utils.js +51 -0
- package/test/need-gcp/test-helpers.js +1 -1
- package/test/need-gcp/workflows/deployment-workflows.test.js +3 -3
|
@@ -1,50 +1,111 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { describe, it, mock } from 'node:test';
|
|
2
|
+
import { describe, it, mock, beforeEach, afterEach } from 'node:test';
|
|
3
3
|
import esmock from 'esmock';
|
|
4
4
|
|
|
5
5
|
describe('triggerCloudBuild', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
6
|
+
const mockBuildId = 'mock-build-id';
|
|
7
|
+
// bW9jay1idWlsZC1pZA== is Buffer.from('mock-build-id').toString('base64')
|
|
8
|
+
const base64BuildId = 'bW9jay1idWlsZC1pZA==';
|
|
9
|
+
const goodSubmitBuildResponse = [
|
|
10
|
+
{
|
|
11
|
+
buildOperation: {
|
|
12
|
+
name: `projects/mock-project/locations/mock-location/operations/${base64BuildId}`,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
const mockSuccessResult = {
|
|
17
|
+
id: mockBuildId,
|
|
18
|
+
status: 'SUCCESS',
|
|
19
|
+
results: { images: [{ name: 'gcr.io/mock-project/mock-image' }] },
|
|
20
|
+
};
|
|
21
|
+
const mockFailureResult = {
|
|
22
|
+
id: mockBuildId,
|
|
23
|
+
status: 'FAILURE',
|
|
24
|
+
logUrl: 'http://mock-log-url.com',
|
|
25
|
+
results: { images: [{ name: 'gcr.io/mock-project/mock-image' }] },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let logAndProgressMock;
|
|
29
|
+
let getServiceMock;
|
|
30
|
+
let createServiceMock;
|
|
31
|
+
let updateServiceMock;
|
|
32
|
+
let servicePathMock;
|
|
33
|
+
let locationPathMock;
|
|
34
|
+
let submitBuildMock;
|
|
35
|
+
let getBuildMock;
|
|
36
|
+
let getEntriesMock;
|
|
37
|
+
let context;
|
|
38
|
+
let setTimeoutMock;
|
|
39
|
+
let checkServiceMock;
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
logAndProgressMock = mock.fn();
|
|
43
|
+
checkServiceMock = mock.fn(() => Promise.resolve(false));
|
|
44
|
+
getServiceMock = mock.fn(() => Promise.reject({ code: 5 })); // Default: service not found
|
|
45
|
+
createServiceMock = mock.fn(() => Promise.resolve());
|
|
46
|
+
updateServiceMock = mock.fn(() => Promise.resolve());
|
|
47
|
+
servicePathMock = mock.fn(
|
|
48
|
+
(projectId, location, serviceId) =>
|
|
49
|
+
`projects/${projectId}/locations/${location}/services/${serviceId}`
|
|
27
50
|
);
|
|
51
|
+
locationPathMock = mock.fn(
|
|
52
|
+
(projectId, location) => `projects/${projectId}/locations/${location}`
|
|
53
|
+
);
|
|
54
|
+
submitBuildMock = mock.fn(() => Promise.resolve(goodSubmitBuildResponse));
|
|
55
|
+
getBuildMock = mock.fn(() => Promise.resolve([mockSuccessResult]));
|
|
56
|
+
getEntriesMock = mock.fn(() =>
|
|
57
|
+
Promise.resolve([[{ data: 'log line 1' }, { data: 'log line 2' }]])
|
|
58
|
+
);
|
|
59
|
+
setTimeoutMock = mock.fn((cb) => cb());
|
|
60
|
+
mock.method(global, 'setTimeout', setTimeoutMock);
|
|
28
61
|
|
|
29
|
-
|
|
62
|
+
context = {
|
|
63
|
+
runClient: {
|
|
64
|
+
getService: getServiceMock,
|
|
65
|
+
createService: createServiceMock,
|
|
66
|
+
updateService: updateServiceMock,
|
|
67
|
+
servicePath: servicePathMock,
|
|
68
|
+
locationPath: locationPathMock,
|
|
69
|
+
},
|
|
70
|
+
buildsClient: {
|
|
71
|
+
submitBuild: submitBuildMock,
|
|
72
|
+
},
|
|
30
73
|
cloudBuildClient: {
|
|
31
|
-
createBuild: mock.fn(() =>
|
|
32
|
-
Promise.resolve([
|
|
33
|
-
{
|
|
34
|
-
metadata: {
|
|
35
|
-
build: {
|
|
36
|
-
id: mockBuildId,
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
])
|
|
41
|
-
),
|
|
42
74
|
getBuild: getBuildMock,
|
|
43
75
|
},
|
|
76
|
+
loggingClient: {
|
|
77
|
+
getEntries: getEntriesMock,
|
|
78
|
+
},
|
|
44
79
|
};
|
|
80
|
+
});
|
|
45
81
|
|
|
82
|
+
afterEach(() => {
|
|
83
|
+
mock.restoreAll();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
async function getTriggerCloudBuild() {
|
|
87
|
+
return await esmock('../../../lib/cloud-api/build.js', {
|
|
88
|
+
'../../../lib/cloud-api/helpers.js': {
|
|
89
|
+
callWithRetry: (fn) => fn(),
|
|
90
|
+
},
|
|
91
|
+
'../../../lib/util/helpers.js': {
|
|
92
|
+
logAndProgress: logAndProgressMock,
|
|
93
|
+
},
|
|
94
|
+
'../../../lib/cloud-api/run.js': {
|
|
95
|
+
checkCloudRunServiceExists: checkServiceMock,
|
|
96
|
+
},
|
|
97
|
+
'../../../lib/clients.js': {
|
|
98
|
+
getRunClient: () => Promise.resolve(context.runClient),
|
|
99
|
+
getBuildsClient: () => Promise.resolve(context.buildsClient),
|
|
100
|
+
getCloudBuildClient: () => Promise.resolve(context.cloudBuildClient),
|
|
101
|
+
getLoggingClient: () => Promise.resolve(context.loggingClient),
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
it('should run successfully and create service when service does not exist', async () => {
|
|
107
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
46
108
|
const result = await triggerCloudBuild(
|
|
47
|
-
context,
|
|
48
109
|
'mock-project',
|
|
49
110
|
'mock-location',
|
|
50
111
|
'mock-bucket',
|
|
@@ -56,216 +117,97 @@ describe('triggerCloudBuild', () => {
|
|
|
56
117
|
);
|
|
57
118
|
|
|
58
119
|
assert.deepStrictEqual(result, mockSuccessResult);
|
|
59
|
-
assert.strictEqual(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
assert.strictEqual(
|
|
120
|
+
assert.strictEqual(checkServiceMock.mock.callCount(), 1);
|
|
121
|
+
assert.strictEqual(submitBuildMock.mock.callCount(), 1);
|
|
122
|
+
assert.strictEqual(getBuildMock.mock.callCount(), 1);
|
|
123
|
+
assert.strictEqual(createServiceMock.mock.callCount(), 1); // 1 dry run
|
|
124
|
+
assert.strictEqual(updateServiceMock.mock.callCount(), 0);
|
|
64
125
|
|
|
65
126
|
const { calls: logCalls } = logAndProgressMock.mock;
|
|
66
|
-
assert.match(logCalls[0].arguments[0], /
|
|
67
|
-
assert.match(logCalls[1].arguments[0], /
|
|
68
|
-
assert.match(logCalls[2].arguments[0], /
|
|
69
|
-
assert.match(logCalls[3].arguments[0], /
|
|
127
|
+
assert.match(logCalls[0].arguments[0], /Performing dry-run creation/);
|
|
128
|
+
assert.match(logCalls[1].arguments[0], /Dry-run validation successful/);
|
|
129
|
+
assert.match(logCalls[2].arguments[0], /Initiating Cloud Build/);
|
|
130
|
+
assert.match(logCalls[3].arguments[0], /Cloud Build job started/);
|
|
131
|
+
assert.match(logCalls[4].arguments[0], /completed successfully/);
|
|
132
|
+
assert.match(logCalls[5].arguments[0], /Image built/);
|
|
70
133
|
});
|
|
71
134
|
|
|
72
|
-
it('should
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const { triggerCloudBuild } = await esmock(
|
|
86
|
-
'../../../lib/cloud-api/build.js',
|
|
87
|
-
{
|
|
88
|
-
'../../../lib/cloud-api/helpers.js': {
|
|
89
|
-
callWithRetry: (fn) => fn(),
|
|
90
|
-
},
|
|
91
|
-
'../../../lib/util/helpers.js': {
|
|
92
|
-
logAndProgress: logAndProgressMock,
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const context = {
|
|
98
|
-
cloudBuildClient: {
|
|
99
|
-
createBuild: mock.fn(() =>
|
|
100
|
-
Promise.resolve([
|
|
101
|
-
{
|
|
102
|
-
metadata: {
|
|
103
|
-
build: {
|
|
104
|
-
id: mockBuildId,
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
])
|
|
109
|
-
),
|
|
110
|
-
getBuild: getBuildMock,
|
|
111
|
-
},
|
|
112
|
-
loggingClient: {
|
|
113
|
-
getEntries: mock.fn(() =>
|
|
114
|
-
Promise.resolve([[{ data: 'log line 1' }, { data: 'log line 2' }]])
|
|
115
|
-
),
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
await assert.rejects(
|
|
120
|
-
() =>
|
|
121
|
-
triggerCloudBuild(
|
|
122
|
-
context,
|
|
123
|
-
'mock-project',
|
|
124
|
-
'mock-location',
|
|
125
|
-
'mock-bucket',
|
|
126
|
-
'mock-blob',
|
|
127
|
-
'mock-repo',
|
|
128
|
-
'gcr.io/mock-project/mock-image',
|
|
129
|
-
true,
|
|
130
|
-
() => {}
|
|
131
|
-
),
|
|
132
|
-
(err) => {
|
|
133
|
-
assert.match(err.message, /Build mock-build-id-failure failed/);
|
|
134
|
-
assert.match(err.message, /log line 1/);
|
|
135
|
-
assert.match(err.message, /log line 2/);
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
assert.strictEqual(
|
|
141
|
-
context.cloudBuildClient.createBuild.mock.callCount(),
|
|
142
|
-
1
|
|
135
|
+
it('should run successfully and update service when service exists', async () => {
|
|
136
|
+
checkServiceMock.mock.mockImplementation(() => Promise.resolve(true));
|
|
137
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
138
|
+
await triggerCloudBuild(
|
|
139
|
+
'mock-project',
|
|
140
|
+
'mock-location',
|
|
141
|
+
'mock-bucket',
|
|
142
|
+
'mock-blob',
|
|
143
|
+
'mock-repo',
|
|
144
|
+
'gcr.io/mock-project/mock-image',
|
|
145
|
+
true,
|
|
146
|
+
() => {}
|
|
143
147
|
);
|
|
144
|
-
assert.strictEqual(context.cloudBuildClient.getBuild.mock.callCount(), 1);
|
|
145
|
-
assert.strictEqual(context.loggingClient.getEntries.mock.callCount(), 1);
|
|
146
|
-
assert.strictEqual(setTimeoutMock.mock.callCount(), 1);
|
|
147
|
-
assert.strictEqual(setTimeoutMock.mock.calls[0].arguments[1], 10000);
|
|
148
148
|
|
|
149
|
+
assert.strictEqual(checkServiceMock.mock.callCount(), 1);
|
|
150
|
+
assert.strictEqual(submitBuildMock.mock.callCount(), 1);
|
|
151
|
+
assert.strictEqual(getBuildMock.mock.callCount(), 1);
|
|
152
|
+
assert.strictEqual(createServiceMock.mock.callCount(), 0);
|
|
153
|
+
assert.strictEqual(updateServiceMock.mock.callCount(), 1); // 1 dry run
|
|
149
154
|
const { calls: logCalls } = logAndProgressMock.mock;
|
|
150
|
-
assert.match(logCalls[0].arguments[0], /
|
|
151
|
-
assert.match(logCalls[1].arguments[0], /Cloud Build job started/);
|
|
152
|
-
assert.match(logCalls[2].arguments[0], /failed with status: FAILURE/);
|
|
153
|
-
assert.match(logCalls[3].arguments[0], /Build logs:/);
|
|
154
|
-
assert.match(logCalls[4].arguments[0], /Attempting to fetch last/);
|
|
155
|
-
assert.match(logCalls[5].arguments[0], /Successfully fetched snippet/);
|
|
155
|
+
assert.match(logCalls[0].arguments[0], /Performing dry-run update/);
|
|
156
156
|
});
|
|
157
157
|
|
|
158
158
|
it('should use buildpacks when no Dockerfile is present', async () => {
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
{
|
|
170
|
-
metadata: {
|
|
171
|
-
build: {
|
|
172
|
-
id: mockBuildId,
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
])
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
const { triggerCloudBuild } = await esmock(
|
|
180
|
-
'../../../lib/cloud-api/build.js',
|
|
181
|
-
{
|
|
182
|
-
'../../../lib/cloud-api/helpers.js': {
|
|
183
|
-
callWithRetry: (fn) => fn(),
|
|
184
|
-
},
|
|
185
|
-
'../../../lib/util/helpers.js': {
|
|
186
|
-
logAndProgress: () => {},
|
|
187
|
-
},
|
|
188
|
-
}
|
|
159
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
160
|
+
const result = await triggerCloudBuild(
|
|
161
|
+
'mock-project',
|
|
162
|
+
'mock-location',
|
|
163
|
+
'mock-bucket',
|
|
164
|
+
'mock-blob',
|
|
165
|
+
'mock-repo',
|
|
166
|
+
'gcr.io/mock-project/mock-image',
|
|
167
|
+
false,
|
|
168
|
+
() => {}
|
|
189
169
|
);
|
|
190
170
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
171
|
+
assert.deepStrictEqual(result, mockSuccessResult);
|
|
172
|
+
assert.strictEqual(submitBuildMock.mock.callCount(), 1);
|
|
173
|
+
const submitBuildRequest = submitBuildMock.mock.calls[0].arguments[0];
|
|
174
|
+
assert.deepStrictEqual(submitBuildRequest.buildpackBuild, {});
|
|
175
|
+
assert.strictEqual(submitBuildRequest.dockerBuild, undefined);
|
|
176
|
+
});
|
|
197
177
|
|
|
178
|
+
it('should use docker build when Dockerfile is present', async () => {
|
|
179
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
198
180
|
await triggerCloudBuild(
|
|
199
|
-
context,
|
|
200
181
|
'mock-project',
|
|
201
182
|
'mock-location',
|
|
202
183
|
'mock-bucket',
|
|
203
184
|
'mock-blob',
|
|
204
185
|
'mock-repo',
|
|
205
186
|
'gcr.io/mock-project/mock-image',
|
|
206
|
-
|
|
187
|
+
true, // hasDockerfile = true
|
|
207
188
|
() => {}
|
|
208
189
|
);
|
|
209
190
|
|
|
210
|
-
assert.strictEqual(
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
assert.strictEqual(
|
|
191
|
+
assert.strictEqual(submitBuildMock.mock.callCount(), 1);
|
|
192
|
+
const submitBuildRequest = submitBuildMock.mock.calls[0].arguments[0];
|
|
193
|
+
assert.deepStrictEqual(submitBuildRequest.dockerBuild, {});
|
|
194
|
+
assert.strictEqual(submitBuildRequest.buildpackBuild, undefined);
|
|
214
195
|
});
|
|
215
196
|
|
|
216
197
|
it('should poll for build status until completion', async () => {
|
|
217
|
-
const mockBuildId = 'mock-build-id-polling';
|
|
218
198
|
const mockWorkingResult = { id: mockBuildId, status: 'WORKING' };
|
|
219
|
-
const mockSuccessResult = {
|
|
220
|
-
id: mockBuildId,
|
|
221
|
-
status: 'SUCCESS',
|
|
222
|
-
results: { images: [{ name: 'gcr.io/mock-project/mock-image' }] },
|
|
223
|
-
};
|
|
224
|
-
|
|
225
199
|
let getBuildCallCount = 0;
|
|
226
|
-
|
|
200
|
+
getBuildMock = mock.fn(() => {
|
|
227
201
|
getBuildCallCount++;
|
|
228
202
|
if (getBuildCallCount === 1) {
|
|
229
203
|
return Promise.resolve([mockWorkingResult]);
|
|
230
204
|
}
|
|
231
205
|
return Promise.resolve([mockSuccessResult]);
|
|
232
206
|
});
|
|
207
|
+
context.cloudBuildClient.getBuild = getBuildMock;
|
|
233
208
|
|
|
234
|
-
const
|
|
235
|
-
const setTimeoutMock = mock.fn((resolve) => resolve());
|
|
236
|
-
mock.method(global, 'setTimeout', setTimeoutMock);
|
|
237
|
-
|
|
238
|
-
const { triggerCloudBuild } = await esmock(
|
|
239
|
-
'../../../lib/cloud-api/build.js',
|
|
240
|
-
{
|
|
241
|
-
'../../../lib/cloud-api/helpers.js': {
|
|
242
|
-
callWithRetry: (fn) => fn(),
|
|
243
|
-
},
|
|
244
|
-
'../../../lib/util/helpers.js': {
|
|
245
|
-
logAndProgress: logAndProgressMock,
|
|
246
|
-
},
|
|
247
|
-
}
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
const context = {
|
|
251
|
-
cloudBuildClient: {
|
|
252
|
-
createBuild: mock.fn(() =>
|
|
253
|
-
Promise.resolve([
|
|
254
|
-
{
|
|
255
|
-
metadata: {
|
|
256
|
-
build: {
|
|
257
|
-
id: mockBuildId,
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
])
|
|
262
|
-
),
|
|
263
|
-
getBuild: getBuildMock,
|
|
264
|
-
},
|
|
265
|
-
};
|
|
266
|
-
|
|
209
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
267
210
|
await triggerCloudBuild(
|
|
268
|
-
context,
|
|
269
211
|
'mock-project',
|
|
270
212
|
'mock-location',
|
|
271
213
|
'mock-bucket',
|
|
@@ -280,60 +222,145 @@ describe('triggerCloudBuild', () => {
|
|
|
280
222
|
assert.strictEqual(setTimeoutMock.mock.callCount(), 1);
|
|
281
223
|
assert.strictEqual(setTimeoutMock.mock.calls[0].arguments[1], 5000);
|
|
282
224
|
const { calls: logCalls } = logAndProgressMock.mock;
|
|
283
|
-
assert.match(logCalls[
|
|
284
|
-
assert.match(logCalls[
|
|
285
|
-
assert.match(logCalls[2].arguments[0], /Build status: WORKING/);
|
|
286
|
-
assert.match(logCalls[3].arguments[0], /completed successfully/);
|
|
287
|
-
assert.match(logCalls[4].arguments[0], /Image built/);
|
|
225
|
+
assert.match(logCalls[4].arguments[0], /Build status: WORKING/);
|
|
226
|
+
assert.match(logCalls[5].arguments[0], /completed successfully/);
|
|
288
227
|
});
|
|
289
228
|
|
|
290
|
-
it('should
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
id: mockBuildId,
|
|
294
|
-
status: 'FAILURE',
|
|
295
|
-
logUrl: 'http://mock-log-url.com',
|
|
296
|
-
};
|
|
229
|
+
it('should throw an error for a failed build and fetch logs', async () => {
|
|
230
|
+
getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
|
|
231
|
+
context.cloudBuildClient.getBuild = getBuildMock;
|
|
297
232
|
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
233
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
234
|
+
await assert.rejects(
|
|
235
|
+
() =>
|
|
236
|
+
triggerCloudBuild(
|
|
237
|
+
'mock-project',
|
|
238
|
+
'mock-location',
|
|
239
|
+
'mock-bucket',
|
|
240
|
+
'mock-blob',
|
|
241
|
+
'mock-repo',
|
|
242
|
+
'gcr.io/mock-project/mock-image',
|
|
243
|
+
true,
|
|
244
|
+
() => {}
|
|
245
|
+
),
|
|
246
|
+
(err) => {
|
|
247
|
+
assert.match(err.message, /Build mock-build-id failed/);
|
|
248
|
+
assert.match(err.message, /log line 1/);
|
|
249
|
+
assert.match(err.message, /log line 2/);
|
|
250
|
+
return true;
|
|
310
251
|
}
|
|
311
252
|
);
|
|
312
253
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
254
|
+
assert.strictEqual(getBuildMock.mock.callCount(), 1);
|
|
255
|
+
assert.strictEqual(getEntriesMock.mock.callCount(), 1);
|
|
256
|
+
assert.strictEqual(setTimeoutMock.mock.callCount(), 1);
|
|
257
|
+
assert.strictEqual(setTimeoutMock.mock.calls[0].arguments[1], 10000);
|
|
258
|
+
|
|
259
|
+
const { calls: logCalls } = logAndProgressMock.mock;
|
|
260
|
+
assert.match(logCalls[4].arguments[0], /failed with status: FAILURE/);
|
|
261
|
+
assert.match(logCalls[5].arguments[0], /Build logs:/);
|
|
262
|
+
assert.match(logCalls[6].arguments[0], /Attempting to fetch last/);
|
|
263
|
+
assert.match(logCalls[7].arguments[0], /Successfully fetched snippet/);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should throw if dry-run creation fails', async () => {
|
|
267
|
+
createServiceMock = mock.fn(() =>
|
|
268
|
+
Promise.reject(new Error('Dry run fail'))
|
|
269
|
+
);
|
|
270
|
+
context.runClient.createService = createServiceMock;
|
|
271
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
272
|
+
await assert.rejects(
|
|
273
|
+
() =>
|
|
274
|
+
triggerCloudBuild(
|
|
275
|
+
'mock-project',
|
|
276
|
+
'mock-location',
|
|
277
|
+
'mock-bucket',
|
|
278
|
+
'mock-blob',
|
|
279
|
+
'mock-repo',
|
|
280
|
+
'gcr.io/mock-project/mock-image',
|
|
281
|
+
true,
|
|
282
|
+
() => {}
|
|
325
283
|
),
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
284
|
+
/Dry-run deployment failed: Dry run fail/
|
|
285
|
+
);
|
|
286
|
+
assert.strictEqual(submitBuildMock.mock.callCount(), 0);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should throw if dry-run update fails', async () => {
|
|
290
|
+
checkServiceMock.mock.mockImplementation(() => Promise.resolve(true));
|
|
291
|
+
updateServiceMock = mock.fn(() =>
|
|
292
|
+
Promise.reject(new Error('Dry run fail'))
|
|
293
|
+
);
|
|
294
|
+
context.runClient.updateService = updateServiceMock;
|
|
295
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
296
|
+
await assert.rejects(
|
|
297
|
+
() =>
|
|
298
|
+
triggerCloudBuild(
|
|
299
|
+
'mock-project',
|
|
300
|
+
'mock-location',
|
|
301
|
+
'mock-bucket',
|
|
302
|
+
'mock-blob',
|
|
303
|
+
'mock-repo',
|
|
304
|
+
'gcr.io/mock-project/mock-image',
|
|
305
|
+
true,
|
|
306
|
+
() => {}
|
|
307
|
+
),
|
|
308
|
+
/Dry-run deployment failed: Dry run fail/
|
|
309
|
+
);
|
|
310
|
+
assert.strictEqual(submitBuildMock.mock.callCount(), 0);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should throw if checkCloudRunServiceExists fails with unexpected error', async () => {
|
|
314
|
+
checkServiceMock.mock.mockImplementation(() =>
|
|
315
|
+
Promise.reject(new Error('Permission denied'))
|
|
316
|
+
);
|
|
317
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
318
|
+
await assert.rejects(
|
|
319
|
+
() =>
|
|
320
|
+
triggerCloudBuild(
|
|
321
|
+
'mock-project',
|
|
322
|
+
'mock-location',
|
|
323
|
+
'mock-bucket',
|
|
324
|
+
'mock-blob',
|
|
325
|
+
'mock-repo',
|
|
326
|
+
'gcr.io/mock-project/mock-image',
|
|
327
|
+
true,
|
|
328
|
+
() => {}
|
|
329
|
+
),
|
|
330
|
+
/Permission denied/
|
|
331
|
+
);
|
|
332
|
+
});
|
|
332
333
|
|
|
334
|
+
it('should throw if submitBuild fails', async () => {
|
|
335
|
+
submitBuildMock = mock.fn(() => Promise.reject(new Error('Submit failed')));
|
|
336
|
+
context.buildsClient.submitBuild = submitBuildMock;
|
|
337
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
338
|
+
await assert.rejects(
|
|
339
|
+
() =>
|
|
340
|
+
triggerCloudBuild(
|
|
341
|
+
'mock-project',
|
|
342
|
+
'mock-location',
|
|
343
|
+
'mock-bucket',
|
|
344
|
+
'mock-blob',
|
|
345
|
+
'mock-repo',
|
|
346
|
+
'gcr.io/mock-project/mock-image',
|
|
347
|
+
true,
|
|
348
|
+
() => {}
|
|
349
|
+
),
|
|
350
|
+
/Submit failed/
|
|
351
|
+
);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should handle failed build when no logs are found', async () => {
|
|
355
|
+
getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
|
|
356
|
+
getEntriesMock = mock.fn(() => Promise.resolve([[]])); // No log entries
|
|
357
|
+
context.cloudBuildClient.getBuild = getBuildMock;
|
|
358
|
+
context.loggingClient.getEntries = getEntriesMock;
|
|
359
|
+
|
|
360
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
333
361
|
await assert.rejects(
|
|
334
362
|
() =>
|
|
335
363
|
triggerCloudBuild(
|
|
336
|
-
context,
|
|
337
364
|
'mock-project',
|
|
338
365
|
'mock-location',
|
|
339
366
|
'mock-bucket',
|
|
@@ -344,68 +371,29 @@ describe('triggerCloudBuild', () => {
|
|
|
344
371
|
() => {}
|
|
345
372
|
),
|
|
346
373
|
(err) => {
|
|
347
|
-
assert.match(err.message, /Build mock-build-id
|
|
348
|
-
assert.doesNotMatch(err.message, /Last log lines/);
|
|
374
|
+
assert.match(err.message, /Build mock-build-id failed/);
|
|
375
|
+
assert.doesNotMatch(err.message, /Last log lines from build/);
|
|
349
376
|
return true;
|
|
350
377
|
}
|
|
351
378
|
);
|
|
352
379
|
|
|
353
380
|
const { calls: logCalls } = logAndProgressMock.mock;
|
|
354
|
-
assert.match(logCalls[
|
|
355
|
-
assert.match(logCalls[
|
|
356
|
-
assert.match(logCalls[2].arguments[0], /failed with status: FAILURE/);
|
|
357
|
-
assert.match(logCalls[3].arguments[0], /Build logs:/);
|
|
358
|
-
assert.match(logCalls[4].arguments[0], /Attempting to fetch last/);
|
|
359
|
-
assert.match(logCalls[5].arguments[0], /No specific log entries retrieved/);
|
|
381
|
+
assert.match(logCalls[6].arguments[0], /Attempting to fetch last/);
|
|
382
|
+
assert.match(logCalls[7].arguments[0], /No specific log entries retrieved/);
|
|
360
383
|
});
|
|
361
384
|
|
|
362
385
|
it('should handle error when fetching logs for a failed build', async () => {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
status: 'FAILURE',
|
|
367
|
-
logUrl: 'http://mock-log-url.com',
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
const getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
|
|
371
|
-
const logAndProgressMock = mock.fn();
|
|
372
|
-
|
|
373
|
-
const { triggerCloudBuild } = await esmock(
|
|
374
|
-
'../../../lib/cloud-api/build.js',
|
|
375
|
-
{
|
|
376
|
-
'../../../lib/cloud-api/helpers.js': {
|
|
377
|
-
callWithRetry: (fn) => fn(),
|
|
378
|
-
},
|
|
379
|
-
'../../../lib/util/helpers.js': {
|
|
380
|
-
logAndProgress: logAndProgressMock,
|
|
381
|
-
},
|
|
382
|
-
}
|
|
386
|
+
getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
|
|
387
|
+
getEntriesMock = mock.fn(() =>
|
|
388
|
+
Promise.reject(new Error('Log fetch error'))
|
|
383
389
|
);
|
|
390
|
+
context.cloudBuildClient.getBuild = getBuildMock;
|
|
391
|
+
context.loggingClient.getEntries = getEntriesMock;
|
|
384
392
|
|
|
385
|
-
const
|
|
386
|
-
cloudBuildClient: {
|
|
387
|
-
createBuild: mock.fn(() =>
|
|
388
|
-
Promise.resolve([
|
|
389
|
-
{
|
|
390
|
-
metadata: {
|
|
391
|
-
build: {
|
|
392
|
-
id: mockBuildId,
|
|
393
|
-
},
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
])
|
|
397
|
-
),
|
|
398
|
-
getBuild: getBuildMock,
|
|
399
|
-
},
|
|
400
|
-
loggingClient: {
|
|
401
|
-
getEntries: mock.fn(() => Promise.reject(new Error('Log fetch error'))),
|
|
402
|
-
},
|
|
403
|
-
};
|
|
404
|
-
|
|
393
|
+
const { triggerCloudBuild } = await getTriggerCloudBuild();
|
|
405
394
|
await assert.rejects(
|
|
406
395
|
() =>
|
|
407
396
|
triggerCloudBuild(
|
|
408
|
-
context,
|
|
409
397
|
'mock-project',
|
|
410
398
|
'mock-location',
|
|
411
399
|
'mock-bucket',
|
|
@@ -416,20 +404,16 @@ describe('triggerCloudBuild', () => {
|
|
|
416
404
|
() => {}
|
|
417
405
|
),
|
|
418
406
|
(err) => {
|
|
419
|
-
assert.match(err.message, /Build mock-build-id
|
|
420
|
-
assert.doesNotMatch(err.message, /Last log lines/);
|
|
407
|
+
assert.match(err.message, /Build mock-build-id failed/);
|
|
408
|
+
assert.doesNotMatch(err.message, /Last log lines from build/);
|
|
421
409
|
return true;
|
|
422
410
|
}
|
|
423
411
|
);
|
|
424
412
|
|
|
425
413
|
const { calls: logCalls } = logAndProgressMock.mock;
|
|
426
|
-
assert.match(logCalls[
|
|
427
|
-
assert.match(logCalls[1].arguments[0], /Cloud Build job started/);
|
|
428
|
-
assert.match(logCalls[2].arguments[0], /failed with status: FAILURE/);
|
|
429
|
-
assert.match(logCalls[3].arguments[0], /Build logs:/);
|
|
430
|
-
assert.match(logCalls[4].arguments[0], /Attempting to fetch last/);
|
|
414
|
+
assert.match(logCalls[6].arguments[0], /Attempting to fetch last/);
|
|
431
415
|
assert.match(
|
|
432
|
-
logCalls[
|
|
416
|
+
logCalls[7].arguments[0],
|
|
433
417
|
/Failed to fetch build logs snippet/
|
|
434
418
|
);
|
|
435
419
|
});
|