com.spotify.helios.cli.CliParser Maven / Gradle / Ivy
/*-
* -\-\-
* Helios Tools
* --
* Copyright (C) 2016 Spotify AB
* --
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* -/-/-
*/
package com.spotify.helios.cli;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.addAll;
import static com.google.common.collect.Iterables.filter;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static net.sourceforge.argparse4j.impl.Arguments.SUPPRESS;
import static net.sourceforge.argparse4j.impl.Arguments.append;
import static net.sourceforge.argparse4j.impl.Arguments.storeTrue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.spotify.helios.cli.command.CliCommand;
import com.spotify.helios.cli.command.DeploymentGroupCreateCommand;
import com.spotify.helios.cli.command.DeploymentGroupInspectCommand;
import com.spotify.helios.cli.command.DeploymentGroupListCommand;
import com.spotify.helios.cli.command.DeploymentGroupRemoveCommand;
import com.spotify.helios.cli.command.DeploymentGroupStatusCommand;
import com.spotify.helios.cli.command.DeploymentGroupStopCommand;
import com.spotify.helios.cli.command.DeploymentGroupWatchCommand;
import com.spotify.helios.cli.command.HostDeregisterCommand;
import com.spotify.helios.cli.command.HostListCommand;
import com.spotify.helios.cli.command.HostRegisterCommand;
import com.spotify.helios.cli.command.JobCreateCommand;
import com.spotify.helios.cli.command.JobDeployCommand;
import com.spotify.helios.cli.command.JobHistoryCommand;
import com.spotify.helios.cli.command.JobInspectCommand;
import com.spotify.helios.cli.command.JobListCommand;
import com.spotify.helios.cli.command.JobRemoveCommand;
import com.spotify.helios.cli.command.JobStartCommand;
import com.spotify.helios.cli.command.JobStatusCommand;
import com.spotify.helios.cli.command.JobStopCommand;
import com.spotify.helios.cli.command.JobUndeployCommand;
import com.spotify.helios.cli.command.JobWatchCommand;
import com.spotify.helios.cli.command.MasterListCommand;
import com.spotify.helios.cli.command.RollingUpdateCommand;
import com.spotify.helios.cli.command.VersionCommand;
import com.spotify.helios.common.LoggingConfig;
import com.spotify.helios.common.Version;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Argument;
import net.sourceforge.argparse4j.inf.ArgumentGroup;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.FeatureControl;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import net.sourceforge.argparse4j.inf.Subparsers;
public class CliParser {
private static final String NAME_AND_VERSION = "Spotify Helios CLI " + Version.POM_VERSION;
private static final String TESTED_DOCKER_VERSION = "1.12.1";
private static final String HELP_ISSUES =
"Report improvements/bugs at https://github.com/spotify/helios/issues";
private static final String HELP_WIKI =
"For documentation see https://github.com/spotify/helios/tree/master/docs";
private final Namespace options;
private final CliCommand command;
private final LoggingConfig loggingConfig;
private final Subparsers commandParsers;
private final CliConfig cliConfig;
private final List targets;
private final String username;
private boolean json;
public CliParser(final String... args)
throws ArgumentParserException, IOException, URISyntaxException {
final ArgumentParser parser = ArgumentParsers.newArgumentParser("helios")
.defaultHelp(true)
.version(format("%s%nTested on Docker %s", NAME_AND_VERSION, TESTED_DOCKER_VERSION))
.description(format("%s%n%n%s%n%s", NAME_AND_VERSION, HELP_ISSUES, HELP_WIKI));
cliConfig = CliConfig.fromUserConfig(System.getenv());
final GlobalArgs globalArgs = addGlobalArgs(parser, cliConfig, true);
commandParsers = parser.addSubparsers()
.metavar("COMMAND")
.title("commands");
setupCommands();
if (args.length == 0) {
parser.printHelp();
throw new ArgumentParserException(parser);
}
try {
this.options = parser.parseArgs(args);
} catch (ArgumentParserException e) {
handleError(parser, e);
throw e;
}
this.command = options.get("command");
final String username = options.getString(globalArgs.usernameArg.getDest());
this.username = (username == null) ? cliConfig.getUsername() : username;
this.json = equal(options.getBoolean(globalArgs.jsonArg.getDest()), true);
this.loggingConfig = new LoggingConfig(options.getInt(globalArgs.verbose.getDest()),
false, null,
options.getBoolean(globalArgs.noLogSetup.getDest()));
// Merge domains and explicit endpoints into master endpoints
final List explicitEndpoints = options.getList(globalArgs.masterArg.getDest());
final List domains = options.getList(globalArgs.domainsArg.getDest());
final String srvName = options.getString(globalArgs.srvNameArg.getDest());
// Order of target precedence:
// 1. endpoints from command line
// 2. domains from command line
// 3. endpoints from config file
// 4. domains from config file
// TODO (dano): complex, refactor and unit test it
this.targets = computeTargets(parser, explicitEndpoints, domains, srvName);
}
private List computeTargets(final ArgumentParser parser,
final List explicitEndpoints,
final List domainsArguments,
final String srvName) {
if (explicitEndpoints != null && !explicitEndpoints.isEmpty()) {
final List targets = Lists.newArrayListWithExpectedSize(explicitEndpoints.size());
for (final String endpoint : explicitEndpoints) {
targets.add(Target.from(URI.create(endpoint)));
}
return targets;
} else if (domainsArguments != null && !domainsArguments.isEmpty()) {
final Iterable domains = parseDomains(domainsArguments);
return Target.from(srvName, domains);
} else if (!cliConfig.getMasterEndpoints().isEmpty()) {
final List cliConfigMasterEndpoints = cliConfig.getMasterEndpoints();
final List targets = Lists.newArrayListWithExpectedSize(
cliConfigMasterEndpoints.size());
for (final URI endpoint : cliConfigMasterEndpoints) {
targets.add(Target.from(endpoint));
}
return targets;
} else if (!cliConfig.getDomainsString().isEmpty()) {
final Iterable domains = parseDomainsString(cliConfig.getDomainsString());
return Target.from(srvName, domains);
}
handleError(parser, new ArgumentParserException(
"no masters specified. Use the -z or -d option to specify which helios "
+ "cluster/master to connect to", parser));
return ImmutableList.of();
}
private Iterable parseDomains(final List domainStrings) {
final Set domains = Sets.newLinkedHashSet();
for (final String s : domainStrings) {
addAll(domains, parseDomainsString(s));
}
return domains;
}
private Iterable parseDomainsString(final String domainsString) {
return filter(asList(domainsString.split(",")), not(equalTo("")));
}
private void setupCommands() {
// Job commands
new JobCreateCommand(parse("create"));
new JobRemoveCommand(parse("remove"));
new JobInspectCommand(parse("inspect"));
new JobDeployCommand(parse("deploy"));
new JobUndeployCommand(parse("undeploy"));
new JobStartCommand(parse("start"));
new JobStopCommand(parse("stop"));
new JobHistoryCommand(parse("history"));
new JobListCommand(parse("jobs"));
new JobStatusCommand(parse("status"));
new JobWatchCommand(parse("watch"));
// Host commands
new HostListCommand(parse("hosts"));
new HostRegisterCommand(parse("register"));
new HostDeregisterCommand(parse("deregister"));
// Master commands
new MasterListCommand(parse("masters"));
// Deployment group commands
new DeploymentGroupCreateCommand(parse("create-deployment-group"));
new DeploymentGroupRemoveCommand(parse("remove-deployment-group"));
new DeploymentGroupListCommand(parse("list-deployment-groups"));
new DeploymentGroupInspectCommand(parse("inspect-deployment-group"));
new DeploymentGroupStatusCommand(parse("deployment-group-status"));
new DeploymentGroupWatchCommand(parse("watch-deployment-group"));
new RollingUpdateCommand(parse("rolling-update"));
new DeploymentGroupStopCommand(parse("stop-deployment-group"));
// Version Command
final Subparser version = parse("version").help("print version of master and client");
new VersionCommand(version);
}
/**
* Use this instead of calling parser.handle error directly. This will print a header with
* links to jira and documentation before the standard error message is printed.
*
* @param parser the parser which will print the standard error message
* @param ex the exception that will be printed
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
private void handleError(ArgumentParser parser, ArgumentParserException ex) {
System.err.println("# " + HELP_ISSUES);
System.err.println("# " + HELP_WIKI);
System.err.println("# ---------------------------------------------------------------");
parser.handleError(ex);
}
public List getTargets() {
return targets;
}
public String getUsername() {
return username;
}
public boolean getJson() {
return json;
}
private static class GlobalArgs {
private final Argument masterArg;
private final Argument domainsArg;
private final Argument srvNameArg;
private final Argument usernameArg;
private final Argument googleCredentialsArg;
private final Argument verbose;
private final Argument noLogSetup;
private final Argument jsonArg;
private final ArgumentGroup globalArgs;
private final boolean topLevel;
GlobalArgs(final ArgumentParser parser, final CliConfig cliConfig) {
this(parser, cliConfig, false);
}
GlobalArgs(final ArgumentParser parser, final CliConfig cliConfig, final boolean topLevel) {
this.globalArgs = parser.addArgumentGroup("global options");
this.topLevel = topLevel;
masterArg = addArgument("-z", "--master")
.action(append())
.help("master endpoints");
domainsArg = addArgument("-d", "--domains")
.setDefault(new ArrayList<>())
.action(append())
.help("domains");
srvNameArg = addArgument("--srv-name")
.setDefault(cliConfig.getSrvName())
.help("master srv name");
usernameArg = addArgument("-u", "--username")
.setDefault(System.getProperty("user.name"))
.help("username");
googleCredentialsArg = addArgument("--google-credentials")
.type(Boolean.class)
.setDefault(true)
.help("enable authentication using access tokens derived from Google Credentials");
verbose = addArgument("-v", "--verbose")
.action(Arguments.count());
addArgument("--version")
.action(Arguments.version())
.help("print version");
jsonArg = addArgument("--json")
.action(storeTrue())
.help("json output");
noLogSetup = addArgument("--no-log-setup")
.action(storeTrue())
.help(SUPPRESS);
// note: because of the way the HeliosClient is constructed, these next arguments are
// read indirectly in cli/Utils.java:
addArgument("-k", "--insecure")
.action(storeTrue())
.help("Disables hostname verification of HTTPS connections. "
+ "Similar to 'curl -k'. "
+ "Useful when using -z flag to connect directly to a master using HTTPS which "
+ "presents a certificate whose subject does not match the actual hostname.");
// for http-timeout and retry-timeout, do not set a default value in the argument, so that
// envrionment variables can be inspected in the Utils client factory.
addArgument("--http-timeout")
.type(Integer.class)
.help("Timeout (in seconds) for each HTTP/S request to the master. "
+ "If this flag is not set, the value in the environment variable "
+ Utils.HTTP_TIMEOUT_ENV_VAR + " will be used. "
+ "If this environment variable is not set, then the default is "
+ Utils.DEFAULT_HTTP_TIMEOUT_SECS + " seconds.");
addArgument("--retry-timeout")
.type(Integer.class)
.help("Total timeout (in seconds) for all of the requests that helios makes to the "
+ "master. If an individual request fails, helios will retry the request again "
+ "until successful or until this timeout elapses. "
+ "If this flag is not set, the value in the environment variable "
+ Utils.TOTAL_TIMEOUT_ENV_VAR + " will be used. "
+ "If this environment variable is not set, then the default is "
+ Utils.DEFAULT_TOTAL_TIMEOUT_SECS + " seconds.");
}
private Argument addArgument(final String... nameOrFlags) {
final FeatureControl defaultControl = topLevel ? null : SUPPRESS;
return globalArgs.addArgument(nameOrFlags).setDefault(defaultControl);
}
}
private GlobalArgs addGlobalArgs(final ArgumentParser parser, final CliConfig cliConfig) {
return new GlobalArgs(parser, cliConfig);
}
private GlobalArgs addGlobalArgs(final ArgumentParser parser, final CliConfig cliConfig,
final boolean topLevel) {
return new GlobalArgs(parser, cliConfig, topLevel);
}
public Namespace getNamespace() {
return options;
}
public CliCommand getCommand() {
return command;
}
public LoggingConfig getLoggingConfig() {
return loggingConfig;
}
private Subparser parse(final String name) {
return parse(commandParsers, name);
}
private Subparser parse(final Subparsers subparsers, final String name) {
final Subparser subparser = subparsers.addParser(name, true);
addGlobalArgs(subparser, cliConfig);
return subparser;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy