Skip to content
This repository was archived by the owner on Dec 17, 2024. It is now read-only.

Commit f0adc6b

Browse files
committed
update preview command to support -e key value env var assignments
Fixes #719
1 parent 8544392 commit f0adc6b

File tree

6 files changed

+134
-21
lines changed

6 files changed

+134
-21
lines changed

app/plugins/modules/composer/lib/create-from-source.js

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,31 @@ openwhiskComposer.wsk = {
3737
triggers: nope
3838
}
3939

40-
const usage = cmd => `Usage: app ${cmd} </path/to/app-src.js>`
41-
4240
const patterns = {
4341
require: /(const [^\s]+)\s*=\s*require\('@ibm-functions\/composer'\)/,
4442
constAppEquals: /.*const ([^\s]+)\s*=\s+composer(.*)/,
4543
return: /^return\s+/
4644
}
4745

46+
/** override values in A with those from B, returning any overwritten values */
47+
const save = (A, B) => {
48+
const overwritten = {}
49+
for (let key in B) {
50+
overwritten[key] = A[key]
51+
A[key] = B[key]
52+
}
53+
return overwritten
54+
}
55+
56+
/** restore values to A using values from B */
57+
const restore = (A, B) => {
58+
for (let key in B) {
59+
if (B[key]) {
60+
A[key] = B[key]
61+
}
62+
}
63+
}
64+
4865
/**
4966
* Take as input a file on disk that makes use of the
5067
* openwhisk-composer library, and return the corresponding FSM.
@@ -130,7 +147,7 @@ exports.compileToFSM = (src, opts={}) => new Promise((resolve, reject) => {
130147
logMessage = ''
131148
try {
132149
const module = { exports: {},
133-
process: { env: process.env, exit: doExit },
150+
process: { env: opts.env || process.env, exit: doExit },
134151
console: { error: msg => errorMessage += msg + '\n',
135152
log: doLog }
136153
},
@@ -150,6 +167,7 @@ exports.compileToFSM = (src, opts={}) => new Promise((resolve, reject) => {
150167
if (typeof res === 'function') {
151168
res = res()
152169
}
170+
153171
if (isValidFSM(res)) {
154172
return res
155173
} else {
@@ -175,14 +193,19 @@ exports.compileToFSM = (src, opts={}) => new Promise((resolve, reject) => {
175193
process.exit = doExit
176194
try {
177195
errorMessage = ''
178-
const json = eval(originalCode)
179-
if (isValidFSM(json)) {
180-
return json
181-
} else {
182-
const maybe = json
183-
console.log = log
184-
process.exit = exit
185-
return maybe
196+
const tmp = save(process.env, opts.env)
197+
try {
198+
const json = eval(originalCode)
199+
if (isValidFSM(json)) {
200+
return json
201+
} else {
202+
const maybe = json
203+
console.log = log
204+
process.exit = exit
205+
return maybe
206+
}
207+
} finally {
208+
restore(process.env, tmp)
186209
}
187210
} catch (e2) {
188211
console.log = log
@@ -198,10 +221,15 @@ exports.compileToFSM = (src, opts={}) => new Promise((resolve, reject) => {
198221
console.log = doLog
199222
process.exit = doExit
200223
errorMessage = ''
201-
const composition = eval(bootstrapWithRequire(originalCode))
202-
console.log = log
203-
process.exit = exit
204-
return composition
224+
const tmp = save(process.env, opts.env)
225+
try {
226+
const composition = eval(bootstrapWithRequire(originalCode))
227+
console.log = log
228+
process.exit = exit
229+
return composition
230+
} finally {
231+
restore(process.env, tmp)
232+
}
205233

206234
} catch (e4) {
207235
console.log = log

app/plugins/modules/composer/lib/usage.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ exports.preview = command => ({
160160
},
161161
oneof: [{ name: 'src.js', docs: 'generate a preview of a Composer source file', file: true },
162162
{ name: 'src.json', docs: 'ibid, but for a pre-compiled composition', file: true }],
163-
optional: [{ name: '--fsm', boolean: true, docs: 'validate and show raw FSM' } ],
163+
optional: [{ name: '--fsm', boolean: true, docs: 'validate and show raw FSM' },
164+
{ name: '--env', alias: '-e', docs: 'Assign a value to an environment variable', narg: 2 }],
164165
sampleInputs: sampleInputs(command),
165166
parents: ['composer', { command: 'composer app' }],
166167
related: ['app create']

app/plugins/modules/composer/lib/viz.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17+
const debug = require('debug')('app preview')
18+
debug('loading')
19+
1720
const { isValidFSM, wskflow, zoomToFitButtons, vizAndfsmViewModes, codeViewMode, handleError } = require('./composer'),
1821
badges = require('./badges'),
1922
{ readFSMFromDisk, compileToFSM } = require('./create-from-source'),
@@ -35,7 +38,9 @@ const viewName = 'preview', // for back button and sidecar header
3538
*
3639
*/
3740
module.exports = (commandTree, prequire) => {
38-
const render = (input, options) => new Promise((resolve, reject) => {
41+
const render = (input, options) => new Promise((resolve, reject) => {
42+
debug('options', options)
43+
3944
let fsmPromise, type, extraModes=[]
4045

4146
if (input.endsWith('.fsm') || input.endsWith('.json')) {
@@ -49,7 +54,7 @@ module.exports = (commandTree, prequire) => {
4954
}
5055
} else if (input.endsWith('.js')) {
5156
type = badges.composerLib
52-
fsmPromise = compileToFSM(input, { code: true })
57+
fsmPromise = compileToFSM(input, Object.assign({ code: true }, options))
5358
extraModes.push(codeViewMode)
5459

5560
} else {
@@ -136,6 +141,22 @@ module.exports = (commandTree, prequire) => {
136141
reject('The specified file does not exist')
137142
}
138143

144+
if (options.env) {
145+
debug('parsing environment variables from command line', options.env)
146+
147+
const environment = {}
148+
for (let idx = 0; idx < options.env.length; idx += 2) {
149+
const key = options.env[idx],
150+
value = options.env[idx + 1]
151+
environment[key] = value
152+
}
153+
154+
options.env = environment
155+
delete options.e
156+
157+
debug('environment', environment)
158+
}
159+
139160
// render now
140161
render(input, options).then(resolve, reject)
141162

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict'
2+
3+
const dbname = 'travis2slack'
4+
const cloudantBinding = process.env['CLOUDANT_PACKAGE_BINDING'];
5+
const slackConfig = {
6+
token: process.env['SLACK_TOKEN'],
7+
username: 'whiskbot',
8+
url: 'https://slack.com/api/chat.postMessage'
9+
}
10+
11+
if (slackConfig.token === undefined) {
12+
console.error('SLACK_TOKEN required in environment.')
13+
process.exit(-1)
14+
}
15+
16+
if (cloudantBinding === undefined) {
17+
console.error('CLOUDANT_PACKAGE_BINDING required in environment.')
18+
process.exit(-1)
19+
}
20+
21+
composer.let({ db: dbname, sc: slackConfig, userID: undefined, name: undefined },
22+
composer.sequence(
23+
`/whisk.system/utils/echo`,
24+
p => { name = p.text; userID = p.user_id },
25+
composer.try(
26+
composer.sequence(
27+
_ => ({ dbname: db, doc: { _id: name, display_name: name, userID: userID, onSuccess: true }, overwrite: false }),
28+
`${cloudantBinding}/write`,
29+
_ => ({ message: "Hi, " + name + ". I will now notify you when TravisCI jobs for your Apache OpenWhisk PRs complete." })
30+
),
31+
// write failed. Try to figure out why
32+
composer.try(
33+
composer.sequence(
34+
p => ({ dbname: db, docid: name }),
35+
`${cloudantBinding}/read-document`,
36+
doc => ({ message: "I'm sorry, but <@" + doc.userID + "> is already subscribed to be notified for PRs by `" + name + "`" })
37+
),
38+
_ => ({ message: "I'm sorry. There was an error updating Cloudant. Try again later." })
39+
)
40+
),
41+
p => Object.assign(sc, { channel: "@" + userID, text: p.message }),
42+
`/whisk.system/slack/post`
43+
))

tests/lib/composer-viz-util.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const input = (file, subdir='.') => ({
3030
path: path.join('data', subdir, file)
3131
})
3232
const composerInput = file => input(file, 'composer-source')
33+
const composerErrorInput = file => input(file, 'composer-source-expect-errors')
3334

3435
/**
3536
* Verify that a node with the given action name exists on the canvas
@@ -38,8 +39,8 @@ const composerInput = file => input(file, 'composer-source')
3839
const verifyNodeExists = (name, isDeployed=false) => app => {
3940
const selector = `#wskflowSVG .node[data-name="/_/${name}"][data-deployed="${isDeployed ? 'deployed' : 'not-deployed'}"]`
4041
console.error(`CHECKING NODE ${name} ${selector}`)
41-
return app.client.elements(selector)
42-
.then(nodes => assert.equal(nodes.value.length, 1))
42+
return app.client.waitUntil(() => app.client.elements(selector)
43+
.then(nodes => nodes.value.length === 1))
4344
.then(() => app)
4445
}
4546
const verifyNodeExistsById = id => app => {
@@ -93,6 +94,7 @@ const verifyTheBasicStuff = (file, badge) => _ => Promise.resolve(_)
9394
module.exports = {
9495
input,
9596
composerInput,
97+
composerErrorInput,
9698
verifyNodeExists,
9799
verifyNodeExistsById,
98100
verifyEdgeExists,

tests/tests/passes/07/composer-viz.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const fs = require('fs'),
2424
{
2525
input,
2626
composerInput,
27+
composerErrorInput,
2728
verifyNodeExists,
2829
verifyNodeExistsById,
2930
verifyEdgeExists,
@@ -44,7 +45,8 @@ const fsm = input('fsm.json'), fsmStruct = JSON.parse(fs.readFileSync(fsm.path).
4445
whileSeq = composerInput('while-seq.js'),
4546
retry5Times = composerInput('retry-5-times.js'),
4647
demo = composerInput('demo.js'),
47-
demoRetain = composerInput('demo-retain.js')
48+
demoRetain = composerInput('demo-retain.js'),
49+
addSubscription = composerErrorInput('addSubscription.js')
4850

4951
/**
5052
* Here starts the test
@@ -222,4 +224,20 @@ describe('show the composer visualization without creating openwhisk assets', fu
222224
.then(verifyEdgeExists('TripleAndIncrement', 'DivideByTwo'))
223225
.then(verifyOutgoingEdgeExists('DivideByTwo'))
224226
.catch(common.oops(this)))
227+
228+
it(`fail to show visualization for addSubscription without -e for env var assignment`, () => cli.do(`preview ${addSubscription.path}`, this.app)
229+
.then(cli.expectError(0, 'SLACK_TOKEN required in environment'))
230+
.catch(common.oops(this)))
231+
232+
it(`fail to show visualization for addSubscription with partial -e for env var assignment`, () => cli.do(`preview ${addSubscription.path} -e SLACK_TOKEN yo`, this.app)
233+
.then(cli.expectError(0, 'CLOUDANT_PACKAGE_BINDING required in environment'))
234+
.catch(common.oops(this)))
235+
236+
it(`show visualization for addSubscription using -e for env var assignment`, () => cli.do(`preview ${addSubscription.path} -e SLACK_TOKEN yo -e CLOUDANT_PACKAGE_BINDING mo`, this.app)
237+
.then(verifyTheBasicStuff(addSubscription.file, 'composerLib'))
238+
.then(verifyNodeExists('write'))
239+
.then(verifyNodeExists('read-document'))
240+
//.then(verifyNodeExists('post'))
241+
//.then(verifyEdgeExists('post', 'Exit'))
242+
.catch(common.oops(this)))
225243
})

0 commit comments

Comments
 (0)