Mercurial > hg > release > thermostat-1.4
changeset 1769:38072c439ed4
Implement non-gui version of 'thermostat setup'.
Reviewed-by: neugens
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-September/015680.html
PR2581
line wrap: on
line diff
--- a/distribution/config/devsetup.input Tue Sep 01 16:03:14 2015 +0200 +++ b/distribution/config/devsetup.input Thu Sep 03 15:19:46 2015 +0200 @@ -2,3 +2,9 @@ ${mongodb.dev.username} ${mongodb.dev.password} ${mongodb.dev.password} +client-tester +tester +tester +agent-tester +tester +tester
--- a/distribution/scripts/thermostat-devsetup Tue Sep 01 16:03:14 2015 +0200 +++ b/distribution/scripts/thermostat-devsetup Thu Sep 03 15:19:46 2015 +0200 @@ -54,17 +54,4 @@ # Call the setup script $THERMOSTAT_SETUP < $DEV_INPUT -retval=$? - -if [ $retval -ne 0 ]; then - echo "Development setup failed." 1>&2 - exit $retval -fi - -# Use example users/roles files: -# thermostat-users-example.properties -# thermostat-roles-example.properties -cp -v $THERMOSTAT_HOME/etc/examples/thermostat-users-example.properties $THERMOSTAT_HOME/etc/thermostat-users.properties -cp -v $THERMOSTAT_HOME/etc/examples/thermostat-roles-example.properties $THERMOSTAT_HOME/etc/thermostat-roles.properties -# Use the agent-tester user as defined in thermostat-users-example.properties -echo -e 'username=agent-tester\npassword=tester' > $USER_THERMOSTAT_HOME/etc/agent.auth +exit $?
--- a/distribution/scripts/thermostat-setup Tue Sep 01 16:03:14 2015 +0200 +++ b/distribution/scripts/thermostat-setup Thu Sep 03 15:19:46 2015 +0200 @@ -35,289 +35,15 @@ # to do so, delete this exception statement from your version. # ##################################################################### -# -# Setup utility script in order to assist users in setting up -# mongodb credentials and set web.xml accordingly. -# -# Upon a new install of thermostat it will not run any command -# until this setup utility has been run. Upon completion this -# script creates a STAMP file in $USER_THERMOSTAT_HOME, namely: -# $USER_THERMOSTAT_HOME/data/setup-complete.stamp -# If the file was created by this utility, it will have content -# similar to: -# "Created by thermostat-setup on YYYY-MM-DD-hh:mm:ss" -# -# Since this script merely uses thermostat commands for setting -# up the mongodb user, this causes a chicken-and-egg problem. -# We work around this by temporily creating an empty -# setup-complete-file and replacing it by the real file upon -# successful completion. - -# For debugging -#set -xv - -tty_flags="$(stty -g)" - -# Interruptions are failures -trap exitFail SIGINT - -displayThermostatBlurb() { - cat <<EOF - -Thermostat Basics: - -Thermostat is a database backed distributed monitoring -solution with support for monitoring multiple JVM instances. -The system is made up of three (3) components: - - 1. Agents which collect data. - 2. Clients, which allow users to visualize collected data. - 3. Storage layer which connects the above componets. - -The storage layer itself is two-tiered. The endpoint agents -and clients communicate with is a web application which uses -Mongodb as a backing database. - -This script helps with a setup where all three components -run on a local system. In particular, it helps with the setup -of the storage layer. First, a user is set up in mongodb which -the web endpoint will use for database connections. Second, -the web endpoint's servlet config is updated so as to use the -credentials of this database user. - -Please see the Thermostat User Guide for more information on -how to connect clients and agents to the pre-configured storage -layer: -http://icedtea.classpath.org/wiki/Thermostat/UserGuide - -------------------------------------------------------------- - -EOF -} - -setThermostatEnvironment() { - # Thermostat home - if [ "x$THERMOSTAT_HOME" = "x" ] ; then - THERMOSTAT_HOME="@thermostat.home@" - fi +if [ x"$THERMOSTAT_INSTALL_DIR" = x ] ; then + THERMOSTAT_INSTALL_DIR="@thermostat.home@" +fi +# Not always are installation directory and thermostat home one and +# the same location. +if [ x"$THERMOSTAT_HOME" = x ] ; then + THERMOSTAT_HOME=${THERMOSTAT_INSTALL_DIR} export THERMOSTAT_HOME - # Thermostat user home - if [ "x$USER_THERMOSTAT_HOME" = "x" ]; then - USER_THERMOSTAT_HOME="$HOME/.thermostat" - fi - export USER_THERMOSTAT_HOME - THERMOSTAT="$THERMOSTAT_HOME/bin/thermostat" -} - -setStampCompleteVars() { - scriptName="$(basename $0)" - timestamp="$(date '+%Y-%m-%d-%H:%M:%S %Z')" - SETUP_TMP_UNLOCK_CONTENT="Temporarily unlocked thermostat via $scriptName on $timestamp" - SETUP_UNLOCK_CONTENT_REGULAR="Created by $scriptName on $timestamp" - SETUP_UNLOCK_CONTENT_READ_ONLY_WEBXML="Created by $scriptName on $timestamp (web.xml read-only)" -} - -exitFail() { - # Remove temp stamp file if we've created it - # in this script already, but failed somewhere - # down the line. - removeTempStampFile - echo 'Thermostat setup failed!' 1>&2 - stty "$tty_flags" - exit 1 -} - -removeTempStampFile() { - if [ ! -z "$SETUP_TMP_UNLOCK_CONTENT" ] && - [ ! -z "$SETUP_COMPLETE_FILE" ] && - [ -e $SETUP_COMPLETE_FILE ] && - grep -s "$SETUP_TMP_UNLOCK_CONTENT" "$SETUP_COMPLETE_FILE" > /dev/null; then - rm "$SETUP_COMPLETE_FILE" - fi -} - -exitSuccess() { - # Remove temporary unlock file and create the actual setup-complete - # file. - removeTempStampFile - echo $SETUP_UNLOCK_CONTENT_REGULAR > "$SETUP_COMPLETE_FILE" - echo -e "\nThermostat setup complete!\n" - echo -e "Be sure to configure thermostat-users.properties and" - echo -e "thermostat-roles.properties before you attempt connections" - echo -e "with agent(s) and/or client(s)." - exit 0 -} - -exitSuccessNotWritable() { - # Remove temporary unlock file. Note, we intentionally - # do not clean up the sed files, since the user might - # want to use them still. - removeTempStampFile - # Create successful stamp file with note that - # web.xml might not have been properly set up. - echo $SETUP_UNLOCK_CONTENT_READ_ONLY_WEBXML > "$SETUP_COMPLETE_FILE" - echo "Thermostat setup complete!" - echo -e "\nBe sure to configure $TH_WEB_AUTH as mentioned above." - echo -e "\nThen, make sure to configure thermostat-users.properties and" - echo -e "thermostat-roles.properties before you attempt connections" - echo -e "with agent(s) and/or client(s)." - exit 0 -} - -unlockThermostat() { - setThermostatEnvironment - setStampCompleteVars - SETUP_COMPLETE_FILE="$USER_THERMOSTAT_HOME/data/setup-complete.stamp" - if [ -e "$SETUP_COMPLETE_FILE" ]; then - echo "File $SETUP_COMPLETE_FILE exists. Skipping setup!" - exit 0 - fi - # Be sure to create the "data" parent directory if it does not - # yet exist. - local datadir="$(dirname $SETUP_COMPLETE_FILE)" - if [ ! -e "$datadir" ]; then - mkdir -p "$datadir" - fi - echo "$SETUP_TMP_UNLOCK_CONTENT" > "$SETUP_COMPLETE_FILE" -} - -runSetup() { - unlockThermostat - setupMongodbUser - TH_WEB_AUTH="$THERMOSTAT_HOME/etc/web.auth" - - if [ ! -e $TH_WEB_AUTH ]; then - echo "File not found: $TH_WEB_AUTH" 1>&2 - exitFail - fi - - if [ ! -w $TH_WEB_AUTH ]; then - echo -e "\n\n$(readlink -f $TH_WEB_AUTH) is NOT writable." - mkdir -p "$USER_THERMOSTAT_HOME/etc/" - TH_WEB_AUTH="$USER_THERMOSTAT_HOME/etc/web.auth" - echo -e "Writing to $TH_WEB_AUTH\n" - fi - local success=0 - echo "storage.username = $USERNAME" > "$TH_WEB_AUTH" - success=$(( $sedSuccess + $? )) - echo "storage.password = $PASSWORD" >> "$TH_WEB_AUTH" - success=$(( $sedSuccess + $? )) - chmod 640 "$TH_WEB_AUTH" - success=$(( $sedSuccess + $? )) - if [ $success -eq 0 ]; then - exitSuccess - else - echo "Automatic substitution of file $TH_WEB_AUTH failed!" 1>&2 - exitFail - fi -} - -readUsername() { - dUsername="$1" - prompt="Please enter the desired Mongodb username (press return in order to use '$dUsername'): " - read -p "$prompt" USERNAME - if [ "x$USERNAME" = "x" ]; then - USERNAME="$dUsername" - fi - echo "Chosen username is '$USERNAME'" -} - -# Read in the desired password, not permitting -# empty passwords. -readFirstPassword() { - prompt="Please enter the desired password for user '$USERNAME': " - while true; do - read -s -p "$prompt" PASSWORD - if [ "x$PASSWORD" = "x" ]; then - echo -e "\nPassword must not be empty. Please try again." - else - echo # Formatting - break - fi - done -} - -# Read in the password confirmation -confirmReadPassword() { - firstTry="$1" - prompt="Please confirm password for user '$USERNAME': " - read -s -p "$prompt" confirmedPwd - # Passwords cannot be empty - if [ "$firstTry" != "$confirmedPwd" ]; then - echo -e "\nPasswords did not match. Please try again." - PASSWORDS_MATCH="no" - else - PASSWORDS_MATCH="yes" - fi -} - -readPassword() { - while true; do - readFirstPassword - confirmReadPassword "$PASSWORD" - if [ "x$PASSWORDS_MATCH" = "xyes" ]; then - break; - fi - done -} - -setupMongodbUser() { - # Generate some form of a random default username - defaultName="thermostat-user-$(date +%s)" - echo -e "\nStarting Mongodb user setup ...\n" - echo -e "Next, you are required to enter the username/password," - echo -e "which the web storage endpoint will use for mongodb" - echo -e "connections.\n" - readUsername "$defaultName" - readPassword - $THERMOSTAT_HOME/bin/thermostat storage --start --permitLocalhostException - MONGOD_RETVAL="$?" - if [ "$MONGOD_RETVAL" -ne 0 ] ; then - echo -e "\nMongodb user setup failed. Error starting storage." 1>&2 - exitFail - fi - sleep 3 - mkdir -p $USER_THERMOSTAT_HOME - touch $USER_THERMOSTAT_HOME/creds.js - chmod u=rw $USER_THERMOSTAT_HOME/creds.js - sed -e s/'\$USERNAME'/"$USERNAME"/g -e s/'\$PASSWORD'/"$PASSWORD"/g $THERMOSTAT_HOME/libs/create-user.js > $USER_THERMOSTAT_HOME/creds.js - mongo 127.0.0.1:27518/thermostat $USER_THERMOSTAT_HOME/creds.js - MONGO_SETUP_RETVAL="$?" - rm $USER_THERMOSTAT_HOME/creds.js - if [ "$MONGO_SETUP_RETVAL" -ne 0 ] ; then - echo -e "\nMongodb user setup failed." 1>&2 - exitFail - fi - $THERMOSTAT_HOME/bin/thermostat storage --stop - MONGO_SETUP_RETVAL="$?" - if [ "$MONGO_SETUP_RETVAL" -ne 0 ] ; then - echo -e "\nMongodb user setup failed." 1>&2 - exitFail - fi - touch "$USER_THERMOSTAT_HOME"/data/mongodb-user-done.stamp -} - -doProceedLoop() { - DO_SETUP=1 - while true; do - read -p "Ready to proceed? Please type 'yes' or 'no': " RESPONSE - if [ "x$RESPONSE" = "xyes" ]; then - break - elif [ "x$RESPONSE" = "xno" ]; then - DO_SETUP=0 - break - else - echo "Unknown response. Please only type 'yes' or 'no'" - fi - done -} - -displayThermostatBlurb -doProceedLoop - -if [ $DO_SETUP -eq 0 ]; then - echo "Exiting on user request. Bye!" - exit 0 -else - runSetup fi +echo "THIS SCRIPT IS DEPRECATED! Please use 'thermostat setup -c' instead." 1>&2 +# Call the thermostat non-gui version of setup +${THERMOSTAT_HOME}/bin/thermostat setup -c
--- a/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/DevWebStorageTest.java Tue Sep 01 16:03:14 2015 +0200 +++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/DevWebStorageTest.java Thu Sep 03 15:19:46 2015 +0200 @@ -119,7 +119,7 @@ Spawn setup = spawnScript(THERMOSTAT_DEV_SETUP_SCRIPT, new String[] {}); setup.expectClose(); String stdout = setup.getCurrentStandardOutContents(); - assertTrue(stdout.contains("Thermostat setup complete!")); + assertTrue(stdout.contains("Setup finished successfully.")); assertTrue(stdout.contains("mongodevuser")); } }
--- a/setup/command/pom.xml Tue Sep 01 16:03:14 2015 +0200 +++ b/setup/command/pom.xml Thu Sep 03 15:19:46 2015 +0200 @@ -63,6 +63,7 @@ <Private-Package> com.redhat.thermostat.setup.command.internal, com.redhat.thermostat.setup.command.internal.model, + com.redhat.thermostat.setup.command.internal.cli, com.redhat.thermostat.setup.command.locale, </Private-Package> <Bundle-Activator>com.redhat.thermostat.setup.command.internal.Activator</Bundle-Activator>
--- a/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/SetupCommand.java Tue Sep 01 16:03:14 2015 +0200 +++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/SetupCommand.java Thu Sep 03 15:19:46 2015 +0200 @@ -38,6 +38,11 @@ import java.awt.EventQueue; import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,10 +50,13 @@ import com.redhat.thermostat.common.cli.Arguments; import com.redhat.thermostat.common.cli.CommandContext; import com.redhat.thermostat.common.cli.CommandException; +import com.redhat.thermostat.common.cli.Console; import com.redhat.thermostat.common.cli.DependencyServices; import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.internal.utils.laf.ThemeManager; import com.redhat.thermostat.launcher.Launcher; +import com.redhat.thermostat.setup.command.internal.SetupWindow; +import com.redhat.thermostat.setup.command.internal.cli.CLISetup; import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup; import com.redhat.thermostat.setup.command.locale.LocaleResources; import com.redhat.thermostat.shared.config.CommonPaths; @@ -59,13 +67,13 @@ private static final Translate<LocaleResources> t = LocaleResources.createLocalizer(); private static final String ORIG_CMD_ARGUMENT_NAME = "origArgs"; + private static final String NON_GUI_OPTION_NAME = "nonGui"; private static final Logger logger = LoggingUtils.getLogger(SetupCommand.class); private final DependencyServices dependentServices = new DependencyServices(); private SetupWindow mainWindow; private CommonPaths paths; private Launcher launcher; private Keyring keyring; - private ThermostatSetup thermostatSetup; private String[] origArgsList; @Override @@ -74,23 +82,46 @@ if (args.hasArgument(ORIG_CMD_ARGUMENT_NAME)) { String origArgs = args.getArgument(ORIG_CMD_ARGUMENT_NAME); origArgsList = origArgs.split("\\|\\|\\|"); + if (isSetupInvocation(origArgsList)) { + String[] optionArgs = Arrays.copyOfRange(origArgsList, 1, origArgsList.length); + args = mergeOriginalArgs(optionArgs, args); + } } + + this.paths = dependentServices.getService(CommonPaths.class); + requireNonNull(paths, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "CommonPaths")); + this.launcher = dependentServices.getService(Launcher.class); + requireNonNull(launcher, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "Launcher")); + this.keyring = dependentServices.getService(Keyring.class); + requireNonNull(keyring, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "Keyring")); + ThermostatSetup setup = createSetup(); + if (args.hasArgument(NON_GUI_OPTION_NAME)) { + runCLISetup(setup, ctx.getConsole()); + } else { + runGUISetup(setup); + } + + runOriginalCommand(origArgsList); + } + + private Arguments mergeOriginalArgs(String[] origArgsList, Arguments args) { + logger.fine("Intercepted setup invocation 'setup' " + Arrays.asList(origArgsList).toString()); + return new MergedSetupArguments(args, origArgsList); + } + + private void runCLISetup(ThermostatSetup setup, Console console) throws CommandException { + CLISetup cliSetup = new CLISetup(setup, console); + cliSetup.run(); + } + + private void runGUISetup(ThermostatSetup setup) throws CommandException { try { setLookAndFeel(); - - this.paths = dependentServices.getService(CommonPaths.class); - requireNonNull(paths, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "CommonPaths")); - this.launcher = dependentServices.getService(Launcher.class); - requireNonNull(launcher, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "Launcher")); - this.keyring = dependentServices.getService(Keyring.class); - requireNonNull(keyring, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "Keyring")); - - createMainWindowAndRun(); + createMainWindowAndRun(setup); } catch (InterruptedException | InvocationTargetException e) { throw new CommandException(t.localize(LocaleResources.SETUP_FAILED), e); } - runOriginalCommand(origArgsList); } private void runOriginalCommand(String[] args) { @@ -100,13 +131,17 @@ if (args.length == 0) { throw new AssertionError("Original command args were empty!"); } - if (args[0].equals("setup")) { + if (isSetupInvocation(args)) { // Do not run setup recursively return; } logger.log(Level.FINE, "Running intercepted command '" + args[0] + "' after setup."); launcher.run(args, false); } + + private boolean isSetupInvocation(String[] args) { + return args[0].equals("setup"); + } public void setPaths(CommonPaths paths) { dependentServices.addService(CommonPaths.class, paths); @@ -130,14 +165,13 @@ return false; } - //package-private for testing - void createMainWindowAndRun() throws CommandException { - thermostatSetup = ThermostatSetup.create(launcher, paths, keyring); - mainWindow = new SetupWindow(thermostatSetup); + // package-private for testing + void createMainWindowAndRun(ThermostatSetup setup) throws CommandException { + mainWindow = new SetupWindow(setup); mainWindow.run(); } - //package-private for testing + // package-private for testing void setLookAndFeel() throws InvocationTargetException, InterruptedException { EventQueue.invokeAndWait(new Runnable() { @Override @@ -147,5 +181,87 @@ } }); } + + // package-private for testing + ThermostatSetup createSetup() { + return ThermostatSetup.create(launcher, paths, keyring); + } + + static class MergedSetupArguments implements Arguments { + + private static final String SINGLE_DASH = "-"; + private static final String DOUBLE_DASH = "--"; + private static final String NON_GUI_SHORT_OPT = "c"; + + private final Arguments argsDelegate; + private final String[] origArgs; + private final Map<String, String> additionalOptions; + + MergedSetupArguments(Arguments args, String[] origArgs) { + this.origArgs = origArgs; + this.argsDelegate = Objects.requireNonNull(args); + this.additionalOptions = buildAdditionalOptions(origArgs); + } + + private Map<String, String> buildAdditionalOptions(String[] origArgs) { + Map<String, String> options = new HashMap<>(); + for (int i = 0; i < origArgs.length; i++) { + String opt = origArgs[i]; + String value = "NONE"; + if (opt.startsWith(DOUBLE_DASH)) { + if (i + 1 < origArgs.length && isOptionArg(origArgs, i)) { + value = origArgs[i + 1]; + i++; // skip argument + } + options.put(opt.substring(2), value); + continue; + } else if (opt.startsWith(SINGLE_DASH)) { + // This is a poor-man's version of short-arg parsing for + // non-gui setup option. + String cleanedOp = opt.substring(1); + if (cleanedOp.equals(NON_GUI_SHORT_OPT)) { + options.put(NON_GUI_OPTION_NAME, Boolean.TRUE.toString()); + } + continue; + } else { + throw new AssertionError("Invalid option for setup: " + opt); + } + } + return options; + } + + private boolean isOptionArg(String[] origArgs, int i) { + return !(origArgs[i + 1].startsWith(SINGLE_DASH) || origArgs[i + 1].startsWith(DOUBLE_DASH)); + } + + @Override + public List<String> getNonOptionArguments() { + return argsDelegate.getNonOptionArguments(); + } + + @Override + public boolean hasArgument(String name) { + boolean delegateHasArgument = argsDelegate.hasArgument(name); + if (delegateHasArgument) { + return true; + } + return additionalOptions.keySet().contains(name); + } + + @Override + public String getArgument(String name) { + String arg = argsDelegate.getArgument(name); + if (arg != null) { + return arg; + } + return additionalOptions.get(name); + } + + @Override + public String toString() { + return "setup [delegate=" + argsDelegate.toString() + ",origArgs=" + Arrays.asList(origArgs).toString() + "]"; + } + + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/cli/CLISetup.java Thu Sep 03 15:19:46 2015 +0200 @@ -0,0 +1,191 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.setup.command.internal.cli; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.redhat.thermostat.common.ApplicationInfo; +import com.redhat.thermostat.common.cli.CommandException; +import com.redhat.thermostat.common.cli.Console; +import com.redhat.thermostat.common.utils.LoggingUtils; +import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup; +import com.redhat.thermostat.setup.command.locale.LocaleResources; +import com.redhat.thermostat.shared.locale.LocalizedString; +import com.redhat.thermostat.shared.locale.Translate; + +public class CLISetup { + + private static final Logger logger = LoggingUtils.getLogger(CLISetup.class); + private static final Translate<LocaleResources> t = LocaleResources.createLocalizer(); + private final ThermostatSetup thermostatSetup; + private final Console console; + private final PrintWriter outWriter; + private final PrintWriter errWriter; + + public CLISetup(ThermostatSetup setup, Console console) { + this.thermostatSetup = setup; + this.console = console; + this.outWriter = new PrintWriter(console.getOutput()); + this.errWriter = new PrintWriter(console.getError()); + } + + public void run() throws CommandException { + runSetup(); + println(LocaleResources.CLI_SETUP_FINISH_SUCCESS); + } + + private void runSetup() throws CommandException { + try { + printBlurb(); + boolean shouldContinue = readContinueAnswer(); + if (!shouldContinue) { + throw new CommandException(t.localize(LocaleResources.SETUP_CANCELLED)); + } + readMongodbCredentials(); + if (thermostatSetup.isWebAppInstalled()) { + readThermostatUserCredentials(); + } + thermostatSetup.commit(); + } catch (IOException e) { + logger.log(Level.INFO, "Setup failed. ", e); + throw new CommandException(t.localize(LocaleResources.SETUP_FAILED), e); + } + } + + // package-private for testing + void readThermostatUserCredentials() throws IOException { + println(LocaleResources.CLI_SETUP_THERMOSTAT_USER_CREDS_INTRO); + LocalizedString clientUsernamePrompt = t.localize(LocaleResources.CLI_SETUP_THERMOSTAT_CLIENT_USERNAME_PROMPT); + UsernameCredentialsReader clientUserReader = new UsernameCredentialsReader(console, clientUsernamePrompt); + String clientUsername = clientUserReader.read(); + LocalizedString passwordPrompt = t.localize(LocaleResources.CLI_SETUP_PASSWORD_PROMPT, clientUsername); + LocalizedString passwordPromptRepeat = t.localize(LocaleResources.CLI_SETUP_PASSWORD_REPEAT_PROMPT, clientUsername); + PasswordCredentialsReader clientPasswordReader = new PasswordCredentialsReader(console, passwordPrompt, passwordPromptRepeat); + char[] clientPassword = clientPasswordReader.readPassword(); + thermostatSetup.createClientAdminUser(clientUsername, clientPassword); + + LocalizedString agentUsernamePrompt = t.localize(LocaleResources.CLI_SETUP_THERMOSTAT_AGENT_USERNAME_PROMPT); + UsernameCredentialsReader agentUserReader = new UsernameCredentialsReader(console, agentUsernamePrompt); + String agentUsername = agentUserReader.read(); + passwordPrompt = t.localize(LocaleResources.CLI_SETUP_PASSWORD_PROMPT, agentUsername); + passwordPromptRepeat = t.localize(LocaleResources.CLI_SETUP_PASSWORD_REPEAT_PROMPT, agentUsername); + PasswordCredentialsReader agentPasswordReader = new PasswordCredentialsReader(console, passwordPrompt, passwordPromptRepeat); + char[] agentPassword = agentPasswordReader.readPassword(); + thermostatSetup.createAgentUser(agentUsername, agentPassword); + } + + // package-private for testing + void readMongodbCredentials() throws IOException { + println(LocaleResources.CLI_SETUP_MONGODB_USER_CREDS_INTRO); + LocalizedString usernamePrompt = t.localize(LocaleResources.CLI_SETUP_MONGODB_USERNAME_PROMPT); + UsernameCredentialsReader usernameReader = new UsernameCredentialsReader(console, usernamePrompt); + String username = usernameReader.read(); + LocalizedString passwordPrompt = t.localize(LocaleResources.CLI_SETUP_PASSWORD_PROMPT, username); + LocalizedString confirmPasswordPrompt = t.localize(LocaleResources.CLI_SETUP_PASSWORD_REPEAT_PROMPT, username); + PasswordCredentialsReader passwordReader = new PasswordCredentialsReader(console, passwordPrompt, confirmPasswordPrompt); + char[] password = passwordReader.readPassword(); + thermostatSetup.createMongodbUser(username, password); + } + + /** + * + * @return {@code true} if user wants to continue, {@code false} otherwise. + * + * @throws IOException + */ + private boolean readContinueAnswer() throws IOException { + final String localizedProceedToken = t.localize(LocaleResources.CLI_SETUP_PROCEED_WORD).getContents(); + final String localizedCancelToken = t.localize(LocaleResources.CLI_SETUP_CANCEL_WORD).getContents(); + LocalizedString yes = t.localize(LocaleResources.CLI_SETUP_YES); + LocalizedString no = t.localize(LocaleResources.CLI_SETUP_NO); + print(LocaleResources.CLI_SETUP_PROCEED_QUESTION, yes.getContents(), no.getContents()); + String input; + InputStream in = console.getInput(); + final int maxTries = 100; + int currTry = 0; + do { + input = readLine(in); + if (input.equals(localizedCancelToken)) { + return false; + } + if (input.equals(localizedProceedToken)) { + return true; + } + printErr(LocaleResources.CLI_SETUP_UNKNOWN_RESPONSE, input, yes.getContents(), no.getContents()); + currTry++; + } while (currTry < maxTries); + logger.log(Level.WARNING, "Tried " + maxTries + " times with invalid input. Cancelling."); + return false; + } + + private String readLine(InputStream in) throws IOException { + int c; + StringBuilder builder = new StringBuilder(); + while ((c = in.read()) != -1) { + char token = (char)c; + if (token == '\n') { + break; + } + builder.append(token); + } + return builder.toString(); + } + + private void printBlurb() { + String userGuideURL = new ApplicationInfo().getUserGuide(); + println(LocaleResources.CLI_SETUP_INTRO, userGuideURL); + } + + private void println(LocaleResources resource, String... strings) { + outWriter.println(t.localize(resource, strings).getContents()); + outWriter.flush(); + } + + private void print(LocaleResources resource, String... strings) { + outWriter.print(t.localize(resource, strings).getContents()); + outWriter.flush(); + } + + private void printErr(LocaleResources resource, String... strings) { + errWriter.println(t.localize(resource, strings).getContents()); + errWriter.flush(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/cli/PasswordCredentialsReader.java Thu Sep 03 15:19:46 2015 +0200 @@ -0,0 +1,141 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.setup.command.internal.cli; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Objects; + +import com.redhat.thermostat.common.cli.Console; +import com.redhat.thermostat.common.tools.StorageAuthInfoGetter; +import com.redhat.thermostat.setup.command.internal.model.UserCredsValidator; +import com.redhat.thermostat.setup.command.locale.LocaleResources; +import com.redhat.thermostat.shared.locale.LocalizedString; +import com.redhat.thermostat.shared.locale.Translate; + +class PasswordCredentialsReader { + + private static final LocalizedString UNUSED = new LocalizedString("ignored"); + private static final Translate<LocaleResources> t = LocaleResources.createLocalizer(); + private final Console console; + private final LocalizedString passwordPrompt; + private final LocalizedString confirmPasswordPrompt; + private final PrintWriter errWriter; + + PasswordCredentialsReader(Console console, LocalizedString passwordPrompt, LocalizedString confirmPasswordPrompt) { + this.console = console; + this.passwordPrompt = passwordPrompt; + this.confirmPasswordPrompt = confirmPasswordPrompt; + this.errWriter = new PrintWriter(console.getError()); + } + + // Read in passwords twice. Once the initial password, a second time we + // expect the same content again for confirmation. + char[] readPassword() throws IOException { + char[] password; + char[] confirmation; + boolean isValid = false; + do { + StorageAuthInfoGetter getter = new StorageAuthInfoGetter(console, UNUSED, passwordPrompt); + password = getter.getPassword(null); + StorageAuthInfoGetter confirmGetter = new StorageAuthInfoGetter(console, UNUSED, confirmPasswordPrompt); + confirmation = confirmGetter.getPassword(null); + try { + verifyPassword(password, confirmation); + Arrays.fill(confirmation, '\0'); + confirmation = null; + isValid = true; + } catch (InvalidPasswordException e) { + printError(LocaleResources.CLI_SETUP_PASSWORD_INVALID); + clearPasswords(password, confirmation); + } catch (PasswordMismatchException e) { + printError(LocaleResources.CLI_SETUP_PASSWORD_MISMATCH); + clearPasswords(password, confirmation); + } + } while (!isValid); + return password; + } + + private void clearPasswords(char[] password, char[] confirmation) { + Arrays.fill(password, '\0'); + Arrays.fill(confirmation, '\0'); + password = null; + confirmation = null; + } + + private void printError(LocaleResources resource, String...strings) { + errWriter.println(t.localize(resource, strings).getContents()); + errWriter.flush(); + } + + private void verifyPassword(char[] password, char[] confirmation) throws InvalidPasswordException, PasswordMismatchException { + checkPasswordsMatch(password, confirmation); + UserCredsValidator validator = new UserCredsValidator(); + try { + validator.validatePassword(password); + validator.validatePassword(confirmation); + } catch (IllegalArgumentException e) { + throw new InvalidPasswordException(); + } + } + + void checkPasswordsMatch(char[] first, char[] second) throws PasswordMismatchException { + // Auth getter never returns null. Use as precondition + Objects.requireNonNull(first); + Objects.requireNonNull(second); + + if (first.length != second.length) { + throw new PasswordMismatchException(); + } + for (int i = 0; i < first.length; i++) { + if (first[i] != second[i]) { + throw new PasswordMismatchException(); + } + } + } + + @SuppressWarnings("serial") + private static class PasswordMismatchException extends Exception { + // nothing + } + + @SuppressWarnings("serial") + private static class InvalidPasswordException extends Exception { + // nothing + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/cli/UsernameCredentialsReader.java Thu Sep 03 15:19:46 2015 +0200 @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.setup.command.internal.cli; + +import java.io.IOException; +import java.io.PrintWriter; + +import com.redhat.thermostat.common.cli.Console; +import com.redhat.thermostat.common.tools.StorageAuthInfoGetter; +import com.redhat.thermostat.setup.command.internal.model.UserCredsValidator; +import com.redhat.thermostat.setup.command.locale.LocaleResources; +import com.redhat.thermostat.shared.locale.LocalizedString; +import com.redhat.thermostat.shared.locale.Translate; + +class UsernameCredentialsReader { + + private static final LocalizedString UNUSED = new LocalizedString("ignored"); + private static final Translate<LocaleResources> t = LocaleResources.createLocalizer(); + private final Console console; + private final LocalizedString usernamePrompt; + private final PrintWriter errWriter; + + UsernameCredentialsReader(Console console, LocalizedString usernamePrompt) { + this.console = console; + this.usernamePrompt = usernamePrompt; + this.errWriter = new PrintWriter(console.getError()); + } + + String read() throws IOException { + StorageAuthInfoGetter getter = new StorageAuthInfoGetter(console, usernamePrompt, UNUSED); + boolean isValid = false; + UserCredsValidator validator = new UserCredsValidator(); + String username = null; + while (!isValid) { + username = getter.getUserName(null); + try { + validator.validateUsername(username); + isValid = true; + } catch (IllegalArgumentException e) { + printError(LocaleResources.CLI_SETUP_USERNAME_INVALID, username); + // continue loop + } + } + return username; + } + + private void printError(LocaleResources resource, String...strings) { + errWriter.println(t.localize(resource, strings).getContents()); + errWriter.flush(); + } + +}
--- a/setup/command/src/main/java/com/redhat/thermostat/setup/command/locale/LocaleResources.java Tue Sep 01 16:03:14 2015 +0200 +++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/locale/LocaleResources.java Thu Sep 03 15:19:46 2015 +0200 @@ -69,13 +69,32 @@ USERNAME, PASSWORD, VERIFY_PASSWORD, + CLI_SETUP_INTRO, + CLI_SETUP_PROCEED_QUESTION, + CLI_SETUP_UNKNOWN_RESPONSE, + CLI_SETUP_PROCEED_WORD, + CLI_SETUP_CANCEL_WORD, + CLI_SETUP_YES, + CLI_SETUP_NO, + CLI_SETUP_PASSWORD_INVALID, + CLI_SETUP_PASSWORD_MISMATCH, + CLI_SETUP_USERNAME_INVALID, + CLI_SETUP_MONGODB_USER_CREDS_INTRO, + CLI_SETUP_MONGODB_USERNAME_PROMPT, + CLI_SETUP_PASSWORD_PROMPT, + CLI_SETUP_USERNAME_REPEAT, + CLI_SETUP_PASSWORD_REPEAT_PROMPT, + CLI_SETUP_THERMOSTAT_USER_CREDS_INTRO, + CLI_SETUP_THERMOSTAT_CLIENT_USERNAME_PROMPT, + CLI_SETUP_THERMOSTAT_AGENT_USERNAME_PROMPT, + CLI_SETUP_FINISH_SUCCESS, ; static final String RESOURCE_BUNDLE = "com.redhat.thermostat.setup.locale.strings"; public static Translate<LocaleResources> createLocalizer() { - return new Translate(RESOURCE_BUNDLE, LocaleResources.class); + return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class); } }
--- a/setup/command/src/main/resources/com/redhat/thermostat/setup/locale/strings.properties Tue Sep 01 16:03:14 2015 +0200 +++ b/setup/command/src/main/resources/com/redhat/thermostat/setup/locale/strings.properties Thu Sep 03 15:19:46 2015 +0200 @@ -73,7 +73,7 @@ SETUP_INTERRUPTED=Setup was interrupted. -SETUP_CANCELLED=Setup was cancelled. +SETUP_CANCELLED=Setup cancelled on user request. PASSWORD_MISMATCH=Passwords don't match! @@ -87,4 +87,47 @@ PASSWORD=Password: -VERIFY_PASSWORD=Verify Password: \ No newline at end of file +VERIFY_PASSWORD=Verify Password: + +CLI_SETUP_INTRO=Thermostat Basics: \n\ + Thermostat is a database backed distributed monitoring \n\ + solution with support for monitoring multiple JVM instances. \n\ + The system is made up of three (3) components: \n\ + \n\ + 1. Agents which collect data. \n\ + 2. Clients, which allow users to visualize collected data. \n\ + 3. Storage layer which connects the above componets. \n\ + \n\ + The storage layer itself is two-tiered. The endpoint agents \n\ + and clients communicate with is a web application which uses \n\ + Mongodb as a backing database. \n\ + \n\ + This script helps with a setup where all three components \n\ + run on a local system. First, a user is set up in mongodb which \n\ + the web endpoint will use for database connections. Second, \n\ + thermostat users for ''thermostat agent'' and thermostat clients \n\ + are set up. \n\ + \n\ + Please see the Thermostat User Guide for more information: \n\ + {0} \n\ + \n\ + -------------------------------------------------------------\n + +CLI_SETUP_PROCEED_QUESTION=Ready to proceed? Please type ''{0}'' or ''{1}'': +CLI_SETUP_UNKNOWN_RESPONSE=Unknown response ''{0}''. Please only type ''{1}'' or ''{2}'' +CLI_SETUP_PROCEED_WORD=yes +CLI_SETUP_CANCEL_WORD=no +CLI_SETUP_YES=yes +CLI_SETUP_NO=no +CLI_SETUP_PASSWORD_MISMATCH=Passwords did not match! +CLI_SETUP_PASSWORD_INVALID=Chosen password invalid! +CLI_SETUP_USERNAME_INVALID=Chosen username ''{0}'' invalid! +CLI_SETUP_MONGODB_USER_CREDS_INTRO=----- Mongodb User Setup ----- +CLI_SETUP_MONGODB_USERNAME_PROMPT=Please enter the desired Mongodb username: +CLI_SETUP_USERNAME_REPEAT=Chosen username is ''{0}''. +CLI_SETUP_PASSWORD_PROMPT=Please enter the desired password for ''{0}'': +CLI_SETUP_PASSWORD_REPEAT_PROMPT=Please repeat password for ''{0}'': +CLI_SETUP_THERMOSTAT_USER_CREDS_INTRO=----- Thermostat User Setup ----- +CLI_SETUP_THERMOSTAT_CLIENT_USERNAME_PROMPT=Please enter the desired username for the Thermostat client: +CLI_SETUP_THERMOSTAT_AGENT_USERNAME_PROMPT=Please enter the desired username for the Thermostat agent: +CLI_SETUP_FINISH_SUCCESS=Setup finished successfully. \ No newline at end of file
--- a/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/SetupCommandTest.java Tue Sep 01 16:03:14 2015 +0200 +++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/SetupCommandTest.java Thu Sep 03 15:19:46 2015 +0200 @@ -36,6 +36,7 @@ package com.redhat.thermostat.setup.command.internal; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.argThat; @@ -45,7 +46,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -64,6 +67,8 @@ import com.redhat.thermostat.common.cli.Console; import com.redhat.thermostat.launcher.Launcher; import com.redhat.thermostat.setup.command.internal.SetupCommand; +import com.redhat.thermostat.setup.command.internal.cli.CharArrayMatcher; +import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup; import com.redhat.thermostat.shared.config.CommonPaths; import com.redhat.thermostat.utils.keyring.Keyring; @@ -79,10 +84,12 @@ private CommonPaths paths; private Launcher launcher; private Keyring keyring; + private ThermostatSetup thermostatSetup; @Before public void setUp() { paths = mock(CommonPaths.class); + when(paths.getUserClientConfigurationFile()).thenReturn(mock(File.class)); ctxt = mock(CommandContext.class); mockArgs = mock(Arguments.class); console = mock(Console.class); @@ -94,6 +101,7 @@ error = new PrintStream(errorBaos); launcher = mock(Launcher.class); keyring = mock(Keyring.class); + thermostatSetup = mock(ThermostatSetup.class); when(ctxt.getArguments()).thenReturn(mockArgs); when(ctxt.getConsole()).thenReturn(console); @@ -112,7 +120,7 @@ } @Override - void createMainWindowAndRun() { + void createMainWindowAndRun(ThermostatSetup setup) { //do nothing } }; @@ -133,7 +141,7 @@ } @Override - void createMainWindowAndRun() { + void createMainWindowAndRun(ThermostatSetup setup) { isSet[0] = true; } }; @@ -199,6 +207,32 @@ verify(launcher, times(0)).run(argThat(new ArgsMatcher(new String[] { "setup" })), eq(false)); } + @Test + public void verifySetupAsOrigCommandNonGui() throws CommandException { + cmd = createSetupCommand(); + setServices(); + + Arguments args = mock(Arguments.class); + CommandContext ctxt = mock(CommandContext.class); + when(ctxt.getArguments()).thenReturn(args); + when(args.hasArgument("origArgs")).thenReturn(true); + when(args.getArgument("origArgs")).thenReturn("setup|||-c"); + when(ctxt.getConsole()).thenReturn(console); + when(thermostatSetup.isWebAppInstalled()).thenReturn(true); + when(console.getInput()) + .thenReturn(new ByteArrayInputStream("yes\nmongo\nm\nm\nclient\nc\nc\nagent\na\na\n".getBytes())); + + cmd.run(ctxt); + verify(thermostatSetup).createAgentUser(eq("agent"), argThat(matchesPassword(new char[] { 'a' }))); + verify(thermostatSetup).createClientAdminUser(eq("client"), argThat(matchesPassword(new char[] { 'c' }))); + verify(thermostatSetup).createMongodbUser(eq("mongo"), argThat(matchesPassword(new char[] { 'm' }))); + verify(launcher, times(0)).run(argThat(new ArgsMatcher(new String[] { "setup", "-c" })), eq(false)); + } + + private CharArrayMatcher matchesPassword(char[] expected) { + return new CharArrayMatcher(expected); + } + private void doTestOriginalCmdRunsAfterSetup(String origArgs, String[] argsList) throws CommandException { cmd = createSetupCommand(); setServices(); @@ -235,12 +269,41 @@ } @Override - void createMainWindowAndRun() { + void createMainWindowAndRun(ThermostatSetup setup) { //do nothing } + + @Override + ThermostatSetup createSetup() { + return thermostatSetup; + } }; } + @Test + public void mergedSetupArgumentsTest() { + Arguments args = mock(Arguments.class); + when(args.hasArgument("nonGui")).thenReturn(false); + String[] origArgs = new String[] { "-c", "--nonGui" }; + SetupCommand.MergedSetupArguments arguments = new SetupCommand.MergedSetupArguments(args, origArgs); + assertTrue(arguments.hasArgument("nonGui")); + // short version of nonGui + origArgs = new String[] { "-c" }; + arguments = new SetupCommand.MergedSetupArguments(args, origArgs); + assertTrue(arguments.hasArgument("nonGui")); + // long version + origArgs = new String[] { "--nonGui" }; + arguments = new SetupCommand.MergedSetupArguments(args, origArgs); + assertTrue(arguments.hasArgument("nonGui")); + + // unrelated option + when(args.getArgument("something")).thenReturn(null); + origArgs = new String[] { "--something", "someVal", "-c" }; + arguments = new SetupCommand.MergedSetupArguments(args, origArgs); + assertEquals("someVal", arguments.getArgument("something")); + assertTrue(arguments.hasArgument("nonGui")); + } + private void setServices() { cmd.setPaths(paths); cmd.setLauncher(launcher);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/CLISetupTest.java Thu Sep 03 15:19:46 2015 +0200 @@ -0,0 +1,207 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.setup.command.internal.cli; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +import org.junit.Before; +import org.junit.Test; + +import com.redhat.thermostat.common.cli.CommandException; +import com.redhat.thermostat.common.cli.Console; +import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup; + +public class CLISetupTest { + + private CLISetup cliSetup; + private ThermostatSetup thermostatSetup; + private Console console; + private ByteArrayOutputStream berr; + private ByteArrayOutputStream bout; + + @Before + public void setup() { + thermostatSetup = mock(ThermostatSetup.class); + console = mock(Console.class); + berr = new ByteArrayOutputStream(); + when(console.getError()).thenReturn(new PrintStream(berr)); + bout = new ByteArrayOutputStream(); + when(console.getOutput()).thenReturn(new PrintStream(bout)); + cliSetup = new CLISetup(thermostatSetup, console); + } + + @Test + public void testCancelProceedLoop() throws IOException { + String input = "no\n"; + ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes()); + when(console.getInput()).thenReturn(mockInStream); + try { + cliSetup.run(); + fail("Setup expected to throw CommandException on cancel"); + } catch (CommandException e) { + assertEquals("Setup cancelled on user request.", e.getMessage()); + } + String output = new String(bout.toByteArray()); + assertTrue(output.contains("Ready to proceed?")); + } + + @Test + public void testProceedLoopWithMoreInput() throws IOException { + String input = "no\nsomethingMore\n"; + ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes()); + when(console.getInput()).thenReturn(mockInStream); + try { + cliSetup.run(); + fail("Setup expected to throw CommandException on cancel"); + } catch (CommandException e) { + assertEquals("Setup cancelled on user request.", e.getMessage()); + } + String output = new String(bout.toByteArray()); + assertTrue(output.contains("Ready to proceed?")); + // Verify that we can still read from the stream and we haven't read too + // much. + byte[] buf = new byte[input.length()]; + int retval = mockInStream.read(buf, 3, input.length() - 3); + assertEquals("Read more bytes than are needed!", input.length() - 3, retval); + assertEquals("Expected 'e' from somethingMor(e)", 'e', (char)buf[input.length() - 2]); + } + + @Test + public void testUnknownInputProceedLoop() throws IOException { + String input = "somethingNotYes\nno\n"; + ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes()); + when(console.getInput()).thenReturn(mockInStream); + try { + cliSetup.run(); + fail("Setup expected to throw CommandException on cancel"); + } catch (CommandException e) { + assertEquals("Setup cancelled on user request.", e.getMessage()); + } + String errBuf = new String(berr.toByteArray()); + assertTrue("Expected unknown input msg! Got " + errBuf, errBuf.startsWith("Unknown response 'somethingNotYes'")); + } + + @Test + public void testNoInputProceedLoopCancels() throws InterruptedException { + String input = ""; + ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes()); + when(console.getInput()).thenReturn(mockInStream); + try { + cliSetup.run(); + fail("Expected setup to cancel"); + } catch (CommandException e) { + assertEquals("Setup cancelled on user request.", e.getMessage()); + } + } + + @Test + public void testReadMongodbCreds() throws IOException { + String input = "somevalidusername\nt\nt\n"; + ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes()); + when(console.getInput()).thenReturn(mockInStream); + cliSetup.readMongodbCredentials(); + verify(thermostatSetup).createMongodbUser(eq("somevalidusername"), argThat(matchesPassword(new char[] { 't' }))); + String output = new String(bout.toByteArray()); + assertTrue("Expected user setup blurb", output.contains("Mongodb User Setup")); + assertTrue("Expected somevalidusername in output. Got: " + output, output.contains("somevalidusername")); + assertEquals("Expected no errors", "", new String(berr.toByteArray())); + } + + @Test + public void testReadMongodbCreds2() throws IOException, CommandException { + String input = "yes\nsomevalidusername\nt\nt\n"; + ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes()); + when(console.getInput()).thenReturn(mockInStream); + when(thermostatSetup.isWebAppInstalled()).thenReturn(false); + cliSetup.run(); + verify(thermostatSetup).createMongodbUser(eq("somevalidusername"), argThat(matchesPassword(new char[] { 't' }))); + String output = new String(bout.toByteArray()); + assertTrue("Expected user setup blurb", output.contains("Mongodb User Setup")); + assertTrue("Expected somevalidusername in output. Got: " + output, output.contains("somevalidusername")); + assertEquals("Expected no errors", "", new String(berr.toByteArray())); + } + + @Test + public void testReadThermostatCreds() throws IOException { + String input = "client-user\nt\nt\nagent-user\nb\nb\n"; + ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes()); + when(console.getInput()).thenReturn(mockInStream); + cliSetup.readThermostatUserCredentials(); + verify(thermostatSetup).createAgentUser(eq("agent-user"), argThat(matchesPassword(new char[] { 'b' }))); + verify(thermostatSetup).createClientAdminUser(eq("client-user"), argThat(matchesPassword(new char[] { 't' }))); + String output = new String(bout.toByteArray()); + assertTrue("Expected user setup blurb", output.contains("Thermostat User Setup")); + assertTrue("Expected client-user in output. Got: " + output, output.contains("client-user")); + assertTrue("Expected agent-user in output. Got: " + output, output.contains("agent-user")); + assertEquals("Expected no errors", "", new String(berr.toByteArray())); + } + + @Test + public void canCreateUsersFromStdInput() throws CommandException { + // simulate webapp being installed + when(thermostatSetup.isWebAppInstalled()).thenReturn(true); + + String input = "yes\nmongodb-user\nfoo\nfoo\nclient-user\nt\nt\nagent-user\nb\nb\n"; + when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes())); + cliSetup.run(); + verify(thermostatSetup).createMongodbUser(eq("mongodb-user"), argThat(matchesPassword(new char[] { 'f', 'o', 'o' }))); + verify(thermostatSetup).createAgentUser(eq("agent-user"), argThat(matchesPassword(new char[] { 'b' }))); + verify(thermostatSetup).createClientAdminUser(eq("client-user"), argThat(matchesPassword(new char[] { 't' }))); + String output = new String(bout.toByteArray()); + assertTrue("Expected user setup blurb", output.contains("Thermostat User Setup")); + assertTrue("Expected client-user in output. Got: " + output, output.contains("client-user")); + assertTrue("Expected agent-user in output. Got: " + output, output.contains("agent-user")); + assertTrue("Expected mongodb-user in output. Got: " + output, output.contains("mongodb-user")); + assertEquals("Expected no errors", "", new String(berr.toByteArray())); + } + + private CharArrayMatcher matchesPassword(char[] array) { + return new CharArrayMatcher(array); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/CharArrayMatcher.java Thu Sep 03 15:19:46 2015 +0200 @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.setup.command.internal.cli; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +public class CharArrayMatcher extends BaseMatcher<char[]> { + + private final char[] expected; + public CharArrayMatcher(char[] expected) { + this.expected = expected; + } + + @Override + public boolean matches(Object arg0) { + if (arg0 == null || arg0.getClass() != char[].class) { + return false; + } + char[] other = (char[])arg0; + boolean matches = (expected.length == other.length); + if (!matches) { + return false; + } + for (int i = 0; i < expected.length; i++) { + matches = matches && expected[i] == other[i]; + } + return matches; + } + + @Override + public void describeTo(Description arg0) { + arg0.appendText(new String(expected)); + } + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/PasswordCredentialsReaderTest.java Thu Sep 03 15:19:46 2015 +0200 @@ -0,0 +1,103 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.setup.command.internal.cli; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertArrayEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.redhat.thermostat.common.cli.Console; +import com.redhat.thermostat.shared.locale.LocalizedString; + +public class PasswordCredentialsReaderTest { + + private Console console; + private PasswordCredentialsReader credsReader; + private LocalizedString passwordPrompt = new LocalizedString("tell me the password: "); + private LocalizedString confirmPasswordPrompt = new LocalizedString("repeat the password: "); + private ByteArrayOutputStream berr; + private ByteArrayOutputStream bout; + + @Before + public void setup() { + console = mock(Console.class); + berr = new ByteArrayOutputStream(); + when(console.getError()).thenReturn(new PrintStream(berr)); + bout = new ByteArrayOutputStream(); + when(console.getOutput()).thenReturn(new PrintStream(bout)); + credsReader = new PasswordCredentialsReader(console, passwordPrompt, confirmPasswordPrompt); + } + + @Test + public void testPasswordsMismatch() throws IOException { + String input = "first\nfirst2\nsecond\nsecond\n"; + when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes())); + char[] password = credsReader.readPassword(); + assertEquals("second", new String(password)); + assertEquals("Passwords did not match!\n", new String(berr.toByteArray())); + assertEquals("tell me the password: \nrepeat the password: \ntell me the password: \nrepeat the password: \n", new String(bout.toByteArray())); + } + + @Test + public void testPasswordInvalid() throws IOException { + String input = "\n\na\na\n"; + when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes())); + char[] password = credsReader.readPassword(); + assertEquals("a", new String(password)); + assertEquals("Chosen password invalid!\n", new String(berr.toByteArray())); + assertEquals("tell me the password: \nrepeat the password: \ntell me the password: \nrepeat the password: \n", new String(bout.toByteArray())); + } + + @Test + public void canGetPassword() throws IOException { + String input = "bar\nbar\n"; + when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes())); + char[] password = credsReader.readPassword(); + assertArrayEquals(new char[] { 'b', 'a', 'r' }, password); + assertEquals("Expected no errors", "", new String(berr.toByteArray())); + assertEquals("tell me the password: \nrepeat the password: \n", new String(bout.toByteArray())); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/UsernameCredentialsReaderTest.java Thu Sep 03 15:19:46 2015 +0200 @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.setup.command.internal.cli; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +import org.junit.Before; +import org.junit.Test; + +import com.redhat.thermostat.common.cli.Console; +import com.redhat.thermostat.shared.locale.LocalizedString; + +public class UsernameCredentialsReaderTest { + + private Console console; + private UsernameCredentialsReader credsReader; + private LocalizedString userPrompt = new LocalizedString("tell me the username: "); + private ByteArrayOutputStream berr; + private ByteArrayOutputStream bout; + + @Before + public void setup() { + console = mock(Console.class); + berr = new ByteArrayOutputStream(); + when(console.getError()).thenReturn(new PrintStream(berr)); + bout = new ByteArrayOutputStream(); + when(console.getOutput()).thenReturn(new PrintStream(bout)); + credsReader = new UsernameCredentialsReader(console, userPrompt); + } + + @Test + public void canGetUsername() throws IOException { + String input = "foo-user\n"; + when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes())); + String username = credsReader.read(); + assertEquals("foo-user", username); + assertEquals("Expected no errors", "", new String(berr.toByteArray())); + assertEquals("tell me the username: foo-user\n", new String(bout.toByteArray())); + } + + @Test + public void testUsernameInvalidAtFirst() throws IOException { + String input = "\ntry-second-time\n"; + when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes())); + String username = credsReader.read(); + assertEquals("try-second-time", username); + assertEquals("Chosen username '' invalid!\n", new String(berr.toByteArray())); + assertEquals("tell me the username: \ntell me the username: try-second-time\n", new String(bout.toByteArray())); + } +}
--- a/setup/distribution/thermostat-plugin.xml Tue Sep 01 16:03:14 2015 +0200 +++ b/setup/distribution/thermostat-plugin.xml Thu Sep 03 15:19:46 2015 +0200 @@ -52,6 +52,12 @@ <required>false</required> <description>A string holding '|||'-separated values of original arguments if a command got intercepted with setup.</description> </option> + <option> + <long>nonGui</long> + <short>c</short> + <required>false</required> + <description>Don't use the graphical user interface, but use the command line instead for interactive questions.</description> + </option> </options> <environments> <environment>cli</environment>