Return-Path: X-Original-To: apmail-brooklyn-commits-archive@minotaur.apache.org Delivered-To: apmail-brooklyn-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 33BC71873D for ; Tue, 18 Aug 2015 11:01:09 +0000 (UTC) Received: (qmail 74074 invoked by uid 500); 18 Aug 2015 11:01:09 -0000 Delivered-To: apmail-brooklyn-commits-archive@brooklyn.apache.org Received: (qmail 74049 invoked by uid 500); 18 Aug 2015 11:01:09 -0000 Mailing-List: contact commits-help@brooklyn.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@brooklyn.incubator.apache.org Delivered-To: mailing list commits@brooklyn.incubator.apache.org Received: (qmail 74040 invoked by uid 99); 18 Aug 2015 11:01:09 -0000 Received: from Unknown (HELO spamd1-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 18 Aug 2015 11:01:09 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd1-us-west.apache.org (ASF Mail Server at spamd1-us-west.apache.org) with ESMTP id 6E2D4DEB98 for ; Tue, 18 Aug 2015 11:01:08 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd1-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 1.421 X-Spam-Level: * X-Spam-Status: No, score=1.421 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RP_MATCHES_RCVD=-0.381, URIBL_BLOCKED=0.001, WEIRD_QUOTING=0.001] autolearn=disabled Received: from mx1-eu-west.apache.org ([10.40.0.8]) by localhost (spamd1-us-west.apache.org [10.40.0.7]) (amavisd-new, port 10024) with ESMTP id XiFH-GEE5IhV for ; Tue, 18 Aug 2015 11:00:55 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-eu-west.apache.org (ASF Mail Server at mx1-eu-west.apache.org) with SMTP id E9DC925623 for ; Tue, 18 Aug 2015 11:00:21 +0000 (UTC) Received: (qmail 68834 invoked by uid 99); 18 Aug 2015 11:00:17 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 18 Aug 2015 11:00:17 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 0ACC5E057C; Tue, 18 Aug 2015 11:00:17 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: heneveld@apache.org To: commits@brooklyn.incubator.apache.org Date: Tue, 18 Aug 2015 11:00:49 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [34/64] incubator-brooklyn git commit: [BROOKLYN-162] Refactor package in ./core/util http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/699b3f65/core/src/main/java/brooklyn/util/internal/ssh/ShellAbstractTool.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/internal/ssh/ShellAbstractTool.java b/core/src/main/java/brooklyn/util/internal/ssh/ShellAbstractTool.java deleted file mode 100644 index 5c839a9..0000000 --- a/core/src/main/java/brooklyn/util/internal/ssh/ShellAbstractTool.java +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 brooklyn.util.internal.ssh; - -import static com.google.common.base.Preconditions.checkArgument; - -import java.io.ByteArrayInputStream; -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.config.ConfigKey; -import brooklyn.util.collections.MutableList; -import brooklyn.util.flags.TypeCoercions; -import brooklyn.util.os.Os; -import brooklyn.util.ssh.BashCommands; -import brooklyn.util.text.Identifiers; -import brooklyn.util.text.StringEscapes.BashStringEscapes; -import brooklyn.util.text.Strings; -import brooklyn.util.time.Duration; -import brooklyn.util.time.Time; - -import com.google.common.base.Joiner; -import com.google.common.base.Objects; -import com.google.common.collect.ImmutableList; - -public abstract class ShellAbstractTool implements ShellTool { - - private static final Logger LOG = LoggerFactory.getLogger(ShellAbstractTool.class); - - protected final File localTempDir; - - public ShellAbstractTool(String localTempDir) { - this(localTempDir == null ? null : new File(Os.tidyPath(localTempDir))); - } - - public ShellAbstractTool(File localTempDir) { - if (localTempDir == null) { - localTempDir = new File(Os.tmp(), "tmpssh-"+Os.user()); - if (!localTempDir.exists()) localTempDir.mkdir(); - Os.deleteOnExitEmptyParentsUpTo(localTempDir, new File(Os.tmp())); - } - this.localTempDir = localTempDir; - } - - public ShellAbstractTool() { - this((File)null); - } - - protected static void warnOnDeprecated(Map props, String deprecatedKey, String correctKey) { - if (props.containsKey(deprecatedKey)) { - if (correctKey != null && props.containsKey(correctKey)) { - Object dv = props.get(deprecatedKey); - Object cv = props.get(correctKey); - if (!Objects.equal(cv, dv)) { - LOG.warn("SshTool detected deprecated key '"+deprecatedKey+"' with different value ("+dv+") "+ - "than new key '"+correctKey+"' ("+cv+"); ambiguous which will be used"); - } else { - // ignore, the deprecated key populated for legacy reasons - } - } else { - Object dv = props.get(deprecatedKey); - LOG.warn("SshTool detected deprecated key '"+deprecatedKey+"' used, with value ("+dv+")"); - } - } - } - - protected static Boolean hasVal(Map map, ConfigKey keyC) { - String key = keyC.getName(); - return map.containsKey(key); - } - - protected static T getMandatoryVal(Map map, ConfigKey keyC) { - String key = keyC.getName(); - checkArgument(map.containsKey(key), "must contain key '"+keyC+"'"); - return TypeCoercions.coerce(map.get(key), keyC.getTypeToken()); - } - - public static T getOptionalVal(Map map, ConfigKey keyC) { - if (keyC==null) return null; - String key = keyC.getName(); - if (map!=null && map.containsKey(key) && map.get(key) != null) { - return TypeCoercions.coerce(map.get(key), keyC.getTypeToken()); - } else { - return keyC.getDefaultValue(); - } - } - - /** returns the value of the key if specified, otherwise defaultValue */ - protected static T getOptionalVal(Map map, ConfigKey keyC, T defaultValue) { - String key = keyC.getName(); - if (map!=null && map.containsKey(key) && map.get(key) != null) { - return TypeCoercions.coerce(map.get(key), keyC.getTypeToken()); - } else { - return defaultValue; - } - } - - protected void closeWhispering(Closeable closeable, Object context) { - closeWhispering(closeable, this, context); - } - - /** - * Similar to Guava's Closeables.closeQuitely, except logs exception at debug with context in message. - */ - protected static void closeWhispering(Closeable closeable, Object context1, Object context2) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException e) { - if (LOG.isDebugEnabled()) { - String msg = String.format("<< exception during close, for %s -> %s (%s); continuing.", - context1, context2, closeable); - if (LOG.isTraceEnabled()) - LOG.debug(msg + ": " + e); - else - LOG.trace(msg, e); - } - } - } - } - - protected File writeTempFile(InputStream contents) { - File tempFile = Os.writeToTempFile(contents, localTempDir, "sshcopy", "data"); - tempFile.setReadable(false, false); - tempFile.setReadable(true, true); - tempFile.setWritable(false); - tempFile.setExecutable(false); - return tempFile; - } - - protected File writeTempFile(String contents) { - return writeTempFile(contents.getBytes()); - } - - protected File writeTempFile(byte[] contents) { - return writeTempFile(new ByteArrayInputStream(contents)); - } - - protected String toScript(Map props, List commands, Map env) { - List allcmds = toCommandSequence(commands, env); - StringBuilder result = new StringBuilder(); - result.append(getOptionalVal(props, PROP_SCRIPT_HEADER)).append('\n'); - - for (String cmd : allcmds) { - result.append(cmd).append('\n'); - } - - return result.toString(); - } - - /** - * Merges the commands and env, into a single set of commands. Also escapes the commands as required. - * - * Not all ssh servers handle "env", so instead convert env into exported variables - */ - protected List toCommandSequence(List commands, Map env) { - List result = new ArrayList((env!=null ? env.size() : 0) + commands.size()); - - if (env!=null) { - for (Entry entry : env.entrySet()) { - if (entry.getKey() == null || entry.getValue() == null) { - LOG.warn("env key-values must not be null; ignoring: key="+entry.getKey()+"; value="+entry.getValue()); - continue; - } - String escapedVal = BashStringEscapes.escapeLiteralForDoubleQuotedBash(entry.getValue().toString()); - result.add("export "+entry.getKey()+"=\""+escapedVal+"\""); - } - } - for (CharSequence cmd : commands) { // objects in commands can be groovy GString so can't treat as String here - result.add(cmd.toString()); - } - - return result; - } - - @Override - public int execScript(Map props, List commands) { - return execScript(props, commands, Collections.emptyMap()); - } - - @Override - public int execCommands(Map props, List commands) { - return execCommands(props, commands, Collections.emptyMap()); - } - - protected static int asInt(Integer input, int valueIfInputNull) { - return input != null ? input : valueIfInputNull; - } - - protected abstract class ToolAbstractExecScript { - protected final Map props; - protected final String separator; - protected final OutputStream out; - protected final OutputStream err; - protected final String scriptDir; - protected final Boolean runAsRoot; - protected final Boolean noExtraOutput; - protected final Boolean noDeleteAfterExec; - protected final String scriptNameWithoutExtension; - protected final String scriptPath; - protected final Duration execTimeout; - - public ToolAbstractExecScript(Map props) { - this.props = props; - this.separator = getOptionalVal(props, PROP_SEPARATOR); - this.out = getOptionalVal(props, PROP_OUT_STREAM); - this.err = getOptionalVal(props, PROP_ERR_STREAM); - - this.scriptDir = getOptionalVal(props, PROP_SCRIPT_DIR); - this.runAsRoot = getOptionalVal(props, PROP_RUN_AS_ROOT); - this.noExtraOutput = getOptionalVal(props, PROP_NO_EXTRA_OUTPUT); - this.noDeleteAfterExec = getOptionalVal(props, PROP_NO_DELETE_SCRIPT); - this.execTimeout = getOptionalVal(props, PROP_EXEC_TIMEOUT); - - String summary = getOptionalVal(props, PROP_SUMMARY); - if (summary!=null) { - summary = Strings.makeValidFilename(summary); - if (summary.length()>30) - summary = summary.substring(0,30); - } - this.scriptNameWithoutExtension = "brooklyn-"+ - Time.makeDateStampString()+"-"+Identifiers.makeRandomId(4)+ - (Strings.isBlank(summary) ? "" : "-"+summary); - this.scriptPath = Os.mergePathsUnix(scriptDir, scriptNameWithoutExtension+".sh"); - } - - /** builds the command to run the given script; - * note that some modes require \$RESULT passed in order to access a variable, whereas most just need $ */ - protected List buildRunScriptCommand() { - MutableList.Builder cmds = MutableList.builder() - .add((runAsRoot ? BashCommands.sudo(scriptPath) : scriptPath) + " < /dev/null") - .add("RESULT=$?"); - if (noExtraOutput==null || !noExtraOutput) - cmds.add("echo Executed "+scriptPath+", result $RESULT"); - if (noDeleteAfterExec!=Boolean.TRUE) { - // use "-f" because some systems have "rm" aliased to "rm -i" - // use "< /dev/null" to guarantee doesn't hang - cmds.add("rm -f "+scriptPath+" < /dev/null"); - } - cmds.add("exit $RESULT"); - return cmds.build(); - } - - protected String getSummary() { - String summary = getOptionalVal(props, PROP_SUMMARY); - return (summary != null) ? summary : scriptPath; - } - - public abstract int run(); - } - - protected abstract class ToolAbstractAsyncExecScript extends ToolAbstractExecScript { - protected final String stdoutPath; - protected final String stderrPath; - protected final String exitStatusPath; - protected final String pidPath; - - public ToolAbstractAsyncExecScript(Map props) { - super(props); - - stdoutPath = Os.mergePathsUnix(scriptDir, scriptNameWithoutExtension + ".stdout"); - stderrPath = Os.mergePathsUnix(scriptDir, scriptNameWithoutExtension + ".stderr"); - exitStatusPath = Os.mergePathsUnix(scriptDir, scriptNameWithoutExtension + ".exitstatus"); - pidPath = Os.mergePathsUnix(scriptDir, scriptNameWithoutExtension + ".pid"); - } - - /** - * Builds the command to run the given script, asynchronously. - * The executed command will return immediately, but the output from the script - * will continue to be written - * note that some modes require \$RESULT passed in order to access a variable, whereas most just need $ */ - @Override - protected List buildRunScriptCommand() { - String touchCmd = String.format("touch %s %s %s %s", stdoutPath, stderrPath, exitStatusPath, pidPath); - String cmd = String.format("nohup sh -c \"( %s > %s 2> %s < /dev/null ) ; echo \\$? > %s \" > /dev/null 2>&1 < /dev/null &", scriptPath, stdoutPath, stderrPath, exitStatusPath); - MutableList.Builder cmds = MutableList.builder() - .add(runAsRoot ? BashCommands.sudo(touchCmd) : touchCmd) - .add(runAsRoot ? BashCommands.sudo(cmd) : cmd) - .add("echo $! > "+pidPath) - .add("RESULT=$?"); - if (noExtraOutput==null || !noExtraOutput) { - cmds.add("echo Executing async "+scriptPath); - } - cmds.add("exit $RESULT"); - return cmds.build(); - } - - /** - * Builds the command to retrieve the exit status of the command, written to stdout. - */ - protected List buildRetrieveStatusCommand() { - // Retrieve exit status from file (writtent to stdout), if populated; - // if not found and pid still running, then return empty string; else exit code 1. - List cmdParts = ImmutableList.of( - "# Retrieve status", // comment is to aid testing - see SshjToolAsyncStubIntegrationTest - "if test -s "+exitStatusPath+"; then", - " cat "+exitStatusPath, - "elif test -s "+pidPath+"; then", - " pid=`cat "+pidPath+"`", - " if ! ps -p $pid > /dev/null < /dev/null; then", - " # no exit status, and not executing; give a few seconds grace in case just about to write exit status", - " sleep 3", - " if test -s "+exitStatusPath+"; then", - " cat "+exitStatusPath+"", - " else", - " echo \"No exit status in "+exitStatusPath+", and pid in "+pidPath+" ($pid) not executing\"", - " exit 1", - " fi", - " fi", - "else", - " echo \"No exit status in "+exitStatusPath+", and "+pidPath+" is empty\"", - " exit 1", - "fi"+"\n"); - String cmd = Joiner.on("\n").join(cmdParts); - - MutableList.Builder cmds = MutableList.builder() - .add((runAsRoot ? BashCommands.sudo(cmd) : cmd)) - .add("RESULT=$?"); - cmds.add("exit $RESULT"); - return cmds.build(); - } - - /** - * Builds the command to retrieve the stdout and stderr of the async command. - * An offset can be given, to only retrieve data starting at a particular character (indexed from 0). - */ - protected List buildRetrieveStdoutAndStderrCommand(int stdoutPosition, int stderrPosition) { - // Note that `tail -c +1` means start at the *first* character (i.e. start counting from 1, not 0) - String catStdoutCmd = "tail -c +"+(stdoutPosition+1)+" "+stdoutPath+" 2> /dev/null"; - String catStderrCmd = "tail -c +"+(stderrPosition+1)+" "+stderrPath+" 2>&1 > /dev/null"; - MutableList.Builder cmds = MutableList.builder() - .add((runAsRoot ? BashCommands.sudo(catStdoutCmd) : catStdoutCmd)) - .add((runAsRoot ? BashCommands.sudo(catStderrCmd) : catStderrCmd)) - .add("RESULT=$?"); - cmds.add("exit $RESULT"); - return cmds.build(); - } - - /** - * Builds the command to retrieve the stdout and stderr of the async command. - * An offset can be given, to only retrieve data starting at a particular character (indexed from 0). - */ - protected List buildLongPollCommand(int stdoutPosition, int stderrPosition, Duration timeout) { - long maxTime = Math.max(1, timeout.toSeconds()); - - // Note that `tail -c +1` means start at the *first* character (i.e. start counting from 1, not 0) - List waitForExitStatusParts = ImmutableList.of( - //Should be careful here because any output will be part of the stdout/stderr streams - "# Long poll", // comment is to aid testing - see SshjToolAsyncStubIntegrationTest - // disown to avoid Terminated message after killing the process - // redirect error output to avoid "file truncated" messages - "tail -c +"+(stdoutPosition+1)+" -f "+stdoutPath+" 2> /dev/null & export TAIL_STDOUT_PID=$!; disown", - "tail -c +"+(stderrPosition+1)+" -f "+stderrPath+" 1>&2 2> /dev/null & export TAIL_STDERR_PID=$!; disown", - "EXIT_STATUS_PATH="+exitStatusPath, - "PID_PATH="+pidPath, - "MAX_TIME="+maxTime, - "COUNTER=0", - "while [ \"$COUNTER\" -lt $MAX_TIME ]; do", - " if test -s $EXIT_STATUS_PATH; then", - " EXIT_STATUS=`cat $EXIT_STATUS_PATH`", - " kill ${TAIL_STDERR_PID} ${TAIL_STDOUT_PID} 2> /dev/null", - " exit $EXIT_STATUS", - " elif test -s $PID_PATH; then", - " PID=`cat $PID_PATH`", - " if ! ps -p $PID > /dev/null 2>&1 < /dev/null; then", - " # no exit status, and not executing; give a few seconds grace in case just about to write exit status", - " sleep 3", - " if test -s $EXIT_STATUS_PATH; then", - " EXIT_STATUS=`cat $EXIT_STATUS_PATH`", - " kill ${TAIL_STDERR_PID} ${TAIL_STDOUT_PID} 2> /dev/null", - " exit $EXIT_STATUS", - " else", - " echo \"No exit status in $EXIT_STATUS_PATH, and pid in $PID_PATH ($PID) not executing\"", - " kill ${TAIL_STDERR_PID} ${TAIL_STDOUT_PID} 2> /dev/null", - " exit 126", - " fi", - " fi", - " fi", - " # No exit status in $EXIT_STATUS_PATH; keep waiting", - " sleep 1", - " COUNTER+=1", - "done", - "kill ${TAIL_STDERR_PID} ${TAIL_STDOUT_PID} 2> /dev/null", - "exit 125"+"\n"); - String waitForExitStatus = Joiner.on("\n").join(waitForExitStatusParts); - - return ImmutableList.of(runAsRoot ? BashCommands.sudo(waitForExitStatus) : waitForExitStatus); - } - - protected List deleteTemporaryFilesCommand() { - ImmutableList.Builder cmdParts = ImmutableList.builder(); - - if (!Boolean.TRUE.equals(noDeleteAfterExec)) { - // use "-f" because some systems have "rm" aliased to "rm -i" - // use "< /dev/null" to guarantee doesn't hang - cmdParts.add( - "rm -f "+scriptPath+" "+stdoutPath+" "+stderrPath+" "+exitStatusPath+" "+pidPath+" < /dev/null"); - } - - // If the buildLongPollCommand didn't complete properly then it might have left tail command running; - // ensure they are killed. - cmdParts.add( - //ignore error output for the case where there are no running processes and kill is called without arguments - "ps aux | grep \"tail -c\" | grep \""+stdoutPath+"\" | grep -v grep | awk '{ printf $2 }' | xargs kill 2> /dev/null", - "ps aux | grep \"tail -c\" | grep \""+stderrPath+"\" | grep -v grep | awk '{ printf $2 }' | xargs kill 2> /dev/null"); - - String cmd = Joiner.on("\n").join(cmdParts.build()); - - return ImmutableList.of(runAsRoot ? BashCommands.sudo(cmd) : cmd); - } - - @Override - public abstract int run(); - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/699b3f65/core/src/main/java/brooklyn/util/internal/ssh/ShellTool.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/internal/ssh/ShellTool.java b/core/src/main/java/brooklyn/util/internal/ssh/ShellTool.java deleted file mode 100644 index 0555039..0000000 --- a/core/src/main/java/brooklyn/util/internal/ssh/ShellTool.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 brooklyn.util.internal.ssh; - -import static brooklyn.entity.basic.ConfigKeys.newConfigKey; -import static brooklyn.entity.basic.ConfigKeys.newStringConfigKey; - -import java.io.OutputStream; -import java.util.List; -import java.util.Map; - -import brooklyn.config.ConfigKey; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.util.os.Os; -import brooklyn.util.time.Duration; - -/** Methods for executing things in an environment (localhost process, or ssh) */ -public interface ShellTool { - - // config which applies to sessions - - public static final ConfigKey PROP_LOCAL_TEMP_DIR = newStringConfigKey( - "localTempDir", - "The directory on the local machine (i.e. running brooklyn) for writing temp files", - Os.mergePaths(Os.tmp(), "brooklyn-"+Os.user()+"-ssh-tmp")); - - // config which applies to calls: - - public static final ConfigKey PROP_RUN_AS_ROOT = newConfigKey("runAsRoot", "When running a script, whether to run as root", Boolean.FALSE); - - public static final ConfigKey PROP_OUT_STREAM = newConfigKey(OutputStream.class, "out", "Stream to which to capture stdout"); - public static final ConfigKey PROP_ERR_STREAM = newConfigKey(OutputStream.class, "err", "Stream to which to capture stderr"); - - public static final ConfigKey PROP_NO_EXTRA_OUTPUT = newConfigKey("noExtraOutput", "Suppresses any decorative output such as result code which some tool commands insert", false); - - public static final ConfigKey PROP_SEPARATOR = newConfigKey("separator", "string to insert between caller-supplied commands being executed as commands", " ; "); - - public static final ConfigKey PROP_SCRIPT_DIR = newConfigKey("scriptDir", "directory where scripts should be copied", "/tmp"); - public static final ConfigKey PROP_SCRIPT_HEADER = newConfigKey("scriptHeader", "lines to insert at the start of scripts generated for caller-supplied commands for script execution", "#!/bin/bash -e\n"); - public static final ConfigKey PROP_DIRECT_HEADER = newConfigKey("directHeader", "commands to run at the target before any caller-supplied commands for direct execution", "exec bash -e"); - - ConfigKey PROP_NO_DELETE_SCRIPT = newConfigKey("noDeleteAfterExec", "Retains the generated script file after executing the commands instead of deleting it", false); - - ConfigKey PROP_SUMMARY = ConfigKeys.newStringConfigKey("summary", "Provides a human-readable summary, used in file generation etc"); - - ConfigKey PROP_EXEC_TIMEOUT = newConfigKey("execTimeout", "Timeout when executing a script", Duration.PRACTICALLY_FOREVER); - - ConfigKey PROP_EXEC_ASYNC = newConfigKey("execAsync", "Executes the script asynchronously, and then polls for the result (and for stdout/stderr)", false); - - ConfigKey PROP_EXEC_ASYNC_POLLING_TIMEOUT = newConfigKey("execAsyncPollTimeout", "Timeout per poll when executing a script asynchronously", Duration.ONE_MINUTE); - - /** - * Executes the set of commands in a shell script. Blocks until completion. - *

- * - * Optional properties are the same common ones as for {@link #execCommands(Map, List, Map)} with the addition of: - *

    - *
  • {@link #PROP_RUN_AS_ROOT} - *
  • {@link #PROP_SCRIPT_DIR} - *
- * - * @return exit status of script - */ - public int execScript(Map props, List commands, Map env); - - /** - * @see #execScript(Map, List, Map) - */ - public int execScript(Map props, List commands); - - /** - * Executes the set of commands using ssh exec. - * - * This is generally more efficient than ssh shell mode (cf {@link #execScript(Map, List, Map)}), - * but is not suitable if you need env values which are only set on a fully-fledged shell, - * or if you want the entire block executed with root permission. - * - * Common optional properties (which also apply to {@link #execScript(Map, List, Map)}) are: - *
    - *
  • {@link #PROP_OUT_STREAM} - *
  • {@link #PROP_ERR_STREAM} - *
  • {@link #PROP_SEPARATOR} (for some modes) - *
  • {@link #PROP_NO_EXTRA_OUTPUT} (often there is no extra output here) - *
- * - * Note that {@link #PROP_RUN_AS_ROOT} is not typically supported here. Prefer {@link #execScript(Map, List, Map)}). - * - * @return exit status of commands - */ - public int execCommands(Map properties, List commands, Map env); - - /** - * @see #execCommands(Map, List, Map) - */ - public int execCommands(Map properties, List commands); - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/699b3f65/core/src/main/java/brooklyn/util/internal/ssh/SshAbstractTool.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/internal/ssh/SshAbstractTool.java b/core/src/main/java/brooklyn/util/internal/ssh/SshAbstractTool.java deleted file mode 100644 index eb0222a..0000000 --- a/core/src/main/java/brooklyn/util/internal/ssh/SshAbstractTool.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 brooklyn.util.internal.ssh; - -import static brooklyn.util.net.Networking.checkPortValid; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import java.io.File; -import java.util.Map; -import java.util.Set; - -import brooklyn.util.os.Os; - -import com.google.common.collect.Iterables; -import com.google.common.collect.Sets; - -public abstract class SshAbstractTool extends ShellAbstractTool implements SshTool { - - protected final String toString; - - protected final String host; - protected final String user; - protected final String password; - protected final int port; - protected String privateKeyPassphrase; - protected String privateKeyData; - protected File privateKeyFile; - protected boolean strictHostKeyChecking; - protected boolean allocatePTY; - - public static interface SshAction { - void clear() throws Exception; - T create() throws Exception; - } - - public static abstract class AbstractSshToolBuilder> { - protected String host; - protected int port = 22; - protected String user = System.getProperty("user.name"); - protected String password; - protected String privateKeyData; - protected String privateKeyPassphrase; - protected Set privateKeyFiles = Sets.newLinkedHashSet(); - protected boolean strictHostKeyChecking = false; - protected boolean allocatePTY = false; - protected File localTempDir = null; - - @SuppressWarnings("unchecked") - protected B self() { - return (B) this; - } - - public B from(Map props) { - host = getMandatoryVal(props, PROP_HOST); - port = getOptionalVal(props, PROP_PORT); - user = getOptionalVal(props, PROP_USER); - - password = getOptionalVal(props, PROP_PASSWORD); - - warnOnDeprecated(props, "privateKey", "privateKeyData"); - privateKeyData = getOptionalVal(props, PROP_PRIVATE_KEY_DATA); - privateKeyPassphrase = getOptionalVal(props, PROP_PRIVATE_KEY_PASSPHRASE); - - // for backwards compatibility accept keyFiles and privateKey - // but sshj accepts only a single privateKeyFile; leave blank to use defaults (i.e. ~/.ssh/id_rsa and id_dsa) - warnOnDeprecated(props, "keyFiles", null); - String privateKeyFile = getOptionalVal(props, PROP_PRIVATE_KEY_FILE); - if (privateKeyFile != null) privateKeyFiles.add(privateKeyFile); - - strictHostKeyChecking = getOptionalVal(props, PROP_STRICT_HOST_KEY_CHECKING); - allocatePTY = getOptionalVal(props, PROP_ALLOCATE_PTY); - - String localTempDirPath = getOptionalVal(props, PROP_LOCAL_TEMP_DIR); - localTempDir = (localTempDirPath == null) ? null : new File(Os.tidyPath(localTempDirPath)); - - return self(); - } - public B host(String val) { - this.host = val; return self(); - } - public B user(String val) { - this.user = val; return self(); - } - public B password(String val) { - this.password = val; return self(); - } - public B port(int val) { - this.port = val; return self(); - } - public B privateKeyPassphrase(String val) { - this.privateKeyPassphrase = val; return self(); - } - /** @deprecated 1.4.0, use privateKeyData */ - public B privateKey(String val) { - this.privateKeyData = val; return self(); - } - public B privateKeyData(String val) { - this.privateKeyData = val; return self(); - } - public B privateKeyFile(String val) { - this.privateKeyFiles.add(val); return self(); - } - public B localTempDir(File val) { - this.localTempDir = val; return self(); - } - public abstract T build(); - } - - protected SshAbstractTool(AbstractSshToolBuilder builder) { - super(builder.localTempDir); - - host = checkNotNull(builder.host, "host"); - port = builder.port; - user = builder.user; - password = builder.password; - strictHostKeyChecking = builder.strictHostKeyChecking; - allocatePTY = builder.allocatePTY; - privateKeyPassphrase = builder.privateKeyPassphrase; - privateKeyData = builder.privateKeyData; - - if (builder.privateKeyFiles.size() > 1) { - throw new IllegalArgumentException("sshj supports only a single private key-file; " + - "for defaults of ~/.ssh/id_rsa and ~/.ssh/id_dsa leave blank"); - } else if (builder.privateKeyFiles.size() == 1) { - String privateKeyFileStr = Iterables.get(builder.privateKeyFiles, 0); - String amendedKeyFile = privateKeyFileStr.startsWith("~") ? (System.getProperty("user.home")+privateKeyFileStr.substring(1)) : privateKeyFileStr; - privateKeyFile = new File(amendedKeyFile); - } else { - privateKeyFile = null; - } - - checkArgument(host.length() > 0, "host value must not be an empty string"); - checkPortValid(port, "ssh port"); - - toString = String.format("%s@%s:%d", user, host, port); - } - - @Override - public String toString() { - return toString; - } - - public String getHostAddress() { - return this.host; - } - - public String getUsername() { - return this.user; - } - - protected SshException propagate(Exception e, String message) throws SshException { - throw new SshException("(" + toString() + ") " + message + ": " + e.getMessage(), e); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/699b3f65/core/src/main/java/brooklyn/util/internal/ssh/SshException.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/internal/ssh/SshException.java b/core/src/main/java/brooklyn/util/internal/ssh/SshException.java deleted file mode 100644 index 20653bb..0000000 --- a/core/src/main/java/brooklyn/util/internal/ssh/SshException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 brooklyn.util.internal.ssh; - -public class SshException extends RuntimeException { - - private static final long serialVersionUID = -5690230838066860965L; - - public SshException(String msg) { - super(msg); - } - - public SshException(String msg, Throwable cause) { - super(msg, cause); - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/699b3f65/core/src/main/java/brooklyn/util/internal/ssh/SshTool.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/internal/ssh/SshTool.java b/core/src/main/java/brooklyn/util/internal/ssh/SshTool.java deleted file mode 100644 index a676599..0000000 --- a/core/src/main/java/brooklyn/util/internal/ssh/SshTool.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 brooklyn.util.internal.ssh; - -import static brooklyn.entity.basic.ConfigKeys.newConfigKey; -import static brooklyn.entity.basic.ConfigKeys.newStringConfigKey; - -import java.io.File; -import java.io.InputStream; -import java.util.List; -import java.util.Map; - -import brooklyn.config.ConfigKey; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.util.stream.KnownSizeInputStream; -import brooklyn.util.time.Duration; - -/** - * Defines the methods available on the various different implementations of SSH, - * and configuration options which are also generally available. - *

- * The config keys in this class can be supplied (or their string equivalents, where the flags/props take {@code Map}) - * to influence configuration, either for the tool/session itself or for individual commands. - *

- * To specify some of these properties on a global basis, use the variants of the keys here - * contained in {@link ConfigKeys} - * (which are generally {@value #BROOKLYN_CONFIG_KEY_PREFIX} prefixed to the names of keys here). - */ -public interface SshTool extends ShellTool { - - /** Public-facing global config keys for Brooklyn are defined in ConfigKeys, - * and have this prefix pre-prended to the config keys in this class. - * These keys are detected from entity/global config and automatically applied to ssh executions. */ - public static final String BROOKLYN_CONFIG_KEY_PREFIX = "brooklyn.ssh.config."; - - public static final ConfigKey PROP_TOOL_CLASS = newStringConfigKey("tool.class", "SshTool implementation to use", null); - - public static final ConfigKey PROP_HOST = newStringConfigKey("host", "Host to connect to (required)", null); - public static final ConfigKey PROP_PORT = newConfigKey("port", "Port on host to connect to", 22); - public static final ConfigKey PROP_USER = newConfigKey("user", "User to connect as", System.getProperty("user.name")); - public static final ConfigKey PROP_PASSWORD = newStringConfigKey("password", "Password to use to connect", null); - - public static final ConfigKey PROP_PRIVATE_KEY_FILE = newStringConfigKey("privateKeyFile", "the path of an ssh private key file; leave blank to use defaults (i.e. ~/.ssh/id_rsa and id_dsa)", null); - public static final ConfigKey PROP_PRIVATE_KEY_DATA = newStringConfigKey("privateKeyData", "the private ssh key (e.g. contents of an id_rsa or id_dsa file)", null); - public static final ConfigKey PROP_PRIVATE_KEY_PASSPHRASE = newStringConfigKey("privateKeyPassphrase", "the passphrase for the ssh private key", null); - public static final ConfigKey PROP_STRICT_HOST_KEY_CHECKING = newConfigKey("strictHostKeyChecking", "whether to check the remote host's identification; defaults to false", false); - public static final ConfigKey PROP_ALLOCATE_PTY = newConfigKey("allocatePTY", "whether to allocate PTY (vt100); if true then stderr is sent to stdout, but sometimes required for sudo'ing due to requiretty", false); - - public static final ConfigKey PROP_CONNECT_TIMEOUT = newConfigKey("connectTimeout", "Timeout in millis when establishing an SSH connection; if 0 then uses default (usually 30s)", 0L); - public static final ConfigKey PROP_SESSION_TIMEOUT = newConfigKey("sessionTimeout", "Timeout in millis for an ssh session; if 0 then uses default", 0L); - public static final ConfigKey PROP_SSH_TRIES = newConfigKey("sshTries", "Max number of times to attempt ssh operations", 4); - public static final ConfigKey PROP_SSH_TRIES_TIMEOUT = newConfigKey("sshTriesTimeout", "Time limit for attempting retries; will not interrupt tasks, but stops retrying after a total amount of elapsed time", Duration.TWO_MINUTES.toMilliseconds()); - public static final ConfigKey PROP_SSH_RETRY_DELAY = newConfigKey("sshRetryDelay", "Time (in milliseconds) before first ssh-retry, after which it will do exponential backoff", 50L); - - // NB -- items above apply for _session_ (a tool), below apply for a _call_ - // TODO would be nice to track which arguments are used, so we can indicate whether extras are supplied - - public static final ConfigKey PROP_PERMISSIONS = newConfigKey("permissions", "Default permissions for files copied/created on remote machine; must be four-digit octal string, default '0644'", "0644"); - public static final ConfigKey PROP_LAST_MODIFICATION_DATE = newConfigKey("lastModificationDate", "Last-modification-date to be set on files copied/created (should be UTC/1000, ie seconds since 1970; default 0 usually means current)", 0L); - public static final ConfigKey PROP_LAST_ACCESS_DATE = newConfigKey("lastAccessDate", "Last-access-date to be set on files copied/created (should be UTC/1000, ie seconds since 1970; default 0 usually means lastModificationDate)", 0L); - public static final ConfigKey PROP_OWNER_UID = newConfigKey("ownerUid", "Default owner UID (not username) for files created on remote machine; default is unset", -1); - - // TODO remove unnecessary "public static final" modifiers - - // TODO Could define the following in SshMachineLocation, or some such? - //public static ConfigKey PROP_LOG_PREFIX = newStringKey("logPrefix", "???", ???); - //public static ConfigKey PROP_NO_STDOUT_LOGGING = newStringKey("noStdoutLogging", "???", ???); - //public static ConfigKey PROP_NO_STDOUT_LOGGING = newStringKey("noStdoutLogging", "???", ???); - - /** - * @throws SshException - */ - public void connect(); - - /** - * @deprecated since 0.7.0; (since much earlier) this ignores the argument in favour of {@link #PROP_SSH_TRIES} - * - * @param maxAttempts - * @throws SshException - */ - public void connect(int maxAttempts); - - public void disconnect(); - - public boolean isConnected(); - - /** - * @see super{@link #execScript(Map, List, Map)} - * @throws SshException If failed to connect - */ - @Override - public int execScript(Map props, List commands, Map env); - - /** - * @see #execScript(Map, List, Map) - */ - @Override - public int execScript(Map props, List commands); - - /** - * @see super{@link #execCommands(Map, List, Map)} - * @throws SshException If failed to connect - */ - @Override - public int execCommands(Map properties, List commands, Map env); - - /** - * @see #execCommands(Map, List, Map) - */ - @Override - public int execCommands(Map properties, List commands); - - /** - * Copies the file to the server at the given path. - * If path is null, empty, '.', '..', or ends with '/' then file name is used. - *

- * The file will not preserve the permission of last _access_ date. - * - * Optional properties are: - *

    - *
  • 'permissions' (e.g. "0644") - see {@link #PROP_PERMISSIONS} - *
  • 'lastModificationDate' see {@link #PROP_LAST_MODIFICATION_DATE}; not supported by all SshTool implementations - *
  • 'lastAccessDate' see {@link #PROP_LAST_ACCESS_DATE}; not supported by all SshTool implementations - *
- * - * @return exit code (not supported by all SshTool implementations, usually throwing on error; - * sometimes possibly returning 0 even on error (?) ) - */ - public int copyToServer(Map props, File localFile, String pathAndFileOnRemoteServer); - - /** - * Closes the given input stream before returning. - * Consider using {@link KnownSizeInputStream} for efficiency when the size of the stream is known. - * - * @see #copyToServer(Map, File, String) - */ - public int copyToServer(Map props, InputStream contents, String pathAndFileOnRemoteServer); - - /** - * @see #copyToServer(Map, File, String) - */ - public int copyToServer(Map props, byte[] contents, String pathAndFileOnRemoteServer); - - /** - * Copies the file from the server at the given path. - * - * @return exit code (not supported by all SshTool implementations, usually throwing on error; - * sometimes possibly returning 0 even on error (?) ) - */ - public int copyFromServer(Map props, String pathAndFileOnRemoteServer, File local); - - // TODO might be more efficicent than copyFrom by way of temp file -// /** -// * Reads from the file at the given path on the remote server. -// */ -// public InputStream streamFromServer(Map props, String pathAndFileOnRemoteServer); - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/699b3f65/core/src/main/java/brooklyn/util/internal/ssh/cli/SshCliTool.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/internal/ssh/cli/SshCliTool.java b/core/src/main/java/brooklyn/util/internal/ssh/cli/SshCliTool.java deleted file mode 100644 index 91b5d91..0000000 --- a/core/src/main/java/brooklyn/util/internal/ssh/cli/SshCliTool.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 brooklyn.util.internal.ssh.cli; - -import static com.google.common.base.Preconditions.checkNotNull; - -import java.io.File; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.config.ConfigKey; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.util.collections.MutableMap; -import brooklyn.util.internal.ssh.SshAbstractTool; -import brooklyn.util.internal.ssh.SshTool; -import brooklyn.util.internal.ssh.process.ProcessTool; -import brooklyn.util.text.StringEscapes.BashStringEscapes; -import brooklyn.util.text.Strings; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; - -/** - * For ssh and scp commands, delegating to system calls. - */ -public class SshCliTool extends SshAbstractTool implements SshTool { - - // TODO No retry support, with backoffLimitedRetryHandler - - private static final Logger LOG = LoggerFactory.getLogger(SshCliTool.class); - - public static final ConfigKey PROP_SSH_EXECUTABLE = ConfigKeys.newStringConfigKey("sshExecutable", "command to execute for ssh (defaults to \"ssh\", but could be overridden to sshg3 for Tectia for example)", "ssh"); - public static final ConfigKey PROP_SSH_FLAGS = ConfigKeys.newStringConfigKey("sshFlags", "flags to pass to ssh, as a space separated list", ""); - public static final ConfigKey PROP_SCP_EXECUTABLE = ConfigKeys.newStringConfigKey("scpExecutable", "command to execute for scp (defaults to \"scp\", but could be overridden to scpg3 for Tectia for example)", "scp"); - - public static Builder builder() { - return new ConcreteBuilder(); - } - - private static class ConcreteBuilder extends Builder { - } - - public static class Builder> extends AbstractSshToolBuilder { - private String sshExecutable; - private String sshFlags; - private String scpExecutable; - - @Override - public B from(Map props) { - super.from(props); - sshExecutable = getOptionalVal(props, PROP_SSH_EXECUTABLE); - sshFlags = getOptionalVal(props, PROP_SSH_FLAGS); - scpExecutable = getOptionalVal(props, PROP_SCP_EXECUTABLE); - return self(); - } - public B sshExecutable(String val) { - this.sshExecutable = val; return self(); - } - public B scpExecutable(String val) { - this.scpExecutable = val; return self(); - } - @SuppressWarnings("unchecked") - public T build() { - return (T) new SshCliTool(this); - } - } - - private final String sshExecutable; - private final String sshFlags; - private final String scpExecutable; - - public SshCliTool(Map map) { - this(builder().from(map)); - } - - private SshCliTool(Builder builder) { - super(builder); - sshExecutable = checkNotNull(builder.sshExecutable); - sshFlags = checkNotNull(builder.sshFlags); - scpExecutable = checkNotNull(builder.scpExecutable); - if (LOG.isTraceEnabled()) LOG.trace("Created SshCliTool {} ({})", this, System.identityHashCode(this)); - } - - @Override - public void connect() { - // no-op - } - - @Override - public void connect(int maxAttempts) { - // no-op - } - - @Override - public void disconnect() { - if (LOG.isTraceEnabled()) LOG.trace("Disconnecting SshCliTool {} ({}) - no-op", this, System.identityHashCode(this)); - // no-op - } - - @Override - public boolean isConnected() { - // TODO Always pretends to be connected - return true; - } - - @Override - public int copyToServer(java.util.Map props, byte[] contents, String pathAndFileOnRemoteServer) { - return copyTempFileToServer(props, writeTempFile(contents), pathAndFileOnRemoteServer); - } - - @Override - public int copyToServer(java.util.Map props, InputStream contents, String pathAndFileOnRemoteServer) { - return copyTempFileToServer(props, writeTempFile(contents), pathAndFileOnRemoteServer); - } - - @Override - public int copyToServer(Map props, File f, String pathAndFileOnRemoteServer) { - if (hasVal(props, PROP_LAST_MODIFICATION_DATE)) { - LOG.warn("Unsupported ssh feature, setting lastModificationDate for {}:{}", this, pathAndFileOnRemoteServer); - } - if (hasVal(props, PROP_LAST_ACCESS_DATE)) { - LOG.warn("Unsupported ssh feature, setting lastAccessDate for {}:{}", this, pathAndFileOnRemoteServer); - } - String permissions = getOptionalVal(props, PROP_PERMISSIONS); - - int uid = getOptionalVal(props, PROP_OWNER_UID); - - int result = scpToServer(props, f, pathAndFileOnRemoteServer); - if (result == 0) { - result = chmodOnServer(props, permissions, pathAndFileOnRemoteServer); - if (result == 0) { - if (uid != -1) { - result = chownOnServer(props, uid, pathAndFileOnRemoteServer); - if (result != 0) { - LOG.warn("Error setting file owner to {}, after copying file {} to {}:{}; exit code {}", new Object[] { uid, pathAndFileOnRemoteServer, this, f, result }); - } - } - } else { - LOG.warn("Error setting file permissions to {}, after copying file {} to {}:{}; exit code {}", new Object[] { permissions, pathAndFileOnRemoteServer, this, f, result }); - } - } else { - LOG.warn("Error copying file {} to {}:{}; exit code {}", new Object[] {pathAndFileOnRemoteServer, this, f, result}); - } - return result; - } - - private int chownOnServer(Map props, int uid, String remote) { - return sshExec(props, "chown "+uid+" "+remote); - } - - private int copyTempFileToServer(Map props, File f, String pathAndFileOnRemoteServer) { - try { - return copyToServer(props, f, pathAndFileOnRemoteServer); - } finally { - f.delete(); - } - } - - @Override - public int copyFromServer(Map props, String pathAndFileOnRemoteServer, File localFile) { - return scpFromServer(props, pathAndFileOnRemoteServer, localFile); - } - - @Override - public int execScript(final Map props, final List commands, final Map env) { - return new ToolAbstractExecScript(props) { - public int run() { - String scriptContents = toScript(props, commands, env); - if (LOG.isTraceEnabled()) LOG.trace("Running shell command at {} as script: {}", host, scriptContents); - copyTempFileToServer(ImmutableMap.of("permissions", "0700"), writeTempFile(scriptContents), scriptPath); - - String cmd = Strings.join(buildRunScriptCommand(), separator); - return asInt(sshExec(props, cmd), -1); - } - }.run(); - } - - @Override - public int execCommands(Map props, List commands, Map env) { - Map props2 = new MutableMap(); - if (props!=null) props2.putAll(props); - props2.put(SshTool.PROP_NO_EXTRA_OUTPUT.getName(), true); - return execScript(props2, commands, env); - } - - private int scpToServer(Map props, File local, String remote) { - String to = (Strings.isEmpty(getUsername()) ? "" : getUsername()+"@")+getHostAddress()+":"+remote; - return scpExec(props, local.getAbsolutePath(), to); - } - - private int scpFromServer(Map props, String remote, File local) { - String from = (Strings.isEmpty(getUsername()) ? "" : getUsername()+"@")+getHostAddress()+":"+remote; - return scpExec(props, from, local.getAbsolutePath()); - } - - private int chmodOnServer(Map props, String permissions, String remote) { - return sshExec(props, "chmod "+permissions+" "+remote); - } - - private int scpExec(Map props, String from, String to) { - File tempFile = null; - try { - List cmd = Lists.newArrayList(); - cmd.add(getOptionalVal(props, PROP_SCP_EXECUTABLE, scpExecutable)); - if (privateKeyFile != null) { - cmd.add("-i"); - cmd.add(privateKeyFile.getAbsolutePath()); - } else if (privateKeyData != null) { - tempFile = writeTempFile(privateKeyData); - cmd.add("-i"); - cmd.add(tempFile.getAbsolutePath()); - } - if (!strictHostKeyChecking) { - cmd.add("-o"); - cmd.add("StrictHostKeyChecking=no"); - } - if (port != 22) { - cmd.add("-P"); - cmd.add(""+port); - } - cmd.add(from); - cmd.add(to); - - if (LOG.isTraceEnabled()) LOG.trace("Executing with command: {}", cmd); - int result = execProcess(props, cmd); - - if (LOG.isTraceEnabled()) LOG.trace("Executed command: {}; exit code {}", cmd, result); - return result; - - } finally { - if (tempFile != null) tempFile.delete(); - } - } - - private int sshExec(Map props, String command) { - File tempKeyFile = null; - try { - List cmd = Lists.newArrayList(); - cmd.add(getOptionalVal(props, PROP_SSH_EXECUTABLE, sshExecutable)); - String propsFlags = getOptionalVal(props, PROP_SSH_FLAGS, sshFlags); - if (propsFlags!=null && propsFlags.trim().length()>0) - cmd.addAll(Arrays.asList(propsFlags.trim().split(" "))); - if (privateKeyFile != null) { - cmd.add("-i"); - cmd.add(privateKeyFile.getAbsolutePath()); - } else if (privateKeyData != null) { - tempKeyFile = writeTempFile(privateKeyData); - cmd.add("-i"); - cmd.add(tempKeyFile.getAbsolutePath()); - } - if (!strictHostKeyChecking) { - cmd.add("-o"); - cmd.add("StrictHostKeyChecking=no"); - } - if (port != 22) { - cmd.add("-P"); - cmd.add(""+port); - } - if (allocatePTY) { - // have to be careful with double -tt as it can leave a shell session active - // when done from bash (ie ssh -tt localhost < /tmp/myscript.sh); - // hover that doesn't seem to be a problem the way we use it from brooklyn - // (and note single -t doesn't work _programmatically_ since the input isn't a terminal) - cmd.add("-tt"); - } - cmd.add((Strings.isEmpty(getUsername()) ? "" : getUsername()+"@")+getHostAddress()); - - cmd.add("bash -c "+BashStringEscapes.wrapBash(command)); - // previously we tried these approaches: - //cmd.add("$(<"+tempCmdFile.getAbsolutePath()+")"); - // only pays attention to the first word; the "; echo Executing ..." get treated as arguments - // to the script in the first word, when invoked from java (when invoked from prompt the behaviour is as desired) - //cmd.add("\""+command+"\""); - // only works if command is a single word - //cmd.add(tempCmdFile.getAbsolutePath()); - // above of course only works if the metafile is copied across - - if (LOG.isTraceEnabled()) LOG.trace("Executing ssh with command: {} (with {})", command, cmd); - int result = execProcess(props, cmd); - - if (LOG.isTraceEnabled()) LOG.trace("Executed command: {}; exit code {}", cmd, result); - return result; - - } finally { - if (tempKeyFile != null) tempKeyFile.delete(); - } - } - - private int execProcess(Map props, List cmdWords) { - OutputStream out = getOptionalVal(props, PROP_OUT_STREAM); - OutputStream err = getOptionalVal(props, PROP_ERR_STREAM); - return ProcessTool.execSingleProcess(cmdWords, null, (File)null, out, err, this); - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/699b3f65/core/src/main/java/brooklyn/util/internal/ssh/process/ProcessTool.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/internal/ssh/process/ProcessTool.java b/core/src/main/java/brooklyn/util/internal/ssh/process/ProcessTool.java deleted file mode 100644 index 90de9f2..0000000 --- a/core/src/main/java/brooklyn/util/internal/ssh/process/ProcessTool.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 brooklyn.util.internal.ssh.process; - -import static brooklyn.entity.basic.ConfigKeys.newConfigKey; -import static brooklyn.entity.basic.ConfigKeys.newStringConfigKey; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.config.ConfigKey; -import brooklyn.util.collections.MutableList; -import brooklyn.util.collections.MutableMap; -import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.internal.ssh.ShellAbstractTool; -import brooklyn.util.internal.ssh.ShellTool; -import brooklyn.util.internal.ssh.SshException; -import brooklyn.util.os.Os; -import brooklyn.util.stream.StreamGobbler; -import brooklyn.util.text.Strings; - -import com.google.common.base.Joiner; -import com.google.common.base.Preconditions; -import com.google.common.base.Throwables; -import com.google.common.io.ByteSource; -import com.google.common.io.ByteStreams; -import com.google.common.io.Files; - -/** Implementation of {@link ShellTool} which runs locally. */ -public class ProcessTool extends ShellAbstractTool implements ShellTool { - - private static final Logger LOG = LoggerFactory.getLogger(ProcessTool.class); - - // applies to calls - - public static final ConfigKey PROP_LOGIN_SHELL = newConfigKey("loginShell", "Causes the commands to be invoked with bash arguments to forcea login shell", Boolean.FALSE); - - public static final ConfigKey PROP_DIRECTORY = newStringConfigKey("directory", "the working directory, for executing commands", null); - - public ProcessTool() { - this(null); - } - - public ProcessTool(Map flags) { - super(getOptionalVal(flags, PROP_LOCAL_TEMP_DIR)); - if (flags!=null) { - MutableMap flags2 = MutableMap.copyOf(flags); - // TODO should remember other flags here? (e.g. NO_EXTRA_OUTPUT, RUN_AS_ROOT, etc) - flags2.remove(PROP_LOCAL_TEMP_DIR.getName()); - if (!flags2.isEmpty()) - LOG.warn(""+this+" ignoring unsupported constructor flags: "+flags); - } - } - - @Override - public int execScript(final Map props, final List commands, final Map env) { - return new ToolAbstractExecScript(props) { - public int run() { - try { - String directory = getOptionalVal(props, PROP_DIRECTORY); - File directoryDir = (directory != null) ? new File(Os.tidyPath(directory)) : null; - - String scriptContents = toScript(props, commands, env); - - if (LOG.isTraceEnabled()) LOG.trace("Running shell process (process) as script:\n{}", scriptContents); - File to = new File(scriptPath); - Files.createParentDirs(to); - ByteSource.wrap(scriptContents.getBytes()).copyTo(Files.asByteSink(to)); - - List cmds = buildRunScriptCommand(); - cmds.add(0, "chmod +x "+scriptPath); - return asInt(execProcesses(cmds, null, directoryDir, out, err, separator, getOptionalVal(props, PROP_LOGIN_SHELL), this), -1); - } catch (IOException e) { - throw Throwables.propagate(e); - } - } - }.run(); - } - - @Override - public int execCommands(Map props, List commands, Map env) { - if (Boolean.FALSE.equals(props.get("blocks"))) { - throw new IllegalArgumentException("Cannot exec non-blocking: command="+commands); - } - OutputStream out = getOptionalVal(props, PROP_OUT_STREAM); - OutputStream err = getOptionalVal(props, PROP_ERR_STREAM); - String separator = getOptionalVal(props, PROP_SEPARATOR); - String directory = getOptionalVal(props, PROP_DIRECTORY); - File directoryDir = (directory != null) ? new File(Os.tidyPath(directory)) : null; - - List allcmds = toCommandSequence(commands, null); - - String singlecmd = Joiner.on(separator).join(allcmds); - if (Boolean.TRUE.equals(getOptionalVal(props, PROP_RUN_AS_ROOT))) { - LOG.warn("Cannot run as root when executing as command; run as a script instead (will run as normal user): "+singlecmd); - } - if (LOG.isTraceEnabled()) LOG.trace("Running shell command (process): {}", singlecmd); - - return asInt(execProcesses(allcmds, env, directoryDir, out, err, separator, getOptionalVal(props, PROP_LOGIN_SHELL), this), -1); - } - - /** - * as {@link #execProcesses(List, Map, OutputStream, OutputStream, String, boolean, Object)} but not using a login shell - * @deprecated since 0.7; use {@link #execProcesses(List, Map, File, OutputStream, OutputStream, String, boolean, Object)} - */ - @Deprecated - public static int execProcesses(List cmds, Map env, OutputStream out, OutputStream err, String separator, Object contextForLogging) { - return execProcesses(cmds, env, (File)null, out, err, separator, false, contextForLogging); - } - - /** - * @deprecated since 0.7; use {@link #execProcesses(List, Map, File, OutputStream, OutputStream, String, boolean, Object)} - */ - @Deprecated - public static int execProcesses(List cmds, Map env, OutputStream out, OutputStream err, String separator, boolean asLoginShell, Object contextForLogging) { - return execProcesses(cmds, env, (File)null, out, err, separator, asLoginShell, contextForLogging); - } - - /** executes a set of commands by sending them as a single process to `bash -c` - * (single command argument of all the commands, joined with separator) - *

- * consequence of this is that you should not normally need to escape things oddly in your commands, - * type them just as you would into a bash shell (if you find exceptions please note them here!) - */ - public static int execProcesses(List cmds, Map env, File directory, OutputStream out, OutputStream err, String separator, boolean asLoginShell, Object contextForLogging) { - MutableList commands = new MutableList().append("bash"); - if (asLoginShell) commands.append("-l"); - commands.append("-c", Strings.join(cmds, Preconditions.checkNotNull(separator, "separator"))); - return execSingleProcess(commands, env, directory, out, err, contextForLogging); - } - - /** - * @deprecated since 0.7; use {@link #execSingleProcess(List, Map, File, OutputStream, OutputStream, Object)} - */ - @Deprecated - public static int execSingleProcess(List cmdWords, Map env, OutputStream out, OutputStream err, Object contextForLogging) { - return execSingleProcess(cmdWords, env, (File)null, out, err, contextForLogging); - } - - /** executes a single process made up of the given command words (*not* bash escaped); - * should be portable across OS's */ - public static int execSingleProcess(List cmdWords, Map env, File directory, OutputStream out, OutputStream err, Object contextForLogging) { - StreamGobbler errgobbler = null; - StreamGobbler outgobbler = null; - - ProcessBuilder pb = new ProcessBuilder(cmdWords); - if (env!=null) { - for (Map.Entry kv: env.entrySet()) pb.environment().put(kv.getKey(), String.valueOf(kv.getValue())); - } - if (directory != null) { - pb.directory(directory); - } - - try { - Process p = pb.start(); - - if (out != null) { - InputStream outstream = p.getInputStream(); - outgobbler = new StreamGobbler(outstream, out, (Logger) null); - outgobbler.start(); - } - if (err != null) { - InputStream errstream = p.getErrorStream(); - errgobbler = new StreamGobbler(errstream, err, (Logger) null); - errgobbler.start(); - } - - int result = p.waitFor(); - - if (outgobbler != null) outgobbler.blockUntilFinished(); - if (errgobbler != null) errgobbler.blockUntilFinished(); - - if (result==255) - // this is not definitive, but tests (and code?) expects throw exception if can't connect; - // only return exit code when it is exit code from underlying process; - // we have no way to distinguish 255 from ssh failure from 255 from the command run through ssh ... - // but probably 255 is from CLI ssh - throw new SshException("exit code 255 from CLI ssh; probably failed to connect"); - - return result; - } catch (InterruptedException e) { - throw Exceptions.propagate(e); - } catch (IOException e) { - throw Exceptions.propagate(e); - } finally { - closeWhispering(outgobbler, contextForLogging, "execProcess"); - closeWhispering(errgobbler, contextForLogging, "execProcess"); - } - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/699b3f65/core/src/main/java/brooklyn/util/internal/ssh/sshj/SshjClientConnection.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/internal/ssh/sshj/SshjClientConnection.java b/core/src/main/java/brooklyn/util/internal/ssh/sshj/SshjClientConnection.java deleted file mode 100644 index 982022a..0000000 --- a/core/src/main/java/brooklyn/util/internal/ssh/sshj/SshjClientConnection.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 brooklyn.util.internal.ssh.sshj; - -import static com.google.common.base.Objects.equal; -import static com.google.common.base.Preconditions.checkNotNull; - -import java.io.File; -import java.io.IOException; - -import net.schmizz.sshj.SSHClient; -import net.schmizz.sshj.transport.verification.PromiscuousVerifier; -import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile; -import net.schmizz.sshj.userauth.password.PasswordUtils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.util.GroovyJavaMethods; -import brooklyn.util.internal.ssh.SshAbstractTool.SshAction; - -import com.google.common.base.Objects; -import com.google.common.net.HostAndPort; - -/** based on code from jclouds */ -public class SshjClientConnection implements SshAction { - - private static final Logger LOG = LoggerFactory.getLogger(SshjClientConnection.class); - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - protected HostAndPort hostAndPort; - protected String username; - protected String password; - protected String privateKeyPassphrase; - protected String privateKeyData; - protected File privateKeyFile; - protected long connectTimeout; - protected long sessionTimeout; - protected boolean strictHostKeyChecking; - - public Builder hostAndPort(HostAndPort hostAndPort) { - this.hostAndPort = hostAndPort; - return this; - } - - public Builder username(String username) { - this.username = username; - return this; - } - - public Builder password(String val) { - this.password = val; - return this; - } - - /** @deprecated use privateKeyData */ - public Builder privateKey(String val) { - this.privateKeyData = val; - return this; - } - - public Builder privateKeyPassphrase(String val) { - this.privateKeyPassphrase = val; - return this; - } - - public Builder privateKeyData(String val) { - this.privateKeyData = val; - return this; - } - - public Builder privateKeyFile(File val) { - this.privateKeyFile = val; - return this; - } - - public Builder strictHostKeyChecking(boolean val) { - this.strictHostKeyChecking = val; - return this; - } - - public Builder connectTimeout(long connectTimeout) { - this.connectTimeout = connectTimeout; - return this; - } - - public Builder sessionTimeout(long sessionTimeout) { - this.sessionTimeout = sessionTimeout; - return this; - } - - public SshjClientConnection build() { - return new SshjClientConnection(this); - } - - protected static Builder fromSSHClientConnection(SshjClientConnection in) { - return new Builder().hostAndPort(in.getHostAndPort()).connectTimeout(in.getConnectTimeout()).sessionTimeout( - in.getSessionTimeout()).username(in.username).password(in.password).privateKey(in.privateKeyData).privateKeyFile(in.privateKeyFile); - } - } - - private final HostAndPort hostAndPort; - private final String username; - private final String password; - private final String privateKeyPassphrase; - private final String privateKeyData; - private final File privateKeyFile; - private final boolean strictHostKeyChecking; - private final int connectTimeout; - private final int sessionTimeout; - - SSHClient ssh; - - private SshjClientConnection(Builder builder) { - this.hostAndPort = checkNotNull(builder.hostAndPort); - this.username = builder.username; - this.password = builder.password; - this.privateKeyPassphrase = builder.privateKeyPassphrase; - this.privateKeyData = builder.privateKeyData; - this.privateKeyFile = builder.privateKeyFile; - this.strictHostKeyChecking = builder.strictHostKeyChecking; - this.connectTimeout = checkInt("connectTimeout", builder.connectTimeout, Integer.MAX_VALUE); - this.sessionTimeout = checkInt("sessionTimeout", builder.sessionTimeout, Integer.MAX_VALUE); - } - - static Integer checkInt(String context, long value, Integer ifTooLarge) { - if (value > Integer.MAX_VALUE) { - LOG.warn("Value '"+value+"' for "+context+" too large in SshjClientConnection; using "+value); - return ifTooLarge; - } - return (int)value; - } - - public boolean isConnected() { - return ssh != null && ssh.isConnected(); - } - - public boolean isAuthenticated() { - return ssh != null && ssh.isAuthenticated(); - } - - @Override - public void clear() { - if (ssh != null && ssh.isConnected()) { - try { - if (LOG.isTraceEnabled()) LOG.trace("Disconnecting SshjClientConnection {} ({})", this, System.identityHashCode(this)); - ssh.disconnect(); - } catch (IOException e) { - if (LOG.isDebugEnabled()) LOG.debug("<< exception disconnecting from {}: {}", e, e.getMessage()); - } - } - ssh = null; - } - - @Override - public SSHClient create() throws Exception { - if (LOG.isTraceEnabled()) LOG.trace("Connecting SshjClientConnection {} ({})", this, System.identityHashCode(this)); - ssh = new net.schmizz.sshj.SSHClient(); - if (!strictHostKeyChecking) { - ssh.addHostKeyVerifier(new PromiscuousVerifier()); - } - if (connectTimeout != 0) { - ssh.setConnectTimeout(connectTimeout); - } - if (sessionTimeout != 0) { - ssh.setTimeout(sessionTimeout); - } - ssh.connect(hostAndPort.getHostText(), hostAndPort.getPortOrDefault(22)); - - if (password != null) { - ssh.authPassword(username, password); - } else if (privateKeyData != null) { - OpenSSHKeyFile key = new OpenSSHKeyFile(); - key.init(privateKeyData, null, - GroovyJavaMethods.truth(privateKeyPassphrase) ? - PasswordUtils.createOneOff(privateKeyPassphrase.toCharArray()) - : null); - ssh.authPublickey(username, key); - } else if (privateKeyFile != null) { - OpenSSHKeyFile key = new OpenSSHKeyFile(); - key.init(privateKeyFile, - GroovyJavaMethods.truth(privateKeyPassphrase) ? - PasswordUtils.createOneOff(privateKeyPassphrase.toCharArray()) - : null); - ssh.authPublickey(username, key); - } else { - // Accept defaults (in ~/.ssh) - ssh.authPublickey(username); - } - - return ssh; - } - - /** - * @return host and port, where port if not present defaults to {@code 22} - */ - public HostAndPort getHostAndPort() { - return hostAndPort; - } - - /** - * @return username used in this ssh - */ - public String getUsername() { - return username; - } - - /** - * - * @return how long to wait for the initial connection to be made - */ - public int getConnectTimeout() { - return connectTimeout; - } - - /** - * - * @return how long to keep the ssh open, or {@code 0} for indefinitely - */ - public int getSessionTimeout() { - return sessionTimeout; - } - - /** - * - * @return the current ssh or {@code null} if not connected - */ - public SSHClient getSSHClient() { - return ssh; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - SshjClientConnection that = SshjClientConnection.class.cast(o); - return equal(this.hostAndPort, that.hostAndPort) && equal(this.username, that.username) - && equal(this.password, that.password) && equal(this.privateKeyData, that.privateKeyData) - && equal(this.privateKeyFile, that.privateKeyFile) && equal(this.ssh, that.ssh); - } - - @Override - public int hashCode() { - return Objects.hashCode(hostAndPort, username, password, privateKeyData, ssh); - } - - @Override - public String toString() { - return Objects.toStringHelper("") - .add("hostAndPort", hostAndPort) - .add("user", username) - .add("ssh", ssh != null ? ssh.hashCode() : null) - .add("password", (password != null ? "xxxxxx" : null)) - .add("privateKeyFile", privateKeyFile) - .add("privateKey", (privateKeyData != null ? "xxxxxx" : null)) - .add("connectTimeout", connectTimeout) - .add("sessionTimeout", sessionTimeout).toString(); - } -}