Skip to content

Commit eec36ce

Browse files
committed
feat(variables): add support for yaml variable input. fixes #615
1 parent f4f0ccf commit eec36ce

File tree

7 files changed

+169
-33
lines changed

7 files changed

+169
-33
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"figures": "^3.2.0",
2020
"halfred": "^2.0.0",
2121
"inquirer": "^8.1.0",
22+
"js-yaml": "^4.1.0",
2223
"jsonwebtoken": "^8.5.1",
2324
"lodash": "^4.17.15",
2425
"moment": "^2.29.0"

src/ValidationErrors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ E('INTERNAL_VARIABLE_USAGE', 'The variable name %s is reserved for internal usag
5252
E('IP_ALLOWLIST_NOT_FOUND', 'Could not find IP Allowlist with id %s in program id %s.')
5353
E('VARIABLES_JSON_PARSE_ERROR', 'Unable to parse variables from provided data.')
5454
E('VARIABLES_JSON_NOT_ARRAY', 'Provided variables input was not an array.')
55+
E('VARIABLES_YAML_PARSE_ERROR', 'Unable to parse variables from provided data.')
56+
E('VARIABLES_YAML_NOT_ARRAY', 'Provided variables input was not an array.')
5557
E('BOTH_BRANCH_AND_TAG_PROVIDED', 'Both branch and tag cannot be specified.')
5658
E('MISSING_PROGRAM_ID', 'Program ID must be specified either as --programId flag or through cloudmanager_programid config value.')
5759
E('MISSING_METRICS', 'Metrics for action %s on execution %s could not be found.')

src/base-variables-command.js

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const fs = require('fs')
1515

1616
const BaseCommand = require('./base-command')
1717
const { cli } = require('cli-ux')
18+
const yaml = require('js-yaml')
1819
const { flags } = require('@oclif/command')
1920
const { getProgramId, createKeyValueObjectFromFlag, getOutputFormat } = require('./cloudmanager-helpers')
2021
const commonFlags = require('./common-flags')
@@ -126,44 +127,25 @@ class BaseVariablesCommand extends BaseCommand {
126127
}
127128
})
128129

130+
const getFileData = promisify(fs.readFile)
131+
129132
if (flags.jsonStdin) {
130133
const rawStdinData = await getPipedData()
131-
this.loadVariablesFromJson(rawStdinData, currentVariableTypes, variables)
134+
BaseVariablesCommand.loadVariablesFromJson(rawStdinData, currentVariableTypes, variables)
132135
} else if (flags.jsonFile) {
133-
const getFileData = promisify(fs.readFile)
134136
const rawFileData = await getFileData(flags.jsonFile)
135-
this.loadVariablesFromJson(rawFileData, currentVariableTypes, variables)
137+
BaseVariablesCommand.loadVariablesFromJson(rawFileData, currentVariableTypes, variables)
138+
} else if (flags.yamlStdin) {
139+
const rawStdinData = await getPipedData()
140+
BaseVariablesCommand.loadVariablesFromYaml(rawStdinData, currentVariableTypes, variables)
141+
} else if (flags.yamlFile) {
142+
const rawFileData = await getFileData(flags.yamlFile)
143+
BaseVariablesCommand.loadVariablesFromYaml(rawFileData, currentVariableTypes, variables)
136144
}
137145

138146
return variables
139147
}
140148

141-
loadVariablesFromJson (rawData, currentVariableTypes, variables) {
142-
let data
143-
try {
144-
data = JSON.parse(rawData)
145-
} catch (e) {
146-
throw new validationCodes.VARIABLES_JSON_PARSE_ERROR()
147-
}
148-
if (!_.isArray(data)) {
149-
throw new validationCodes.VARIABLES_JSON_NOT_ARRAY()
150-
}
151-
data.forEach(item => {
152-
if (item.name && !_.isUndefined(item.value)) {
153-
if (!item.type) {
154-
item.type = 'string'
155-
}
156-
const currentVariableKey = item.service ? `${item.service}:${item.name}` : item.name
157-
if (currentVariableTypes[currentVariableKey] && !item.value) {
158-
item.type = currentVariableTypes[currentVariableKey]
159-
}
160-
if (!variables.find(variable => variable.name === item.name && variables.services === item.service)) {
161-
variables.push(item)
162-
}
163-
}
164-
})
165-
}
166-
167149
validateVariables (flags, variables) { }
168150
}
169151

@@ -190,10 +172,20 @@ BaseVariablesCommand.setterFlags = {
190172
jsonStdin: flags.boolean({
191173
default: false,
192174
description: 'if set, read variables from a JSON array provided as standard input; variables set through --variable or --secret flag will take precedence',
175+
exclusive: ['jsonFile', 'yamlStdin', 'yamlFile'],
193176
}),
194177
jsonFile: flags.string({
195178
description: 'if set, read variables from a JSON array provided as a file; variables set through --variable or --secret flag will take precedence',
196-
exclusive: ['jsonStdin'],
179+
exclusive: ['jsonStdin', 'yamlStdin', 'yamlFile'],
180+
}),
181+
yamlStdin: flags.boolean({
182+
default: false,
183+
description: 'if set, read variables from a YAML array provided as standard input; variables set through --variable or --secret flag will take precedence',
184+
exclusive: ['jsonStdin', 'jsonFile', 'yamlFile'],
185+
}),
186+
yamlFile: flags.string({
187+
description: 'if set, read variables from a YAML array provided as a file; variables set through --variable or --secret flag will take precedence',
188+
exclusive: ['jsonStdin', 'jsonFile', 'yamlStdin'],
197189
}),
198190
...commonFlags.outputFormat,
199191
}
@@ -202,4 +194,47 @@ BaseVariablesCommand.getterFlags = {
202194
...commonFlags.outputFormat,
203195
}
204196

197+
const loadVariableData = (array, currentVariableTypes, variables) => {
198+
array.forEach(item => {
199+
if (item.name && !_.isUndefined(item.value)) {
200+
if (!item.type) {
201+
item.type = 'string'
202+
}
203+
const currentVariableKey = item.service ? `${item.service}:${item.name}` : item.name
204+
if (currentVariableTypes[currentVariableKey] && !item.value) {
205+
item.type = currentVariableTypes[currentVariableKey]
206+
}
207+
if (!variables.find(variable => variable.name === item.name && variables.services === item.service)) {
208+
variables.push(item)
209+
}
210+
}
211+
})
212+
}
213+
214+
BaseVariablesCommand.loadVariablesFromJson = (rawData, currentVariableTypes, variables) => {
215+
let data
216+
try {
217+
data = JSON.parse(rawData)
218+
} catch (e) {
219+
throw new validationCodes.VARIABLES_JSON_PARSE_ERROR()
220+
}
221+
if (!_.isArray(data)) {
222+
throw new validationCodes.VARIABLES_JSON_NOT_ARRAY()
223+
}
224+
loadVariableData(data, currentVariableTypes, variables)
225+
}
226+
227+
BaseVariablesCommand.loadVariablesFromYaml = (rawData, currentVariableTypes, variables) => {
228+
let data
229+
try {
230+
data = yaml.load(rawData)
231+
} catch (e) {
232+
throw new validationCodes.VARIABLES_YAML_PARSE_ERROR()
233+
}
234+
if (!_.isArray(data)) {
235+
throw new validationCodes.VARIABLES_YAML_NOT_ARRAY()
236+
}
237+
loadVariableData(data, currentVariableTypes, variables)
238+
}
239+
205240
module.exports = BaseVariablesCommand
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
Copyright 2022 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
7+
Unless required by applicable law or agreed to in writing, software distributed under
8+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
OF ANY KIND, either express or implied. See the License for the specific language
10+
governing permissions and limitations under the License.
11+
*/
12+
13+
const { promisify } = require('util')
14+
const fs = require('fs')
15+
const path = require('path')
16+
const BaseVariablesCommand = require('../src/base-variables-command')
17+
18+
test('multiline json', async () => {
19+
const getFileData = promisify(fs.readFile)
20+
const rawFileData = await getFileData(path.join(__dirname, 'data', 'variables.json'))
21+
22+
const variables = []
23+
BaseVariablesCommand.loadVariablesFromJson(rawFileData, {}, variables)
24+
25+
expect(variables).toHaveLength(1)
26+
expect(variables[0].value.split('\n')).toHaveLength(3)
27+
})
28+
29+
test('multiline yaml', async () => {
30+
const getFileData = promisify(fs.readFile)
31+
const rawFileData = await getFileData(path.join(__dirname, 'data', 'variables.yaml'))
32+
33+
const variables = []
34+
BaseVariablesCommand.loadVariablesFromYaml(rawFileData, {}, variables)
35+
36+
expect(variables).toHaveLength(1)
37+
expect(variables[0].value.split('\n')).toHaveLength(3)
38+
})

test/commands/environment/set-variables.test.js

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,22 @@ test('set-environment-variables - stdin - not JSON', async () => {
291291
await expect(runResult).rejects.toThrow('Unable to parse variables from provided data.')
292292
})
293293

294+
test('set-environment-variables - stdin - bad YAML', async () => {
295+
setCurrentOrgId('good')
296+
setStore({
297+
cloudmanager_programid: '4',
298+
})
299+
300+
getPipedData.mockResolvedValue('{garbage')
301+
302+
expect.assertions(2)
303+
304+
const runResult = SetEnvironmentVariablesCommand.run(['1', '--yamlStdin'])
305+
306+
await expect(runResult instanceof Promise).toBeTruthy()
307+
await expect(runResult).rejects.toThrow('Unable to parse variables from provided data.')
308+
})
309+
294310
test('set-environment-variables - file - not JSON', async () => {
295311
setCurrentOrgId('good')
296312
setStore({
@@ -307,7 +323,23 @@ test('set-environment-variables - file - not JSON', async () => {
307323
await expect(runResult).rejects.toThrow('Unable to parse variables from provided data.')
308324
})
309325

310-
test('set-environment-variables - stdin - not array', async () => {
326+
test('set-environment-variables - file - bad YAML', async () => {
327+
setCurrentOrgId('good')
328+
setStore({
329+
cloudmanager_programid: '4',
330+
})
331+
332+
mockFileContent = '{garbage'
333+
334+
expect.assertions(2)
335+
336+
const runResult = SetEnvironmentVariablesCommand.run(['1', '--yamlFile', mockFileName])
337+
338+
await expect(runResult instanceof Promise).toBeTruthy()
339+
await expect(runResult).rejects.toThrow('Unable to parse variables from provided data.')
340+
})
341+
342+
test('set-environment-variables - JSON stdin - not array', async () => {
311343
setCurrentOrgId('good')
312344
setStore({
313345
cloudmanager_programid: '4',
@@ -323,7 +355,23 @@ test('set-environment-variables - stdin - not array', async () => {
323355
await expect(runResult).rejects.toThrow('Provided variables input was not an array.')
324356
})
325357

326-
test('set-environment-variables - stdin - secret and variable', async () => {
358+
test('set-environment-variables - YAML stdin - not array', async () => {
359+
setCurrentOrgId('good')
360+
setStore({
361+
cloudmanager_programid: '4',
362+
})
363+
364+
getPipedData.mockResolvedValue('test: true')
365+
366+
expect.assertions(2)
367+
368+
const runResult = SetEnvironmentVariablesCommand.run(['1', '--yamlStdin'])
369+
370+
await expect(runResult instanceof Promise).toBeTruthy()
371+
await expect(runResult).rejects.toThrow('Provided variables input was not an array.')
372+
})
373+
374+
test('set-environment-variables - JSON stdin - secret and variable', async () => {
327375
setCurrentOrgId('good')
328376
setStore({
329377
cloudmanager_programid: '4',
@@ -496,7 +544,7 @@ test('set-environment-variables - both jsonStdin and jsonFile', async () => {
496544

497545
await expect(runResult instanceof Promise).toBeTruthy()
498546

499-
await expect(runResult).rejects.toThrow('--jsonStdin= cannot also be provided when using --jsonFile=')
547+
await expect(runResult).rejects.toThrow('--jsonFile= cannot also be provided when using --jsonStdin=')
500548
})
501549

502550
test('set-environment-variables - author variable', async () => {

test/data/variables.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"name" : "mltest",
4+
"value" : "abc\ndef\nghi"
5+
}
6+
7+
]

test/data/variables.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- name: mltest
2+
value: |-
3+
abc
4+
def
5+
ghi

0 commit comments

Comments
 (0)