/* * JBoss, Home of Professional Open Source * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors * as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package org.jboss.as.cli.scriptsupport; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.concurrent.Callable; import org.jboss.as.cli.CliInitializationException; import org.jboss.as.cli.CommandContext; import org.jboss.as.cli.CommandContextFactory; import org.jboss.as.cli.CommandLineException; import org.jboss.as.cli.impl.CommandContextConfiguration; import org.jboss.as.cli.operation.impl.DefaultCallbackHandler; import org.jboss.as.cli.parsing.operation.OperationFormat; import org.jboss.dmr.ModelNode; /** * This class is intended to be used with JVM-based scripting languages. It acts * as a facade to the CLI public API, providing a single class that can be used * to connect, run CLI commands, and disconnect. It also removes the need to * catch checked exceptions. * * @author Stan Silvert ssilvert@redhat.com (C) 2012 Red Hat Inc. */ public class CLI { private CommandContext ctx; // This parsedCommand is required in order to determinate if the command // is an operation or a command private final DefaultCallbackHandler handler = new DefaultCallbackHandler(true); private CLI() { // only allow new instances from newInstance() method. initOfflineContext(); } /** * Create a new CLI instance. * * @return The CLI instance. */ public static CLI newInstance() { return new CLI(); } /** * Return the CLI CommandContext. This allows a script developer full access * to CLI facilities if needed. CommandContext can mute during CLI lifetime. * An unconnected CLI instance has a context that handles offline commands. * When connected to a server, the original context is replaced with a * context allowing to interact with the remote server. * * @return The CommandContext. */ public CommandContext getCommandContext() { return ctx; } /** * Connect to the server using the default host and port. */ public void connect() { doConnect(() -> { return CommandContextFactory.getInstance().newCommandContext(); }); } /** * Connect to the server using the default host and port. * * @param username The user name for logging in. * @param password The password for logging in. */ public void connect(String username, char[] password) { doConnect(() -> { return CommandContextFactory.getInstance(). newCommandContext(username, password); }); } /** * Connect to the server using a specified host and port. * * @param controllerHost The host name. * @param controllerPort The port. * @param username The user name for logging in. * @param password The password for logging in. */ public void connect(String controller, String username, char[] password) { connect(controller, username, password, null); } /** * Connect to the server using a specified host and port. * * @param controller * @param username The user name for logging in. * @param clientBindAddress * @param password The password for logging in. */ public void connect(String controller, String username, char[] password, String clientBindAddress) { doConnect(() -> { return CommandContextFactory.getInstance(). newCommandContext(new CommandContextConfiguration.Builder() .setController(controller) .setUsername(username) .setPassword(password) .setClientBindAddress(clientBindAddress) .build()); }); } /** * Connect to the server using a specified host and port. * * @param controllerHost The host name. * @param controllerPort The port. * @param username The user name for logging in. * @param password The password for logging in. */ public void connect(String controllerHost, int controllerPort, String username, char[] password) { connect("remote+http", controllerHost, controllerPort, username, password, null); } /** * Connect to the server using a specified host and port. * * @param controllerHost The host name. * @param controllerPort The port. * @param username The user name for logging in. * @param password The password for logging in. * @param clientBindAddress the client bind address. */ public void connect(String controllerHost, int controllerPort, String username, char[] password, String clientBindAddress) { connect("remote+http", controllerHost, controllerPort, username, password, clientBindAddress); } /** * Connect to the server using a specified host and port. * @param protocol The protocol * @param controllerHost The host name. * @param controllerPort The port. * @param username The user name for logging in. * @param password The password for logging in. */ public void connect(String protocol, String controllerHost, int controllerPort, String username, char[] password) { connect(protocol, controllerHost, controllerPort, username, password, null); } /** * Connect to the server using a specified host and port. * @param protocol The protocol * @param controllerHost The host name. * @param controllerPort The port. * @param username The user name for logging in. * @param password The password for logging in. */ public void connect(String protocol, String controllerHost, int controllerPort, String username, char[] password, String clientBindAddress) { doConnect(() -> { return CommandContextFactory.getInstance().newCommandContext( new CommandContextConfiguration.Builder(). setController(constructUri(protocol, controllerHost, controllerPort)) .setUsername(username) .setPassword(password) .setClientBindAddress(clientBindAddress) .build()); }); } /** * Disconnect from the server. */ public void disconnect() { try { if (!isConnected()) { throw new IllegalStateException("Not connected to server."); } ctx.terminateSession(); } finally { // Back to offline context initOfflineContext(); } } /** * Execute a CLI command. This can be any command that you might execute on * the CLI command line, including both server-side operations and local * commands such as 'cd' or 'cn'. * * @param cliCommand A CLI command. * @return A result object that provides all information about the execution * of the command. */ public Result cmd(String cliCommand) { try { // The intent here is to return a Response when this is doable. if (ctx.isWorkflowMode() || ctx.isBatchMode()) { ctx.handle(cliCommand); return new Result(cliCommand, ctx.getExitCode()); } handler.parse(ctx.getCurrentNodePath(), cliCommand, ctx); if (handler.getFormat() == OperationFormat.INSTANCE) { ModelNode request = ctx.buildRequest(cliCommand); ModelNode response = ctx.execute(request, cliCommand); return new Result(cliCommand, request, response); } else { ctx.handle(cliCommand); return new Result(cliCommand, ctx.getExitCode()); } } catch (CommandLineException cfe) { throw new IllegalArgumentException("Error handling command: " + cliCommand, cfe); } catch (IOException ioe) { throw new IllegalStateException("Unable to send command " + cliCommand + " to server.", ioe); } } private String constructUri(final String protocol, final String host, final int port) { try { URI uri = new URI(protocol, null, host, port, null, null, null); // String the leading '//' if there is no protocol. return protocol == null ? uri.toString().substring(2) : uri.toString(); } catch (URISyntaxException e) { throw new IllegalStateException("Unable to construct URI.", e); } } private void initOfflineContext() { try { ctx = CommandContextFactory.getInstance().newCommandContext(); } catch (CliInitializationException e) { throw new IllegalStateException("Unable to initialize " + "command context.", e); } } private boolean isConnected() { return ctx.getConnectionInfo() != null; } private void doConnect(Callable<CommandContext> callable) { if (isConnected()) { throw new IllegalStateException("Already connected to server."); } CommandContext newContext = null; try { newContext = callable.call(); newContext.connectController(); } catch (Exception ex) { if (newContext != null) { newContext.terminateSession(); } if (ex instanceof CliInitializationException) { throw new IllegalStateException("Unable to initialize " + "command context.", ex); } if (ex instanceof CommandLineException) { throw new IllegalStateException("Unable to connect " + "to controller.", ex); } throw new IllegalStateException(ex); } ctx = newContext; } /** * The Result class provides all information about an executed CLI command. */ public class Result { private final String cliCommand; private ModelNode request; private ModelNode response; private boolean isSuccess = false; private boolean isLocalCommand = false; Result(String cliCommand, ModelNode request, ModelNode response) { this.cliCommand = cliCommand; this.request = request; this.response = response; this.isSuccess = response.get("outcome").asString().equals("success"); } Result(String cliCommand, int exitCode) { this.cliCommand = cliCommand; this.isSuccess = exitCode == 0; this.isLocalCommand = true; } /** * Return the original command as a String. * @return The original CLI command. */ public String getCliCommand() { return this.cliCommand; } /** * If the command resulted in a server-side operation, return the * ModelNode representation of the operation. * * @return The request as a ModelNode, or <code>null</code> if this was * a local command. */ public ModelNode getRequest() { return this.request; } /** * If the command resulted in a server-side operation, return the * ModelNode representation of the response. * * @return The server response as a ModelNode, or <code>null</code> if * this was a local command. */ public ModelNode getResponse() { return this.response; } /** * Return true if the command was successful. For a server-side * operation, this is determined by the outcome of the operation on the * server side. * * @return <code>true</code> if the command was successful, * <code>false</code> otherwise. */ public boolean isSuccess() { return this.isSuccess; } /** * Return true if the command was only executed locally and did not * result in a server-side operation. * * @return <code>true</code> if the command was only executed locally, * <code>false</code> otherwise. */ public boolean isLocalCommand() { return this.isLocalCommand; } } }