This closes #106 pull request at github/apache/ant repomaster
| @@ -14,6 +14,10 @@ Fixed bugs: | |||||
| files. | files. | ||||
| Bugzilla Report 63874 | Bugzilla Report 63874 | ||||
| * sshexec, sshsession and scp now support a new sshConfig parameter. | |||||
| It specified the SSH configuration file (typically ${user.home}/.ssh/config) | |||||
| defining the username and keyfile to be used per host. | |||||
| Other changes: | Other changes: | ||||
| -------------- | -------------- | ||||
| @@ -142,6 +142,14 @@ information. This task has been tested with <code>jsch-0.1.2</code> and later.< | |||||
| <td>Passphrase for your private key.</td> | <td>Passphrase for your private key.</td> | ||||
| <td>No; defaults to an empty string</td> | <td>No; defaults to an empty string</td> | ||||
| </tr> | </tr> | ||||
| <tr> | |||||
| <td>sshConfig</td> | |||||
| <td>Location of the file holding the OpenSSH style configuration (e.g. <code>${user.home}/.ssh/config</code>). | |||||
| The username and the key file are read from the configuration file, | |||||
| unless they are already specified in the task parameters. | |||||
| <em>since Ant 1.10.8</em></td> | |||||
| <td>No</td> | |||||
| </tr> | |||||
| <tr> | <tr> | ||||
| <td>verbose</td> | <td>verbose</td> | ||||
| <td>Determines whether SCP outputs verbosely to the user. Currently this means outputting | <td>Determines whether SCP outputs verbosely to the user. Currently this means outputting | ||||
| @@ -104,6 +104,14 @@ JSCh earlier than 0.1.28.</p> | |||||
| <td>Passphrase for your private key.</td> | <td>Passphrase for your private key.</td> | ||||
| <td>No; defaults to an empty string</td> | <td>No; defaults to an empty string</td> | ||||
| </tr> | </tr> | ||||
| <tr> | |||||
| <td>sshConfig</td> | |||||
| <td>Location of the file holding the OpenSSH style configuration (e.g. <code>${user.home}/.ssh/config</code>). | |||||
| The username and the key file are read from the configuration file, | |||||
| unless they are already specified in the task parameters. | |||||
| <em>since Ant 1.10.8</em></td> | |||||
| <td>No</td> | |||||
| </tr> | |||||
| <tr> | <tr> | ||||
| <td>suppresssystemout</td> | <td>suppresssystemout</td> | ||||
| <td>Whether to suppress system out. <em>since Ant 1.9.0</em></td> | <td>Whether to suppress system out. <em>since Ant 1.9.0</em></td> | ||||
| @@ -110,6 +110,14 @@ JSCh earlier than 0.1.28.</p> | |||||
| <td>Passphrase for your private key.</td> | <td>Passphrase for your private key.</td> | ||||
| <td>No; defaults to an empty string</td> | <td>No; defaults to an empty string</td> | ||||
| </tr> | </tr> | ||||
| <tr> | |||||
| <td>sshConfig</td> | |||||
| <td>Location of the file holding the OpenSSH style configuration (e.g. <code>${user.home}/.ssh/config</code>). | |||||
| The username and the key file are read from the configuration file, | |||||
| unless they are already specified in the task parameters. | |||||
| <em>since Ant 1.10.8</em></td> | |||||
| <td>No</td> | |||||
| </tr> | |||||
| <tr> | <tr> | ||||
| <td>timeout</td> | <td>timeout</td> | ||||
| <td>Give up if the connection cannot be established within the specified time (given in | <td>Give up if the connection cannot be established within the specified time (given in | ||||
| @@ -18,6 +18,11 @@ | |||||
| package org.apache.tools.ant.taskdefs.optional.ssh; | package org.apache.tools.ant.taskdefs.optional.ssh; | ||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import com.jcraft.jsch.ConfigRepository; | |||||
| import com.jcraft.jsch.OpenSSHConfig; | |||||
| import org.apache.tools.ant.BuildException; | import org.apache.tools.ant.BuildException; | ||||
| import org.apache.tools.ant.Project; | import org.apache.tools.ant.Project; | ||||
| import org.apache.tools.ant.Task; | import org.apache.tools.ant.Task; | ||||
| @@ -42,6 +47,7 @@ public abstract class SSHBase extends Task implements LogListener { | |||||
| private boolean failOnError = true; | private boolean failOnError = true; | ||||
| private boolean verbose; | private boolean verbose; | ||||
| private final SSHUserInfo userInfo; | private final SSHUserInfo userInfo; | ||||
| private String sshConfig; | |||||
| private int serverAliveCountMax = 3; | private int serverAliveCountMax = 3; | ||||
| private int serverAliveIntervalSeconds = 0; | private int serverAliveIntervalSeconds = 0; | ||||
| @@ -106,6 +112,24 @@ public abstract class SSHBase extends Task implements LogListener { | |||||
| return verbose; | return verbose; | ||||
| } | } | ||||
| /** | |||||
| * Get the OpenSSH config file (~/.ssh/config). | |||||
| * @return the OpenSSH config file | |||||
| * @since Ant 1.10.8 | |||||
| */ | |||||
| public String getSshConfig() { | |||||
| return sshConfig; | |||||
| } | |||||
| /** | |||||
| * Set the OpenSSH config file (~/.ssh/config). | |||||
| * @param sshConfig the OpenSSH config file | |||||
| * @since Ant 1.10.8 | |||||
| */ | |||||
| public void setSshConfig(String sshConfig) { | |||||
| this.sshConfig = sshConfig; | |||||
| } | |||||
| /** | /** | ||||
| * Set the serverAliveCountMax value. | * Set the serverAliveCountMax value. | ||||
| * @param countMax int | * @param countMax int | ||||
| @@ -235,6 +259,37 @@ public abstract class SSHBase extends Task implements LogListener { | |||||
| this.port = SSH_PORT; | this.port = SSH_PORT; | ||||
| } | } | ||||
| /** | |||||
| * Load the SSH configuration file. | |||||
| * @throws BuildException on error | |||||
| */ | |||||
| protected void loadSshConfig() throws BuildException { | |||||
| if (sshConfig != null && (userInfo.getName() == null || userInfo.getKeyfile() == null)) { | |||||
| if (!new File(sshConfig).exists()) { | |||||
| throw new BuildException("The SSH configuration file specified doesn't exist: " + sshConfig); | |||||
| } | |||||
| log("Loading SSH configuration file " + sshConfig, Project.MSG_DEBUG); | |||||
| ConfigRepository.Config config = null; | |||||
| try { | |||||
| config = OpenSSHConfig.parseFile(sshConfig).getConfig(host); | |||||
| } catch (IOException e) { | |||||
| throw new BuildException("Failed to load the SSH configuration file " + sshConfig, e); | |||||
| } | |||||
| host = config.getHostname(); | |||||
| if (userInfo.getName() == null) { | |||||
| userInfo.setName(config.getUser()); | |||||
| } | |||||
| if (userInfo.getKeyfile() == null) { | |||||
| log("Using SSH key file " + config.getValue("IdentityFile") + " for host " + host, Project.MSG_INFO); | |||||
| userInfo.setKeyfile(config.getValue("IdentityFile")); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * Open an ssh session. | * Open an ssh session. | ||||
| * @return the opened session | * @return the opened session | ||||
| @@ -284,6 +284,9 @@ public class SSHExec extends SSHBase { | |||||
| if (getHost() == null) { | if (getHost() == null) { | ||||
| throw new BuildException("Host is required."); | throw new BuildException("Host is required."); | ||||
| } | } | ||||
| loadSshConfig(); | |||||
| if (getUserInfo().getName() == null) { | if (getUserInfo().getName() == null) { | ||||
| throw new BuildException("Username is required."); | throw new BuildException("Username is required."); | ||||
| } | } | ||||
| @@ -132,6 +132,9 @@ public class SSHSession extends SSHBase { | |||||
| if (getHost() == null) { | if (getHost() == null) { | ||||
| throw new BuildException("Host is required."); | throw new BuildException("Host is required."); | ||||
| } | } | ||||
| loadSshConfig(); | |||||
| if (getUserInfo().getName() == null) { | if (getUserInfo().getName() == null) { | ||||
| throw new BuildException("Username is required."); | throw new BuildException("Username is required."); | ||||
| } | } | ||||
| @@ -435,19 +435,22 @@ public class Scp extends SSHBase { | |||||
| throw new BuildException("no username was given. Can't authenticate."); | throw new BuildException("no username was given. Can't authenticate."); | ||||
| } | } | ||||
| if (getUserInfo().getPassword() == null | |||||
| && getUserInfo().getKeyfile() == null) { | |||||
| throw new BuildException( | |||||
| "neither password nor keyfile for user %s has been given. Can't authenticate.", | |||||
| getUserInfo().getName()); | |||||
| } | |||||
| final int indexOfPath = uri.indexOf(':', indexOfAt + 1); | final int indexOfPath = uri.indexOf(':', indexOfAt + 1); | ||||
| if (indexOfPath == -1) { | if (indexOfPath == -1) { | ||||
| throw new BuildException("no remote path in %s", uri); | throw new BuildException("no remote path in %s", uri); | ||||
| } | } | ||||
| setHost(uri.substring(indexOfAt + 1, indexOfPath)); | setHost(uri.substring(indexOfAt + 1, indexOfPath)); | ||||
| loadSshConfig(); | |||||
| if (getUserInfo().getPassword() == null | |||||
| && getUserInfo().getKeyfile() == null) { | |||||
| throw new BuildException( | |||||
| "neither password nor keyfile for user %s has been given. Can't authenticate.", | |||||
| getUserInfo().getName()); | |||||
| } | |||||
| String remotePath = uri.substring(indexOfPath + 1); | String remotePath = uri.substring(indexOfPath + 1); | ||||
| if (remotePath.isEmpty()) { | if (remotePath.isEmpty()) { | ||||
| remotePath = "."; | remotePath = "."; | ||||