From 53c6a2376af534064006ff25071e7287b34a14f5 Mon Sep 17 00:00:00 2001 From: "adrian.f.cole" Date: Sun, 3 Jan 2010 07:00:06 +0000 Subject: [PATCH] revamped task so that it has no external deps. Added patched SSHExec task git-svn-id: http://jclouds.googlecode.com/svn/trunk@2581 3d8758e0-26b5-11de-8745-db77d3ebf521 --- tools/antcontrib/pom.xml | 5 - .../antcontrib/samples/javaoverssh/README.txt | 2 +- .../ant/taskdefs/optional/ssh/SSHExec.java | 394 +++++++++++++ .../org/jclouds/tools/ant/JavaOverSsh.java | 523 ++++++++---------- .../jclouds/tools/ant/JavaOverSshTest.java | 88 ++- .../java/org/jclouds/tools/ant/TestClass.java | 2 + 6 files changed, 674 insertions(+), 340 deletions(-) create mode 100644 tools/antcontrib/src/main/java/org/apache/tools/ant/taskdefs/optional/ssh/SSHExec.java diff --git a/tools/antcontrib/pom.xml b/tools/antcontrib/pom.xml index b8b132697b..d229759bbb 100644 --- a/tools/antcontrib/pom.xml +++ b/tools/antcontrib/pom.xml @@ -33,11 +33,6 @@ - - org.jboss.shrinkwrap - shrinkwrap-impl-base - 1.0.0-alpha-3 - ${project.groupId} jclouds-scriptbuilder diff --git a/tools/antcontrib/samples/javaoverssh/README.txt b/tools/antcontrib/samples/javaoverssh/README.txt index 39800928bc..34cf2739c9 100755 --- a/tools/antcontrib/samples/javaoverssh/README.txt +++ b/tools/antcontrib/samples/javaoverssh/README.txt @@ -22,4 +22,4 @@ - ex. curl http://jclouds.rimuhosting.com/maven2/snapshots/org/jclouds/jclouds-antcontrib/1.0-SNAPSHOT/jclouds-antcontrib-1.0-20091215.023231-1-jar-with-dependencies.jar >jclouds-antcontrib-all.jar 2. invoke ant, adding the library above, and passing the properties 'host' 'username' 'keyfile' which corresponds to your remote credentials - ex. ant -lib ~/.m2/repository/org/jclouds/jclouds-antcontrib/1.0-SNAPSHOT/jclouds-antcontrib-1.0-SNAPSHOT-jar-with-dependencies.jar -Dhost=67.202.42.237 -Dusername=root -Dkeyfile=$HOME/.ssh/id_dsa - - ex. ant -lib jclouds-antcontrib-all.jar -Dhost=67.202.42.237 -Dusername=root -Dkeyfile=$HOME/.ssh/id_dsa + - ex. ant -lib ~/.m2/repository/org/jclouds/jclouds-antcontrib/1.0-SNAPSHOT/jclouds-antcontrib-1.0-SNAPSHOT-jar-with-dependencies.jar -Dhost=localhost -Dusername=$USER -Dkeyfile=$HOME/.ssh/id_dsa diff --git a/tools/antcontrib/src/main/java/org/apache/tools/ant/taskdefs/optional/ssh/SSHExec.java b/tools/antcontrib/src/main/java/org/apache/tools/ant/taskdefs/optional/ssh/SSHExec.java new file mode 100644 index 0000000000..2d91ee14b7 --- /dev/null +++ b/tools/antcontrib/src/main/java/org/apache/tools/ant/taskdefs/optional/ssh/SSHExec.java @@ -0,0 +1,394 @@ +/* + * 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 org.apache.tools.ant.taskdefs.optional.ssh; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringReader; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.KeepAliveOutputStream; +import org.apache.tools.ant.util.TeeOutputStream; + +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; + +/** + * Executes a command on a remote machine via ssh. + * @since Ant 1.6 (created February 2, 2003) + */ +public class SSHExec extends SSHBase { + + private static final int BUFFER_SIZE = 8192; + private static final int RETRY_INTERVAL = 500; + + /** the command to execute via ssh */ + private String command = null; + + /** units are milliseconds, default is 0=infinite */ + private long maxwait = 0; + + /** for waiting for the command to finish */ + private Thread thread = null; + + private String outputProperty = null; + private File outputFile = null; + private String errorProperty = null; + private File errorFile = null; + private String resultProperty; + private boolean append = false; + + private Resource commandResource = null; + + private static final String TIMEOUT_MESSAGE = + "Timeout period exceeded, connection dropped."; + + /** + * Constructor for SSHExecTask. + */ + public SSHExec() { + super(); + } + + /** + * Sets the command to execute on the remote host. + * + * @param command The new command value + */ + public void setCommand(String command) { + this.command = command; + } + + /** + * Sets a commandResource from a file + * @param f the value to use. + * @since Ant 1.7.1 + */ + public void setCommandResource(String f) { + this.commandResource = new FileResource(new File(f)); + } + + /** + * The connection can be dropped after a specified number of + * milliseconds. This is sometimes useful when a connection may be + * flaky. Default is 0, which means "wait forever". + * + * @param timeout The new timeout value in seconds + */ + public void setTimeout(long timeout) { + maxwait = timeout; + } + + /** + * If used, stores the output of the command to the given file. + * + * @param output The file to write to. + */ + public void setOutput(File output) { + outputFile = output; + } + + /** + * If used, stores the error of the command to the given file. + * + * @param error The file to write to. + */ + public void setError(File error) { + this.errorFile = error; + } + + /** + * Determines if the output is appended to the file given in + * setOutput. Default is false, that is, overwrite + * the file. + * + * @param append True to append to an existing file, false to overwrite. + */ + public void setAppend(boolean append) { + this.append = append; + } + + /** + * If set, the output of the command will be stored in the given property. + * + * @param property The name of the property in which the command output + * will be stored. + */ + public void setOutputproperty(String property) { + outputProperty = property; + } + + /** + * Set the property name whose value should be set to the error of + * the process. + * + * @param property The name of a property in which the standard error of + * the command should be stored. + */ + public void setErrorproperty(String property) { + errorProperty = property; + } + + /** + * Set the name of the property in which the return code of the + * command should be stored. Only of interest if failonerror=false. + * + * @param resultProperty name of property. + * + * @since Ant 1.6 + */ + public void setResultProperty(String resultProperty) { + this.resultProperty = resultProperty; + } + + /** + * Execute the command on the remote host. + * + * @exception BuildException Most likely a network error or bad parameter. + */ + public void execute() throws BuildException { + if (getHost() == null) { + throw new BuildException("Host is required."); + } + if (getUserInfo().getName() == null) { + throw new BuildException("Username is required."); + } + if (getUserInfo().getKeyfile() == null + && getUserInfo().getPassword() == null) { + throw new BuildException("Password or Keyfile is required."); + } + if (command == null && commandResource == null) { + throw new BuildException("Command or commandResource is required."); + } + + Session session = null; + + try { + session = openSession(); + /* called once */ + if (command != null) { + log("cmd : " + command, Project.MSG_INFO); + ExecResponse response = executeCommand(session, command); + if (outputProperty != null) { + getProject().setNewProperty(outputProperty, response.getOutput()); + } + if (errorProperty != null) { + getProject().setNewProperty(outputProperty, response.getError()); + } + if (resultProperty != null) { + getProject().setNewProperty(resultProperty, response.getCode()+""); + } + } else { // read command resource and execute for each command + try { + BufferedReader br = new BufferedReader( + new InputStreamReader(commandResource.getInputStream())); + String cmd; + StringBuilder output = new StringBuilder(); + StringBuilder error = new StringBuilder(); + int lastCode = -1; + while ((cmd = br.readLine()) != null) { + log("cmd : " + cmd, Project.MSG_INFO); + ExecResponse response = executeCommand(session, cmd); + output.append(response.getOutput()); + error.append(response.getError()); + lastCode = response.getCode(); + } + if (outputProperty != null) { + getProject().setNewProperty(outputProperty, output.toString()); + } + if (errorProperty != null) { + getProject().setNewProperty(outputProperty, error.toString()); + } + if (resultProperty != null) { + getProject().setNewProperty(resultProperty, lastCode+""); + } + FileUtils.close(br); + } catch (IOException e) { + throw new BuildException(e); + } + } + } catch (JSchException e) { + throw new BuildException(e); + } finally { + if (session != null && session.isConnected()) { + session.disconnect(); + } + } + } + private static class ExecResponse { + + private final String output; + private final String error; + private final int code; + + public ExecResponse(String output, String error, int code) { + this.output = output; + this.error = error; + this.code = code; + } + + public String getError() { + return error; + } + + public String getOutput() { + return output; + } + + public int getCode() { + return code; + } + + } + + private ExecResponse executeCommand(Session session, String cmd) + throws BuildException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TeeOutputStream tee = new TeeOutputStream(out, new KeepAliveOutputStream(System.out)); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + TeeOutputStream teeErr = new TeeOutputStream(err, new KeepAliveOutputStream(System.err)); + int ec = -1; + + try { + final ChannelExec channel; + session.setTimeout((int) maxwait); + /* execute the command */ + channel = (ChannelExec) session.openChannel("exec"); + channel.setCommand(cmd); + channel.setOutputStream(tee); + channel.setExtOutputStream(tee); + channel.setErrStream(teeErr); + channel.connect(); + // wait for it to finish + thread = + new Thread() { + public void run() { + while (!channel.isClosed()) { + if (thread == null) { + return; + } + try { + sleep(RETRY_INTERVAL); + } catch (Exception e) { + // ignored + } + } + } + }; + + thread.start(); + thread.join(maxwait); + + if (thread.isAlive()) { + // ran out of time + thread = null; + if (getFailonerror()) { + throw new BuildException(TIMEOUT_MESSAGE); + } else { + log(TIMEOUT_MESSAGE, Project.MSG_ERR); + } + } else { + //success + if (outputFile != null) { + writeToFile(out.toString(), append, outputFile); + } + if (errorFile != null) { + writeToFile(err.toString(), append, errorFile); + } + // this is the wrong test if the remote OS is OpenVMS, + // but there doesn't seem to be a way to detect it. + ec = channel.getExitStatus(); + if (ec != 0) { + String msg = "Remote command failed with exit status " + ec; + if (getFailonerror()) { + throw new BuildException(msg); + } else { + log(msg, Project.MSG_ERR); + } + } + } + } catch (BuildException e) { + throw e; + } catch (JSchException e) { + if (e.getMessage().indexOf("session is down") >= 0) { + if (getFailonerror()) { + throw new BuildException(TIMEOUT_MESSAGE, e); + } else { + log(TIMEOUT_MESSAGE, Project.MSG_ERR); + } + } else { + if (getFailonerror()) { + throw new BuildException(e); + } else { + log("Caught exception: " + e.getMessage(), + Project.MSG_ERR); + } + } + } catch (Exception e) { + if (getFailonerror()) { + throw new BuildException(e); + } else { + log("Caught exception: " + e.getMessage(), Project.MSG_ERR); + } + } + return new ExecResponse(out.toString(), err.toString(), ec); + } + + /** + * Writes a string to a file. If destination file exists, it may be + * overwritten depending on the "append" value. + * + * @param from string to write + * @param to file to write to + * @param append if true, append to existing file, else overwrite + * @exception Exception most likely an IOException + */ + private void writeToFile(String from, boolean append, File to) + throws IOException { + FileWriter out = null; + try { + out = new FileWriter(to.getAbsolutePath(), append); + StringReader in = new StringReader(from); + char[] buffer = new char[BUFFER_SIZE]; + int bytesRead; + while (true) { + bytesRead = in.read(buffer); + if (bytesRead == -1) { + break; + } + out.write(buffer, 0, bytesRead); + } + out.flush(); + } finally { + if (out != null) { + out.close(); + } + } + } +} diff --git a/tools/antcontrib/src/main/java/org/jclouds/tools/ant/JavaOverSsh.java b/tools/antcontrib/src/main/java/org/jclouds/tools/ant/JavaOverSsh.java index 86b82603f2..376f369fa6 100644 --- a/tools/antcontrib/src/main/java/org/jclouds/tools/ant/JavaOverSsh.java +++ b/tools/antcontrib/src/main/java/org/jclouds/tools/ant/JavaOverSsh.java @@ -21,324 +21,201 @@ package org.jclouds.tools.ant; import static com.google.common.base.Preconditions.checkNotNull; import static org.jclouds.scriptbuilder.domain.Statements.exec; -import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; +import java.security.SecureRandom; import java.util.List; -import java.util.zip.ZipFile; import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; import org.apache.tools.ant.Project; +import org.apache.tools.ant.Target; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.optional.ssh.SSHExec; import org.apache.tools.ant.taskdefs.optional.ssh.SSHUserInfo; +import org.apache.tools.ant.taskdefs.optional.ssh.Scp; import org.apache.tools.ant.types.CommandlineJava; import org.apache.tools.ant.types.Environment; -import org.apache.tools.ant.util.KeepAliveOutputStream; -import org.apache.tools.ant.util.TeeOutputStream; -import org.jboss.shrinkwrap.api.Archives; -import org.jboss.shrinkwrap.api.exporter.ZipExporter; -import org.jboss.shrinkwrap.api.importer.ExplodedImporter; -import org.jboss.shrinkwrap.api.importer.ZipImporter; -import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; import org.jclouds.scriptbuilder.domain.OsFamily; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.StatementList; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.common.io.Closeables; -import com.jcraft.jsch.ChannelExec; -import com.jcraft.jsch.ChannelSftp; -import com.jcraft.jsch.JSch; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; /** - * Ported from Jsch SSHExec task. + * Version of the Java task that executes over ssh. * * @author Adrian Cole */ public class JavaOverSsh extends Java { - /** Default listen port for SSH daemon */ - private static final int SSH_PORT = 22; + private final SSHExec exec; + private final Scp scp; + private final SSHUserInfo userInfo; + private String jvm = "/usr/bin/java"; private File localDirectory; private File remoteDirectory; private Environment env = new Environment(); - private String host; - private String knownHosts; - private int port = SSH_PORT; - private SSHUserInfo userInfo = new SSHUserInfo(); + private OsFamily osFamily = OsFamily.UNIX; - - /** units are milliseconds, default is 0=infinite */ - private long maxwait = 0; - - /** for waiting for the command to finish */ - private Thread watchDog = null; - private File outputFile; - private String outputProperty; - private String resultProperty; private File errorFile; private String errorProperty; + private File outputFile; + private String outputProperty; private boolean append; - private static final String TIMEOUT_MESSAGE = "Timeout period exceeded, connection dropped."; - public JavaOverSsh() { super(); setFork(true); + exec = new SSHExec(); + scp = new Scp(); + userInfo = new SSHUserInfo(); } public JavaOverSsh(Task owner) { - super(owner); - setFork(true); + this(); + bindToOwner(owner); } @Override public int executeJava() throws BuildException { - String command = convertJavaToScript(getCommandLine()); - - InputStream classpathJar = (getCommandLine().getClasspath() != null) ? makeClasspathJarOrNull(getCommandLine() - .getClasspath().list()) - : null; - - InputStream bootClasspathJar = (getCommandLine().getBootclasspath() != null) ? makeClasspathJarOrNull(getCommandLine() - .getBootclasspath().list()) - : null; - - InputStream currentDirectoryZip = Archives.create("cwd.zip", ZipExporter.class).as( - ExplodedImporter.class).importDirectory(localDirectory).as(ZipExporter.class) - .exportZip(); - - if (getHost() == null) { - throw new BuildException("Host is required."); - } - if (getUserInfo().getName() == null) { - throw new BuildException("Username is required."); - } - if (getUserInfo().getKeyfile() == null && getUserInfo().getPassword() == null) { - throw new BuildException("Password or Keyfile is required."); - } - - Session session = null; - try { - // execute the command - session = openSession(); - session.setTimeout((int) maxwait); - ChannelSftp sftp = null; - sftp = (ChannelSftp) session.openChannel("sftp"); - sftp.connect(); - sftp.put(currentDirectoryZip, remoteDirectory + "/cwd.zip"); - Closeables.closeQuietly(currentDirectoryZip); - - if (classpathJar != null || bootClasspathJar != null) { - if (classpathJar != null) { - sftp.put(classpathJar, remoteDirectory + "/classpath.jar"); - Closeables.closeQuietly(classpathJar); - } - if (bootClasspathJar != null) { - sftp.put(classpathJar, remoteDirectory + "/boot-classpath.jar"); - Closeables.closeQuietly(classpathJar); - } - } - - final ChannelExec channel = (ChannelExec) session.openChannel("exec"); - channel.setCommand(command); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - TeeOutputStream teeOut = new TeeOutputStream(out, new KeepAliveOutputStream(System.out)); - ByteArrayOutputStream err = new ByteArrayOutputStream(); - TeeOutputStream teeErr = new TeeOutputStream(err, new KeepAliveOutputStream(System.err)); - - channel.setOutputStream(teeOut); - channel.setExtOutputStream(teeOut); - channel.setErrStream(teeErr); - - channel.connect(); - - // wait for it to finish - watchDog = new Thread() { - public void run() { - while (!channel.isEOF()) { - if (watchDog == null) { - return; - } - try { - sleep(500); - } catch (Exception e) { - // ignored - } - } - } - }; - - watchDog.start(); - watchDog.join(maxwait); - - if (watchDog.isAlive()) { - // ran out of time - - throw new BuildException(TIMEOUT_MESSAGE); - } else { - // completed successfully - return writeOutputAndReturnExitStatus(channel, out, err); - } - } catch (BuildException e) { - throw e; - } catch (JSchException e) { - if (e.getMessage().indexOf("session is down") >= 0) { - throw new BuildException(TIMEOUT_MESSAGE, e); - } else { - throw new BuildException(e); - } - } catch (Exception e) { - throw new BuildException(e); - } finally { - if (session != null && session.isConnected()) { - session.disconnect(); - } - } - } - - InputStream makeClasspathJarOrNull(String... paths) { - if (paths != null && paths.length > 0) { - JavaArchive classpathArchive = Archives.create("classpath.jar", JavaArchive.class); - for (String path : paths) { - File file = new File(path); - if (file.exists()) { - if (file.isFile()) { - try { - classpathArchive.as(ZipImporter.class).importZip( - new ZipFile(file.getAbsolutePath())); - } catch (IOException e) { - throw new BuildException(e); - } - } else { - classpathArchive.as(ExplodedImporter.class).importDirectory(file); - } - } - } - return classpathArchive.as(ZipExporter.class).exportZip(); - } - return null; - } - - private int writeOutputAndReturnExitStatus(final ChannelExec channel, ByteArrayOutputStream out, - ByteArrayOutputStream err) throws IOException { - if (outputProperty != null) { - getProject().setProperty(outputProperty, out.toString()); - } - if (outputFile != null) { - writeToFile(err.toString(), append, outputFile); - } - if (errorProperty != null) { - getProject().setProperty(errorProperty, err.toString()); - } - if (errorFile != null) { - writeToFile(out.toString(), append, errorFile); - } - if (resultProperty != null) { - getProject().setProperty(resultProperty, channel.getExitStatus() + ""); - } - return channel.getExitStatus(); - } - - /** - * Writes a string to a file. If destination file exists, it may be overwritten depending on the - * "append" value. - * - * @param from - * string to write - * @param to - * file to write to - * @param append - * if true, append to existing file, else overwrite - * @exception Exception - * most likely an IOException - */ - private void writeToFile(String from, boolean append, File to) throws IOException { - FileWriter out = null; - try { - out = new FileWriter(to.getAbsolutePath(), append); - StringReader in = new StringReader(from); - char[] buffer = new char[8192]; - int bytesRead; - while (true) { - bytesRead = in.read(buffer); - if (bytesRead == -1) { - break; - } - out.write(buffer, 0, bytesRead); - } - out.flush(); - } finally { - if (out != null) { - out.close(); - } - } - } - - String convertJavaToScript(CommandlineJava commandLine) { checkNotNull(jvm, "jvm must be set"); - checkNotNull(localDirectory, "dir must be set"); checkNotNull(remoteDirectory, "remotedir must be set"); + // must copy the files over first as we are changing the system properties based on this. + + if (localDirectory != null) { + FileSet cwd = new FileSet(); + cwd.setDir(localDirectory); + mkdirAndCopyTo(remoteDirectory.getAbsolutePath(), ImmutableList.of(cwd)); + } + + if (getCommandLine().getClasspath() != null) { + copyPathTo(getCommandLine().getClasspath(), remoteDirectory.getAbsolutePath() + + "/classpath"); + } + if (getCommandLine().getBootclasspath() != null) { + copyPathTo(getCommandLine().getBootclasspath(), remoteDirectory.getAbsolutePath() + + "/bootclasspath"); + } + + String command = convertJavaToScriptNormalizingPaths(getCommandLine()); + + String random = new SecureRandom().nextInt() + ""; + exec.setResultProperty(random); + exec.setFailonerror(false); + exec.setCommand(command); + exec.setError(errorFile); + exec.setErrorproperty(errorProperty); + exec.setOutput(outputFile); + exec.setOutputproperty(outputProperty); + exec.setAppend(append); + exec.execute(); + return Integer.parseInt(getProject().getProperty(random)); + } + + private void mkdirAndCopyTo(String destination, Iterable sets) { + scp.init(); + exec.setCommand(exec("{md} " + destination).render(osFamily)); + exec.execute(); + String scpDestination = getScpDir(destination); + System.out.println("Sending to: " + scpDestination); + for (FileSet set : sets) + scp.addFileset(set); + scp.setTodir(scpDestination); + scp.execute(); + } + + private String getScpDir(String path) { + return String.format("%s:%s@%s:%s", userInfo.getName(), + userInfo.getKeyfile() == null ? userInfo.getPassword() : userInfo.getPassphrase(), + scp.getHost(), path); + } + + void resetPathToUnderPrefixIfExistsAndIsFileIfNotExistsAddAsIs(Path path, String prefix, + StringBuilder destination) { + if (path == null) + return; + String[] paths = path.list(); + if (paths != null && paths.length > 0) { + for (int i = 0; i < paths.length; i++) { + File file = new File(paths[i]); + if (file.exists()) { + // directories are flattened under the prefix anyway, so there's no need to add them + // to the path + if (file.isFile()) + destination.append("{ps}").append(file.getName()); + } else { + // if the file doesn't exist, it is probably a "forward reference" to something that + // is already on the remote machine + destination.append("{ps}").append(file.getAbsolutePath()); + } + } + } + } + + void copyPathTo(Path path, String destination) { + List filesets = Lists.newArrayList(); + if (path.list() != null && path.list().length > 0) { + for (String filepath : path.list()) { + File file = new File(filepath); + if (file.exists()) { + FileSet fileset = new FileSet(); + if (file.isFile()) { + fileset.setFile(file); + } else { + fileset.setDir(file); + } + filesets.add(fileset); + } + } + } + mkdirAndCopyTo(destination, filesets); + } + + String convertJavaToScriptNormalizingPaths(CommandlineJava commandLine) { List statements = Lists.newArrayList(); String[] environment = env.getVariables(); if (environment != null) { for (int i = 0; i < environment.length; i++) { log("Setting environment variable: " + environment[i], Project.MSG_VERBOSE); - statements.add(exec("{export} " + environment[i])); + String[] keyValue = environment[i].split("="); + statements.add(exec(String.format("{export} %s={vq}%s{vq}", keyValue[0], keyValue[1]))); } } statements.add(exec("{cd} " + remoteDirectory)); - statements.add(exec("jar -xf cwd.zip")); - StringBuilder commandBuilder = new StringBuilder(jvm); - if (commandLine.getBootclasspath() != null - && commandLine.getBootclasspath().list().length > 0) { - commandBuilder.append(" -Xbootclasspath:boot-classpath.jar"); + if (getCommandLine().getBootclasspath() != null) { + commandBuilder.append(" -Xbootclasspath:bootclasspath"); + resetPathToUnderPrefixIfExistsAndIsFileIfNotExistsAddAsIs(commandLine.getBootclasspath(), + "bootclasspath", commandBuilder); } if (commandLine.getVmCommand().getArguments() != null && commandLine.getVmCommand().getArguments().length > 0) { - commandBuilder.append(" "); - String[] variables = commandLine.getVmCommand().getArguments(); - for (int i = 0; i < variables.length; i++) { - commandBuilder.append(variables[i]); - if (i + 1 < variables.length) - commandBuilder.append(" "); - } - } - if (commandLine.getClasspath() != null && commandLine.getClasspath().list().length > 0) { - commandBuilder.append(" -cp classpath.jar"); + commandBuilder.append(" ").append( + Joiner.on(' ').join(commandLine.getVmCommand().getArguments())); } + commandBuilder.append(" -cp classpath"); + resetPathToUnderPrefixIfExistsAndIsFileIfNotExistsAddAsIs(commandLine.getClasspath(), + "classpath", commandBuilder); + if (commandLine.getSystemProperties() != null && commandLine.getSystemProperties().getVariables() != null && commandLine.getSystemProperties().getVariables().length > 0) { - commandBuilder.append(" "); - String[] variables = commandLine.getSystemProperties().getVariables(); - for (int i = 0; i < variables.length; i++) { - commandBuilder.append(variables[i]); - if (i + 1 < variables.length) - commandBuilder.append(" "); - } + commandBuilder.append(" ").append( + Joiner.on(' ').join(commandLine.getSystemProperties().getVariables())); } commandBuilder.append(" ").append(commandLine.getClassname()); if (commandLine.getJavaCommand().getArguments() != null && commandLine.getJavaCommand().getArguments().length > 0) { - commandBuilder.append(" "); - String[] variables = commandLine.getJavaCommand().getArguments(); - for (int i = 0; i < variables.length; i++) { - commandBuilder.append(variables[i]); - if (i + 1 < variables.length) - commandBuilder.append(" "); - } + commandBuilder.append(" ").append( + Joiner.on(' ').join(commandLine.getJavaCommand().getArguments())); } statements.add(exec(commandBuilder.toString())); @@ -351,6 +228,10 @@ public class JavaOverSsh extends Java { env.addVariable(var); } + /** + * Note that if the {@code dir} property is set, this will be copied recursively to the remote + * host. + */ @Override public void setDir(File localDir) { this.localDirectory = checkNotNull(localDir, "dir"); @@ -378,7 +259,8 @@ public class JavaOverSsh extends Java { * The new host value */ public void setHost(String host) { - this.host = host; + exec.setHost(host); + scp.setHost(host); } /** @@ -387,7 +269,7 @@ public class JavaOverSsh extends Java { * @return the host */ public String getHost() { - return host; + return exec.getHost(); } /** @@ -397,6 +279,8 @@ public class JavaOverSsh extends Java { * The new username value */ public void setUsername(String username) { + exec.setUsername(username); + scp.setUsername(username); userInfo.setName(username); } @@ -407,6 +291,8 @@ public class JavaOverSsh extends Java { * The new password value */ public void setPassword(String password) { + exec.setPassword(password); + scp.setPassword(password); userInfo.setPassword(password); } @@ -417,7 +303,11 @@ public class JavaOverSsh extends Java { * The new keyfile value */ public void setKeyfile(String keyfile) { + exec.setKeyfile(keyfile); + scp.setKeyfile(keyfile); userInfo.setKeyfile(keyfile); + if (userInfo.getPassphrase() == null) + userInfo.setPassphrase(""); } /** @@ -427,6 +317,8 @@ public class JavaOverSsh extends Java { * The new passphrase value */ public void setPassphrase(String passphrase) { + exec.setPassphrase(passphrase); + scp.setPassphrase(passphrase); userInfo.setPassphrase(passphrase); } @@ -439,7 +331,8 @@ public class JavaOverSsh extends Java { * a path to the known hosts file. */ public void setKnownhosts(String knownHosts) { - this.knownHosts = knownHosts; + exec.setKnownhosts(knownHosts); + scp.setKnownhosts(knownHosts); } /** @@ -449,6 +342,8 @@ public class JavaOverSsh extends Java { * if true trust the identity of unknown hosts. */ public void setTrust(boolean yesOrNo) { + exec.setTrust(yesOrNo); + scp.setTrust(yesOrNo); userInfo.setTrust(yesOrNo); } @@ -459,7 +354,8 @@ public class JavaOverSsh extends Java { * port number of remote host. */ public void setPort(int port) { - this.port = port; + exec.setPort(port); + scp.setPort(port); } /** @@ -468,7 +364,7 @@ public class JavaOverSsh extends Java { * @return the port */ public int getPort() { - return port; + return exec.getPort(); } /** @@ -479,42 +375,8 @@ public class JavaOverSsh extends Java { */ public void init() throws BuildException { super.init(); - this.knownHosts = System.getProperty("user.home") + "/.ssh/known_hosts"; - this.port = SSH_PORT; - } - - /** - * Open an ssh seession. - * - * @return the opened session - * @throws JSchException - * on error - */ - protected Session openSession() throws JSchException { - JSch jsch = new JSch(); - if (null != userInfo.getKeyfile()) { - jsch.addIdentity(userInfo.getKeyfile()); - } - - if (!userInfo.getTrust() && knownHosts != null) { - log("Using known hosts: " + knownHosts, Project.MSG_DEBUG); - jsch.setKnownHosts(knownHosts); - } - - Session session = jsch.getSession(userInfo.getName(), host, port); - session.setUserInfo(userInfo); - log("Connecting to " + host + ":" + port); - session.connect(); - return session; - } - - /** - * Get the user information. - * - * @return the user information - */ - protected SSHUserInfo getUserInfo() { - return userInfo; + exec.init(); + scp.init(); } /** @@ -525,36 +387,85 @@ public class JavaOverSsh extends Java { * The new timeout value in seconds */ public void setTimeout(long timeout) { - maxwait = timeout; + exec.setTimeout(timeout); + } + + /** + * Set the verbose flag. + * + * @param verbose + * if true output more verbose logging + * @since Ant 1.6.2 + */ + public void setVerbose(boolean verbose) { + exec.setVerbose(verbose); + scp.setVerbose(verbose); } @Override - public void setError(File out) { - this.errorFile = out; + public void setError(File error) { + this.errorFile = error; } @Override - public void setErrorProperty(String errorProp) { - this.errorProperty = errorProp; + public void setErrorProperty(String property) { + errorProperty = property; } @Override public void setOutput(File out) { - this.outputFile = out; + outputFile = out; } @Override public void setOutputproperty(String outputProp) { - this.outputProperty = outputProp; - } - - @Override - public void setResultProperty(String resultProperty) { - this.resultProperty = resultProperty; + outputProperty = outputProp; } @Override public void setAppend(boolean append) { this.append = append; } + + @Override + public void setProject(Project project) { + super.setProject(project); + exec.setProject(project); + scp.setProject(project); + } + + @Override + public void setOwningTarget(Target target) { + super.setOwningTarget(target); + exec.setOwningTarget(target); + scp.setOwningTarget(target); + } + + @Override + public void setTaskName(String taskName) { + super.setTaskName(taskName); + exec.setTaskName(taskName); + scp.setTaskName(taskName); + } + + @Override + public void setDescription(String description) { + super.setDescription(description); + exec.setDescription(description); + scp.setDescription(description); + } + + @Override + public void setLocation(Location location) { + super.setLocation(location); + exec.setLocation(location); + scp.setLocation(location); + } + + @Override + public void setTaskType(String type) { + super.setTaskType(type); + exec.setTaskType(type); + scp.setTaskType(type); + } } diff --git a/tools/antcontrib/src/test/java/org/jclouds/tools/ant/JavaOverSshTest.java b/tools/antcontrib/src/test/java/org/jclouds/tools/ant/JavaOverSshTest.java index 8e5bed33e9..539ebae90a 100644 --- a/tools/antcontrib/src/test/java/org/jclouds/tools/ant/JavaOverSshTest.java +++ b/tools/antcontrib/src/test/java/org/jclouds/tools/ant/JavaOverSshTest.java @@ -24,33 +24,39 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Map.Entry; import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; import org.apache.tools.ant.types.Environment; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Environment.Variable; import org.testng.annotations.Test; +import com.google.common.collect.Iterables; + /** * @author Adrian Cole */ @Test(groups = "unit", testName = "jclouds.JavaOverSshTest") public class JavaOverSshTest { + public static final Entry LAST_ENV = Iterables.getLast(System.getenv() + .entrySet()); + // TODO, this test will break in windows public void testFull() throws SecurityException, NoSuchMethodException { - JavaOverSsh task = createTask(); - assertEquals( - String - .format( - "cd /tmp/foo\njar -xf cwd.zip\n%s -Xms256 -cp classpath.jar -Dfooble=baz -Dfoo=bar org.jclouds.tools.ant.TestClass hello world\n", - System.getProperty("java.home") + "/bin/java", System - .getProperty("user.dir")), task.convertJavaToScript(task - .getCommandLine())); + JavaOverSsh task = makeJavaOverSsh(); + String expected = String + .format( + "export %s=\"%s\"%ncd /tmp/foo\n%s -Xms16m -Xmx32m -cp classpath -Dfooble=baz -Dfoo=bar org.jclouds.tools.ant.TestClass %s hello world\n", + LAST_ENV.getKey(), LAST_ENV.getValue(), System.getProperty("java.home") + + "/bin/java", LAST_ENV.getKey()); + assertEquals(task.convertJavaToScriptNormalizingPaths(task.getCommandLine()), expected); } - private JavaOverSsh createTask() { + private Java populateTask(Java task) { Project p = new Project(); - JavaOverSsh task = new JavaOverSsh(); task.setProject(p); task.setClassname(TestClass.class.getName()); task.createClasspath().add(new Path(p, "target/test-classes")); @@ -62,20 +68,43 @@ public class JavaOverSshTest { Variable prop2 = new Environment.Variable(); prop2.setKey("foo"); prop2.setValue("bar"); - task.addSysproperty(prop2); - task.createJvmarg().setValue("-Xms256"); + task.createJvmarg().setValue("-Xms16m"); + task.createJvmarg().setValue("-Xmx32m"); + Variable env = new Environment.Variable(); + env.setKey(LAST_ENV.getKey()); + env.setValue(LAST_ENV.getValue()); + task.addEnv(env); + task.createArg().setValue(env.getKey()); task.createArg().setValue("hello"); task.createArg().setValue("world"); task.setDir(new File(System.getProperty("user.dir"))); - task.setRemotedir(new File("/tmp/foo")); task.setFork(true); task.setJvm(System.getProperty("java.home") + "/bin/java"); + task.setOutputproperty("out"); + task.setErrorProperty("err"); + task.setResultProperty("result"); return task; } @Test(enabled = false, groups = { "live" }) public void testSsh() throws NumberFormatException, FileNotFoundException, IOException { + Java java = makeJava(); + java.execute(); + + JavaOverSsh javaOverSsh = makeJavaOverSsh(); + addDestinationTo(javaOverSsh); + javaOverSsh.execute(); + + assertEquals(javaOverSsh.getProject().getProperty("out"), javaOverSsh.getProject() + .getProperty("out")); + assertEquals(javaOverSsh.getProject().getProperty("err"), javaOverSsh.getProject() + .getProperty("err")); + assertEquals(javaOverSsh.getProject().getProperty("result"), javaOverSsh.getProject() + .getProperty("result")); + } + + private void addDestinationTo(JavaOverSsh javaOverSsh) throws UnknownHostException { String sshHost = System.getProperty("jclouds.test.ssh.host"); String sshPort = System.getProperty("jclouds.test.ssh.port"); String sshUser = System.getProperty("jclouds.test.ssh.username"); @@ -85,23 +114,26 @@ public class JavaOverSshTest { int port = (sshPort != null) ? Integer.parseInt(sshPort) : 22; InetAddress host = (sshHost != null) ? InetAddress.getByName(sshHost) : InetAddress .getLocalHost(); - - JavaOverSsh task = createTask(); - task.setHost(host.getHostAddress()); - task.setPort(port); - task.setTrust(true); - task.setUsername(sshUser); + javaOverSsh.setHost(host.getHostAddress()); + javaOverSsh.setPort(port); + javaOverSsh.setUsername(sshUser); if (sshKeyFile != null && !sshKeyFile.trim().equals("")) { - task.setKeyfile(sshKeyFile); + javaOverSsh.setKeyfile(sshKeyFile); } else { - task.setPassword(sshPass); + javaOverSsh.setPassword(sshPass); } - task.setOutputproperty("out"); - task.setErrorProperty("err"); - task.setResultProperty("result"); - task.execute(); - assertEquals(task.getProject().getProperty("out"), "[hello, world]\n"); - assertEquals(task.getProject().getProperty("err"), "err\n"); - assertEquals(task.getProject().getProperty("result"), "3"); + } + + private JavaOverSsh makeJavaOverSsh() { + JavaOverSsh task = new JavaOverSsh(); + populateTask(task); + task.setRemotedir(new File("/tmp/foo")); + task.setVerbose(true); + task.setTrust(true); + return task; + } + + private Java makeJava() { + return populateTask(new Java()); } } diff --git a/tools/antcontrib/src/test/java/org/jclouds/tools/ant/TestClass.java b/tools/antcontrib/src/test/java/org/jclouds/tools/ant/TestClass.java index 8cf6db3807..385be827f7 100644 --- a/tools/antcontrib/src/test/java/org/jclouds/tools/ant/TestClass.java +++ b/tools/antcontrib/src/test/java/org/jclouds/tools/ant/TestClass.java @@ -5,6 +5,8 @@ import java.util.Arrays; public class TestClass { public static void main(String... args) { + System.out.println("env:"); + System.out.println(System.getenv(args[0])); File cwd = new File(System.getProperty("user.dir")); System.out.println("children:"); for (File child : cwd.listFiles())