Skip to content

Commit

Permalink
[FIXED JENKINS-7351] Add support for HTTP(S) username/password creden…
Browse files Browse the repository at this point in the history
…tials.

Not yet implemented: SSL client certificates, SSH private keys.
  • Loading branch information
jglick committed Sep 7, 2013
1 parent 3a88986 commit 6a60763
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 41 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -78,5 +78,10 @@
<artifactId>annotations</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>1.7.6</version>
</dependency>
</dependencies>
</project>
37 changes: 28 additions & 9 deletions src/main/java/hudson/plugins/mercurial/Cache.java
@@ -1,5 +1,8 @@
package hudson.plugins.mercurial;

import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.EnvVars;
Expand All @@ -8,6 +11,7 @@
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.util.ArgumentListBuilder;

import java.io.IOException;
import java.math.BigInteger;
Expand Down Expand Up @@ -35,6 +39,8 @@ class Cache {
*/
private final String remote;

private final StandardUsernameCredentials credentials;

/**
* Hashed value of {@link #remote} that only contains characters that are safe as a directory name.
*/
Expand All @@ -46,18 +52,19 @@ class Cache {
private final ReentrantLock masterLock = new ReentrantLock(true);
private final Map<String, ReentrantLock> slaveNodesLocksMap = new HashMap<String, ReentrantLock>();

private Cache(String remote, String hash) {
private Cache(String remote, String hash, StandardUsernameCredentials credentials) {
this.remote = remote;
this.hash = hash;
this.credentials = credentials;
}

private static final Map<String, Cache> CACHES = new HashMap<String, Cache>();

public synchronized static @NonNull Cache fromURL(String remote) {
String h = hashSource(remote);
public synchronized static @NonNull Cache fromURL(String remote, StandardUsernameCredentials credentials) {
String h = hashSource(remote, credentials);
Cache cache = CACHES.get(h);
if (cache == null) {
CACHES.put(h, cache = new Cache(remote, h));
CACHES.put(h, cache = new Cache(remote, h, credentials));
}
return cache;
}
Expand Down Expand Up @@ -110,14 +117,25 @@ private synchronized ReentrantLock getLockForSlaveNode(String node) {
masterLock.lockInterruptibly();
try {
listener.getLogger().println("Acquired master cache lock.");
// TODO use getCredentials()
if (masterCache.isDirectory()) {
if (MercurialSCM.joinWithPossibleTimeout(masterHg.pull().pwd(masterCache), true, listener) != 0) {
ArgumentListBuilder args = masterHg.seed(true).add("pull");
if (credentials instanceof UsernamePasswordCredentials) {
args.addMasked(MercurialSCM.getSourceWithCredentials(remote, (StandardUsernamePasswordCredentials) credentials));
}
if (MercurialSCM.joinWithPossibleTimeout(masterHg.launch(args).pwd(masterCache), true, listener) != 0) {
listener.error("Failed to update " + masterCache);
return null;
}
} else {
masterCaches.mkdirs();
if (MercurialSCM.joinWithPossibleTimeout(masterHg.clone("--noupdate", remote, masterCache.getRemote()), fromPolling, listener) != 0) {
ArgumentListBuilder args = masterHg.seed(true).add("clone").add("--noupdate");
if (credentials instanceof UsernamePasswordCredentials) {
args.addMasked(MercurialSCM.getSourceWithCredentials(remote, (StandardUsernamePasswordCredentials) credentials));
} else {
args.add(remote);
}
if (MercurialSCM.joinWithPossibleTimeout(masterHg.launch(args.add(masterCache.getRemote())), fromPolling, listener) != 0) {
listener.error("Failed to clone " + remote);
return null;
}
Expand Down Expand Up @@ -213,18 +231,19 @@ private synchronized ReentrantLock getLockForSlaveNode(String node) {
/**
* Hash a URL into a string that only contains characters that are safe as directory names.
*/
static String hashSource(String source) {
static String hashSource(String source, StandardUsernameCredentials credentials) {
if (!source.endsWith("/")) {
source += "/";
}
Matcher m = Pattern.compile(".+[/]([^/:]+)(:\\d+)?[/]?").matcher(source);
String digestible = credentials == null ? source : source + '#' + credentials.getId();
BigInteger hash;
try {
hash = new BigInteger(1, MessageDigest.getInstance("SHA-1").digest(source.getBytes("UTF-8")));
hash = new BigInteger(1, MessageDigest.getInstance("SHA-1").digest(digestible.getBytes("UTF-8")));
} catch (Exception x) {
throw new AssertionError(x);
}
return String.format("%040X%s", hash, m.matches() ? "-" + m.group(1) : "");
return String.format("%040X%s%s", hash, m.matches() ? "-" + m.group(1) : "", credentials == null ? "" : "-" + credentials.getUsername().replace("@", "-at-"));
}

}
37 changes: 30 additions & 7 deletions src/main/java/hudson/plugins/mercurial/HgExe.java
Expand Up @@ -87,21 +87,40 @@ public HgExe(MercurialSCM scm, Launcher launcher, Node node, TaskListener listen
this.capability = Capability.get(this);
}

private ProcStarter l(ArgumentListBuilder args) {
/**
* Prepares to start the Mercurial command.
* @param args some arguments as created by {@link #seed} and then appended to
* @return a process starter with the correct launcher, arguments, listener, and environment variables configured
*/
public ProcStarter launch(ArgumentListBuilder args) {
// set the default stdout
return MercurialSCM.launch(launcher).cmds(args).stdout(listener).envs(env);
}

private ArgumentListBuilder seed(boolean allowDebug) {
/**
* Starts creating an argument list.
* Initially adds only the Mercurial executable itself, possibly with a debug flag.
* @param allowDebug whether to add a debug flag if the configured installation requested it
* @return a builder
*/
public ArgumentListBuilder seed(boolean allowDebug) {
return (allowDebug ? base : baseNoDebug).clone();
}

@Deprecated
/**
* @deprecated Unused, since we need more control over the argument list in order to support credentials.
*/
public ProcStarter pull() {
return run("pull");
}

@Deprecated
/**
* @deprecated Unused, since we need more control over the argument list in order to support credentials.
*/
public ProcStarter clone(String... args) {
return l(seed(true).add("clone").add(args));
return launch(seed(true).add("clone").add(args));
}

public ProcStarter bundleAll(String file) {
Expand All @@ -114,7 +133,7 @@ public ProcStarter bundle(Collection<String> bases, String file) {
args.add("--base", head);
}
args.add(file);
return l(args);
return launch(args);
}

public ProcStarter init(FilePath path) {
Expand All @@ -133,11 +152,15 @@ public ProcStarter cleanAll() {
* Runs arbitrary command.
*/
public ProcStarter run(String... args) {
return l(seed(true).add(args));
return launch(seed(true).add(args));
}

/**
* @deprecated Use {@link #seed} and {@link #launch} instead.
*/
@Deprecated
public ProcStarter run(ArgumentListBuilder args) {
return l(seed(true).add(args.toCommandArray()));
return launch(seed(true).add(args.toCommandArray()));
}

/**
Expand Down Expand Up @@ -213,7 +236,7 @@ public String popen(FilePath repository, TaskListener listener, boolean useTimeo
args = seed(false).add(args.toCommandArray());

ByteArrayOutputStream rev = new ByteArrayOutputStream();
if (MercurialSCM.joinWithPossibleTimeout(l(args).pwd(repository).stdout(rev), useTimeout, listener) == 0) {
if (MercurialSCM.joinWithPossibleTimeout(launch(args).pwd(repository).stdout(rev), useTimeout, listener) == 0) {
return rev.toString();
} else {
listener.error("Failed to run " + args.toStringWithQuote());
Expand Down

0 comments on commit 6a60763

Please sign in to comment.