blob: 28c450f573a52fc86c6a93a51268bff81aa21549 [file] [log] [blame]
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.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.security.SecureRandom;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeoutException;
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.Replace;
import org.apache.tools.ant.taskdefs.Replace.Replacefilter;
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.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Environment.Variable;
import org.jclouds.scriptbuilder.InitScript;
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 org.jclouds.scriptbuilder.domain.Statements;
import org.jclouds.tools.ant.util.SSHExecute;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
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;
import com.jcraft.jsch.JSchException;
/**
* Version of the Java task that executes over ssh.
*
* @author Adrian Cole
*/
public class SSHJava extends Java {
private final SSHExecute exec;
private final Scp scp;
private final SSHUserInfo userInfo;
private File localDirectory;
File remotebase;
@VisibleForTesting
File remotedir;
@VisibleForTesting
Environment env = new Environment();
private OsFamily osFamily = OsFamily.UNIX;
private File errorFile;
private String errorProperty;
private File outputFile;
private String outputProperty;
String id = "sshjava" + new SecureRandom().nextLong();
private boolean append;
@VisibleForTesting
final LinkedHashMap<String, String> shiftMap = Maps.newLinkedHashMap();
@VisibleForTesting
final LinkedHashMap<String, String> replace = Maps.newLinkedHashMap();
public SSHJava() {
super();
exec = new SSHExecute();
exec.setProject(getProject());
scp = new Scp();
userInfo = new SSHUserInfo();
scp.init();
setFork(true);
setTrust(true);
}
public SSHJava(Task owner) {
this();
bindToOwner(owner);
}
public void setId(String id) {
this.id = id;
}
@Override
public int executeJava() throws BuildException {
checkNotNull(remotebase, "remotebase must be set");
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);
String command = createInitScript(osFamily, id, remotedir.getAbsolutePath(), env, 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);
}
replaceAllTokensIn(localDirectory);
FileSet cwd = new FileSet();
cwd.setDir(localDirectory);
if (osFamily == OsFamily.UNIX) {
log("removing old contents: " + remotedir.getAbsolutePath(), Project.MSG_VERBOSE);
sshexec(exec("rm -rf " + remotedir.getAbsolutePath()).render(osFamily));
} else {
// TODO need recursive remove on windows
}
mkdirAndCopyTo(remotedir.getAbsolutePath(), ImmutableList.of(cwd));
for (Entry<String, String> entry : shiftMap.entrySet()) {
FileSet set = new FileSet();
File source = new File(entry.getKey());
if (source.isDirectory()) {
set.setDir(new File(entry.getKey()));
mkdirAndCopyTo(remotebase.getAbsolutePath() + ShellToken.FS.to(osFamily) + entry.getValue(), ImmutableList
.of(set));
} else {
String destination = remotebase.getAbsolutePath() + ShellToken.FS.to(osFamily)
+ new File(entry.getValue()).getParent();
sshexec(exec("{md} " + destination).render(osFamily));
scp.init();
String scpDestination = getScpDir(destination);
log("staging: " + scpDestination, Project.MSG_VERBOSE);
scp.setFile(source.getAbsolutePath());
scp.setTodir(scpDestination);
scp.execute();
}
}
if (getCommandLine().getClasspath() != null) {
copyPathTo(getCommandLine().getClasspath(), remotedir.getAbsolutePath() + "/classpath");
}
if (getCommandLine().getBootclasspath() != null) {
copyPathTo(getCommandLine().getBootclasspath(), remotedir.getAbsolutePath() + "/bootclasspath");
}
if (osFamily == OsFamily.UNIX) {
sshexec(exec("chmod 755 " + remotedir.getAbsolutePath() + "{fs}init.{sh}").render(osFamily));
}
Statement statement = new StatementList(exec("{cd} " + remotedir.getAbsolutePath()), exec(remotedir
.getAbsolutePath()
+ "{fs}init.{sh} init"), exec(remotedir.getAbsolutePath() + "{fs}init.{sh} run"));
try {
return sshexecRedirectStreams(statement);
} catch (IOException e) {
throw new BuildException(e, getLocation());
}
}
void replaceAllTokensIn(File directory) {
Replace replacer = new Replace();
replacer.setProject(getProject());
replacer.setDir(directory);
Map<String, String> map = Maps.newLinkedHashMap();
// this has to go first
map.put(directory.getAbsolutePath(), remotedir.getAbsolutePath());
map.putAll(Maps.transformValues(shiftMap, new Function<String, String>() {
@Override
public String apply(String in) {
return remotebase + ShellToken.FS.to(osFamily) + in;
}
}));
map.putAll(replace);
for (Entry<String, String> entry : map.entrySet()) {
Replacefilter filter = replacer.createReplacefilter();
filter.setToken(entry.getKey());
filter.setValue(entry.getValue());
}
replacer.execute();
}
private int sshexec(String command) {
try {
return exec.execute(command);
} catch (JSchException e) {
throw new BuildException(e, getLocation());
} catch (IOException e) {
throw new BuildException(e, getLocation());
} catch (TimeoutException e) {
throw new BuildException(e, getLocation());
}
}
private int sshexecRedirectStreams(Statement statement) throws IOException {
exec.setStreamHandler(redirector.createHandler());
log("starting java as:\n" + statement.render(osFamily), Project.MSG_VERBOSE);
int exitStatus;
try {
exitStatus = sshexec(statement.render(osFamily));
} finally {
redirector.complete();
}
return exitStatus;
}
private void mkdirAndCopyTo(String destination, Iterable<FileSet> sets) {
if (Iterables.size(sets) == 0) {
log("no content: " + destination, Project.MSG_DEBUG);
return;
}
if (sshexec(exec("test -d " + destination).render(osFamily)) == 0) {// TODO windows
log("already created: " + destination, Project.MSG_VERBOSE);
return;
}
sshexec(exec("{md} " + destination).render(osFamily));
scp.init();
String scpDestination = getScpDir(destination);
log("staging: " + scpDestination, Project.MSG_VERBOSE);
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++) {
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);
}
}
}
}
void copyPathTo(Path path, String destination) {
List<FileSet> 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();
if (file.isFile()) {
fileset.setFile(file);
} else {
fileset.setDir(file);
}
filesets.add(fileset);
}
}
}
mkdirAndCopyTo(destination, filesets);
}
String reprefix(String in) {
log("comparing: " + in, Project.MSG_DEBUG);
for (Entry<String, String> entry : shiftMap.entrySet()) {
if (in.startsWith(entry.getKey())) {
log("match shift map: " + entry.getKey(), Project.MSG_DEBUG);
in = remotebase + ShellToken.FS.to(osFamily) + entry.getValue() + in.substring(entry.getKey().length());
}
}
for (Entry<String, String> entry : replace.entrySet()) {
if (in.startsWith(entry.getKey())) {
log("match replaceMap: " + entry.getKey(), Project.MSG_DEBUG);
in = entry.getValue() + in.substring(entry.getKey().length());
}
}
log("now: " + in, Project.MSG_DEBUG);
return in;
}
String createInitScript(OsFamily osFamily, String id, String basedir, Environment env,
CommandlineJava commandLine) {
Map<String, String> 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_DEBUG);
String[] keyValue = environment[i].split("=");
envVariables.put(keyValue[0], keyValue[1]);
}
}
StringBuilder commandBuilder = new StringBuilder(commandLine.getVmCommand().getExecutable());
if (commandLine.getBootclasspath() != null) {
commandBuilder.append(" -Xbootclasspath:bootclasspath");
resetPathToUnderPrefixIfExistsAndIsFileIfNotExistsAddAsIs(commandLine.getBootclasspath(),
"bootclasspath", commandBuilder);
}
if (commandLine.getVmCommand().getArguments() != null
&& commandLine.getVmCommand().getArguments().length > 0) {
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(" ").append(
Joiner.on(' ').join(commandLine.getSystemProperties().getVariables()));
}
commandBuilder.append(" ").append(commandLine.getClassname());
if (commandLine.getJavaCommand().getArguments() != null
&& commandLine.getJavaCommand().getArguments().length > 0) {
commandBuilder.append(" ").append(
Joiner.on(' ').join(commandLine.getJavaCommand().getArguments()));
}
InitScript testInitBuilder = InitScript.builder().name(id).home(basedir).exportVariables(envVariables)
.run(Statements.interpret( commandBuilder.toString())).build();
return testInitBuilder.render(osFamily);
}
@Override
public void addEnv(Environment.Variable var) {
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");
}
/**
* 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
public void setFork(boolean fork) {
if (!fork)
throw new IllegalArgumentException("this only operates when fork is set");
}
/**
* Remote host, either DNS name or IP.
*
* @param host
* The new host value
*/
public void setHost(String host) {
exec.setHost(host);
scp.setHost(host);
}
/**
* Username known to remote host.
*
* @param username
* The new username value
*/
public void setUsername(String username) {
exec.setUsername(username);
scp.setUsername(username);
userInfo.setName(username);
}
/**
* Sets the password for the user.
*
* @param password
* The new password value
*/
public void setPassword(String password) {
exec.setPassword(password);
scp.setPassword(password);
userInfo.setPassword(password);
}
/**
* Sets the keyfile for the user.
*
* @param keyfile
* The new keyfile value
*/
public void setKeyfile(String keyfile) {
exec.setKeyfile(keyfile);
scp.setKeyfile(keyfile);
userInfo.setKeyfile(keyfile);
if (userInfo.getPassphrase() == null)
userInfo.setPassphrase("");
}
/**
* Sets the passphrase for the users key.
*
* @param passphrase
* The new passphrase value
*/
public void setPassphrase(String passphrase) {
exec.setPassphrase(passphrase);
scp.setPassphrase(passphrase);
userInfo.setPassphrase(passphrase);
}
/**
* Sets the path to the file that has the identities of all known hosts. This is used by SSH
* protocol to validate the identity of the host. The default is
* <i>${user.home}/.ssh/known_hosts</i>.
*
* @param knownHosts
* a path to the known hosts file.
*/
public void setKnownhosts(String knownHosts) {
exec.setKnownhosts(knownHosts);
scp.setKnownhosts(knownHosts);
}
/**
* Setting this to true trusts hosts whose identity is unknown.
*
* @param yesOrNo
* if true trust the identity of unknown hosts.
*/
public void setTrust(boolean yesOrNo) {
exec.setTrust(yesOrNo);
scp.setTrust(yesOrNo);
userInfo.setTrust(yesOrNo);
}
/**
* Changes the port used to connect to the remote host.
*
* @param port
* port number of remote host.
*/
public void setPort(int port) {
exec.setPort(port);
scp.setPort(port);
}
/**
* 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 &quot;wait forever&quot;.
*
* @param timeout
* The new timeout value in seconds
*/
public void setTimeout(long timeout) {
exec.setTimeout(timeout);
}
@Override
public void setProject(Project project) {
super.setProject(project);
exec.setProject(project);
scp.setProject(project);
}
@Override
public void setOwningTarget(Target target) {
super.setOwningTarget(target);
scp.setOwningTarget(target);
}
@Override
public void setTaskName(String taskName) {
super.setTaskName(taskName);
scp.setTaskName(taskName);
}
@Override
public void setDescription(String description) {
super.setDescription(description);
scp.setDescription(description);
}
@Override
public void setLocation(Location location) {
super.setLocation(location);
scp.setLocation(location);
}
@Override
public void setTaskType(String type) {
super.setTaskType(type);
scp.setTaskType(type);
}
@Override
public String toString() {
return "SSHJava [append=" + append + ", env=" + env + ", errorFile=" + errorFile + ", errorProperty="
+ errorProperty + ", localDirectory=" + localDirectory + ", osFamily=" + osFamily + ", outputFile="
+ outputFile + ", outputProperty=" + outputProperty + ", remoteDirectory=" + remotebase + ", userInfo="
+ userInfo + "]";
}
@Override
public void addSysproperty(Variable sysp) {
if (sysp.getKey().startsWith("sshjava.shift.")) {
shiftMap.put(sysp.getKey().replaceFirst("sshjava.shift.", ""), sysp.getValue());
} else if (sysp.getKey().startsWith("sshjava.replace.")) {
replace.put(sysp.getKey().replaceFirst("sshjava.replace.", ""), sysp.getValue());
} else if (sysp.getKey().equals("sshjava.id")) {
setId(sysp.getValue());
} else if (sysp.getKey().equals("sshjava.host")) {
setHost(sysp.getValue());
} else if (sysp.getKey().equals("sshjava.port") && !sysp.getValue().equals("")) {
setPort(Integer.parseInt(sysp.getValue()));
} else if (sysp.getKey().equals("sshjava.username")) {
setUsername(sysp.getValue());
} else if (sysp.getKey().equals("sshjava.password") && !sysp.getValue().equals("")) {
setPassword(sysp.getValue());
} else if (sysp.getKey().equals("sshjava.keyfile") && !sysp.getValue().equals("")) {
setKeyfile(sysp.getValue());
} else if (sysp.getKey().equals("sshjava.remotebase")) {
setRemotebase(new File(sysp.getValue()));
} else {
super.addSysproperty(sysp);
}
}
}