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

Commit 107a163

Browse files
nbaudisreggeenr
andauthored
Feature/Enable composer to deploy to IAM-based IBM Cloud Functions namespaces (#70)
* first draft for enabling IAM * refactored iam-specific code fragments; adjusted gitignore; added prettier * several code changes and improvements * added correct license header; tweaked travis build config Co-authored-by: Enrico Regge <[email protected]>
1 parent 5eb3316 commit 107a163

File tree

7 files changed

+175
-21
lines changed

7 files changed

+175
-21
lines changed

Diff for: .gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
node_modules
22
openwhisk
3+
4+
package-lock.json
5+
demo.json

Diff for: .prettierrc.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
module.exports = {
19+
singleQuote: true,
20+
printWidth: 120,
21+
semi: false,
22+
trailingComma: 'none'
23+
};

Diff for: .travis.yml

+2-9
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,14 @@
1717

1818
language: node_js
1919
node_js:
20-
- 10
20+
- 14
2121
services:
2222
- docker
23-
24-
notifications:
25-
email: false
26-
webhooks:
27-
urls:
28-
# travis2slack webhook to enable DMs on openwhisk-team.slack.com to PR authors with TravisCI results
29-
secure: "bv5wMNLWLTTwn02xUNGqOSVgDfU2ZzZ6dPHjeoLdkaCkeZYmT15FmXQ7GQ78Cql293Om35nZHc2t54Kxzyq+BWkfKZXo6oF60wxZjaHunipc/+F3CMi+QjFEU7IwMoGom9HFZsTfoYeVPcCaXGKuVD/DMZHfE9TI7kM3/XsDJO+DbHDMxdE7OZUx1lfSNwCIXQrGdQ1w2S4QDnfVbQwmzZXvdzYr0RdVp8jCC4TD5GKr0rP3YemWSlUOO91heAJL217EUlbUr2jcr90x/GRjcPOocX7TobSdxiynk64cKJOODKiacwhHMBICSEKOiMvUelah9hFmmq40wm/oqnJkBzD7SoHCr/P47WjHu8GVW1obGzCldPHKAxTHc0zBS31H10nwYLV7Fo/z7qM0AEwikT7oqHizZVf1U1ywggrBAFrSb/LJBgdSPa4iXPr6t1phUYc+dZX20a4deNyFiHdFaprMNhSiOY6/NgD1Z+4wTFUzVTW96FdinfnjGsdwV46WPP7a7v3wf+bag6+pnT0i6uLFXwUvBmyuvvRYFGKf8UsqIZ2aZKqHZzOLofgdnd8I/0HRPk42aq7GJ1pyA7MXpVnH7MLWyLKk1nNXrsWTdsn8INwox9JaQ3JiefjOovnPKaEWBa6qTfQALkk9uj2gLNsZvqTx3iKSQn747VFGlY0="
30-
3123
env:
3224
global:
3325
- __OW_IGNORE_CERTS=true
3426
- REDIS=redis://172.17.0.1:6379
27+
- IC_FN_CONFIG_FILE=./test/cf-plugin-config.json
3528
before_install:
3629
- ./travis/scancode.sh
3730
before_script:

Diff for: bin/deploy.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ if (argv._.length !== 2 || path.extname(argv._[1]) !== '.json') {
4646
console.error(' -A, --annotation-file KEY=FILE add KEY annotation with FILE content')
4747
console.error(' --apihost HOST API HOST')
4848
console.error(' --apiversion VERSION API VERSION')
49-
console.error(' --basic force basic authentication')
49+
console.error(' --basic force basic authentication. Note: this option can only be chosen for CF-based namespaces')
5050
console.error(' --bearer force bearer token authentication')
5151
console.error(' -i, --insecure bypass certificate checking')
5252
console.error(' --kind KIND the KIND of the conductor action runtime')

Diff for: client.js

+69-11
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,21 @@
2222
const conductor = require('./conductor')
2323
const fs = require('fs')
2424
const openwhisk = require('openwhisk')
25+
const ibmcloudUtils = require('./ibmcloud-utils')
2526
const os = require('os')
2627
const path = require('path')
2728

29+
const NS_TYPE_CF = 'CF'
30+
const NS_TYPE_IAM = 'IAM'
31+
2832
// return enhanced openwhisk client capable of deploying compositions
2933
module.exports = function (options, basic, bearer) {
3034
// try to extract apihost and key first from whisk property file file and then from process.env
3135
let apihost
3236
let apiversion
3337
let apikey
3438
let ignorecerts
35-
let namespace = '_'
39+
let namespace = ibmcloudUtils.getNamespaceId()
3640
let token
3741
let authHandler
3842

@@ -49,31 +53,77 @@ module.exports = function (options, basic, bearer) {
4953
apiversion = parts[1]
5054
} else if (parts[0] === 'AUTH') {
5155
apikey = parts[1]
52-
} else if (parts[0] === 'NAMESPACE') {
53-
namespace = parts[1]
5456
} else if (parts[0] === 'APIGW_ACCESS_TOKEN') {
5557
token = parts[1]
5658
}
5759
}
5860
}
59-
} catch (error) { }
61+
} catch (error) {}
6062

6163
if (process.env.__OW_API_HOST) apihost = process.env.__OW_API_HOST
6264
if (process.env.__OW_API_KEY) apikey = process.env.__OW_API_KEY
6365
if (process.env.__OW_NAMESPACE) namespace = process.env.__OW_NAMESPACE
6466
if (process.env.__OW_IGNORE_CERTS) ignorecerts = process.env.__OW_IGNORE_CERTS
6567
if (process.env.__OW_APIGW_TOKEN) token = process.env.__OW_APIGW_TOKEN
6668

67-
if (bearer || (!basic && namespace !== '_')) {
68-
// switch from basic auth to bearer token
69+
// check whether there is a CLI argument that overrides the API key retrieved from the config or env
70+
if (options && options.api_key) apikey = options.api_key
71+
72+
// retrieve the namespace type
73+
// we need to apply different authentication strategies for CF and IAM
74+
const namespaceType = ibmcloudUtils.getNamespaceType()
75+
76+
//
77+
// check for IAM-based namespaces, first
78+
if (namespaceType === NS_TYPE_IAM) {
79+
// for authentication, we'll use the user IAM access token
80+
const iamToken = ibmcloudUtils.getIamAuthHeader()
81+
82+
// define an appropriate authentication handler for IAM
6983
authHandler = {
7084
getAuthHeader: () => {
71-
return Promise.resolve(`Bearer ${token}`)
85+
// use bearer token for IAM authentication
86+
return Promise.resolve(iamToken)
7287
}
7388
}
89+
90+
// ignore the API key value, in case of an IAM-based namespace
91+
apikey = undefined
92+
93+
//
94+
// in case of CF-based namespaces, the user can opt-in for using bearer token authentication instead of using the default basic authentication
95+
} else if (namespaceType === NS_TYPE_CF) {
96+
if (bearer && !basic) {
97+
// switch from basic auth to bearer token
98+
authHandler = {
99+
getAuthHeader: () => {
100+
return Promise.resolve(`Bearer ${token}`)
101+
}
102+
}
103+
} else if (apikey) {
104+
// use basic auth
105+
authHandler = {
106+
getAuthHeader: () => {
107+
const apiKeyBase64 = Buffer.from(apikey).toString('base64')
108+
return Promise.resolve(`Basic ${apiKeyBase64}`)
109+
}
110+
}
111+
}
112+
}
113+
114+
const namespaceDisplayName = namespace || '_'
115+
116+
console.log(`deploying to ${namespaceType}-based namespace '${namespaceDisplayName}' ...`)
117+
118+
if (!authHandler) {
119+
throw new Error(
120+
`Failed to determine the authentication strategy for the ${namespaceType}-based namespace '${namespaceDisplayName}'`
121+
)
74122
}
75123

76-
const wsk = openwhisk(Object.assign({ apihost, apiversion, api_key: apikey, auth_handler: authHandler, namespace, ignore_certs: ignorecerts }, options))
124+
const wsk = openwhisk(
125+
Object.assign({ apihost, apiversion, auth_handler: authHandler, namespace, ignore_certs: ignorecerts }, options)
126+
)
77127
wsk.compositions = new Compositions(wsk)
78128
return wsk
79129
}
@@ -90,9 +140,17 @@ class Compositions {
90140
return Object.assign({}, action, httpOptions)
91141
}
92142

93-
const actions = (composition.actions || []).concat(conductor.generate(composition, debug, kind, timeout, memory, logs)).map(addHttpOptions)
94-
return actions.reduce((promise, action) => promise.then(() => overwrite && this.actions.delete(action).catch(() => { }))
95-
.then(() => this.actions.create(action)), Promise.resolve())
143+
const actions = (composition.actions || [])
144+
.concat(conductor.generate(composition, debug, kind, timeout, memory, logs))
145+
.map(addHttpOptions)
146+
return actions
147+
.reduce(
148+
(promise, action) =>
149+
promise
150+
.then(() => overwrite && this.actions.delete(action).catch(() => {}))
151+
.then(() => this.actions.create(action)),
152+
Promise.resolve()
153+
)
96154
.then(() => actions)
97155
}
98156
}

Diff for: ibmcloud-utils.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
'use strict'
18+
19+
const fs = require('fs')
20+
const os = require('os')
21+
const path = require('path')
22+
23+
const getCloudFunctionsConfig = () => {
24+
try {
25+
// read the ibm cloud functions cli config file
26+
const ibmCloudFunctionsPropsPath =
27+
process.env.IC_FN_CONFIG_FILE || path.join(os.homedir(), '.bluemix/plugins/cloud-functions/config.json')
28+
const ibmCloudFunctionsConfig = JSON.parse(fs.readFileSync(ibmCloudFunctionsPropsPath, { encoding: 'utf8' }))
29+
30+
return ibmCloudFunctionsConfig
31+
} catch (error) {
32+
console.error('Could not open ibmcloud functions plugin config')
33+
throw error
34+
}
35+
}
36+
37+
const getNamespaceType = () => {
38+
return getCloudFunctionsConfig().WskCliNamespaceMode
39+
}
40+
41+
const getNamespaceId = () => {
42+
return getCloudFunctionsConfig().WskCliNamespaceId
43+
}
44+
45+
/**
46+
* return a Apache OpenWhisk Client SDK for JavaScript compliant authentication header token,
47+
* which can be used within a custom authentication handler
48+
* see https://github.com/apache/openwhisk-client-js#using-3rd-party-authentication-handler
49+
* for further details
50+
*/
51+
const getIamAuthHeader = () => {
52+
// for authentication, we'll use the user IAM access token
53+
let iamToken
54+
try {
55+
// read the IAM Access token from the ibm cloud config file
56+
const ibmCloudPropsPath = process.env.IC_CONFIG_FILE || path.join(os.homedir(), '.bluemix/config.json')
57+
const ibmCloudConfig = JSON.parse(fs.readFileSync(ibmCloudPropsPath, { encoding: 'utf8' }))
58+
59+
iamToken = ibmCloudConfig.IAMToken
60+
} catch (error) {
61+
console.error('Could not open ibmcloud config')
62+
throw error
63+
}
64+
65+
// return an object that provides a getAuthHeader function to comply with the authentication handler interface
66+
// required by OpenWhisk Client SDK for JavaScript
67+
return iamToken
68+
}
69+
70+
module.exports = {
71+
getIamAuthHeader,
72+
getNamespaceType,
73+
getNamespaceId
74+
}

Diff for: test/cf-plugin-config.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"WskCliNamespaceMode": "CF"
3+
}

0 commit comments

Comments
 (0)