/**
* DeployMan # Thomas Uhrig (Stuttgart, 2014) # www.tuhrig.de
*/
package de.tuhrig.deployman.interfaces;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import jline.console.ConsoleReader;
import jline.console.completer.StringsCompleter;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.Parser;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.PropertyConfigurator;
import de.tuhrig.deployman.DeployMan;
import de.tuhrig.deployman.aws.Aim;
import de.tuhrig.deployman.aws.Ec2;
import de.tuhrig.deployman.aws.Rds;
import de.tuhrig.deployman.docker.DockerRemoteClient;
import de.tuhrig.deployman.launch.FormationValidator;
import de.tuhrig.deployman.launch.Launcher;
import de.tuhrig.deployman.repo.ConfigRepository;
import de.tuhrig.deployman.repo.FileMonitor;
import de.tuhrig.deployman.repo.ImageRepository;
import de.tuhrig.deployman.repo.Repository;
import de.tuhrig.deployman.ssh.RemoteLog;
import de.tuhrig.deployman.ssh.SshClient;
import static de.tuhrig.deployman.DeployMan.*;
/**
* This is the command line interface for this projekt. It contains a main-method to run the project
* as a JAR-file from the command line. It create a command line parser with several options. This
* parser is used to parse the command given to the main method and to call the appropriate actions
* depending on the command. This class is the entry point to the application.
*
* @author tuhrig
*/
public class CommandLineInterface // NO_UCD (unused code)
{
/**
* A simple functional interface to implement the call-backs executed by each command. This
* interface is only used in this class and therefore locale to this class (and it's really small
* ;).
*/
private static interface Actor {
public void act(CommandLine cmd);
}
private static final String OPT_INFO = "info";
private static final String OPT_CONFIG = "config";
private static final String OPT_HELP = "help";
private static final String OPT_EXIT = "exit";
private static final String OPT_SSH = "ssh";
private static final String OPT_HEALTH = "health";
private static final String OPT_INSTANCES = "instances";
private static final String OPT_DATABASES = "databases";
private static final String OPT_TERMINATE = "kill";
private static final String OPT_RUN = "run";
private static final String OPT_TAG = "tag";
private static final String OPT_BIND = "bind";
private static final String OPT_LOG = "log";
private static final String OPT_VALIDATE = "validate";
private static final String OPT_INIT = "init";
private static final String OPT_REPO = "rp";
private static final String OPT_FORMATIONS = "formations";
private static final String OPT_MONITOR = "monitor";
private static final String OPT_SYNC = "sync";
private static final String OPT_UPLOAD_IMAGE = "upload_image";
private static final String OPT_UPLOAD_CONFIG = "upload_config";
private static final String OPT_CREATE_PROFILE = "create_profile";
private static final String OPT_OPEN_PORT = "open_port";
private static final String OPT_DOCKER_IMAGES = "docker_images";
private static final String OPT_DOCKER_CONTAINERS = "docker_containers";
private static final String OPT_DOCKER_INFO = "docker_info";
private static CommandLine cmd;
private static Options options = new Options();
private static Parser parser = new BasicParser();
private static Map<String, Actor> handlers = new HashMap<>();
public static void main(String[] args) throws Exception {
DeployMan.disableLogger();
DeployMan.readSystemProperties();
Properties props = new Properties();
props.load(new FileInputStream("log4j.properties"));
PropertyConfigurator.configure(props);
// if we start the program without any command line
// option, we start the interactive shell
if (args.length == 0) {
DeployMan.printTitle();
openRepl();
}
// if we have command line arguments, we execute these
// arguments directly (without printing the header to keep
// everything as short as possible)
else {
initializeOptions();
parse(args);
act();
}
}
private static void openRepl() throws IOException {
// we need to initialize the options first to
// enable the autocompletion which needs the
// names of the options (to complete them)
initializeOptions();
PrintWriter out = new PrintWriter(System.out);
ConsoleReader reader = new ConsoleReader();
reader.setBellEnabled(false);
reader.addCompleter(new StringsCompleter(handlers.keySet()));
reader.setHistoryEnabled(true);
do {
try {
String line = reader.readLine("$: ");
out.flush();
if (!line.startsWith(MINUS))
line = MINUS + line;
String[] args = line.split(BLANK);
console.newLine();
// writes the parsed arguments back to the console
// this is somehow good for debugging but a little
// bit much clutter
// console.write( "<< " + Arrays.toString( args ) );
// console.newLine();
initializeOptions();
parse(args);
act();
} catch (Exception e) {
e.printStackTrace();
console.newLine();
}
} while (true);
}
private static void act() throws Exception {
// if we have not option, the user just pressed enter
// and we display a new line just like in any other CMD
if (cmd.getOptions().length == 0)
return;
handlers.get(cmd.getOptions()[0].getOpt()).act(cmd);
}
private static void parse(String[] args) throws ParseException {
cmd = parser.parse(options, args);
}
@SuppressWarnings("nls")
private static void initializeOptions() {
// @formatter:off
//
//
// GENERAL
//
//
build(OPT_EXIT, "Exits the interactive shell.", cmd -> {
shutdown();
});
build(OPT_INFO, "Displayes information about Deploy-Man.", cmd -> {
DeployMan.printInfo();
});
build(OPT_CONFIG, "Configures Deploy-Man with a properties file.", cmd -> {
String file = value(OPT_CONFIG);
DeployMan.setUserPropertiesFile(file);
DeployMan.saveSystemProperties();
}, "file");
build(OPT_HELP, "Displayes this text.", cmd -> {
DeployMan.printHelp(options);
});
//
//
// REPO
//
//
build(OPT_REPO, "Shows information about the repository.", cmd -> {
String location = value(OPT_REPO);
new Repository().printInfo(location);
}, "location");
build(OPT_FORMATIONS, "Shows all available formations.", cmd -> {
new Repository().printInfo("formations");
});
build(OPT_INIT, "Creates a S3 bucket used to store images and configs.", cmd -> {
new Repository().init();
});
//
//
// DOCKER
//
//
build(OPT_DOCKER_IMAGES, "Displayes Docker images.", cmd -> {
String instance = value(OPT_DOCKER_IMAGES);
new DockerRemoteClient().printImages(instance);
}, "instance");
build(OPT_DOCKER_INFO, "Displayes Docker information.", cmd -> {
String instance = value(OPT_DOCKER_INFO);
new DockerRemoteClient().printInfo(instance);
}, "instance");
build(OPT_DOCKER_CONTAINERS, "Displayes Docker containers.", cmd -> {
String instance = value(OPT_DOCKER_CONTAINERS);
new DockerRemoteClient().printContainers(instance);
}, "instance");
//
//
// UPLOAD
//
//
build(OPT_MONITOR, "Monitors the locale images or configs (including deletion!) to S3.",
cmd -> {
String folder = value(OPT_MONITOR);
new FileMonitor(false).monitor(folder);
}, "monitor");
build(OPT_SYNC, "Syncs locale images or configs to S3.", cmd -> {
String folder = value(OPT_SYNC);
new FileMonitor().sync(folder);
}, "folder");
build(OPT_UPLOAD_IMAGE, "Uploads a image from the locale repository to the S3 bucket.",
cmd -> {
String file = value(OPT_UPLOAD_IMAGE);
new ImageRepository().uploadLocaleFile(file);
}, "file");
build(OPT_UPLOAD_CONFIG, "Uploads a config from the locale repository to the S3 bucket.",
cmd -> {
String file = value(OPT_UPLOAD_CONFIG);
new ConfigRepository().uploadLocaleFile(file);
}, "file");
//
//
// EC2
//
//
build(OPT_INSTANCES, "Displayes AWS EC2 instances.", cmd -> {
new Ec2().printEC2Instances();
});
build(OPT_TERMINATE, "Terminates an EC2 instance.", cmd -> {
String id = value(OPT_TERMINATE);
new Ec2().terminateEC2InstanceById(id);
}, "instance");
build(OPT_HEALTH, "Checks the health status of the instance.", cmd -> {
String instance = value(OPT_HEALTH);
console.printHealth(instance);
}, "instance");
build(OPT_LOG, "Prints the last log messages of the deployment log.", cmd -> {
String instance = value(OPT_LOG);
new RemoteLog().printLast10LinesOfDeployManLog(instance);
}, "instance");
build(OPT_TAG, "Tags an EC2 instance.", cmd -> {
String id = value(OPT_TAG, 0);
String tag = value(OPT_TAG, 1);
new Ec2().tag(id, tag);
}, "instance", "tag");
build(OPT_CREATE_PROFILE, "Creates a profile to use AWS S3 in AWS EC2.", cmd -> {
new Aim().createS3BucketProfile();
});
//
//
// SSH
//
//
build(OPT_SSH, "Opens a SSH session with the given machine.", cmd -> {
new SshClient().openSshShell(value(OPT_SSH));
}, "instance");
//
//
// PORT
//
//
build(OPT_OPEN_PORT, "Opens the port on the given security group.", cmd -> {
String[] params = values(OPT_OPEN_PORT);
String group = params[0];
int port = Integer.valueOf(params[1]);
new Ec2().openPort(group, port);
}, "group", "port");
build(OPT_BIND, "Binds the instance to the Elastic IP.", cmd -> {
String instance = value(OPT_BIND, 0);
String ip = value(OPT_BIND, 1);
new Ec2().associate(instance, ip);
}, "instance", "ip");
//
//
// DATABASES
//
//
build(OPT_DATABASES, "Displayes AWS RDBs instances.", cmd -> {
new Rds().printDatabases();
});
//
//
// LAUNCH
//
//
build(OPT_RUN, "Runs a formation file.", cmd -> {
String file = value(OPT_RUN);
new Launcher().run(file);
}, "file");
build(OPT_VALIDATE, "Validates a formation file and prints messages.", cmd -> {
String file = value(OPT_VALIDATE);
for (String message : new FormationValidator().validate(file))
console.write(message);
console.newLine();
}, "file");
// @formatter:on
}
@SuppressWarnings("static-access")
private static void build(String name, String description, Actor handler, String... options) {
int args = options.length;
String argNames = "";
if (args == 1)
argNames = options[0];
else
argNames = StringUtils.join(options, "> <");
Option option =
OptionBuilder.withArgName(argNames).hasArgs(args).withValueSeparator(' ')
.withDescription(description).create(name);
add(option, handler);
}
private static void shutdown() {
console.write("Shutdown...");
System.exit(0);
}
private static String value(String option, int index) {
return values(option)[index];
}
private static String[] values(String option) {
return cmd.getOptionValues(option);
}
private static String value(String option) {
return cmd.getOptionValue(option);
}
/**
* Add an Apache Commons CLI option and a handler object to call when the option occurs.
*/
@SuppressWarnings("nls")
private static void add(Option option, Actor handler) {
// @formatter:off
// decorate the actual handler with a handler
// which tracks the execution time
handlers.put(option.getOpt(), cmd -> {
Date before = new Date();
handler.act(cmd);
Date after = new Date();
console.write("[Done in " + (after.getTime() - before.getTime()) + "ms]");
console.newLine();
});
options.addOption(option);
// @formatter:on
}
}