Skip to content

Commit

Permalink
Merge pull request #35 from ibm-wch/rel4.0.1
Browse files Browse the repository at this point in the history
development changes for 4.0.1 release
  • Loading branch information
Kevin Tapperson authored Dec 3, 2018
2 parents 5f5e072 + f81659e commit e6da7ee
Show file tree
Hide file tree
Showing 20 changed files with 485 additions and 464 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

### 4.0 changes since 3.2

- Add support for storing user credentials in the operating system key manager (Windows and Mac OS only).
- Increment minimum Node.js version to 8.x.

### 3.2 changes since 3.1

- Add full support for push, pull, list, delete, and compare of multiple site definitions and the associated pages. The site-context option can be used to specify a site for an operation.
Expand Down
80 changes: 60 additions & 20 deletions CLI/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const Q = require("q");
const utils = ToolsApi.getUtils();
const options = ToolsApi.getOptions();
const prompt = require("prompt");
const creds = require("@ibm-wch-sdk/cli-credentials");
const i18n = utils.getI18N(__dirname, ".json", "en");

class InitCommand extends BaseCommand {
Expand All @@ -45,38 +46,49 @@ class InitCommand extends BaseCommand {
* @private
*/
getInitPromptSchema (context) {
const user = this.getCommandLineOption("user");
const url = this.getCommandLineOption("url");
const user = this.getCommandLineOption("user");
const password = this.getCommandLineOption("password");
const storeCredentials = this.getCommandLineOption("storeCredentials") && (process.platform === "darwin" || process.platform === "win32");

// If all options were specified on the command line, then a prompt schema is not required.
if (user && url) {
if (url && user && (password || !storeCredentials)) {
return null;
}

const schema = {};

// If the url option was not specified on the command line, add it to the prompt schema.
if (!url) {
const defaultUrl = options.getProperty(context, "x-ibm-dx-tenant-base-url");

schema["x-ibm-dx-tenant-base-url"] = {
description: i18n.__('cli_init_url'),
default: defaultUrl ? defaultUrl : "",
required: storeCredentials
};
}

// If the user option was not specified on the command line, add it to the prompt schema.
if (!user) {
const defaultUser = options.getProperty(context, "username");

schema["username"] = {
description: i18n.__('cli_init_user_name'),
default: defaultUser ? defaultUser : "",
required: false
}
required: storeCredentials
};
}

// If the url option was not specified on the command line, add it to the prompt schema.
if (!url) {
const defaultUrl = options.getProperty(context, "x-ibm-dx-tenant-base-url");

schema["x-ibm-dx-tenant-base-url"] = {
description: i18n.__('cli_init_url'),
default: defaultUrl ? defaultUrl : "",
required: false
}
// If the password option was not specified and the store-credentials option was, add password to the prompt schema.
if (!password && storeCredentials) {
schema["password"] = {
description: user ? i18n.__('cli_base_password_for_user', {username: user}) : i18n.__('cli_base_password'),
default: "",
hidden: true,
required: storeCredentials
};
}

return schema;
}

Expand All @@ -103,15 +115,20 @@ class InitCommand extends BaseCommand {
* @private
*/
updateOptions (context, promptedOptions) {
const self = this;

// The new options to be persisted should include all of the values entered via prompt.
const newOptions = promptedOptions || {};

// Add any options specified on the command line to the prompted options.
if (this.getCommandLineOption("url")) {
newOptions["x-ibm-dx-tenant-base-url"] = this.getCommandLineOption("url");
}
if (this.getCommandLineOption("user")) {
newOptions["username"] = this.getCommandLineOption("user");
}
if (this.getCommandLineOption("url")) {
newOptions["x-ibm-dx-tenant-base-url"] = this.getCommandLineOption("url");
if (this.getCommandLineOption("password")) {
newOptions["password"] = this.getCommandLineOption("password");
}

this.checkForNumericOption("retryMaxAttempts", "retryMaxAttempts", newOptions);
Expand All @@ -123,8 +140,22 @@ class InitCommand extends BaseCommand {
// Update the appropriate options file and display the result.
try {
newOptions["x-ibm-dx-tenant-base-url"] = newOptions["x-ibm-dx-tenant-base-url"].trim();
// Remove the password before saving.
const password = newOptions["password"];
delete newOptions["password"];
const optionsFilePath = options.setOptions(context, newOptions, true, this.getCommandLineOption("dir"));
this.getProgram().successMessage(i18n.__('cli_init_success', {"path": optionsFilePath}));

const storeCredentials = this.getCommandLineOption("storeCredentials");
if (storeCredentials && (process.platform === "darwin" || process.platform === "win32")) {
creds.wchStoreCredentials({baseUrl: newOptions["x-ibm-dx-tenant-base-url"], username: newOptions["username"], password: password})
.then(function () {
self.getProgram().successMessage(i18n.__('cli_init_store_creds_success', {"url": newOptions["x-ibm-dx-tenant-base-url"]}));
})
.catch(function (err) {
self.getProgram().errorMessage(i18n.__('cli_init_store_creds_error', {"url": newOptions["x-ibm-dx-tenant-base-url"], message: err.toString()}));
});
}
} catch (e) {
this.getProgram().errorMessage(i18n.__('cli_init_error', {message: e.toString()}));
}
Expand All @@ -145,9 +176,17 @@ class InitCommand extends BaseCommand {
const toolsApi = new ToolsApi();
const context = toolsApi.getContext();

// Handle the various validation checks.
if (this.getCommandLineOption("storeCredentials") && !(process.platform === "darwin" || process.platform === "win32")) {
// --store-credentials is not supported on this OS.
this.errorMessage(i18n.__("cli_init_store_creds_not_supported", {"platform": process.platform}));
this.resetCommandLineOptions();
return;
}

// Extend the options using the options file in the specified directory.
const dir = this.getCommandLineOption("dir");
if (dir ) {
if (dir) {
options.extendOptionsFromDirectory(context, dir);
}

Expand Down Expand Up @@ -199,8 +238,7 @@ class InitCommand extends BaseCommand {
* isn't terminated and these values need to be reset.
*/
resetCommandLineOptions () {
this.setCommandLineOption("url", undefined);
this.setCommandLineOption("dir", undefined);
this.setCommandLineOption("storeCredentials", undefined);
this.setCommandLineOption("retryMaxAttempts", undefined);
this.setCommandLineOption("retryMinTime", undefined);
this.setCommandLineOption("retryMaxTime", undefined);
Expand All @@ -218,8 +256,10 @@ function initCommand (program) {
program
.command('init')
.description(i18n.__('cli_init_description', {"product_name": utils.ProductName}))
.option('--user <user>', i18n.__('cli_init_opt_user_name'))
.option('--url <url>', i18n.__('cli_init_opt_url', {"product_name": utils.ProductName}))
.option('--user <user>', i18n.__('cli_init_opt_user_name'))
.option('--password <password>', i18n.__('cli_opt_password'))
.option('--store-credentials', i18n.__('cli_init_opt_store_credentials'))
.option('--dir <directory>', i18n.__('cli_init_opt_dir'))
.option('--retry-max-attempts <n>', i18n.__('cli_init_opt_retry_max_attempts'))
.option('--retry-min-time <n>', i18n.__('cli_init_opt_retry_min_time'), parseInt)
Expand Down
88 changes: 59 additions & 29 deletions CLI/lib/baseCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const i18n = utils.getI18N(__dirname, ".json", "en");
const prompt = require("prompt");
const Q = require("q");
const ProductVersion = require("../package.json").version;
const creds = require("@ibm-wch-sdk/cli-credentials");
const cliLog = "cli" + " " + ProductVersion;

class BaseCommand {
Expand Down Expand Up @@ -692,6 +693,7 @@ class BaseCommand {
*/
handleAuthenticationOptions (context) {
const defer = Q.defer();
const self = this;
let schemaInput;

// Get the user name.
Expand Down Expand Up @@ -747,39 +749,67 @@ class BaseCommand {
};
}

if (schemaInput) {
// Prompt for the user name and/or password.
prompt.message = '';
prompt.delimiter = ' ';
prompt.start();
const schemaProps = {properties: schemaInput};
const self = this;
prompt.get(schemaProps, function (err, result) {
if (err) {
// FUTURE How do we get an error, and should we reject if we do?
console.warn(err.message);
defer.resolve();
} else {
if (result.username) {
// Add the specified user name to the API options.
username = result.username;
self.setApiOption("username", username);
}
if (result.password) {
// Add the specified password to the API options.
password = result.password;
self.setApiOption("password", password);
const keystoreDeferred = Q.defer();
// If we don't have a password, try the OS keystore.
if (schemaInput && schemaInput.password) {
const baseUrl = options.getRelevantOption(context, this.getApiOptions(), "x-ibm-dx-tenant-base-url");
creds.wchGetCredentials(baseUrl)
.then(function (osCredentials) {
if (osCredentials && osCredentials.username && osCredentials.password) {
self.getLogger().info(i18n.__("cli_found_credentials", {"url": baseUrl, "username": osCredentials.username}));
// Use the credentials from the OS keystore if a username wasn't specified
// or the username specified matches the username retrieved from the OS keystore.
if (!username || username === osCredentials.username) {
self.setApiOption("username", osCredentials.username);
self.setApiOption("password", osCredentials.password);
// Reset the schemaInput since we don't need to prompt now.
schemaInput = undefined;
}
}

// The user name and password have been provided, so resolve the promise.
defer.resolve();
}
});
keystoreDeferred.resolve();
})
.catch(function (err) {
self.getLogger().warn(i18n.__("cli_error_loading_credentials", {"url": baseUrl, "err": err.message}));
keystoreDeferred.resolve();
});
} else {
// Did not need to prompt for user name and password, so resolve the promise.
defer.resolve();
keystoreDeferred.resolve();
}

keystoreDeferred.promise.then(function () {
if (schemaInput) {
// Prompt for the user name and/or password.
prompt.message = '';
prompt.delimiter = ' ';
prompt.start();
const schemaProps = {properties: schemaInput};
prompt.get(schemaProps, function (err, result) {
if (err) {
// FUTURE How do we get an error, and should we reject if we do?
console.warn(err.message);
defer.resolve();
} else {
if (result.username) {
// Add the specified user name to the API options.
username = result.username;
self.setApiOption("username", username);
}
if (result.password) {
// Add the specified password to the API options.
password = result.password;
self.setApiOption("password", password);
}

// The user name and password have been provided, so resolve the promise.
defer.resolve();
}
});
} else {
// Did not need to prompt for user name and password, so resolve the promise.
defer.resolve();
}
});

return defer.promise;
}

Expand Down
Loading

0 comments on commit e6da7ee

Please sign in to comment.