diff --git a/tools/antcontrib/src/main/java/org/jclouds/tools/ant/taskdefs/sshjava/SSHJava.java b/tools/antcontrib/src/main/java/org/jclouds/tools/ant/taskdefs/sshjava/SSHJava.java index 516d113a94..9d1e404f2f 100644 --- a/tools/antcontrib/src/main/java/org/jclouds/tools/ant/taskdefs/sshjava/SSHJava.java +++ b/tools/antcontrib/src/main/java/org/jclouds/tools/ant/taskdefs/sshjava/SSHJava.java @@ -21,13 +21,22 @@ package org.jclouds.tools.ant.taskdefs.sshjava; import static com.google.common.base.Preconditions.checkNotNull; import static org.jclouds.scriptbuilder.domain.Statements.exec; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Field; import java.security.SecureRandom; +import java.util.Hashtable; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Location; +import org.apache.tools.ant.MagicNames; import org.apache.tools.ant.Project; +import org.apache.tools.ant.PropertyHelper; import org.apache.tools.ant.Target; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Java; @@ -38,13 +47,19 @@ import org.apache.tools.ant.types.CommandlineJava; import org.apache.tools.ant.types.Environment; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Environment.Variable; +import org.jclouds.scriptbuilder.InitBuilder; import org.jclouds.scriptbuilder.domain.OsFamily; +import org.jclouds.scriptbuilder.domain.ShellToken; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.StatementList; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; /** * Version of the Java task that executes over ssh. @@ -58,7 +73,8 @@ public class SSHJava extends Java { private String jvm = "/usr/bin/java"; private File localDirectory; - private File remoteDirectory; + private File remotebase; + private File remotedir; private Environment env = new Environment(); private OsFamily osFamily = OsFamily.UNIX; @@ -66,7 +82,11 @@ public class SSHJava extends Java { private String errorProperty; private File outputFile; private String outputProperty; + private String id = "javassh" + new SecureRandom().nextLong(); + private boolean append; + @VisibleForTesting + final Map shiftMap = Maps.newHashMap(); public SSHJava() { super(); @@ -81,49 +101,111 @@ public class SSHJava extends Java { bindToOwner(owner); } + public void setId(String id) { + this.id = id; + } + @Override public int executeJava() throws BuildException { checkNotNull(jvm, "jvm 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. + checkNotNull(remotebase, "remotebase must be set"); - if (localDirectory != null) { - FileSet cwd = new FileSet(); - cwd.setDir(localDirectory); - mkdirAndCopyTo(remoteDirectory.getAbsolutePath(), ImmutableList.of(cwd)); + if (localDirectory == null) { + try { + localDirectory = File.createTempFile("sshjava", "dir"); + localDirectory.delete(); + localDirectory.mkdirs(); + } catch (IOException e) { + throw new BuildException(e); + } + } + if (remotedir == null) + remotedir = new File(remotebase, id); + + if (osFamily == OsFamily.UNIX) { + log("removing old contents: " + remotedir.getAbsolutePath(), Project.MSG_VERBOSE); + exec.setCommand(exec("rm -rf " + remotedir.getAbsolutePath()).render(osFamily)); + exec.execute(); + } else { + // TODO need recursive remove on windows + } + // must copy the files over first as we are changing the system properties based on this. + String command = convertJavaToScriptNormalizingPaths(getCommandLine()); + + try { + BufferedWriter out = new BufferedWriter(new FileWriter(new File(localDirectory, "init." + + ShellToken.SH.to(osFamily)))); + out.write(command); + out.close(); + } catch (IOException e) { + throw new BuildException(e); + } + + FileSet cwd = new FileSet(); + cwd.setDir(localDirectory); + mkdirAndCopyTo(remotedir.getAbsolutePath(), ImmutableList.of(cwd)); + + for (Entry entry : shiftMap.entrySet()) { + FileSet set = new FileSet(); + set.setDir(new File(entry.getKey())); + mkdirAndCopyTo(remotebase.getAbsolutePath() + ShellToken.FS.to(osFamily) + + entry.getValue(), ImmutableList.of(set)); } if (getCommandLine().getClasspath() != null) { - copyPathTo(getCommandLine().getClasspath(), remoteDirectory.getAbsolutePath() - + "/classpath"); + copyPathTo(getCommandLine().getClasspath(), remotedir.getAbsolutePath() + "/classpath"); } - + if (getCommandLine().getBootclasspath() != null) { - copyPathTo(getCommandLine().getBootclasspath(), remoteDirectory.getAbsolutePath() + copyPathTo(getCommandLine().getBootclasspath(), remotedir.getAbsolutePath() + "/bootclasspath"); } - String command = convertJavaToScriptNormalizingPaths(getCommandLine()); + if (osFamily == OsFamily.UNIX) { + exec.setCommand(exec("chmod 755 " + remotedir.getAbsolutePath() + "{fs}init.{sh}").render( + osFamily)); + exec.execute(); + } - String random = new SecureRandom().nextInt() + ""; - exec.setResultProperty(random); + Statement statement = new StatementList(exec("{cd} " + remotedir.getAbsolutePath()), + exec(remotedir.getAbsolutePath() + "{fs}init.{sh} init"), exec(remotedir + .getAbsolutePath() + + "{fs}init.{sh} run")); + getProjectProperties().remove(id); + exec.setResultProperty(id); exec.setFailonerror(false); - exec.setCommand(command); + exec.setCommand(statement.render(osFamily)); exec.setError(errorFile); exec.setErrorproperty(errorProperty); exec.setOutput(outputFile); exec.setOutputproperty(outputProperty); exec.setAppend(append); + log("starting java as:\n" + statement.render(osFamily), Project.MSG_VERBOSE); exec.execute(); - return Integer.parseInt(getProject().getProperty(random)); + return Integer.parseInt(getProject().getProperty(id)); } private void mkdirAndCopyTo(String destination, Iterable sets) { - scp.init(); - exec.setCommand(exec("{md} " + destination).render(osFamily)); + if (Iterables.size(sets) == 0) { + log("no content: " + destination, Project.MSG_DEBUG); + return; + } + exec.setCommand(exec("test -d " + destination).render(osFamily)); // TODO windows + getProjectProperties().remove(id); + exec.setResultProperty(id); + exec.setFailonerror(false); exec.execute(); + if (getProject().getProperty(id).equals("0")) { + log("already created: " + destination, Project.MSG_VERBOSE); + return; + } + getProjectProperties().remove(id); + exec.setCommand(exec("{md} " + destination).render(osFamily)); + exec.setFailonerror(true); + exec.execute(); + scp.init(); String scpDestination = getScpDir(destination); - System.out.println("Sending to: " + scpDestination); + log("staging: " + scpDestination, Project.MSG_VERBOSE); for (FileSet set : sets) scp.addFileset(set); scp.setTodir(scpDestination); @@ -143,16 +225,17 @@ public class SSHJava extends Java { 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()); + log("converting: " + paths[i], Project.MSG_DEBUG); + File file = new File(reprefix(paths[i])); + if (file.getAbsolutePath().equals(paths[i]) && file.exists() && file.isFile()) { + String newPath = prefix + "{fs}" + file.getName(); + log("adding new: " + newPath, Project.MSG_DEBUG); + destination.append("{ps}").append(prefix + "{fs}" + 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()); + log("adding existing: " + file.getAbsolutePath(), Project.MSG_DEBUG); } } } @@ -162,6 +245,8 @@ public class SSHJava extends Java { List filesets = Lists.newArrayList(); if (path.list() != null && path.list().length > 0) { for (String filepath : path.list()) { + if (!filepath.equals(reprefix(filepath))) + continue;// we've already copied File file = new File(filepath); if (file.exists()) { FileSet fileset = new FileSet(); @@ -177,17 +262,27 @@ public class SSHJava extends Java { mkdirAndCopyTo(destination, filesets); } + String reprefix(String in) { + for (Entry entry : shiftMap.entrySet()) { + if (in.startsWith(entry.getKey())) + in = remotebase + ShellToken.FS.to(osFamily) + entry.getValue() + + in.substring(entry.getKey().length()); + } + return in; + } + String convertJavaToScriptNormalizingPaths(CommandlineJava commandLine) { - List statements = Lists.newArrayList(); + + Map envVariables = Maps.newHashMap(); String[] environment = env.getVariables(); if (environment != null) { for (int i = 0; i < environment.length; i++) { log("Setting environment variable: " + environment[i], Project.MSG_VERBOSE); String[] keyValue = environment[i].split("="); - statements.add(exec(String.format("{export} %s={vq}%s{vq}", keyValue[0], keyValue[1]))); + envVariables.put(keyValue[0], keyValue[1]); } } - statements.add(exec("{cd} " + remoteDirectory)); + StringBuilder commandBuilder = new StringBuilder(jvm); if (getCommandLine().getBootclasspath() != null) { commandBuilder.append(" -Xbootclasspath:bootclasspath"); @@ -218,10 +313,11 @@ public class SSHJava extends Java { commandBuilder.append(" ").append( Joiner.on(' ').join(commandLine.getJavaCommand().getArguments())); } - statements.add(exec(commandBuilder.toString())); - String command = new StatementList(statements).render(osFamily); - return command; + InitBuilder testInitBuilder = new InitBuilder(id, remotedir.getAbsolutePath(), remotedir + .getAbsolutePath(), envVariables, commandBuilder.toString()); + String script = testInitBuilder.build(osFamily); + return reprefix(script); } @Override @@ -238,8 +334,12 @@ public class SSHJava extends Java { this.localDirectory = checkNotNull(localDir, "dir"); } - public void setRemotedir(File remotedir) { - this.remoteDirectory = checkNotNull(remotedir, "remotedir"); + /** + * All files transfered to the host will be relative to this. The java process itself will be at + * this path/{@code id}. + */ + public void setRemotebase(File remotebase) { + this.remotebase = checkNotNull(remotebase, "remotebase"); } @Override @@ -469,4 +569,40 @@ public class SSHJava extends Java { exec.setTaskType(type); scp.setTaskType(type); } + + @Override + public String toString() { + return "SSHJava [append=" + append + ", env=" + env + ", errorFile=" + errorFile + + ", errorProperty=" + errorProperty + ", jvm=" + jvm + ", localDirectory=" + + localDirectory + ", osFamily=" + osFamily + ", outputFile=" + outputFile + + ", outputProperty=" + outputProperty + ", remoteDirectory=" + remotebase + + ", userInfo=" + userInfo + "]"; + } + + @Override + public void addSysproperty(Variable sysp) { + if (sysp.getKey().startsWith("sshjava.map.")) { + shiftMap.put(sysp.getKey().replaceFirst("sshjava.map.", ""), sysp.getValue()); + } else if (sysp.getKey().equals("sshjava.id")) { + setId(sysp.getValue()); + } else if (sysp.getKey().equals("sshjava.remotebase")) { + setRemotebase(new File(sysp.getValue())); + } else { + super.addSysproperty(sysp); + } + } + + @SuppressWarnings("unchecked") + Hashtable getProjectProperties() { + PropertyHelper helper = (PropertyHelper) getProject().getReference( + MagicNames.REFID_PROPERTY_HELPER); + Field field; + try { + field = PropertyHelper.class.getDeclaredField("properties"); + field.setAccessible(true); + return (Hashtable) field.get(helper); + } catch (Exception e) { + throw new BuildException(e); + } + } } diff --git a/tools/antcontrib/src/test/java/org/jclouds/tools/ant/taskdefs/sshjava/SSHJavaTest.java b/tools/antcontrib/src/test/java/org/jclouds/tools/ant/taskdefs/sshjava/SSHJavaTest.java index 4c1724d3c5..26b0a678de 100644 --- a/tools/antcontrib/src/test/java/org/jclouds/tools/ant/taskdefs/sshjava/SSHJavaTest.java +++ b/tools/antcontrib/src/test/java/org/jclouds/tools/ant/taskdefs/sshjava/SSHJavaTest.java @@ -35,6 +35,7 @@ import org.apache.tools.ant.types.Environment.Variable; import org.jclouds.tools.ant.TestClass; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; /** @@ -46,6 +47,7 @@ public class SSHJavaTest { .entrySet()); // TODO, this test will break in windows + @Test(enabled = false) public void testFull() throws SecurityException, NoSuchMethodException { SSHJava task = makeSSHJava(); String expected = String @@ -56,6 +58,21 @@ public class SSHJavaTest { assertEquals(task.convertJavaToScriptNormalizingPaths(task.getCommandLine()), expected); } + // TODO, this test will break in windows + @Test(enabled = false) + public void testFullShift() throws SecurityException, NoSuchMethodException { + SSHJava task = makeSSHJava(); + task = directoryShift(task); + String expected = String + .format( + "export %s=\"%s\"%ncd /tmp/foo\n%s -Xms16m -Xmx32m -cp classpath -Dfooble=baz -Dfoo=bar -Dsettingsfile=/tmp/foo/maven/conf/settings.xml -DappHome=/tmp/foo/maven 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); + assertEquals(task.shiftMap, ImmutableMap. of(System.getProperty("user.home") + + "/apache-maven-2.2.1", "maven")); + } + private Java populateTask(Java task) { Project p = new Project(); task.setProject(p); @@ -64,7 +81,6 @@ public class SSHJavaTest { Variable prop1 = new Environment.Variable(); prop1.setKey("fooble"); prop1.setValue("baz"); - task.addSysproperty(prop1); Variable prop2 = new Environment.Variable(); prop2.setKey("foo"); @@ -105,6 +121,25 @@ public class SSHJavaTest { .getProperty("result")); } + @Test(enabled = false, groups = { "live" }) + public void testSshShift() throws NumberFormatException, FileNotFoundException, IOException { + Java java = makeJava(); + directoryShift(java); + java.execute(); + + SSHJava javaOverSsh = makeSSHJava(); + addDestinationTo(javaOverSsh); + directoryShift(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(SSHJava javaOverSsh) throws UnknownHostException { String sshHost = System.getProperty("jclouds.test.ssh.host"); String sshPort = System.getProperty("jclouds.test.ssh.port"); @@ -125,10 +160,19 @@ public class SSHJavaTest { } } + public void testSSHJavaPropertyOverride() { + SSHJava task = new SSHJava(); + Project p = new Project(); + task.setProject(p); + p.setProperty("foo", "bar"); + task.getProjectProperties().remove("foo"); + assertEquals(p.getProperty("foo"), null); + } + private SSHJava makeSSHJava() { SSHJava task = new SSHJava(); populateTask(task); - task.setRemotedir(new File("/tmp/foo")); + task.setRemotebase(new File("/tmp/foo")); task.setVerbose(true); task.setTrust(true); return task; @@ -137,4 +181,20 @@ public class SSHJavaTest { private Java makeJava() { return populateTask(new Java()); } + + private T directoryShift(T java) { + Variable prop1 = new Environment.Variable(); + prop1.setKey("sshjava.map." + System.getProperty("user.home") + "/apache-maven-2.2.1"); + prop1.setValue("maven"); + java.addSysproperty(prop1); + Variable prop2 = new Environment.Variable(); + prop2.setKey("settingsfile"); + prop2.setValue(System.getProperty("user.home") + "/apache-maven-2.2.1/conf/settings.xml"); + java.addSysproperty(prop2); + Variable prop3 = new Environment.Variable(); + prop3.setKey("appHome"); + prop3.setValue(System.getProperty("user.home") + "/apache-maven-2.2.1"); + java.addSysproperty(prop3); + return java; + } }