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

Commit 50498f2

Browse files
authored
Document compose command (#48)
1 parent 15eeed8 commit 50498f2

File tree

3 files changed

+171
-4
lines changed

3 files changed

+171
-4
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ One way to deploy a composition is to use the `compose` command:
8686
compose demo.js --deploy demo
8787
```
8888
```
89-
ok: created /_/authenticate,/_/success,/_/failure,/_/demo
89+
ok: created actions /_/authenticate,/_/success,/_/failure,/_/demo
9090
```
9191
The `compose` command synthesizes and deploys an action named `demo` that
9292
implements the composition. It also deploys the composed actions if definitions

bin/compose

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,28 @@ const vm = require('vm')
77
const minimist = require('minimist')
88
const composer = require('../composer')
99

10-
const argv = minimist(process.argv.slice(2), { string: ['apihost', 'auth', 'deploy'], boolean: ['insecure', 'encode'], alias: { auth: 'u', insecure: 'i' } })
10+
const argv = minimist(process.argv.slice(2), {
11+
string: ['apihost', 'auth', 'deploy'],
12+
boolean: ['insecure', 'encode', 'json'],
13+
alias: { auth: 'u', insecure: 'i' }
14+
})
1115

12-
if (argv._.length !== 1 || argv.deploy === '') {
13-
console.error('Usage: compose <composition.js[on]> [--deploy <name> | --encode] [--apihost <host>] [--auth <key>] [--insecure]')
16+
let count = 0
17+
if (argv.json) count++
18+
if (argv.encode) count++
19+
if (typeof argv.deploy !== 'undefined') count++
20+
21+
if (argv._.length !== 1 || count > 1 || argv.deploy === '') {
22+
console.error('Usage:')
23+
console.error(' compose composition.js[on] command [flags]')
24+
console.error('Commands:')
25+
console.error(' --json output the json representation for the composition (default command)')
26+
console.error(' --deploy NAME deploy the composition with name NAME')
27+
console.error(' --encode output the conductor action code for the composition')
28+
console.error('Flags:')
29+
console.error(' --apihost HOST API HOST')
30+
console.error(' -u, --auth KEY authorization KEY')
31+
console.error(' -i, --insecure bypass certificate checking')
1432
return
1533
}
1634

docs/COMPOSE.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Compose Command
2+
3+
The `compose` command makes it possible to encode and deploy compositions.
4+
```
5+
compose
6+
```
7+
```
8+
Usage:
9+
compose composition.js[on] command [flags]
10+
Commands:
11+
--json output the json representation for the composition (default command)
12+
--deploy NAME deploy the composition with name NAME
13+
--encode output the conductor action code for the composition
14+
Flags:
15+
--apihost HOST API HOST
16+
-u, --auth KEY authorization KEY
17+
-i, --insecure bypass certificate checking
18+
```
19+
The `compose` command requires either a Javascript file that evaluates to a composition (for example [demo.js](../samples/demo.js)) or a JSON file that encodes a composition (for example [demo.json](../samples/demo.json)). The JSON format is documented in [FORMAT.md](FORMAT.md).
20+
21+
The `compose` command has three mode of operation:
22+
- By default or when the `--json` option is specified, the command returns the composition encoded as a JSON dictionary.
23+
- When the `--deploy` option is specified, the command deploys the composition with the desired name.
24+
- When the `--encode` option is specified, the command returns the Javascript code for the [conductor action](https://github.com/apache/incubator-openwhisk/blob/master/docs/conductors.md) for the composition.
25+
26+
## Encoding
27+
28+
By default, the `compose` command returns the composition encoded as a JSON dictionary:
29+
```
30+
compose demo.js
31+
```
32+
```json
33+
{
34+
"actions": [
35+
{
36+
"name": "/_/authenticate",
37+
"action": {
38+
"exec": {
39+
"kind": "nodejs:default",
40+
"code": "function main({ password }) { return { value: password === 'abc123' } }"
41+
}
42+
}
43+
},
44+
{
45+
"name": "/_/success",
46+
"action": {
47+
"exec": {
48+
"kind": "nodejs:default",
49+
"code": "function main() { return { message: 'success' } }"
50+
}
51+
}
52+
},
53+
{
54+
"name": "/_/failure",
55+
"action": {
56+
"exec": {
57+
"kind": "nodejs:default",
58+
"code": "function main() { return { message: 'failure' } }"
59+
}
60+
}
61+
}
62+
],
63+
"composition": [
64+
{
65+
"type": "if",
66+
"test": [
67+
{
68+
"type": "action",
69+
"name": "/_/authenticate"
70+
}
71+
],
72+
"consequent": [
73+
{
74+
"type": "action",
75+
"name": "/_/success"
76+
}
77+
],
78+
"alternate": [
79+
{
80+
"type": "action",
81+
"name": "/_/failure"
82+
}
83+
]
84+
}
85+
]
86+
}
87+
```
88+
The evaluation context for the Javascript code includes the `composer` object implicitly defined as:
89+
```javascript
90+
composer = require('@ibm-functions/composer')
91+
```
92+
93+
## Deployment
94+
95+
The `--deploy` option makes it possible to deploy a composition (Javascript or JSON) given the desired name for the composition:
96+
```
97+
compose demo.js --deploy demo
98+
```
99+
```
100+
ok: created actions /_/authenticate,/_/success,/_/failure,/_/demo
101+
```
102+
Or:
103+
```
104+
compose demo.js > demo.json
105+
compose demo.json --deploy demo
106+
```
107+
```
108+
ok: created actions /_/authenticate,/_/success,/_/failure,/_/demo
109+
```
110+
The `compose` command synthesizes and deploys a conductor action that implements the
111+
composition with the given name. It also deploys the composed actions if
112+
definitions are provided for them as part of the composition.
113+
114+
The `compose` command outputs the list of deployed actions or an error result. If an error occurs during deployment, the state of the various actions is unknown.
115+
116+
The `compose` command deletes the deployed actions before recreating them if necessary. As a result, default parameters, limits, and annotations on preexisting actions are lost.
117+
118+
### Configuration
119+
120+
Like the OpenWhisk CLI, the `compose` command supports the following flags for specifying the OpenWhisk deployment to use:
121+
```
122+
--apihost HOST API HOST
123+
-u, --auth KEY authorization KEY
124+
-i, --insecure bypass certificate checking
125+
```
126+
If the `--apihost` flag is absent, the environment variable `__OW_API_HOST` is used in its place. If neither is available, the `compose` command attempts to obtain the api host from the whisk property file for the current user.
127+
128+
If the `--auth` flag is absent, the environment variable `__OW_API_KEY` is used in its place. If neither is available, the `compose` command attempts to obtain the authorization key information from the whisk property file for the current user.
129+
130+
The default path for the whisk property file is `$HOME/.wskprops`. It can be altered by setting the `WSK_CONFIG_FILE` environment variable.
131+
132+
## Code generation
133+
134+
The `compose` command returns the code of the conductor action for the composition (Javascript or JSON) when invoked with the `--encode` option:
135+
```
136+
compose demo.js --encode
137+
```
138+
```javascript
139+
const main=(function init(e,t){function r(e,t){return e.slice(-1)[0].next=1,e.push(...t),e}const a=function e(t,a=""){if(Array.isArray(t))return 0===t.length?[{type:"pass",path:a}]:t.map((t,r)=>e(t,a+"["+r+"]")).reduce(r);const n=t.options||{};switch(t.type){case"action":return[{type:"action",name:t.name,path:a}];case"function":return[{type:"function",exec:t.exec,path:a}];case"literal":return[{type:"literal",value:t.value,path:a}];case"finally":var s=e(t.body,a+".body");const l=e(t.finalizer,a+".finalizer");return(o=[[{type:"try",path:a}],s,[{type:"exit",path:a}],l].reduce(r))[0].catch=o.length-l.length,o;case"let":return s=e(t.body,a+".body"),[[{type:"let",let:t.declarations,path:a}],s,[{type:"exit",path:a}]].reduce(r);case"retain":s=e(t.body,a+".body");var o=[[{type:"push",path:a}],s,[{type:"pop",collect:!0,path:a}]].reduce(r);return n.field&&(o[0].field=n.field),o;case"try":s=e(t.body,a+".body");const h=r(e(t.handler,a+".handler"),[{type:"pass",path:a}]);return(o=[[{type:"try",path:a}],s].reduce(r))[0].catch=o.length,o.slice(-1)[0].next=h.length,o.push(...h),o;case"if":var p=e(t.consequent,a+".consequent"),c=r(e(t.alternate,a+".alternate"),[{type:"pass",path:a}]);return n.nosave||(p=r([{type:"pop",path:a}],p)),n.nosave||(c=r([{type:"pop",path:a}],c)),o=r(e(t.test,a+".test"),[{type:"choice",then:1,else:p.length+1,path:a}]),n.nosave||(o=r([{type:"push",path:a}],o)),p.slice(-1)[0].next=c.length,o.push(...p),o.push(...c),o;case"while":return p=e(t.body,a+".body"),c=[{type:"pass",path:a}],n.nosave||(p=r([{type:"pop",path:a}],p)),n.nosave||(c=r([{type:"pop",path:a}],c)),o=r(e(t.test,a+".test"),[{type:"choice",then:1,else:p.length+1,path:a}]),n.nosave||(o=r([{type:"push",path:a}],o)),p.slice(-1)[0].next=1-o.length-p.length,o.push(...p),o.push(...c),o;case"dowhile":var i=e(t.test,a+".test");return n.nosave||(i=r([{type:"push",path:a}],i)),o=[e(t.body,a+".body"),i,[{type:"choice",then:1,else:2,path:a}]].reduce(r),n.nosave?(o.slice(-1)[0].then=1-o.length,o.slice(-1)[0].else=1):(o.push({type:"pop",path:a}),o.slice(-1)[0].next=1-o.length),c=[{type:"pass",path:a}],n.nosave||(c=r([{type:"pop",path:a}],c)),o.push(...c),o}}(t),n=e=>"object"==typeof e&&null!==e&&!Array.isArray(e),s=e=>Promise.reject({code:400,error:e}),o=e=>Promise.reject((e=>({code:"number"==typeof e.code&&e.code||500,error:"string"==typeof e.error&&e.error||e.message||"string"==typeof e&&e||"An internal error occurred"}))(e));return t=>Promise.resolve().then(()=>(function(t){let r=0,p=[];if(void 0!==t.$resume){if(!n(t.$resume))return s("The type of optional $resume parameter must be object");if(r=t.$resume.state,p=t.$resume.stack,void 0!==r&&"number"!=typeof r)return s("The type of optional $resume.state parameter must be number");if(!Array.isArray(p))return s("The type of $resume.stack must be an array");delete t.$resume,c()}function c(){if(n(t)||(t={value:t}),void 0!==t.error)for(t={error:t.error},r=void 0;p.length>0&&"number"!=typeof(r=p.shift().catch););}function i(r){function a(e,t){const r=p.find(t=>void 0!==t.let&&void 0!==t.let[e]);void 0!==r&&(r.let[e]=JSON.parse(JSON.stringify(t)))}const n=p.reduceRight((e,t)=>"object"==typeof t.let?Object.assign(e,t.let):e,{});let s="(function(){try{";for(const e in n)s+=`var ${e}=arguments[1]['${e}'];`;s+=`return eval((${r}))(arguments[0])}finally{`;for(const e in n)s+=`arguments[1]['${e}']=${e};`;s+="}})";try{return e(s)(t,n)}finally{for(const e in n)a(e,n[e])}}for(;;){if(void 0===r)return console.log("Entering final state"),console.log(JSON.stringify(t)),t.error?t:{params:t};const e=a[r];console.log(`Entering state ${r} at path fsm${e.path}`);const n=r;switch(r=void 0===e.next?void 0:n+e.next,e.type){case"choice":r=n+(t.value?e.then:e.else);break;case"try":p.unshift({catch:n+e.catch});break;case"let":p.unshift({let:JSON.parse(JSON.stringify(e.let))});break;case"exit":if(0===p.length)return o(`State ${n} attempted to pop from an empty stack`);p.shift();break;case"push":p.unshift(JSON.parse(JSON.stringify({params:e.field?t[e.field]:t})));break;case"pop":if(0===p.length)return o(`State ${n} attempted to pop from an empty stack`);t=e.collect?{params:p.shift().params,result:t}:p.shift().params;break;case"action":return{action:e.name,params:t,state:{$resume:{state:r,stack:p}}};case"literal":t=JSON.parse(JSON.stringify(e.value)),c();break;case"function":let a;try{a=i(e.exec.code)}catch(e){console.error(e),a={error:`An exception was caught at state ${n} (see log for details)`}}"function"==typeof a&&(a={error:`State ${n} evaluated to a function`}),t=JSON.parse(JSON.stringify(void 0===a?t:a)),c();break;case"pass":c();break;default:return o(`State ${n} has an unknown type`)}}})(t)).catch(o)})(eval,[{"type":"if","test":[{"type":"action","name":"/_/authenticate"}],"consequent":[{"type":"action","name":"/_/success"}],"alternate":[{"type":"action","name":"/_/failure"}]}])
140+
```
141+
This code may be deployed using the OpenWhisk CLI:
142+
```
143+
compose demo.js > demo-conductor.js
144+
wsk action create demo demo-conductor.js -a conductor true
145+
```
146+
```
147+
ok: created action demo
148+
```
149+
The conductor action code does not include definitions for nested actions or compositions.

0 commit comments

Comments
 (0)