@ibm-cloud/cd-tools 1.0.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/release.yml +35 -0
- package/LICENSE +201 -0
- package/README.md +2 -0
- package/cmd/check-secrets.js +106 -0
- package/cmd/copy-toolchain.js +333 -0
- package/cmd/direct-transfer.js +288 -0
- package/cmd/index.js +13 -0
- package/cmd/utils/logger.js +173 -0
- package/cmd/utils/requests.js +359 -0
- package/cmd/utils/terraform.js +441 -0
- package/cmd/utils/utils.js +128 -0
- package/cmd/utils/validate.js +503 -0
- package/config.js +202 -0
- package/index.js +24 -0
- package/package.json +28 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Licensed Materials - Property of IBM
|
|
3
|
+
* (c) Copyright IBM Corporation 2025. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Note to U.S. Government Users Restricted Rights:
|
|
6
|
+
* Use, duplication or disclosure restricted by GSA ADP Schedule
|
|
7
|
+
* Contract with IBM Corp.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import axios from 'axios';
|
|
11
|
+
import axiosRetry from 'axios-retry';
|
|
12
|
+
|
|
13
|
+
import { logger, LOG_STAGES } from './logger.js';
|
|
14
|
+
|
|
15
|
+
axiosRetry(axios, {
|
|
16
|
+
retries: 3,
|
|
17
|
+
retryDelay: axiosRetry.exponentialDelay,
|
|
18
|
+
retryCondition: (error) => {
|
|
19
|
+
return axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response?.status === 500;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
axios.interceptors.request.use(request => {
|
|
24
|
+
logger.debug(`${request.method.toUpperCase()} ${request.url}`, LOG_STAGES.setup);
|
|
25
|
+
if (request.data) {
|
|
26
|
+
const body = typeof request.data === 'string'
|
|
27
|
+
? request.data
|
|
28
|
+
: JSON.stringify(request.data);
|
|
29
|
+
logger.log(`Https Request body: ${body}`, LOG_STAGES.setup);
|
|
30
|
+
}
|
|
31
|
+
return request;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
axios.interceptors.response.use(response => {
|
|
35
|
+
if (response.data) {
|
|
36
|
+
let body = typeof response.data === 'string'
|
|
37
|
+
? response.data
|
|
38
|
+
: JSON.stringify(response.data);
|
|
39
|
+
if (response.data.access_token) // Redact user access token in logs
|
|
40
|
+
body = body.replaceAll(response.data.access_token, '<USER ACCESS TOKEN>');
|
|
41
|
+
logger.log(`Https Response body: ${body}`, LOG_STAGES.setup);
|
|
42
|
+
}
|
|
43
|
+
return response;
|
|
44
|
+
}, error => {
|
|
45
|
+
if (error.response) {
|
|
46
|
+
logger.log(`Error response status: ${error.response.status} ${error.response.statusText}`);
|
|
47
|
+
logger.log(`Error response body: ${JSON.stringify(error.response.data)}`);
|
|
48
|
+
} else {
|
|
49
|
+
logger.log(`Error message: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
return Promise.reject(error);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
async function getBearerToken(apiKey) {
|
|
55
|
+
const iamUrl = 'https://iam.cloud.ibm.com/identity/token';
|
|
56
|
+
const params = new URLSearchParams();
|
|
57
|
+
params.append('grant_type', 'urn:ibm:params:oauth:grant-type:apikey');
|
|
58
|
+
params.append('apikey', apiKey);
|
|
59
|
+
params.append('response_type', 'cloud_iam');
|
|
60
|
+
const options = {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
url: iamUrl,
|
|
63
|
+
headers: {
|
|
64
|
+
'Accept': 'application/json',
|
|
65
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
66
|
+
},
|
|
67
|
+
data: params,
|
|
68
|
+
validateStatus: () => true
|
|
69
|
+
};
|
|
70
|
+
const response = await axios(options);
|
|
71
|
+
if (response.status !== 200) {
|
|
72
|
+
throw Error('There was a problem getting a bearer token using IBMCLOUD_API_KEY');
|
|
73
|
+
}
|
|
74
|
+
return response.data.access_token;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function getAccountId(bearer, apiKey) {
|
|
78
|
+
const iamUrl = 'https://iam.cloud.ibm.com/v1/apikeys/details';
|
|
79
|
+
const options = {
|
|
80
|
+
method: 'GET',
|
|
81
|
+
url: iamUrl,
|
|
82
|
+
headers: {
|
|
83
|
+
'Authorization': `Bearer ${bearer}`,
|
|
84
|
+
'Content-Type': 'application/json',
|
|
85
|
+
'IAM-ApiKey': apiKey
|
|
86
|
+
},
|
|
87
|
+
validateStatus: () => true
|
|
88
|
+
};
|
|
89
|
+
const response = await axios(options);
|
|
90
|
+
if (response.status !== 200) {
|
|
91
|
+
throw Error('There was a problem getting account_id using IBMCLOUD_API_KEY');
|
|
92
|
+
}
|
|
93
|
+
return response.data.account_id;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function getToolchain(bearer, toolchainId, region) {
|
|
97
|
+
const apiBaseUrl = `https://api.${region}.devops.cloud.ibm.com/toolchain/v2`;
|
|
98
|
+
const options = {
|
|
99
|
+
method: 'GET',
|
|
100
|
+
url: `${apiBaseUrl}/toolchains/${toolchainId}`,
|
|
101
|
+
headers: {
|
|
102
|
+
'Accept': 'application/json',
|
|
103
|
+
'Authorization': `Bearer ${bearer}`,
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
},
|
|
106
|
+
validateStatus: () => true
|
|
107
|
+
};
|
|
108
|
+
const response = await axios(options);
|
|
109
|
+
switch (response.status) {
|
|
110
|
+
case 200:
|
|
111
|
+
return response.data;
|
|
112
|
+
case 404:
|
|
113
|
+
throw Error('The toolchain with provided CRN was not found or is not accessible');
|
|
114
|
+
default:
|
|
115
|
+
throw Error(response.statusText);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function getToolchainsByName(bearer, accountId, toolchainName) {
|
|
120
|
+
const apiBaseUrl = 'https://api.global-search-tagging.cloud.ibm.com/v3';
|
|
121
|
+
const options = {
|
|
122
|
+
url: apiBaseUrl + '/resources/search',
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: {
|
|
125
|
+
'Authorization': `Bearer ${bearer}`,
|
|
126
|
+
'Content-Type': 'application/json',
|
|
127
|
+
},
|
|
128
|
+
data: {
|
|
129
|
+
'query': `service_name:toolchain AND name:"${toolchainName}" AND doc.state:ACTIVE`,
|
|
130
|
+
'fields': ['doc.resource_group_id', 'doc.region_id']
|
|
131
|
+
},
|
|
132
|
+
params: { account_id: accountId },
|
|
133
|
+
validateStatus: () => true
|
|
134
|
+
};
|
|
135
|
+
const response = await axios(options);
|
|
136
|
+
switch (response.status) {
|
|
137
|
+
case 200:
|
|
138
|
+
return response.data.items.map(item => { return { resource_group_id: item.doc.resource_group_id, region_id: item.doc.region_id } });
|
|
139
|
+
default:
|
|
140
|
+
throw Error('Get toolchains failed');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function getToolchainTools(bearer, toolchainId, region) {
|
|
145
|
+
const apiBaseUrl = `https://api.${region}.devops.cloud.ibm.com/toolchain/v2`;
|
|
146
|
+
const options = {
|
|
147
|
+
method: 'GET',
|
|
148
|
+
url: `${apiBaseUrl}/toolchains/${toolchainId}/tools`,
|
|
149
|
+
headers: {
|
|
150
|
+
'Accept': 'application/json',
|
|
151
|
+
'Authorization': `Bearer ${bearer}`,
|
|
152
|
+
'Content-Type': 'application/json',
|
|
153
|
+
},
|
|
154
|
+
params: { limit: 150 },
|
|
155
|
+
validateStatus: () => true
|
|
156
|
+
};
|
|
157
|
+
const response = await axios(options);
|
|
158
|
+
switch (response.status) {
|
|
159
|
+
case 200:
|
|
160
|
+
return response.data;
|
|
161
|
+
default:
|
|
162
|
+
throw Error(response.statusText);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function getPipelineData(bearer, pipelineId, region) {
|
|
167
|
+
const apiBaseUrl = `https://api.${region}.devops.cloud.ibm.com/pipeline/v2`;
|
|
168
|
+
const options = {
|
|
169
|
+
method: 'GET',
|
|
170
|
+
url: `${apiBaseUrl}/tekton_pipelines/${pipelineId}`,
|
|
171
|
+
headers: {
|
|
172
|
+
'Accept': 'application/json',
|
|
173
|
+
'Authorization': `Bearer ${bearer}`,
|
|
174
|
+
'Content-Type': 'application/json',
|
|
175
|
+
},
|
|
176
|
+
validateStatus: () => true
|
|
177
|
+
};
|
|
178
|
+
const response = await axios(options);
|
|
179
|
+
switch (response.status) {
|
|
180
|
+
case 200:
|
|
181
|
+
return response.data;
|
|
182
|
+
default:
|
|
183
|
+
throw Error(response.statusText);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// takes in resource group ID or name
|
|
188
|
+
async function getResourceGroupId(bearer, accountId, resourceGroup) {
|
|
189
|
+
const apiBaseUrl = 'https://api.global-search-tagging.cloud.ibm.com/v3';
|
|
190
|
+
const options = {
|
|
191
|
+
url: apiBaseUrl + '/resources/search',
|
|
192
|
+
method: 'POST',
|
|
193
|
+
headers: {
|
|
194
|
+
'Authorization': `Bearer ${bearer}`,
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
},
|
|
197
|
+
data: {
|
|
198
|
+
'query': `type:resource-group AND (name:${resourceGroup} OR doc.id:${resourceGroup}) AND doc.state:ACTIVE`,
|
|
199
|
+
'fields': ['doc.id']
|
|
200
|
+
},
|
|
201
|
+
params: { account_id: accountId },
|
|
202
|
+
validateStatus: () => true
|
|
203
|
+
};
|
|
204
|
+
const response = await axios(options);
|
|
205
|
+
switch (response.status) {
|
|
206
|
+
case 200:
|
|
207
|
+
if (response.data.items.length != 1) throw Error('The resource group with provided ID or name was not found or is not accessible');
|
|
208
|
+
return response.data.items[0].doc.id;
|
|
209
|
+
default:
|
|
210
|
+
throw Error('The resource group with provided ID or name was not found or is not accessible');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function getAppConfigHealthcheck(bearer, tcId, toolId, region) {
|
|
215
|
+
const apiBaseUrl = 'https://cloud.ibm.com/devops/api/v1';
|
|
216
|
+
const options = {
|
|
217
|
+
url: apiBaseUrl + '/appconfig/healthcheck',
|
|
218
|
+
method: 'GET',
|
|
219
|
+
headers: {
|
|
220
|
+
'Authorization': `Bearer ${bearer}`,
|
|
221
|
+
'Content-Type': 'application/json',
|
|
222
|
+
},
|
|
223
|
+
params: { toolchainId: tcId, serviceId: toolId, env_id: `ibm:yp:${region}` },
|
|
224
|
+
validateStatus: () => true
|
|
225
|
+
};
|
|
226
|
+
const response = await axios(options);
|
|
227
|
+
switch (response.status) {
|
|
228
|
+
case 200:
|
|
229
|
+
return
|
|
230
|
+
default:
|
|
231
|
+
throw Error('Healthcheck failed');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function getSecretsHealthcheck(bearer, tcId, toolName, region) {
|
|
236
|
+
const apiBaseUrl = 'https://cloud.ibm.com/devops/api/v1';
|
|
237
|
+
const options = {
|
|
238
|
+
url: apiBaseUrl + '/secrets/healthcheck',
|
|
239
|
+
method: 'GET',
|
|
240
|
+
headers: {
|
|
241
|
+
'Authorization': `Bearer ${bearer}`,
|
|
242
|
+
'Content-Type': 'application/json',
|
|
243
|
+
},
|
|
244
|
+
params: { toolchainId: tcId, integrationName: toolName, env_id: `ibm:yp:${region}` },
|
|
245
|
+
validateStatus: () => true
|
|
246
|
+
};
|
|
247
|
+
const response = await axios(options);
|
|
248
|
+
switch (response.status) {
|
|
249
|
+
case 200:
|
|
250
|
+
return
|
|
251
|
+
default:
|
|
252
|
+
throw Error('Healthcheck failed');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function getGitOAuth(bearer, targetRegion, gitId) {
|
|
257
|
+
const url = 'https://cloud.ibm.com/devops/git/api/v1/tokens';
|
|
258
|
+
const options = {
|
|
259
|
+
url: url,
|
|
260
|
+
method: 'GET',
|
|
261
|
+
headers: {
|
|
262
|
+
'Authorization': `Bearer ${bearer}`,
|
|
263
|
+
'Content-Type': 'application/json',
|
|
264
|
+
},
|
|
265
|
+
// TODO: replace return_uri with "official" endpoint
|
|
266
|
+
params: { env_id: `ibm:yp:${targetRegion}`, git_id: gitId, console_url: 'https://cloud.ibm.com', return_uri: `https://cloud.ibm.com/devops/git?env_id=ibm:yp:${targetRegion}` },
|
|
267
|
+
validateStatus: () => true
|
|
268
|
+
};
|
|
269
|
+
const response = await axios(options);
|
|
270
|
+
switch (response.status) {
|
|
271
|
+
case 200:
|
|
272
|
+
return response.data?.access_token;
|
|
273
|
+
case 500:
|
|
274
|
+
throw Error(response.data?.authorizationURI);
|
|
275
|
+
default:
|
|
276
|
+
throw Error('Get git OAuth failed');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function getGritUserProject(privToken, region, user, projectName) {
|
|
281
|
+
const url = `https://${region}.git.cloud.ibm.com/api/v4/users/${user}/projects`
|
|
282
|
+
const options = {
|
|
283
|
+
url: url,
|
|
284
|
+
method: 'GET',
|
|
285
|
+
headers: {
|
|
286
|
+
'PRIVATE-TOKEN': privToken
|
|
287
|
+
},
|
|
288
|
+
params: { simple: true, search: projectName },
|
|
289
|
+
validateStatus: () => true
|
|
290
|
+
};
|
|
291
|
+
const response = await axios(options);
|
|
292
|
+
switch (response.status) {
|
|
293
|
+
case 200:
|
|
294
|
+
const found = response.data?.find((entry) => entry['path'] === projectName);
|
|
295
|
+
if (!found) throw Error('GRIT user project not found');
|
|
296
|
+
return;
|
|
297
|
+
default:
|
|
298
|
+
throw Error('Get GRIT user project failed');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function getGritGroup(privToken, region, groupName) {
|
|
303
|
+
const url = `https://${region}.git.cloud.ibm.com/api/v4/groups`
|
|
304
|
+
const options = {
|
|
305
|
+
url: url,
|
|
306
|
+
method: 'GET',
|
|
307
|
+
headers: {
|
|
308
|
+
'PRIVATE-TOKEN': privToken
|
|
309
|
+
},
|
|
310
|
+
params: { simple: true, search: groupName },
|
|
311
|
+
validateStatus: () => true
|
|
312
|
+
};
|
|
313
|
+
const response = await axios(options);
|
|
314
|
+
switch (response.status) {
|
|
315
|
+
case 200:
|
|
316
|
+
const found = response.data?.find((entry) => entry['full_path'] === groupName);
|
|
317
|
+
if (!found) throw Error('GRIT group not found');
|
|
318
|
+
return found['id'];
|
|
319
|
+
default:
|
|
320
|
+
throw Error('Get GRIT group failed');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async function getGritGroupProject(privToken, region, groupId, projectName) {
|
|
324
|
+
const url = `https://${region}.git.cloud.ibm.com/api/v4/groups/${groupId}/projects`
|
|
325
|
+
const options = {
|
|
326
|
+
url: url,
|
|
327
|
+
method: 'GET',
|
|
328
|
+
headers: {
|
|
329
|
+
'PRIVATE-TOKEN': privToken
|
|
330
|
+
},
|
|
331
|
+
params: { simple: true, search: projectName },
|
|
332
|
+
validateStatus: () => true
|
|
333
|
+
};
|
|
334
|
+
const response = await axios(options);
|
|
335
|
+
switch (response.status) {
|
|
336
|
+
case 200:
|
|
337
|
+
const found = response.data?.find((entry) => entry['path'] === projectName);
|
|
338
|
+
if (!found) throw Error('GRIT group project not found');
|
|
339
|
+
return;
|
|
340
|
+
default:
|
|
341
|
+
throw Error('Get GRIT group project failed');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export {
|
|
346
|
+
getBearerToken,
|
|
347
|
+
getAccountId,
|
|
348
|
+
getToolchain,
|
|
349
|
+
getToolchainsByName,
|
|
350
|
+
getToolchainTools,
|
|
351
|
+
getPipelineData,
|
|
352
|
+
getResourceGroupId,
|
|
353
|
+
getAppConfigHealthcheck,
|
|
354
|
+
getSecretsHealthcheck,
|
|
355
|
+
getGitOAuth,
|
|
356
|
+
getGritUserProject,
|
|
357
|
+
getGritGroup,
|
|
358
|
+
getGritGroupProject
|
|
359
|
+
}
|