Skip to content

Commit

Permalink
Add support for multiple credentials (subject to JENKINS-23160 until …
Browse files Browse the repository at this point in the history
…we upgrade the credentials plugin dependency)
  • Loading branch information
stephenc committed Aug 11, 2014
1 parent 75ace62 commit 3bc16fd
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 41 deletions.
7 changes: 4 additions & 3 deletions pom.xml
Expand Up @@ -29,7 +29,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.466</version>
<version>1.509</version>
</parent>

<artifactId>ssh-agent</artifactId>
Expand Down Expand Up @@ -120,12 +120,12 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>1.9.2</version>
<version>1.15</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ssh-credentials</artifactId>
<version>1.6</version>
<version>1.6.1</version>
</dependency>
<!-- jenkins dependencies -->
<!-- test dependencies -->
Expand Down Expand Up @@ -180,6 +180,7 @@
<extensions>true</extensions>
<configuration>
<pluginFirstClassLoader>true</pluginFirstClassLoader>
<compatibleSinceVersion>1.5</compatibleSinceVersion>
</configuration>
</plugin>
<plugin>
Expand Down
Expand Up @@ -30,12 +30,15 @@
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Extension;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractDescribableImpl;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.security.ACL;
Expand All @@ -49,11 +52,16 @@
import org.kohsuke.stapler.Stapler;

import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* A build wrapper that provides an SSH agent using supplied credentials
Expand All @@ -62,17 +70,64 @@ public class SSHAgentBuildWrapper extends BuildWrapper {
/**
* The {@link com.cloudbees.jenkins.plugins.sshcredentials.SSHUser#getId()} of the credentials to use.
*/
private final String user;
private transient String user;

/**
* The {@link com.cloudbees.plugins.credentials.common.StandardUsernameCredentials#getId()}s of the credentials
* to use.
*
* @since 1.5
*/
private final List<String> credentialIds;

/**
* Constructs a new instance.
*
* @param user the {@link SSHUserPrivateKey#getId()} of the credentials to use.
* @deprecated use {@link #SSHAgentBuildWrapper(java.util.List)}
*/
@DataBoundConstructor
@Deprecated
@SuppressWarnings("unused") // used via stapler
public SSHAgentBuildWrapper(String user) {
this.user = user;
this(Collections.singletonList(user));
}

/**
* Constructs a new instance.
*
* @param credentialHolders the {@link com.cloudbees.plugins.credentials.common.StandardUsernameCredentials#getId
* ()}s
* of the credentials to use.
* @since 1.5
*/
@DataBoundConstructor
@SuppressWarnings("unused") // used via stapler
public SSHAgentBuildWrapper(CredentialHolder[] credentialHolders) {
this(CredentialHolder.toIdList(credentialHolders));
}

/**
* Constructs a new instance.
*
* @param credentialIds the {@link com.cloudbees.plugins.credentials.common.StandardUsernameCredentials#getId()}s
* of the credentials to use.
* @since 1.5
*/
@SuppressWarnings("unused") // used via stapler
public SSHAgentBuildWrapper(List<String> credentialIds) {
this.credentialIds = new ArrayList<String>(new LinkedHashSet<String>(credentialIds));
}

/**
* Migrate legacy data format.
*
* @since 1.5
*/
private Object readResolve() throws ObjectStreamException {
if (user != null) {
return new SSHAgentBuildWrapper(Collections.singletonList(user));
}
return this;
}

/**
Expand All @@ -81,15 +136,44 @@ public SSHAgentBuildWrapper(String user) {
* @return the {@link SSHUserPrivateKey#getId()} of the credentials to use.
*/
@SuppressWarnings("unused") // used via stapler
@Deprecated
public String getUser() {
return user;
return credentialIds.isEmpty() ? null : credentialIds.get(0);
}

/**
* Gets the {@link com.cloudbees.plugins.credentials.common.StandardUsernameCredentials#getId()}s of the
* credentials to use.
*
* @return the {@link com.cloudbees.plugins.credentials.common.StandardUsernameCredentials#getId()}s of the
* credentials to use.
* @since 1.5
*/
public List<String> getCredentialIds() {
return Collections.unmodifiableList(credentialIds);
}

/**
* Returns the value objects used to hold the credential ids.
*
* @return the value objects used to hold the credential ids.
* @since 1.5
*/
@SuppressWarnings("unused") // used via stapler
public CredentialHolder[] getCredentialHolders() {
List<CredentialHolder> result = new ArrayList<CredentialHolder>(credentialIds.size());
for (String id : credentialIds) {
result.add(new CredentialHolder(id));
}
return result.toArray(new CredentialHolder[result.size()]);
}

/**
* {@inheritDoc}
*/
@Override
public void preCheckout(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
public void preCheckout(AbstractBuild build, Launcher launcher, BuildListener listener)
throws IOException, InterruptedException {
build.getEnvironments().add(createSSHAgentEnvironment(build, launcher, listener));
}

Expand All @@ -105,22 +189,25 @@ public Environment setUp(AbstractBuild build, final Launcher launcher, BuildList
};
}

private Environment createSSHAgentEnvironment(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
SSHUserPrivateKey userPrivateKey = null;
for (SSHUserPrivateKey u : CredentialsProvider
.lookupCredentials(SSHUserPrivateKey.class, build.getProject(), ACL.SYSTEM, null)) {
if (user.equals(u.getId())) {
userPrivateKey = u;
break;
private Environment createSSHAgentEnvironment(AbstractBuild build, Launcher launcher, BuildListener listener)
throws IOException, InterruptedException {
List<SSHUserPrivateKey> userPrivateKeys = new ArrayList<SSHUserPrivateKey>();
Set<String> ids = new LinkedHashSet<String>(getCredentialIds());
for (SSHUserPrivateKey u : CredentialsProvider.lookupCredentials(SSHUserPrivateKey.class, build.getProject(),
ACL.SYSTEM, Collections.<DomainRequirement>emptyList())) {
if (ids.contains(u.getId())) {
userPrivateKeys.add(u);
}
}
if (userPrivateKey == null) {
if (userPrivateKeys.isEmpty()) {
listener.fatalError(Messages.SSHAgentBuildWrapper_CredentialsNotFound());
return null;
}
listener.getLogger().println(Messages.SSHAgentBuildWrapper_UsingCredentials(description(userPrivateKey)));
for (SSHUserPrivateKey userPrivateKey : userPrivateKeys) {
listener.getLogger().println(Messages.SSHAgentBuildWrapper_UsingCredentials(description(userPrivateKey)));
}
try {
return new SSHAgentEnvironment(launcher, listener, userPrivateKey);
return new SSHAgentEnvironment(launcher, listener, userPrivateKeys);
} catch (IOException e) {
throw new IOException2(Messages.SSHAgentBuildWrapper_CouldNotStartAgent(), e);
} catch (InterruptedException e) {
Expand Down Expand Up @@ -165,20 +252,6 @@ public String getDisplayName() {
return Messages.SSHAgentBuildWrapper_DisplayName();
}

/**
* Populate the list of credentials available to the job.
*
* @return the list box model.
*/
@SuppressWarnings("unused") // used by stapler
public ListBoxModel doFillUserItems() {
Item item = Stapler.getCurrentRequest().findAncestorObject(Item.class);
return new SSHUserListBoxModel().withAll(
CredentialsProvider.lookupCredentials(SSHUserPrivateKey.class, item, ACL.SYSTEM,
Collections.<DomainRequirement>emptyList())
);
}

}

/**
Expand All @@ -198,9 +271,25 @@ private class SSHAgentEnvironment extends Environment {
* @param listener the listener for reporting progress.
* @param sshUserPrivateKey the private key to add to the agent.
* @throws Throwable if things go wrong.
* @deprecated use {@link #SSHAgentEnvironment(hudson.Launcher, hudson.model.BuildListener, java.util.List)}
*/
@Deprecated
public SSHAgentEnvironment(Launcher launcher, final BuildListener listener,
final SSHUserPrivateKey sshUserPrivateKey) throws Throwable {
this(launcher, listener, Collections.singletonList(sshUserPrivateKey));
}

/**
* Construct the environment and initialize on the remote node.
*
* @param launcher the launcher for the remote node.
* @param listener the listener for reporting progress.
* @param sshUserPrivateKeys the private keys to add to the agent.
* @throws Throwable if things go wrong.
* @since 1.5
*/
public SSHAgentEnvironment(Launcher launcher, final BuildListener listener,
final List<SSHUserPrivateKey> sshUserPrivateKeys) throws Throwable {
RemoteAgent agent = null;
listener.getLogger().println("[ssh-agent] Looking for ssh-agent implementation...");
Map<String, Throwable> faults = new LinkedHashMap<String, Throwable>();
Expand All @@ -222,17 +311,19 @@ public SSHAgentEnvironment(Launcher launcher, final BuildListener listener,
listener.getLogger().println("[ssh-agent] * " + fault.getKey());
StringWriter sw = new StringWriter();
fault.getValue().printStackTrace(new PrintWriter(sw));
for (String line: StringUtils.split(sw.toString(),"\n")) {
for (String line : StringUtils.split(sw.toString(), "\n")) {
listener.getLogger().println("[ssh-agent] " + line);
}
}
throw new RuntimeException("[ssh-agent] Could not find a suitable ssh-agent provider.");
}
this.agent = agent;
final Secret passphrase = sshUserPrivateKey.getPassphrase();
final String effectivePassphrase = passphrase == null ? null : passphrase.getPlainText();
for (String privateKey : sshUserPrivateKey.getPrivateKeys()) {
agent.addIdentity(privateKey, effectivePassphrase, description(sshUserPrivateKey));
for (SSHUserPrivateKey sshUserPrivateKey : sshUserPrivateKeys) {
final Secret passphrase = sshUserPrivateKey.getPassphrase();
final String effectivePassphrase = passphrase == null ? null : passphrase.getPlainText();
for (String privateKey : sshUserPrivateKey.getPrivateKeys()) {
agent.addIdentity(privateKey, effectivePassphrase, description(sshUserPrivateKey));
}
}
listener.getLogger().println(Messages.SSHAgentBuildWrapper_Started());
}
Expand All @@ -258,4 +349,83 @@ public boolean tearDown(AbstractBuild build, BuildListener listener)
return true;
}
}

/**
* A value object to make it possible to pass back multiple credentials via the UI.
*
* @since 1.5
*/
public static class CredentialHolder extends AbstractDescribableImpl<CredentialHolder> {

/**
* The id.
*/
private final String id;

/**
* Stapler's constructor.
*
* @param id the ID.
*/
@DataBoundConstructor
public CredentialHolder(String id) {
this.id = id;
}

/**
* Gets the id.
*
* @return the id.
*/
public String getId() {
return id;
}

/**
* Converts an array of value objects into a list of ids.
*
* @param credentialHolders the array of value objects.
* @return the possibly empty but never null list of ids.
*/
@NonNull
public static List<String> toIdList(@Nullable CredentialHolder[] credentialHolders) {
List<String> result = new ArrayList<String>(credentialHolders == null ? 0 : credentialHolders.length);
if (credentialHolders != null) {
for (CredentialHolder h : credentialHolders) {
result.add(h.getId());
}
}
return result;
}

/**
* Our descriptor.
*/
@Extension
public static class DescriptorImpl extends Descriptor<CredentialHolder> {

/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return Messages.SSHAgentBuildWrapper_CredentialHolder_DisplayName();
}

/**
* Populate the list of credentials available to the job.
*
* @return the list box model.
*/
@SuppressWarnings("unused") // used by stapler
public ListBoxModel doFillIdItems() {
Item item = Stapler.getCurrentRequest().findAncestorObject(Item.class);
return new SSHUserListBoxModel().withAll(
CredentialsProvider.lookupCredentials(SSHUserPrivateKey.class, item, ACL.SYSTEM,
Collections.<DomainRequirement>emptyList())
);
}

}
}
}
Expand Up @@ -27,4 +27,5 @@ SSHAgentBuildWrapper.Started=[ssh-agent] Started.
SSHAgentBuildWrapper.Stopped=[ssh-agent] Stopped.
SSHAgentBuildWrapper.UnableToReadKey=[ssh-agent] Unable to read key\: {0}
SSHAgentBuildWrapper.UsingCredentials=[ssh-agent] Using credentials {0}
SSHAgentBuildWrapper.CouldNotStartAgent=[ssh-agent] Unable to start agent
SSHAgentBuildWrapper.CouldNotStartAgent=[ssh-agent] Unable to start agent
SSHAgentBuildWrapper.CredentialHolder.DisplayName=Credentials

0 comments on commit 3bc16fd

Please sign in to comment.