support openwhisk credentials stored in .env file and Adobe I/O Runtime variable names (#73)
* in verbose mode log where openwhisk credentials are picked up from
* AIO_* vars should take precedence over ~/.wskprops (and WSK_CONFIG_FILE)
diff --git a/.gitignore b/.gitignore
index 73ce74b..aa36988 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,5 @@
build
coverage.lcov
test-results
+
+.env
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index a39ebd8..ec6eb41 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -824,6 +824,11 @@
"esutils": "^2.0.2"
}
},
+ "dotenv": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
+ "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
+ },
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -2353,6 +2358,12 @@
}
}
},
+ "mock-fs": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.12.0.tgz",
+ "integrity": "sha512-/P/HtrlvBxY4o/PzXY9cCNBrdylDNxg7gnrv2sMNxj+UJ2m8jSpl0/A6fuJeNAWr99ZvGWH8XCbE0vmnM5KupQ==",
+ "dev": true
+ },
"mock-require": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz",
diff --git a/package.json b/package.json
index f53afc3..cac0c22 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"clone": "^2.1.2",
"debug": "^4.1.1",
"dockerode": "^3.2.0",
+ "dotenv": "^8.2.0",
"fetch-retry": "^3.1.0",
"fs-extra": "^8.1.0",
"get-port": "^5.1.1",
@@ -69,6 +70,7 @@
"eslint-plugin-mocha": "^6.3.0",
"mocha": "^7.1.0",
"mocha-multi-reporters": "^1.1.7",
+ "mock-fs": "^4.12.0",
"mock-require": "^3.0.3",
"nock": "^12.0.2",
"nyc": "^15.0.0",
diff --git a/src/debugger.js b/src/debugger.js
index f2e1596..40a829a 100644
--- a/src/debugger.js
+++ b/src/debugger.js
@@ -55,7 +55,7 @@
this.wskProps = wskprops.get();
if (Object.keys(this.wskProps).length === 0) {
- log.error(`Error: Missing openwhisk credentials. Found no ~/.wskprops file or WSK_* environment variable.`);
+ log.error(`Error: Missing openwhisk credentials. Found no ~/.wskprops or .env file or WSK_* environment variable.`);
process.exit(1);
}
if (argv.ignoreCerts) {
diff --git a/src/wskprops.js b/src/wskprops.js
index 4f4c8fb..9d6ac49 100644
--- a/src/wskprops.js
+++ b/src/wskprops.js
@@ -21,20 +21,24 @@
'use strict';
+const log = require('./log');
+
+const dotenv = require('dotenv');
const path = require('path');
const fs = require('fs-extra');
const ENV_PARAMS = ['OW_APIHOST', 'OW_AUTH', 'OW_NAMESPACE', 'OW_APIGW_ACCESS_TOKEN'];
-function getWskPropsFile() {
+function getWskPropsUserHomeFile() {
const Home = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
- return process.env.WSK_CONFIG_FILE || path.format({ dir: Home, base: '.wskprops' });
+ return path.format({ dir: Home, base: '.wskprops' });
}
function readWskPropsFile() {
- const wskFilePath = getWskPropsFile();
+ const wskFilePath = process.env.WSK_CONFIG_FILE || getWskPropsUserHomeFile();
if (fs.existsSync(wskFilePath)) {
+ log.verbose(`Using openwhisk credentials from ${wskFilePath}${process.env.WSK_CONFIG_FILE ? " (set by WSK_CONFIG_FILE)" : ""}`);
return fs.readFileSync(wskFilePath, 'utf8');
} else {
return null;
@@ -55,17 +59,38 @@
return wskProps;
}
+function getAioEnvProps() {
+ const envProps = {};
+ // do first, as OW_* ones later shall take precedence
+ if (process.env.AIO_runtime_auth) {
+ envProps.apihost = "https://adobeioruntime.net";
+ envProps.auth = process.env.AIO_runtime_auth;
+ envProps.namespace = process.env.AIO_runtime_namespace;
+ log.verbose(`Using openwhisk credential from AIO_runtime_auth environment variable`);
+ }
+ return envProps;
+}
+
function getWskEnvProps() {
const envProps = {};
ENV_PARAMS.forEach((envName) => {
- if (process.env[envName]) envProps[envName.slice(3).toLowerCase()] = process.env[envName];
+ if (process.env[envName]) {
+ const key = envName.slice(3).toLowerCase();
+ envProps[key] = process.env[envName];
+ if (key === "auth" || key === "api_key") {
+ log.verbose(`Using openwhisk credential from ${envName} environment variable`);
+ }
+ }
});
return envProps;
}
module.exports = {
get() {
- const props = Object.assign(getWskProps(), getWskEnvProps());
+ // load .env file if present
+ dotenv.config();
+
+ const props = Object.assign(getWskProps(), getAioEnvProps(), getWskEnvProps());
if (props.auth) {
props.api_key = props.auth;
delete props.auth;
diff --git a/test/test.js b/test/test.js
index 3d501ea..ce8904d 100644
--- a/test/test.js
+++ b/test/test.js
@@ -41,7 +41,11 @@
}
async function beforeEach() {
+ delete process.env.OW_AUTH;
+ delete process.env.OW_NAMESPACE;
+ delete process.env.OW_APIHOST;
process.env.WSK_CONFIG_FILE = path.join(process.cwd(), "test/wskprops");
+
openwhisk = nock(FAKE_OPENWHISK_SERVER);
mockOpenwhiskSwagger(openwhisk);
diff --git a/test/wskprops.test.js b/test/wskprops.test.js
new file mode 100644
index 0000000..8f81a4b
--- /dev/null
+++ b/test/wskprops.test.js
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/* eslint-env mocha */
+
+'use strict';
+
+const wskprops = require('../src/wskprops');
+const assert = require('assert');
+const mockFs = require('mock-fs');
+const os = require('os');
+
+function resetEnvVars() {
+ delete process.env.OW_AUTH;
+ delete process.env.OW_NAMESPACE;
+ delete process.env.OW_APIHOST;
+ delete process.env.WSK_CONFIG_FILE;
+ delete process.env.AIO_runtime_auth;
+ delete process.env.AIO_runtime_namespace;
+}
+
+describe('wskprops', function() {
+
+ beforeEach(function() {
+ resetEnvVars();
+ });
+
+ afterEach(function() {
+ resetEnvVars();
+ mockFs.restore();
+ });
+
+ it("should read WSK_CONFIG_FILE", async function() {
+ process.env.WSK_CONFIG_FILE = "some/wskprops";
+ mockFs({
+ "some/wskprops":
+`APIHOST=https://some-wskprops
+NAMESPACE=some-wskprops-namespace
+AUTH=some-wskprops-auth`
+ });
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://some-wskprops");
+ assert.strictEqual(props.namespace, "some-wskprops-namespace");
+ assert.strictEqual(props.api_key, "some-wskprops-auth");
+ });
+
+ it("should read ~/.wskprops", async function() {
+ mockFs({
+ [`${os.homedir()}/.wskprops`]:
+`APIHOST=https://home-wskprops
+NAMESPACE=home-wskprops-namespace
+AUTH=home-wskprops-auth`
+ });
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://home-wskprops");
+ assert.strictEqual(props.namespace, "home-wskprops-namespace");
+ assert.strictEqual(props.api_key, "home-wskprops-auth");
+ });
+
+ it("should read OW_* vars", async function() {
+ process.env.OW_APIHOST = "https://ow_apihost";
+ process.env.OW_NAMESPACE = "ow_namespace";
+ process.env.OW_AUTH = "ow_auth";
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://ow_apihost");
+ assert.strictEqual(props.namespace, "ow_namespace");
+ assert.strictEqual(props.api_key, "ow_auth");
+ });
+
+ it("should give OW_* vars precedence over WSK_CONFIG_FILE", async function() {
+ process.env.WSK_CONFIG_FILE = "some/wskprops";
+
+ process.env.OW_APIHOST = "https://ow_apihost";
+ process.env.OW_NAMESPACE = "ow_namespace";
+ process.env.OW_AUTH = "ow_auth";
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://ow_apihost");
+ assert.strictEqual(props.namespace, "ow_namespace");
+ assert.strictEqual(props.api_key, "ow_auth");
+ });
+
+ it("should read AIO_* vars", async function() {
+ process.env.AIO_runtime_namespace = "aio_namespace";
+ process.env.AIO_runtime_auth = "aio_auth";
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://adobeioruntime.net");
+ assert.strictEqual(props.namespace, "aio_namespace");
+ assert.strictEqual(props.api_key, "aio_auth");
+ });
+
+ it("should give AIO_* vars precedence over WSK_CONFIG_FILE", async function() {
+ process.env.WSK_CONFIG_FILE = "some/wskprops";
+ process.env.AIO_runtime_namespace = "aio_namespace";
+ process.env.AIO_runtime_auth = "aio_auth";
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://adobeioruntime.net");
+ assert.strictEqual(props.namespace, "aio_namespace");
+ assert.strictEqual(props.api_key, "aio_auth");
+ });
+
+ it("should give AIO_* vars precedence over ~/.wskprops", async function() {
+ mockFs({
+ [`${os.homedir()}/.wskprops`]:
+`APIHOST=https://home-wskprops
+NAMESPACE=home-wskprops-namespace
+AUTH=home-wskprops-auth`
+ });
+
+ process.env.AIO_runtime_namespace = "aio_namespace";
+ process.env.AIO_runtime_auth = "aio_auth";
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://adobeioruntime.net");
+ assert.strictEqual(props.namespace, "aio_namespace");
+ assert.strictEqual(props.api_key, "aio_auth");
+ });
+
+ it("should give OW_* precedence over AIO_* vars", async function() {
+ process.env.AIO_runtime_namespace = "aio_namespace";
+ process.env.AIO_runtime_auth = "aio_auth";
+
+ process.env.OW_APIHOST = "https://ow_apihost";
+ process.env.OW_NAMESPACE = "ow_namespace";
+ process.env.OW_AUTH = "ow_auth";
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://ow_apihost");
+ assert.strictEqual(props.namespace, "ow_namespace");
+ assert.strictEqual(props.api_key, "ow_auth");
+ });
+
+ it("should read AIO_* from .env", async function() {
+ mockFs({
+ ".env":
+`AIO_runtime_namespace=aio_namespace
+AIO_runtime_auth=aio_auth`
+ });
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://adobeioruntime.net");
+ assert.strictEqual(props.namespace, "aio_namespace");
+ assert.strictEqual(props.api_key, "aio_auth");
+ });
+
+ it("should read WSK_CONFIG_FILE from .env", async function() {
+ mockFs({
+ ".env": "WSK_CONFIG_FILE=some/wskprops",
+ "some/wskprops":
+`APIHOST=https://some-wskprops
+NAMESPACE=some-wskprops-namespace
+AUTH=some-wskprops-auth`
+ });
+
+ const props = wskprops.get();
+ assert.strictEqual(props.apihost, "https://some-wskprops");
+ assert.strictEqual(props.namespace, "some-wskprops-namespace");
+ assert.strictEqual(props.api_key, "some-wskprops-auth");
+ });
+
+});