Skip to content

Commit 657e655

Browse files
add support for ENCRYPTED Variables in pipeline run cmd (#813)
1 parent 14ab467 commit 657e655

File tree

14 files changed

+287
-60
lines changed

14 files changed

+287
-60
lines changed

codefresh-release.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -445,14 +445,13 @@ steps:
445445
update_documentation:
446446
stage: documentation
447447
title: "Update documentation http://cli.codefresh.io"
448-
image: docker:18.01
448+
image: codefresh/build-cli
449449
commands:
450-
- "apk update && apk add git nodejs"
451-
- "npm install"
450+
- "yarn"
452451
- "echo cleaning previous public dir and recreating worktree"
453-
- "rm -rf public && git worktree prune && git worktree add -B gh-pages public origin/gh-pages"
452+
- "rm -rf public && git worktree prune && git worktree add -B gh-pages public origin/gh-pages"
454453
- "echo Building public docs"
455-
- "npm run build-public-docs"
454+
- "yarn run build-public-docs"
456455
- "echo Push new docs to gh-pages detached branch"
457456
- 'git config --global user.email "[email protected]" && git config --global user.name "Automated CI"'
458457
- 'cd public && git add --all && git commit -m "Publish new documentation for version ${{PACKAGE_VERSION}}" && git push https://${{GITHUB_TOKEN}}@github.com/codefresh-io/cli.git'

codefresh.yml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ steps:
328328
type: codefresh-run
329329
arguments:
330330
PIPELINE_ID: 'codefresh-io/cli/release'
331-
DETACH: true
331+
TRIGGER_ID: codefresh-io/cli_1
332332
BRANCH: master
333333
VARIABLE:
334334
- PACKAGE_VERSION=${{PACKAGE_VERSION}}
@@ -342,3 +342,32 @@ steps:
342342
- name: create_manifest_list
343343
on:
344344
- success
345+
346+
execute_e2e_pipeline:
347+
stage: final
348+
title: "Execute E2E pipeline for image of this commit"
349+
type: codefresh-run
350+
arguments:
351+
PIPELINE_ID: 'cli-v1-e2e/root'
352+
VARIABLE:
353+
- CLI_VERSION=${{CF_SHORT_REVISION}}
354+
when:
355+
steps:
356+
- name: push_step_alpine
357+
on:
358+
- success
359+
360+
build_documentation:
361+
stage: test
362+
title: "build documentation http://cli.codefresh.io"
363+
image: codefresh/build-cli
364+
commands:
365+
- "echo Building public docs"
366+
- "yarn run build-public-docs"
367+
environment:
368+
- HUGO_VERSION=0.32.0
369+
when:
370+
steps:
371+
- name: install_dependencies
372+
on:
373+
- success

docs/content/pipelines/Run Pipeline.md

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,44 @@ The pipeline will be triggered multiple times according to the array length.
1515

1616
#### Variable yaml file with 2 sets of variables
1717
```yaml
18-
- key: value
19-
key2: key1
20-
- key: value
21-
key2: key2
18+
- VARIABLE_A: value_a_for_the_first_build
19+
VARIABLE_B: value_b_for_the_first_build
20+
- VARIABLE_A: value_a_for_the_first_build
21+
VARIABLE_B: value_b_for_the_first_build
2222
```
2323
2424
#### Variable json file with 2 sets of variables
2525
```json
2626
[
2727
{
28-
"key": "value",
29-
"key2": "key1"
28+
"VARIABLE_A": "value_a_for_the_first_build",
29+
"VARIABLE_B": "value_b_for_the_first_build"
3030
},
3131
{
32-
"key": "value",
33-
"key2": "key2"
32+
"VARIABLE_A": "value_a_for_the_first_build",
33+
"VARIABLE_B": "value_b_for_the_first_build"
34+
}
35+
]
36+
```
37+
### Use encrypted variables in Codefresh build runs
38+
#### Variable yaml file with single variable set with encrypted variables
39+
```yaml
40+
- key:
41+
val: value
42+
encrypted: true
43+
key2: val2
44+
45+
```
46+
47+
#### Variable json file single variable set with encrypted variables
48+
```json
49+
[
50+
{
51+
"key": {
52+
"val": "value",
53+
"encrypted": true
54+
},
55+
"key2": "key1"
3456
}
3557
]
3658
```

lib/interface/cli/commands/annotation/create.cmd.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const command = new Command({
3131
.example('codefresh create annotation image 2dfacdaad466 coverage=75%', 'Annotate entity with a single label')
3232
.example('codefresh create annotation image 2dfacdaad466 coverage=75% tests_passed=true', 'Annotate entity with multiple labels')
3333
// eslint-disable-next-line max-len
34-
.example('codefresh create annotation image 2dfacdaad466 coverage=75% tests_passed=true --display coverage', 'Annotate entity with multiple labels and display selection'),
34+
.example('codefresh create annotation workflow 643d807b85bbe35931ae2282 ENV=prod tests_passed=true --display ENV', 'Annotate entity with multiple labels and display selection'),
3535
handler: async (argv) => {
3636
const { entityType, entityId, labels, display } = argv;
3737

lib/interface/cli/commands/pipeline/pipeline.sdk.spec.js

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
const yaml = require('js-yaml');
2+
const request = require('requestretry');
3+
const fs = require('fs');
14
const DEFAULTS = require('../../defaults');
25
const getCmd = require('./get.cmd').toCommand();
36
const deleteCmd = require('./delete.cmd').toCommand();
@@ -10,18 +13,21 @@ jest.mock('../../helpers/validation'); // eslint-disable-line
1013
jest.mock('../../../../../check-version');
1114
jest.mock('../../completion/helpers', () => { // eslint-disable-line
1215
return {
13-
authContextWrapper: func => func,
16+
authContextWrapper: (func) => func,
1417
};
1518
});
1619

20+
jest.mock('../../helpers/general', () => ({
21+
...jest.requireActual('../../helpers/general'),
22+
isCompatibleApiVersion: () => true,
23+
}));
24+
1725
jest.mock('../../../../logic/entities/Pipeline', () => { // eslint-disable-line
1826
return {
19-
fromResponse: res => res,
27+
fromResponse: (res) => res,
2028
};
2129
});
2230

23-
const request = require('requestretry');
24-
2531
const DEFAULT_RESPONSE = request.__defaultResponse();
2632

2733
describe('pipeline', () => {
@@ -57,11 +63,11 @@ describe('pipeline', () => {
5763
});
5864

5965
it('should return default limit', async () => {
60-
expect(_getLimit(undefined,false)).toEqual(DEFAULTS.GET_LIMIT_RESULTS);
66+
expect(_getLimit(undefined, false)).toEqual(DEFAULTS.GET_LIMIT_RESULTS);
6167
});
6268

6369
it('should return `unlimited` value', async () => {
64-
expect(_getLimit(undefined,true)).toEqual(DEFAULTS.GET_ALL_PIPELINES_LIMIT);
70+
expect(_getLimit(undefined, true)).toEqual(DEFAULTS.GET_ALL_PIPELINES_LIMIT);
6571
});
6672
});
6773

@@ -84,6 +90,69 @@ describe('pipeline', () => {
8490
});
8591
});
8692

93+
describe('run', () => {
94+
it('should handle running pipeline with encrypted variables', async () => {
95+
const argv = { name: 'some name',
96+
detach: true,
97+
annotation: [],
98+
variable: [
99+
'secret=secret',
100+
'VAR1=VAL1',
101+
],
102+
encrypted: ['secret'],
103+
};
104+
const pip = new CfPipeline(argv);
105+
await pip.run();
106+
expect(pip.executionRequests[0].options.variables).toEqual([
107+
{
108+
key: 'secret',
109+
value: 'secret',
110+
encrypted: true,
111+
},
112+
{
113+
key: 'VAR1',
114+
value: 'VAL1',
115+
},
116+
]);
117+
await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line
118+
});
119+
120+
it('should handle running pipeline with encrypted variables passing inside json file', async () => {
121+
const rawFile = fs.readFileSync('lib/interface/cli/commands/pipeline/test-files/var.json', 'utf8');
122+
123+
const argv = { name: 'some name',
124+
detach: true,
125+
annotation: [],
126+
'var-file': JSON.parse(rawFile),
127+
};
128+
const pip = new CfPipeline(argv);
129+
await pip.run();
130+
expect(pip.executionRequests[0].options.variables).toEqual(
131+
[{ key: 'help6', value: '85858' },
132+
{ key: 'should_be_encrepted', value: '0000' },
133+
{ encrypted: true, key: 'help7', value: 'test' }],
134+
);
135+
await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line
136+
});
137+
138+
it('should handle running pipeline with encrypted variables passing inside yaml file', async () => {
139+
const rawFile = fs.readFileSync('lib/interface/cli/commands/pipeline//test-files/var.yml', 'utf8');
140+
141+
const argv = { name: 'some name',
142+
detach: true,
143+
annotation: [],
144+
'var-file': yaml.safeLoad(rawFile),
145+
};
146+
const pip = new CfPipeline(argv);
147+
await pip.run();
148+
expect(pip.executionRequests[0].options.variables).toEqual(
149+
[{ key: 'VAR1', value: 'VAL1' },
150+
{ encrypted: true, key: 'VAR2', value: 'VAL2' }],
151+
);
152+
await verifyResponsesReturned([DEFAULT_RESPONSE]); // eslint-disable-line
153+
});
154+
});
155+
87156
describe('runImpl', () => {
88157
it('should handle running pipeline', async () => {
89158
const argv = { name: 'some name', detach: true };

lib/interface/cli/commands/pipeline/run.base.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
const _ = require('lodash');
22
const Promise = require('bluebird');
3-
const { prepareKeyValueFromCLIEnvOption } = require('../../helpers/general');
3+
const CFError = require('cf-errors');
4+
const { prepareKeyValueFromCLIEnvOption,
5+
markEncryptedFlagOnRequestedVariables,
6+
prepareKeyValueObjectsFromEnvFileOption,
7+
prepareKeyValueObjectsFromCLIEnvOption,
8+
isCompatibleApiVersion,
9+
} = require('../../helpers/general');
410
const { validatePipelineYaml } = require('../../helpers/validation');
511
const { printResult } = require('../root/validate.cmd');
6-
const CFError = require('cf-errors');
712
const { sdk } = require('../../../../logic');
13+
const defaults = require('../../defaults');
814

915
class RunBaseCommand {
1016
constructor(argv) {
@@ -55,22 +61,34 @@ class RunBaseCommand {
5561
packName,
5662
},
5763
};
58-
64+
const encryptedVarsSupported = await isCompatibleApiVersion({
65+
supportedVersion: defaults.MIN_API_VERSION_FOR_ENCRYPTED_VARS_SUPPORT_IN_RUN_CMD,
66+
});
5967
if (variablesFromFile) {
6068
_.forEach(variablesFromFile, (variables) => {
6169
const request = _.cloneDeep(executionRequestTemplate);
62-
request.options.variables = variables;
70+
if (encryptedVarsSupported) {
71+
request.options.variables = prepareKeyValueObjectsFromEnvFileOption(variables);
72+
} else {
73+
request.options.variables = variables;
74+
}
6375
this.executionRequests.push(request);
6476
});
6577
} else {
66-
const variables = prepareKeyValueFromCLIEnvOption(this.argv.variable);
78+
let variables;
79+
if (encryptedVarsSupported) {
80+
const varsArr = prepareKeyValueObjectsFromCLIEnvOption(this.argv.variable);
81+
variables = markEncryptedFlagOnRequestedVariables(varsArr, this.argv.encrypted);
82+
} else {
83+
variables = prepareKeyValueFromCLIEnvOption(this.argv.variable);
84+
}
6785
const request = _.cloneDeep(executionRequestTemplate);
6886
request.options.variables = variables;
6987
request.options.contexts = contexts;
7088
this.executionRequests.push(request);
7189
}
7290

73-
const results = await Promise.all(this.executionRequests.map(request => this.runImpl(request)));
91+
const results = await Promise.all(this.executionRequests.map((request) => this.runImpl(request)));
7492
const findMaxReducer = (accumulator, currentValue) => (currentValue > accumulator ? currentValue : accumulator);
7593
const exitCode = results.reduce(findMaxReducer);
7694
await this.postRunRequest();

lib/interface/cli/commands/pipeline/run.cmd.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
const debug = require('debug')('codefresh:cli:run:pipeline');
22
const Command = require('../../Command');
3-
const { crudFilenameOption } = require('../../helpers/general');
3+
const { crudFilenameOption, isCompatibleApiVersion } = require('../../helpers/general');
44
const RunLocalCommand = require('./run.local');
55
const RunExternalCommand = require('./run.cf');
6+
const defaults = require('../../defaults');
67

78
function getCommandFlavor(argv) {
89
if (argv.local) {
@@ -86,6 +87,22 @@ const run = new Command({
8687
default: [],
8788
alias: 'v',
8889
})
90+
.option('encrypted', {
91+
array: true,
92+
alias: 'e',
93+
describe: 'Variable names to encrypt',
94+
default: [],
95+
})
96+
.check(async (argv) => {
97+
const encryptedVarsSupported = await isCompatibleApiVersion({
98+
supportedVersion: defaults.MIN_API_VERSION_FOR_ENCRYPTED_VARS_SUPPORT_IN_RUN_CMD,
99+
});
100+
if (!encryptedVarsSupported && argv.encrypted.length > 0) {
101+
throw new Error('The "encrypted" option is only supported in API versions '
102+
+ `equal to or greater than ${defaults.MIN_API_VERSION_FOR_ENCRYPTED_VARS_SUPPORT_IN_RUN_CMD}.`);
103+
}
104+
return true;
105+
})
89106
.option('detach', {
90107
alias: 'd',
91108
describe: 'Run pipeline and print build ID',
@@ -126,6 +143,7 @@ const run = new Command({
126143
.example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master', 'Defining the source control context using a branch')
127144
.example('codefresh run PIPELINE_ID | PIPELINE_NAME -s=52b992e783d2f84dd0123c70ac8623b4f0f938d1', 'Defining the source control context using a commit')
128145
.example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master -v key1=value1 -v key2=value2', 'Setting variables through the command')
146+
.example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master -v key1=value1 -v key2=value2 -e key1', 'Setting variables through the command with encrypted option')
129147
.example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master --var-file ./var_file.yml', 'Settings variables through a yml file')
130148
.example('codefresh run PIPELINE_ID | PIPELINE_NAME -b=master --context context', 'Inject contexts to the pipeline execution')
131149
.example('codefresh run PIPELINE_ID | PIPELINE_NAME --skip step1 step2 step3', 'Skip specific steps');
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"build1": {
3+
"help6": "85858",
4+
"should_be_encrepted": "0000",
5+
"help7": {
6+
"value": "test",
7+
"encrypted": true
8+
}
9+
}
10+
}
11+
12+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
build1:
2+
VAR1: 'VAL1'
3+
VAR2:
4+
value: VAL2
5+
encrypted: true

lib/interface/cli/commands/project/apply.cmd.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const _ = require('lodash');
44
const { sdk } = require('../../../../logic');
55

66
const applyRoot = require('../root/apply.cmd');
7-
const { prepareKeyValueObjectsFromCLIEnvOption, ignoreHttpError } = require('../../helpers/general');
7+
const { prepareKeyValueObjectsFromCLIEnvOption, ignoreHttpError, markEncryptedFlagOnRequestedVariables } = require('../../helpers/general');
88

99
const command = new Command({
1010
command: 'project [id|name]',
@@ -61,14 +61,7 @@ const command = new Command({
6161
encrypted,
6262
} = argv;
6363

64-
const variableMap = _.reduce(variables, (acc, v) => _.assign(acc, { [v.key]: v }), {});
65-
_.forEach(encrypted, (varName) => {
66-
const variable = variableMap[varName];
67-
if (!variable) {
68-
throw new CFError(`Variable is not provided: "${varName}"`);
69-
}
70-
variable.encrypted = true;
71-
});
64+
const requestedProjectVariables = markEncryptedFlagOnRequestedVariables(variables, encrypted);
7265

7366
let project = await sdk.projects.get({ id }).catch(ignoreHttpError);
7467
project = project || await sdk.projects.getByName({ name }).catch(ignoreHttpError);
@@ -81,7 +74,7 @@ const command = new Command({
8174
const updatePayload = _.pickBy({
8275
projectName,
8376
tags: tags || existingTags,
84-
variables: variables || existingVariables,
77+
variables: requestedProjectVariables || existingVariables,
8578
}, _.identity);
8679

8780
await sdk.projects.patch({ id: project.id }, updatePayload);

0 commit comments

Comments
 (0)