/*
* Copyright (C) 2012 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.setup.console.cmd;
import com.intel.dcsg.cpg.configuration.PropertiesConfiguration;
import com.intel.dcsg.cpg.extensions.Extensions;
import com.intel.dcsg.cpg.extensions.ImplementationRegistrar;
import com.intel.dcsg.cpg.extensions.Registrar;
import com.intel.dcsg.cpg.util.PascalCaseNamingStrategy;
import com.intel.dcsg.cpg.validation.Fault;
import com.intel.mtwilson.launcher.ExtensionCacheLauncher;
import com.intel.dcsg.cpg.console.Command;
import com.intel.mtwilson.MyFilesystem;
import com.intel.mtwilson.launcher.ExtensionDirectoryLauncher;
import com.intel.mtwilson.setup.ConfigurationException;
import com.intel.mtwilson.setup.SetupException;
import com.intel.mtwilson.setup.SetupTask;
import com.intel.mtwilson.setup.ValidationException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
/**
* This setup command is a bridge between mtwilson-console and the new
* mtwilson-setup tasks
*
* Usage: setup [--force] [task1 task2 ... taskN]
*
* Specifying task names is optional; you can specify one or more tasks
* as individual arguments to be run. If you don't specify tasks then all
* tasks will be run.
*
* If a task is already configured and validated it will be skipped,
* unless you provide the --force option which will run tasks even if
* they are already validated.
*
* If you want only to validate the configuration but not execute any of
* the tasks, use the --noexec option.
*
* @author jbuhacoff
*/
public class SetupManager implements Command {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SetupManager.class);
private org.apache.commons.configuration.Configuration options = null;
@Override
public void setOptions(org.apache.commons.configuration.Configuration options) {
this.options = options;
}
/**
* This method returns false by default with the assumption that
* if a setup task has already been completed the administrator
* wants to skip it.
*
* @return default false
*/
protected boolean isForceEnabled() {
return options.getBoolean("force", false);
}
/**
* This method returns true by default with the assumption that
* the administrator WANTS to run the setup tasks. To do a
* dry run (configure and validate but no execution) use the
* @{code --no-exec} option to disable execution.
* @return default true
*/
protected boolean isExecEnabled() {
return options.getBoolean("exec", true);
}
/**
* This method returns false by default
* with the assumption that setup tasks
* are ordered and that later tasks depend on the results of earlier tasks;
* therefore if an earlier task fails the entire set should stop because
* trying subsequent tasks will likely only generate precondition
* errors that are
* distracting from the root cause.
*
* @return default false
*/
protected boolean continueAfterRuntimeException() {
return options.getBoolean("continue", false);
}
/**
* loading extensions listed in extensions.cache is fast but if
* the user is trying to run a newly added setup task then it would
* not be found there
* by adding the option --no-ext-cache the user can force scanning
* of all jar files in the classpath for extensions
* @return
*/
protected boolean useExtensionCacheFile() {
return options.getBoolean("ext-cache", true);
}
protected File getConfigurationFile() {
File file = new File(MyFilesystem.getApplicationFilesystem().getConfigurationPath() + File.separator + "mtwilson.properties");
return file;
}
protected void registerExtensions() {
if( useExtensionCacheFile() ) {
ExtensionCacheLauncher launcher = new ExtensionCacheLauncher();
launcher.setRegistrars(new Registrar[]{new ImplementationRegistrar(SetupTask.class)});
launcher.run(); // loads application jars, scans extension jars for the plugins as specified by getRegistrars()
}
else {
ExtensionDirectoryLauncher launcher = new ExtensionDirectoryLauncher();
launcher.setRegistrars(new Registrar[]{new ImplementationRegistrar(SetupTask.class)});
// by default the application java folder will be scanned
// which is /opt/mtwilson/java or /opt/trustagent/java
// but user can choose to scan another folder by specifying the
// --ext-java=/path/to/folder option
// note that this only makes sense when --no-ext-cache is also used
if( options.getString("ext-java") != null ) {
launcher.setJavaFolder(new File(options.getString("ext-java")));
}
launcher.run(); // loads application jars, scans extension jars for the plugins as specified by getRegistrars()
}
}
/**
* Optional arguments are the task names to execute; if not provided then
* all available tasks will be executed in a pre-defined order (see
* SetupTaskFactory invoked by subclasses of this command)
*
* @param args
* @throws Exception
*/
@Override
public void execute(String[] args) throws Exception {
registerExtensions();
// now find the setup tasks that the user has asked for or use a default set
if (args.length == 0) {
log.error("One or more tasks must be specified");
// execute(getAllSetupTasks());
return;
}
execute(getSetupTasksByName(args));
}
protected List<SetupTask> getSetupTasksByName(String[] names) throws IOException {
ArrayList<String> tasksNotFound = new ArrayList<>();
ArrayList<SetupTask> tasks = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
String hyphenatedTaskName = names[i];
SetupTask task = findSetupTaskByName(hyphenatedTaskName);
if (task == null) {
log.error("Setup task not found: {}", hyphenatedTaskName);
tasksNotFound.add(hyphenatedTaskName);
} else {
tasks.add(task);
}
}
if (!tasksNotFound.isEmpty()) {
throw new IOException("Unknown tasks: " + StringUtils.join(tasksNotFound, ", "));
}
return tasks;
}
protected List<SetupTask> getAllSetupTasks() throws IOException {
/*
List<SetupTask> tasks = Extensions.findAll(SetupTask.class);
for (SetupTask task : tasks) {
execute(task);
}
*/
return Collections.EMPTY_LIST;
}
protected Map<String, String> getConversionMap() {
HashMap<String, String> map = new HashMap<>();
map.put("mtwilson", "MtWilson"); // so a name like download-mtwilson-tls-certificate will be translated to DownloadMtWilsonTlsCertificate instead of DownloadMtwilsonTlsCertificate (notice the 'w' in MtWilson)
map.put("ca", "CA"); // privacy-ca becomes PrivacyCA, endorsement-ca becomes EndorsementCA, etc.
return map;
}
protected SetupTask findSetupTaskByName(String hyphenatedTaskName) throws IOException {
PascalCaseNamingStrategy namingStrategy = new PascalCaseNamingStrategy(getConversionMap());
String className = namingStrategy.toPascalCase(hyphenatedTaskName);
List<SetupTask> tasks = Extensions.findAll(SetupTask.class);
for (SetupTask task : tasks) {
if (task.getClass().getSimpleName().equals(className)) {
// found it!
return task;
}
}
return null;
}
protected void execute(SetupTask... tasks) throws IOException {
execute(Arrays.asList(tasks));
}
protected void execute(List<SetupTask> tasks) throws IOException {
PropertiesConfiguration properties = loadConfiguration();
// Configuration env = new KeyTransformerConfiguration(new AllCapsNamingStrategy(), new EnvironmentConfiguration()); // transforms mtwilson.ssl.cert.sha1 to MTWILSON_SSL_CERT_SHA1
// MutableCompositeConfiguration configuration = new MutableCompositeConfiguration(properties, env);
boolean error = false;
try {
for (SetupTask setupTask : tasks) {
String taskName = setupTask.getClass().getSimpleName();
setupTask.setConfiguration(properties);
// log.debug("set tpm owner password {} for task {}", properties.getString("tpm.owner.secret"), taskName);
try {
if( setupTask.isConfigured() && setupTask.isValidated() && !isForceEnabled() ) {
log.debug("Skipping {}", taskName);
}
else if( !isExecEnabled() ) {
// only show validation errors, don't run the task
List<Fault> configurationFaults = setupTask.getConfigurationFaults();
for (Fault fault : configurationFaults) {
log.warn("Configuring {}: {}", taskName, fault.toString());
}
List<Fault> validationFaults = setupTask.getValidationFaults();
for (Fault fault : validationFaults) {
log.warn("Validating {}: {}", taskName, fault.toString());
}
}
else {
for (Fault fault : setupTask.getConfigurationFaults()) {
log.debug("Configuration check in {}: {}", taskName, fault.toString());
}
for (Fault fault : setupTask.getValidationFaults()) {
log.debug("Validation check in {}: {}", taskName, fault.toString());
}
log.debug("Running {}", taskName);
try {
setupTask.run(); // calls isConfigured and isValidated automatically and throws ConfigurationException, SetupException, or ValidationException on error
}
catch(IllegalStateException e) {
error = true;
log.error("Error from {}: {}", taskName, e.getMessage());
List<Fault> configurationFaults = setupTask.getConfigurationFaults();
for (Fault fault : configurationFaults) {
log.warn("Configuring {}: {}", taskName, fault.toString());
}
List<Fault> validationFaults = setupTask.getValidationFaults();
for (Fault fault : validationFaults) {
log.warn("Validating {}: {}", taskName, fault.toString());
}
if( !continueAfterRuntimeException() ) {
break;
}
}
}
} catch (ConfigurationException e) {
error = true;
log.error("Cannot configure {}: {}", taskName, e.getMessage());
log.debug("Configuration error", e);
} catch (ValidationException e) {
error = true;
log.error("Cannot validate {}: {}", taskName, e.getMessage());
log.debug("Validation error", e);
} catch (SetupException e) {
error = true;
log.error("Cannot run {}: {}", taskName, e.getMessage());
log.debug("Runtime error", e); // debug stack trace
}
}
} catch (Exception e) {
error = true;
log.error("Setup error: {}", e.getMessage());
log.debug("Setup error", e);
}
storeConfiguration(properties);
if( error ) {
// the main application (cpg-console Main) will print this
// message and return a non-zero exit code
throw new IllegalStateException("Encountered errors during setup");
}
}
protected PropertiesConfiguration loadConfiguration() throws IOException {
File file = getConfigurationFile();
if (file.exists()) {
log.debug("Loading configuration file {}", file.getAbsolutePath());
try (FileInputStream in = new FileInputStream(file)) {
Properties properties = new Properties();
properties.load(in);
PropertiesConfiguration configuration = new PropertiesConfiguration(properties);
return configuration;
}
}
return new PropertiesConfiguration();
}
protected void storeConfiguration(PropertiesConfiguration configuration) throws IOException {
// write the configuration back to disk
log.debug("Starting store configuration to file");
File file = getConfigurationFile();
try (FileOutputStream out = new FileOutputStream(file)) {
Properties properties = beforeStore(configuration.getProperties());
properties.store(out, "saved by mtwilson setup");
}
log.debug("Finished store configuration to file");
}
/**
*
* @param properties
* @return must not return null
*/
protected Properties beforeStore(Properties properties) {
return properties;
}
}