/*
* RHQ Management Platform
* Copyright (C) 2005-2012 Red Hat, Inc.
* All rights reserved.
*
* This program 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 version 2 of the License.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.installer;
import gnu.getopt.Getopt;
import gnu.getopt.LongOpt;
import java.io.Console;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.crypto.CryptoUtil;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.enterprise.server.installer.InstallerService.AlreadyInstalledException;
import org.rhq.enterprise.server.installer.InstallerService.AutoInstallDisabledException;
/**
* The entry point to the RHQ Installer.
*
* @author John Mazzitelli
*/
public class Installer {
private static final Log LOG = LogFactory.getLog(Installer.class);
private static final int EXIT_CODE_ALREADY_INSTALLED = 0;
private static final int EXIT_CODE_INSTALLATION_DONE = 0;
private static final int EXIT_CODE_AUTOINSTALL_DISABLED = 1;
private static final int EXIT_CODE_INSTALLATION_ERROR = 2;
private InstallerConfiguration installerConfig;
private enum WhatToDo {
DISPLAY_USAGE, DO_NOTHING, TEST, SETUPDB, LIST_SERVERS, INSTALL, UPDATESTORAGESCHEMA, CLEARCOLUMNFAMILIES, LIST_VERSIONS, UPGRADE
}
public static void main(String[] args) {
try {
final Installer installer = new Installer();
installer.doInstall(args);
} catch (Exception e) {
LOG.error("The installer will now exit due to previous errors", e);
System.exit(EXIT_CODE_INSTALLATION_ERROR);
}
System.exit(EXIT_CODE_INSTALLATION_DONE);
}
public Installer() {
this.installerConfig = new InstallerConfiguration();
}
public InstallerConfiguration getInstallerConfiguration() {
return this.installerConfig;
}
public void doInstall(String[] args) throws Exception {
WhatToDo[] thingsToDo = processArguments(args);
for (WhatToDo whatToDo : thingsToDo) {
switch (whatToDo) {
case DISPLAY_USAGE: {
displayUsage();
continue;
}
case LIST_SERVERS: {
new InstallerServiceImpl(installerConfig).listServers();
continue;
}
case LIST_VERSIONS: {
new InstallerServiceImpl(installerConfig).listVersions();
continue;
}
case TEST: {
try {
new InstallerServiceImpl(installerConfig).test();
} catch (AutoInstallDisabledException e) {
LOG.error(e.getMessage());
System.exit(EXIT_CODE_AUTOINSTALL_DISABLED);
} catch (AlreadyInstalledException e) {
LOG.info(e.getMessage());
System.exit(EXIT_CODE_ALREADY_INSTALLED);
}
continue;
}
case SETUPDB: {
try {
final InstallerService installerService = new InstallerServiceImpl(installerConfig);
final HashMap<String, String> serverProperties = installerService.getServerProperties();
installerService.prepareDatabase(serverProperties, null, null, false);
LOG.info("Database setup is complete.");
} catch (Exception e) {
LOG.error(ThrowableUtil.getAllMessages(e));
System.exit(EXIT_CODE_INSTALLATION_ERROR);
}
continue;
}
case CLEARCOLUMNFAMILIES: {
try {
final InstallerService installerService = new InstallerServiceImpl(installerConfig);
final HashMap<String, String> serverProperties = installerService.getServerProperties();
installerService.clearColumnFamilies(serverProperties);
LOG.info("Clearing unused column families from storage inventory is complete.");
} catch (Exception e) {
LOG.error(ThrowableUtil.getAllMessages(e));
System.exit(EXIT_CODE_INSTALLATION_ERROR);
}
continue;
}
case UPDATESTORAGESCHEMA: {
try {
final InstallerService installerService = new InstallerServiceImpl(installerConfig);
final HashMap<String, String> serverProperties = installerService.getServerProperties();
installerService.updateStorageSchema(serverProperties);
LOG.info("Storage schema update is complete.");
} catch (Exception e) {
LOG.error(ThrowableUtil.getAllMessages(e));
System.exit(EXIT_CODE_INSTALLATION_ERROR);
}
continue;
}
case INSTALL:
case UPGRADE: {
try {
final InstallerService installerService = new InstallerServiceImpl(installerConfig);
final HashMap<String, String> serverProperties = installerService.preInstall();
installerService.install(serverProperties, null, null, (WhatToDo.UPGRADE == whatToDo));
LOG.info("Installation is complete. The server should be ready shortly.");
} catch (AutoInstallDisabledException e) {
LOG.error(e.getMessage());
System.exit(EXIT_CODE_AUTOINSTALL_DISABLED);
} catch (AlreadyInstalledException e) {
LOG.info(e.getMessage());
System.exit(EXIT_CODE_ALREADY_INSTALLED);
}
continue;
}
case DO_NOTHING: {
continue; // this will occur if processArguments() already did the work
}
default: {
throw new IllegalStateException("Please report this bug: " + whatToDo);
}
}
}
return;
}
private void displayUsage() {
StringBuilder usage = new StringBuilder("RHQ Installer\n");
usage.append("\t--help, -H: this help text").append("\n");
usage.append("\t-Dname=value: set system properties for the Installer VM").append("\n");
usage.append("\t--host=<hostname>, -h: hostname where the app server is running").append("\n");
usage.append("\t--port=<port>, -p: talk to the app server over this management port").append("\n");
usage.append("\t--test, -t: test the validity of the server properties (install not performed)").append("\n");
usage.append("\t--force, -f: force the installer to try to install everything").append("\n");
usage.append("\t--listservers, -l: show list of known installed servers (install not performed)").append("\n");
usage.append("\t--listversions, -v: show list of server and storage node versions (install not performed)")
.append("\n");
usage.append("\t--setupdb, -b: only perform database schema creation or update").append("\n");
usage.append("\t--updatestorageschema, -u: only perform storage cluster schema update").append("\n");
usage.append("\t--upgrade, -g: this is an upgrade installation (as opposed to a new install)").append("\n");
usage
.append("\t--encodevalue, -e: prompts for password or value to encode for editing configuration files for agent or server");
usage.append("\n");
LOG.info(usage);
}
private WhatToDo[] processArguments(String[] args) throws Exception {
String sopts = "-:HD:h:p:e:buflt";
LongOpt[] lopts = { new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'H'),
new LongOpt("host", LongOpt.REQUIRED_ARGUMENT, null, 'h'),
new LongOpt("port", LongOpt.REQUIRED_ARGUMENT, null, 'p'),
new LongOpt("encodevalue", LongOpt.NO_ARGUMENT, null, 'e'),
new LongOpt("setupdb", LongOpt.NO_ARGUMENT, null, 'b'),
new LongOpt("updatestorageschema", LongOpt.NO_ARGUMENT, null, 'u'),
new LongOpt("clearcolumnfamilies", LongOpt.NO_ARGUMENT, null, 'c'),
new LongOpt("upgrade", LongOpt.NO_ARGUMENT, null, 'g'),
new LongOpt("listservers", LongOpt.NO_ARGUMENT, null, 'l'),
new LongOpt("listversions", LongOpt.NO_ARGUMENT, null, 'v'),
new LongOpt("force", LongOpt.NO_ARGUMENT, null, 'f'), new LongOpt("test", LongOpt.NO_ARGUMENT, null, 't') };
boolean test = false;
boolean listservers = false;
boolean listversions = false;
boolean setupdb = false;
boolean upgrade = false;
boolean clearcolumnfamilies = false;
boolean updatestorage = false;
String valueToEncode = null;
String associatedProperty = null;
Getopt getopt = new Getopt("installer", args, sopts, lopts);
int code;
while ((code = getopt.getopt()) != -1) {
switch (code) {
case ':':
case '?': {
// for now both of these should exit
LOG.error("Invalid option");
return new WhatToDo[] { WhatToDo.DISPLAY_USAGE };
}
case 1: {
// this will catch non-option arguments (which we don't currently support)
LOG.error("Unknown option: " + getopt.getOptarg());
return new WhatToDo[] { WhatToDo.DISPLAY_USAGE };
}
case 'H': {
return new WhatToDo[] { WhatToDo.DISPLAY_USAGE };
}
case 'D': {
// set a system property
String sysprop = getopt.getOptarg();
int i = sysprop.indexOf("=");
String name;
String value;
if (i == -1) {
name = sysprop;
value = "true";
} else {
name = sysprop.substring(0, i);
value = sysprop.substring(i + 1, sysprop.length());
}
System.setProperty(name, value);
LOG.info("System property set: " + name + "=" + value);
break;
}
case 'h': {
String hostString = getopt.getOptarg();
if (hostString == null) {
throw new IllegalArgumentException("Missing host value");
}
this.installerConfig.setManagementHost(hostString);
break;
}
case 'p': {
String portString = getopt.getOptarg();
if (portString == null) {
throw new IllegalArgumentException("Missing port value");
}
this.installerConfig.setManagementPort(Integer.parseInt(portString));
break;
}
case 'e': {
// Prompt for the property and value to be encoded.
// Don't use a command line option because the plain text password
// could get captured in command history.
Console console = System.console();
if (null != console) {
associatedProperty = "rhq.autoinstall.server.admin.password";
if (!confirm(console, "Property " + associatedProperty)) {
associatedProperty = "rhq.server.database.password";
if (!confirm(console, "Property " + associatedProperty)) {
associatedProperty = ask(console, "Property: ");
}
}
String prompt = "Value: ";
if (associatedProperty != null && associatedProperty.toLowerCase().contains("password")) {
prompt = "Password: ";
}
valueToEncode = String.valueOf(console.readLine("%s", prompt));
} else {
LOG.error("NO CONSOLE!");
}
break;
}
case 'b': {
setupdb = true;
break; // don't return, in case we need to allow more args
}
case 'g': {
upgrade = true;
break; // don't return, in case we need to allow more args
}
case 'u': {
updatestorage = true;
break; // don't return, in case we need to allow more args
}
case 'c': {
clearcolumnfamilies = true;
break;
}
case 'f': {
this.installerConfig.setForceInstall(true);
break; // don't return, in case we need to allow more args
}
case 'l': {
listservers = true;
break; // don't return, we need to allow more args to be processed, like -p or -h
}
case 't': {
test = true;
break; // don't return, we need to allow more args to be processed, like -p or -h
}
case 'v': {
listversions = true;
break; // don't return, we need to allow more args to be processed, like -p or -h
}
}
}
// if value encoding was asked, that's all we do on the execution
if (valueToEncode != null) {
String encodedValue;
if ("rhq.autoinstall.server.admin.password".equals(associatedProperty)) {
encodedValue = CryptoUtil.createPasswordHash("MD5", CryptoUtil.BASE64_ENCODING, null, null,
valueToEncode);
} else {
encodedValue = new InstallerServiceImpl(installerConfig).obfuscatePassword(String
.valueOf(valueToEncode));
}
System.out.println(" ");
System.out.println(" ");
if ("rhq.server.database.password".equals(associatedProperty)
|| "rhq.autoinstall.server.admin.password".equals(associatedProperty)
|| "rhq.storage.password".equals(associatedProperty)) {
System.out.println("Encoded password for rhq-server.properties:");
System.out.println(" " + associatedProperty + "=" + encodedValue);
System.out.println(" ");
} else {
String prompt = "value";
if (associatedProperty != null && associatedProperty.toLowerCase().contains("password")) {
prompt = "password";
}
System.out.println("!!! WARNING !!!");
System.out
.println("Both standalone-full.xml and rhq-server.properties need to be updated if a property from rhq-server.properties is used in standalone-full.xml");
System.out.println("!!! WARNING !!!");
System.out.println(" ");
System.out.println("Encoded " + prompt + " for rhq-server.properties:");
System.out.println(" " + associatedProperty + "=RESTRICTED::" + encodedValue);
System.out.println(" ");
System.out.println("Encoded " + prompt + " for standalone-full.xml with selected " + prompt
+ " as default:");
System.out.println(" ${VAULT::restricted::" + associatedProperty + "::" + encodedValue + "}");
System.out.println(" ");
System.out.println("Encoded " + prompt + " for standalone-full.xml without default:");
System.out.println(" ${VAULT::restricted::" + associatedProperty + ":: }");
System.out.println(" ");
System.out.println("Encoded " + prompt + " for agent-configuration.xml:");
System.out.println(" <entry key=\"" + associatedProperty + "\" value=\"RESTRICTED::" + encodedValue
+ "\" />");
System.out.println(" ");
}
System.out.println("Please consult the documentation for additional help.");
System.out.println(" ");
return new WhatToDo[] { WhatToDo.DO_NOTHING };
}
if (test || setupdb || updatestorage || listservers || listversions || clearcolumnfamilies) {
ArrayList<WhatToDo> whatToDo = new ArrayList<WhatToDo>();
if (test) {
whatToDo.add(WhatToDo.TEST);
}
if (setupdb) {
whatToDo.add(WhatToDo.SETUPDB);
}
if (updatestorage) {
whatToDo.add(WhatToDo.UPDATESTORAGESCHEMA);
}
if(clearcolumnfamilies) {
whatToDo.add(WhatToDo.CLEARCOLUMNFAMILIES);
}
if (listservers) {
whatToDo.add(WhatToDo.LIST_SERVERS);
}
if (listversions) {
whatToDo.add(WhatToDo.LIST_VERSIONS);
}
return whatToDo.toArray(new WhatToDo[whatToDo.size()]);
}
return new WhatToDo[] { (upgrade ? WhatToDo.UPGRADE : WhatToDo.INSTALL) };
}
private String ask(Console console, String prompt) {
String response = "";
do {
response = String.valueOf(console.readLine("%s", prompt).trim());
} while (response.isEmpty());
return response;
}
private boolean confirm(Console console, String option) {
String response = "";
do {
response = ask(console, option + " [y/n]: ").trim().toLowerCase();
} while (!(response.startsWith("y") || response.startsWith("n")));
return response.startsWith("y");
}
}