@ibm-cloud/cd-tools 1.5.3 → 1.6.1
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/cmd/copy-toolchain.js +10 -16
- package/cmd/export-secrets.js +323 -0
- package/cmd/index.js +2 -2
- package/cmd/utils/logger.js +18 -12
- package/cmd/utils/requests.js +125 -39
- package/cmd/utils/terraform.js +26 -5
- package/cmd/utils/utils.js +67 -3
- package/cmd/utils/validate.js +10 -6
- package/create-s2s-script.js +21 -8
- package/index.js +11 -4
- package/package.json +1 -1
- package/test/README.md +2 -2
- package/test/config/local.template.json +1 -1
- package/test/copy-toolchain/functionalities.test.js +13 -15
- package/test/copy-toolchain/input-validation.test.js +7 -6
- package/test/copy-toolchain/tf-import.test.js +2 -6
- package/test/copy-toolchain/tool-validation.test.js +1 -4
- package/cmd/check-secrets.js +0 -111
package/test/README.md
CHANGED
|
@@ -36,6 +36,6 @@ You can customize the behavior of the tests by defining configuration properties
|
|
|
36
36
|
| `TEST_TEMP_DIR` | `string` | `test/.tmp` | The directory to store temporary files generated by test cases |
|
|
37
37
|
| `TEST_LOG_DIR` | `string` | `test/.test-logs` | The directory to store test run log files |
|
|
38
38
|
| `IBMCLOUD_API_KEY` | `string` | `null` | The IBM Cloud API Key used to run the tests |
|
|
39
|
-
| `LOG_DUMP` | `boolean` | `false` | When set to `true`, individual test case's process's log file generation is enabled
|
|
39
|
+
| `LOG_DUMP` | `boolean` | `false` | When set to `true`, individual test case's process's log file generation is enabled, and logs are written to `TEST_TEMP_DIR` |
|
|
40
40
|
| `DISABLE_SPINNER` | `boolean` | `true` | When set to `true`, visual spinner is disabled across all test cases' processes |
|
|
41
|
-
| `VERBOSE_MODE` | `boolean` | `false` |
|
|
41
|
+
| `VERBOSE_MODE` | `boolean` | `false` | When set to `true`, each test case's log output increases |
|
|
@@ -11,8 +11,6 @@ import path from 'node:path';
|
|
|
11
11
|
import nconf from 'nconf';
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
|
|
14
|
-
import * as chai from 'chai';
|
|
15
|
-
chai.config.truncateThreshold = 0;
|
|
16
14
|
import { expect, assert } from 'chai';
|
|
17
15
|
|
|
18
16
|
import { assertPtyOutput, assertExecError, areFilesInDir, deleteCreatedToolchains, parseTcIdAndRegion } from '../utils/testUtils.js';
|
|
@@ -39,10 +37,19 @@ describe('copy-toolchain: Test functionalities', function () {
|
|
|
39
37
|
const testCases = [
|
|
40
38
|
{
|
|
41
39
|
name: 'Terraform Version Verification',
|
|
42
|
-
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r',
|
|
40
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region],
|
|
43
41
|
expected: /✔ Terraform Version:/,
|
|
44
42
|
options: {
|
|
45
|
-
exitCondition:
|
|
43
|
+
exitCondition: `(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`,
|
|
44
|
+
timeout: 10000
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'CLI Version Verification',
|
|
49
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region],
|
|
50
|
+
expected: /✔ cd-tools Version:/,
|
|
51
|
+
options: {
|
|
52
|
+
exitCondition: `(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`,
|
|
46
53
|
timeout: 10000
|
|
47
54
|
}
|
|
48
55
|
},
|
|
@@ -58,10 +65,10 @@ describe('copy-toolchain: Test functionalities', function () {
|
|
|
58
65
|
},
|
|
59
66
|
{
|
|
60
67
|
name: 'Log file is created successfully',
|
|
61
|
-
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r',
|
|
68
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region],
|
|
62
69
|
expected: null,
|
|
63
70
|
options: {
|
|
64
|
-
exitCondition:
|
|
71
|
+
exitCondition: `(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`,
|
|
65
72
|
timeout: 10000,
|
|
66
73
|
cwd: TEMP_DIR + '/' + 'log-file-is-created-successfully'
|
|
67
74
|
},
|
|
@@ -83,15 +90,6 @@ describe('copy-toolchain: Test functionalities', function () {
|
|
|
83
90
|
assert.isTrue(toolchainData.id === toolchainId, 'Was toolchain created successfully without any confirmations?');
|
|
84
91
|
}
|
|
85
92
|
},
|
|
86
|
-
{
|
|
87
|
-
name: 'Prompt User when toolchain name already exists in resource group',
|
|
88
|
-
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TARGET_REGIONS[0]],
|
|
89
|
-
expected: new RegExp(`Warning! A toolchain named \'${TEST_TOOLCHAINS['empty'].name}\' already exists in:[\\s\\S]*?Resource Group:[\\s\\S]*?${R2R_CLI_RG_ID}`),
|
|
90
|
-
options: {
|
|
91
|
-
exitCondition: '(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):',
|
|
92
|
-
timeout: 10000
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
93
|
{
|
|
96
94
|
name: 'Prompt User when toolchain name already exists in region',
|
|
97
95
|
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region, '-g', DEFAULT_RG_ID],
|
|
@@ -11,9 +11,7 @@ import path from 'node:path';
|
|
|
11
11
|
import nconf from 'nconf';
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
|
|
14
|
-
import * as chai from 'chai';
|
|
15
14
|
import { expect } from 'chai';
|
|
16
|
-
chai.config.truncateThreshold = 0;
|
|
17
15
|
|
|
18
16
|
import mocks from '../data/mocks.js';
|
|
19
17
|
import { assertExecError, assertPtyOutput } from '../utils/testUtils.js';
|
|
@@ -81,12 +79,12 @@ describe('copy-toolchain: Test user input handling', function () {
|
|
|
81
79
|
{
|
|
82
80
|
name: 'Invalid Resource Group name is provided',
|
|
83
81
|
cmd: [CLI_PATH, COMMAND, '-c', validCrn, '-r', TARGET_REGIONS[0], '-g', mocks.invalidRgName],
|
|
84
|
-
expected: /
|
|
82
|
+
expected: /No matching resource groups were found for the provided id\(s\) or name\(s\)/,
|
|
85
83
|
},
|
|
86
84
|
{
|
|
87
85
|
name: 'Invalid Resource Group ID is provided',
|
|
88
86
|
cmd: [CLI_PATH, COMMAND, '-c', validCrn, '-r', TARGET_REGIONS[0], '-g', mocks.invalidRgId],
|
|
89
|
-
expected: /
|
|
87
|
+
expected: /No matching resource groups were found for the provided id\(s\) or name\(s\)/,
|
|
90
88
|
},
|
|
91
89
|
{
|
|
92
90
|
name: 'Non-existent GRIT mapping file provided',
|
|
@@ -127,10 +125,13 @@ describe('copy-toolchain: Test user input handling', function () {
|
|
|
127
125
|
const invalidUserInputCases = [
|
|
128
126
|
{
|
|
129
127
|
name: 'Invalid Toolchain tag is provided',
|
|
130
|
-
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r',
|
|
128
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region],
|
|
131
129
|
expected: /Provided tag is invalid/,
|
|
132
130
|
options: {
|
|
133
|
-
questionAnswerMap: {
|
|
131
|
+
questionAnswerMap: {
|
|
132
|
+
[`(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`]: '',
|
|
133
|
+
'(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': mocks.invalidTag
|
|
134
|
+
},
|
|
134
135
|
exitCondition: 'Validation failed',
|
|
135
136
|
timeout: 10000
|
|
136
137
|
}
|
|
@@ -10,9 +10,6 @@
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import nconf from 'nconf';
|
|
12
12
|
|
|
13
|
-
import * as chai from 'chai';
|
|
14
|
-
chai.config.truncateThreshold = 0;
|
|
15
|
-
|
|
16
13
|
import { assertTfResourcesInDir, assertPtyOutput } from '../utils/testUtils.js';
|
|
17
14
|
import { TEST_TOOLCHAINS } from '../data/test-toolchains.js';
|
|
18
15
|
|
|
@@ -46,12 +43,12 @@ describe('copy-toolchain: Test import-terraform output', function () {
|
|
|
46
43
|
ibm_cd_tekton_pipeline_trigger: 1,
|
|
47
44
|
ibm_cd_tekton_pipeline_trigger_property: 1,
|
|
48
45
|
ibm_cd_toolchain: 1,
|
|
46
|
+
ibm_cd_toolchain_tool_cos: 1,
|
|
49
47
|
ibm_cd_toolchain_tool_custom: 1,
|
|
50
48
|
ibm_cd_toolchain_tool_githubconsolidated: 1,
|
|
51
49
|
ibm_cd_toolchain_tool_pipeline: 1,
|
|
52
50
|
ibm_cd_toolchain_tool_secretsmanager: 1,
|
|
53
51
|
ibm_cd_toolchain_tool_slack: 1,
|
|
54
|
-
ibm_iam_authorization_policy: 1
|
|
55
52
|
});
|
|
56
53
|
}
|
|
57
54
|
},
|
|
@@ -71,12 +68,12 @@ describe('copy-toolchain: Test import-terraform output', function () {
|
|
|
71
68
|
ibm_cd_tekton_pipeline_trigger: 1,
|
|
72
69
|
ibm_cd_tekton_pipeline_trigger_property: 1,
|
|
73
70
|
ibm_cd_toolchain: 1,
|
|
71
|
+
ibm_cd_toolchain_tool_cos: 1,
|
|
74
72
|
ibm_cd_toolchain_tool_custom: 1,
|
|
75
73
|
ibm_cd_toolchain_tool_githubconsolidated: 1,
|
|
76
74
|
ibm_cd_toolchain_tool_pipeline: 1,
|
|
77
75
|
ibm_cd_toolchain_tool_secretsmanager: 1,
|
|
78
76
|
ibm_cd_toolchain_tool_slack: 1,
|
|
79
|
-
ibm_iam_authorization_policy: 1
|
|
80
77
|
});
|
|
81
78
|
}
|
|
82
79
|
},
|
|
@@ -102,7 +99,6 @@ describe('copy-toolchain: Test import-terraform output', function () {
|
|
|
102
99
|
ibm_cd_toolchain_tool_pipeline: 1,
|
|
103
100
|
ibm_cd_toolchain_tool_secretsmanager: 1,
|
|
104
101
|
ibm_cd_toolchain_tool_slack: 1,
|
|
105
|
-
ibm_iam_authorization_policy: 1
|
|
106
102
|
})
|
|
107
103
|
}
|
|
108
104
|
},
|
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import nconf from 'nconf';
|
|
12
12
|
|
|
13
|
-
import * as chai from 'chai';
|
|
14
|
-
chai.config.truncateThreshold = 0;
|
|
15
13
|
import { expect } from 'chai';
|
|
16
14
|
|
|
17
15
|
import { assertPtyOutput, deleteCreatedToolchains } from '../utils/testUtils.js';
|
|
@@ -58,7 +56,6 @@ describe('copy-toolchain: Test tool validation', function () {
|
|
|
58
56
|
},
|
|
59
57
|
assertionFunc: (output) => {
|
|
60
58
|
expect(output).to.match(/Warning! The following tools contain secrets that cannot be migrated/);
|
|
61
|
-
expect(output).to.match(/cloudobjectstorage[\s\S]*?cos_api_key/);
|
|
62
59
|
expect(output).to.match(/slack[\s\S]*?api_token/);
|
|
63
60
|
expect(output).to.match(/pipeline[\s\S]*?properties.doi-ibmcloud-api-key/);
|
|
64
61
|
}
|
|
@@ -87,7 +84,7 @@ describe('copy-toolchain: Test tool validation', function () {
|
|
|
87
84
|
timeout: 30000
|
|
88
85
|
},
|
|
89
86
|
assertionFunc: (output) => {
|
|
90
|
-
expect(output).to.match(/Warning! The following GRIT integration\(s\)
|
|
87
|
+
expect(output).to.match(/Warning! The following GRIT integration\(s\) with auth_type "pat" are unsupported during migration and will automatically be converted to auth_type "oauth"/);
|
|
91
88
|
expect(output).to.match(/hostedgit/);
|
|
92
89
|
expect(output).to.match(/Warning! The following tools contain secrets that cannot be migrated/);
|
|
93
90
|
expect(output).to.match(/githubconsolidated[\s\S]*?api_token/);
|
package/cmd/check-secrets.js
DELETED
|
@@ -1,111 +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
|
-
'use strict';
|
|
10
|
-
|
|
11
|
-
import { exit } from 'node:process';
|
|
12
|
-
import { Command } from 'commander';
|
|
13
|
-
import { parseEnvVar, decomposeCrn, isSecretReference } from './utils/utils.js';
|
|
14
|
-
import { logger, LOG_STAGES } from './utils/logger.js';
|
|
15
|
-
import { getBearerToken, getToolchainTools, getPipelineData } from './utils/requests.js';
|
|
16
|
-
import { SECRET_KEYS_MAP } from '../config.js';
|
|
17
|
-
|
|
18
|
-
const command = new Command('check-secrets')
|
|
19
|
-
.description('Checks if you have any stored secrets in your toolchain or pipelines')
|
|
20
|
-
.requiredOption('-c, --toolchain-crn <crn>', 'The CRN of the source toolchain to check')
|
|
21
|
-
.option('-a --apikey <api key>', 'IBM Cloud IAM API key with permissions to read the toolchain.')
|
|
22
|
-
.showHelpAfterError()
|
|
23
|
-
.hook('preAction', cmd => cmd.showHelpAfterError(false)) // only show help during validation
|
|
24
|
-
.action(main);
|
|
25
|
-
|
|
26
|
-
async function main(options) {
|
|
27
|
-
const toolchainCrn = options.toolchainCrn;
|
|
28
|
-
const apiKey = options.apikey || parseEnvVar('IBMCLOUD_API_KEY');
|
|
29
|
-
|
|
30
|
-
if (!apiKey) {
|
|
31
|
-
logger.error('Missing IBM Cloud IAM API key', LOG_STAGES.setup);
|
|
32
|
-
exit(1);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
logger.print(`Checking secrets for toolchain ${toolchainCrn}...`);
|
|
36
|
-
|
|
37
|
-
const decomposedCrn = decomposeCrn(toolchainCrn);
|
|
38
|
-
|
|
39
|
-
const token = await getBearerToken(apiKey);
|
|
40
|
-
const toolchainId = decomposedCrn.serviceInstance;
|
|
41
|
-
const region = decomposedCrn.location;
|
|
42
|
-
|
|
43
|
-
const getToolsRes = await getToolchainTools(token, toolchainId, region);
|
|
44
|
-
|
|
45
|
-
const toolResults = [];
|
|
46
|
-
const pipelineResults = [];
|
|
47
|
-
|
|
48
|
-
if (getToolsRes?.tools?.length > 0) {
|
|
49
|
-
for (let i = 0; i < getToolsRes.tools.length; i++) {
|
|
50
|
-
const tool = getToolsRes.tools[i];
|
|
51
|
-
|
|
52
|
-
// Skip iff it's GitHub/GitLab/GRIT integration with OAuth
|
|
53
|
-
if (['githubconsolidated', 'github_integrated', 'gitlab', 'hostedgit'].includes(tool.tool_type_id) && (tool.parameters?.auth_type === '' || tool.parameters?.auth_type === 'oauth'))
|
|
54
|
-
continue;
|
|
55
|
-
|
|
56
|
-
// Check tool integrations for any plain text secret values
|
|
57
|
-
if (SECRET_KEYS_MAP[tool.tool_type_id]) {
|
|
58
|
-
SECRET_KEYS_MAP[tool.tool_type_id].forEach((entry) => {
|
|
59
|
-
const updateableSecretParam = entry.key;
|
|
60
|
-
if (tool.parameters[updateableSecretParam] && !isSecretReference(tool.parameters[updateableSecretParam]) && tool.parameters[updateableSecretParam].length > 0) {
|
|
61
|
-
toolResults.push({
|
|
62
|
-
'Tool ID': tool.id,
|
|
63
|
-
'Tool Type': tool.tool_type_id,
|
|
64
|
-
'Property Name': updateableSecretParam
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
});
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// For tekton pipelines, check for any plain text secret properties
|
|
71
|
-
if (tool.tool_type_id === 'pipeline' && tool.parameters?.type === 'tekton') {
|
|
72
|
-
const pipelineData = await getPipelineData(token, tool.id, region);
|
|
73
|
-
|
|
74
|
-
pipelineData?.properties.forEach((prop) => {
|
|
75
|
-
if (prop.type === 'secure' && !isSecretReference(prop.value) && prop.value.length > 0) {
|
|
76
|
-
pipelineResults.push({
|
|
77
|
-
'Pipeline ID': pipelineData.id,
|
|
78
|
-
'Trigger Name': '-',
|
|
79
|
-
'Property Name': prop.name
|
|
80
|
-
});
|
|
81
|
-
};
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
pipelineData?.triggers.forEach((trigger) => {
|
|
85
|
-
trigger.properties?.forEach((prop) => {
|
|
86
|
-
if (prop.type === 'secure' && !isSecretReference(prop.value) && prop.value.length > 0) {
|
|
87
|
-
pipelineResults.push({
|
|
88
|
-
'Pipeline ID': pipelineData.id,
|
|
89
|
-
'Trigger Name': trigger.name,
|
|
90
|
-
'Property Name': prop.name
|
|
91
|
-
});
|
|
92
|
-
};
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
if (toolResults.length > 0) {
|
|
100
|
-
logger.print();
|
|
101
|
-
logger.print('The following plain text properties were found in tool integrations bound to the toolchain:');
|
|
102
|
-
logger.table(toolResults);
|
|
103
|
-
};
|
|
104
|
-
if (pipelineResults.length > 0) {
|
|
105
|
-
logger.print();
|
|
106
|
-
logger.print('The following plain text properties were found in Tekton pipeline(s) bound to the toolchain:');
|
|
107
|
-
logger.table(pipelineResults);
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
export default command;
|