@ibm-cloud/cd-tools 1.2.2 → 1.2.4
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/README.md +1 -1
- package/cmd/check-secrets.js +7 -3
- package/cmd/copy-toolchain.js +6 -4
- package/cmd/utils/import-terraform.js +9 -2
- package/cmd/utils/requests.js +8 -1
- package/cmd/utils/terraform.js +9 -5
- package/cmd/utils/validate.js +10 -8
- package/config.js +59 -59
- package/index.js +3 -2
- package/package.json +2 -2
- package/test/README.md +9 -8
- package/test/config/local.template.json +2 -1
- package/test/copy-toolchain/functionalities.test.js +198 -0
- package/test/copy-toolchain/{validation.test.js → input-validation.test.js} +45 -15
- package/test/copy-toolchain/tf-import.test.js +117 -0
- package/test/copy-toolchain/tool-validation.test.js +107 -0
- package/test/data/mocks.js +21 -1
- package/test/data/test-toolchains.js +44 -6
- package/test/setup.js +22 -5
- package/test/utils/testUtils.js +109 -7
- package/test/copy-toolchain/import.test.js +0 -11
- package/test/copy-toolchain/terraform.test.js +0 -11
package/test/setup.js
CHANGED
|
@@ -23,10 +23,23 @@ const TEMP_DIR = resolve(nconf.get('TEST_TEMP_DIR'));
|
|
|
23
23
|
const LOG_DIR = resolve(nconf.get('TEST_LOG_DIR'));
|
|
24
24
|
const DEBUG_MODE = nconf.get('TEST_DEBUG_MODE');
|
|
25
25
|
|
|
26
|
+
function sleep(ms) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
setTimeout(resolve, ms);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
export const mochaHooks = {
|
|
27
|
-
beforeAll() {
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
async beforeAll() {
|
|
34
|
+
for (let i = 0; i < 3; i++) {
|
|
35
|
+
try {
|
|
36
|
+
fs.rmSync(TEMP_DIR, { recursive: true, force: true });
|
|
37
|
+
fs.rmSync(LOG_DIR, { recursive: true, force: true });
|
|
38
|
+
} catch {}
|
|
39
|
+
if (!fs.existsSync(TEMP_DIR) && !fs.existsSync(LOG_DIR)) break;
|
|
40
|
+
await sleep(1000);
|
|
41
|
+
}
|
|
42
|
+
fs.mkdirSync(TEMP_DIR, { recursive: true });
|
|
30
43
|
},
|
|
31
44
|
beforeEach() {
|
|
32
45
|
if (DEBUG_MODE === true && LOG_DIR) {
|
|
@@ -35,10 +48,14 @@ export const mochaHooks = {
|
|
|
35
48
|
resolve(LOG_DIR, this.currentTest.parent.command, testTitle + '.log') :
|
|
36
49
|
resolve(LOG_DIR, testTitle + '.log');
|
|
37
50
|
logger.createLogStream(logFile);
|
|
51
|
+
// Adding logging for log stream creation and closing, because there's cases of missing test log files when running tests in parallel, most likely because of the singleton logger,
|
|
52
|
+
// causing some sort of race condition happening
|
|
53
|
+
// console.info(`Created test log stream for test case '${this.currentTest.title}'`);
|
|
38
54
|
}
|
|
39
55
|
},
|
|
40
|
-
afterEach() {
|
|
41
|
-
logger.close();
|
|
56
|
+
async afterEach() {
|
|
57
|
+
await logger.close();
|
|
58
|
+
// console.info(`Closed test log stream for test case '${this.currentTest.title}'`);
|
|
42
59
|
},
|
|
43
60
|
afterAll() {
|
|
44
61
|
if (fs.existsSync(TEMP_DIR) && DEBUG_MODE === false) fs.rmSync(TEMP_DIR, { recursive: true });
|
package/test/utils/testUtils.js
CHANGED
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { promisify } from 'util';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
11
13
|
import child_process from 'child_process';
|
|
12
14
|
import stripAnsi from 'strip-ansi';
|
|
13
15
|
import pty from 'node-pty';
|
|
16
|
+
import { parse as tfToJson } from '@cdktf/hcl2json'
|
|
14
17
|
import nconf from 'nconf';
|
|
15
|
-
import { expect } from 'chai';
|
|
18
|
+
import { expect, assert } from 'chai';
|
|
16
19
|
|
|
17
20
|
import { getBearerToken, deleteToolchain } from '../../cmd/utils/requests.js';
|
|
18
21
|
import { logger } from '../../cmd/utils/logger.js';
|
|
@@ -20,15 +23,53 @@ import { logger } from '../../cmd/utils/logger.js';
|
|
|
20
23
|
nconf.env('__');
|
|
21
24
|
nconf.file('local', 'test/config/local.json');
|
|
22
25
|
|
|
26
|
+
const TEMP_DIR = nconf.get('TEST_TEMP_DIR');
|
|
23
27
|
const IBMCLOUD_API_KEY = nconf.get('IBMCLOUD_API_KEY');
|
|
24
28
|
|
|
25
29
|
function cleanOutput(data) {
|
|
26
30
|
if (typeof data === 'string') return stripAnsi(data).replace(/\r/g, '').trim();
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
function searchDirectory(currentPath) {
|
|
34
|
+
const foundFiles = [];
|
|
35
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
38
|
+
if (entry.isDirectory()) {
|
|
39
|
+
foundFiles.push(...searchDirectory(fullPath));
|
|
40
|
+
} else {
|
|
41
|
+
foundFiles.push(path.join(currentPath, entry.name));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return foundFiles;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function parseTcIdAndRegion(output) {
|
|
48
|
+
const pattern = /See cloned toolchain: https:\/\/cloud\.ibm\.com\/devops\/toolchains\/([a-zA-Z0-9-]+)\?env_id=ibm\:yp\:([a-zA-Z0-9-]+)/;
|
|
49
|
+
const match = output.match(pattern);
|
|
50
|
+
|
|
51
|
+
if (match) {
|
|
52
|
+
const toolchainId = match[1];
|
|
53
|
+
const region = match[2];
|
|
54
|
+
return { toolchainId, region };
|
|
55
|
+
} else {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
export async function execCommand(fullCommand, options) {
|
|
30
61
|
const commandStr = `node ${fullCommand.join(' ')}`;
|
|
31
62
|
const execPromise = promisify(child_process.exec);
|
|
63
|
+
|
|
64
|
+
if (!options) {
|
|
65
|
+
options = { cwd: TEMP_DIR }
|
|
66
|
+
} else {
|
|
67
|
+
options.cwd ??= TEMP_DIR;
|
|
68
|
+
if (!fs.existsSync(options.cwd)) {
|
|
69
|
+
fs.mkdirSync(options.cwd, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
32
73
|
try {
|
|
33
74
|
const { stdout, stderr } = await execPromise(commandStr, options);
|
|
34
75
|
if (stderr) {
|
|
@@ -51,12 +92,14 @@ export async function execCommand(fullCommand, options) {
|
|
|
51
92
|
export function runPtyProcess(fullCommand, options) {
|
|
52
93
|
const {
|
|
53
94
|
timeout = 0,
|
|
54
|
-
cwd =
|
|
95
|
+
cwd = TEMP_DIR,
|
|
55
96
|
env = process.env,
|
|
56
97
|
questionAnswerMap = {},
|
|
57
98
|
exitCondition = '',
|
|
58
99
|
} = options;
|
|
59
100
|
|
|
101
|
+
if (!fs.existsSync(cwd)) fs.mkdirSync(cwd, { recursive: true });
|
|
102
|
+
|
|
60
103
|
return new Promise((resolve, reject) => {
|
|
61
104
|
try {
|
|
62
105
|
const ptyProcess = pty.spawn('node', fullCommand, {
|
|
@@ -106,7 +149,7 @@ export function runPtyProcess(fullCommand, options) {
|
|
|
106
149
|
});
|
|
107
150
|
}
|
|
108
151
|
|
|
109
|
-
export async function
|
|
152
|
+
export async function deleteCreatedToolchains(toolchainsToDelete) {
|
|
110
153
|
if (toolchainsToDelete && typeof toolchainsToDelete === 'object' && toolchainsToDelete.size > 0) {
|
|
111
154
|
const token = await getBearerToken(IBMCLOUD_API_KEY);
|
|
112
155
|
const deletePromises = [...toolchainsToDelete.entries()].map(([id, region]) => deleteToolchain(token, id, region));
|
|
@@ -114,24 +157,83 @@ export async function testSuiteCleanup(toolchainsToDelete) {
|
|
|
114
157
|
}
|
|
115
158
|
}
|
|
116
159
|
|
|
117
|
-
export async function
|
|
160
|
+
export async function assertExecError(fullCommand, expectedMessage, options, assertionFn) {
|
|
118
161
|
try {
|
|
119
162
|
const output = await execCommand(fullCommand, options);
|
|
120
163
|
logger.dump(output);
|
|
121
164
|
throw new Error('Expected command to fail but it succeeded');
|
|
122
165
|
} catch (e) {
|
|
123
166
|
logger.dump(e.message);
|
|
124
|
-
|
|
167
|
+
if (assertionFn) {
|
|
168
|
+
const res = assertionFn(e.message);
|
|
169
|
+
if (res instanceof Promise) await res;
|
|
170
|
+
} else if (expectedMessage) {
|
|
171
|
+
expect(e.message).to.match(expectedMessage);
|
|
172
|
+
} else {
|
|
173
|
+
assert.fail('No assertion function or expected message provided.');
|
|
174
|
+
}
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
177
|
|
|
128
|
-
export async function
|
|
178
|
+
export async function assertPtyOutput(fullCommand, expectedMessage, options, assertionFn) {
|
|
129
179
|
try {
|
|
130
180
|
const output = await runPtyProcess(fullCommand, options);
|
|
131
181
|
logger.dump(output);
|
|
132
|
-
|
|
182
|
+
if (assertionFn) {
|
|
183
|
+
const res = assertionFn(output);
|
|
184
|
+
if (res instanceof Promise) await res;
|
|
185
|
+
} else if (expectedMessage) {
|
|
186
|
+
expect(output).to.match(expectedMessage);
|
|
187
|
+
} else {
|
|
188
|
+
assert.fail('No assertion function or expected message provided.');
|
|
189
|
+
}
|
|
190
|
+
return parseTcIdAndRegion(output);
|
|
133
191
|
} catch (e) {
|
|
134
192
|
logger.dump(e.message);
|
|
135
193
|
throw (e);
|
|
136
194
|
}
|
|
137
195
|
}
|
|
196
|
+
|
|
197
|
+
export function areFilesInDir(dirPath, filePatterns) {
|
|
198
|
+
const foundFiles = searchDirectory(dirPath);
|
|
199
|
+
for (const pattern of filePatterns) {
|
|
200
|
+
const regex = new RegExp(pattern);
|
|
201
|
+
if (!foundFiles.some(file => regex.test(file))) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function assertTfResourcesInDir(dirPath, expectedResourcesMap) {
|
|
209
|
+
const resourceCounter = {};
|
|
210
|
+
|
|
211
|
+
const foundFiles = searchDirectory(dirPath);
|
|
212
|
+
const allResources = [];
|
|
213
|
+
for (const file of foundFiles) {
|
|
214
|
+
if (!file.endsWith('.tf')) continue;
|
|
215
|
+
const fileName = path.basename(file);
|
|
216
|
+
const tfFile = fs.readFileSync(file, 'utf8');
|
|
217
|
+
const tfFileObject = await tfToJson(fileName, tfFile);
|
|
218
|
+
if (tfFileObject.resource) allResources.push(tfFileObject.resource);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const resourceMap of allResources) {
|
|
222
|
+
for (const resourceType of Object.keys(resourceMap)) {
|
|
223
|
+
resourceCounter[resourceType] = (resourceCounter[resourceType] || 0) + 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Check if all expected resources are present
|
|
227
|
+
for (const [resourceType, expectedCount] of Object.entries(expectedResourcesMap)) {
|
|
228
|
+
if (resourceCounter[resourceType] !== expectedCount) {
|
|
229
|
+
assert.fail(`Expected ${expectedCount} ${resourceType} resource(s) but found ${resourceCounter[resourceType] || 0}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Check if there are unexpected resources
|
|
233
|
+
for (const [resourceType, count] of Object.entries(resourceCounter)) {
|
|
234
|
+
if (!(resourceType in expectedResourcesMap)) {
|
|
235
|
+
assert.fail(`Unexpected ${resourceType} resource found. (Count: ${count})`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
assert.ok(true, 'Directory contains all expected resources');
|
|
239
|
+
}
|
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
describe('copy-toolchain: Test import-terraform output', function () {
|
|
11
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
describe('copy-toolchain: Test Terraform output', function () {
|
|
11
|
-
});
|