Skip to content

Commit

Permalink
Call conduit over HTTP
Browse files Browse the repository at this point in the history
This removes much of the reliance on `arc` being installed.

Also refactor apply patch into a task -- I realize this should be a separate
commit, but all of this stuff is intertwined and I wanted to break it out to
make this refactor easier.
  • Loading branch information
ascandella committed Aug 9, 2015
1 parent 0e30f25 commit 5ea8cf4
Show file tree
Hide file tree
Showing 17 changed files with 589 additions and 140 deletions.
1 change: 1 addition & 0 deletions phabricator-plugin.iml
Expand Up @@ -130,6 +130,7 @@
<orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:2.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.apache.httpcomponents:httpclient:tests:4.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jenkins-ci.main:jenkins-war:war:1.609" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.jenkins-ci.main:jenkins-core:1.609" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.jenkins-ci.plugins.icon-shim:icon-set:1.0.5" level="project" />
Expand Down
8 changes: 8 additions & 0 deletions pom.xml
Expand Up @@ -96,6 +96,14 @@
<scope>test</scope>
<version>4.12</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Expand Up @@ -20,11 +20,13 @@

package com.uber.jenkins.phabricator;

import com.uber.jenkins.phabricator.conduit.ArcanistClient;
import com.uber.jenkins.phabricator.conduit.ArcanistUsageException;
import com.uber.jenkins.phabricator.conduit.ConduitAPIClient;
import com.uber.jenkins.phabricator.conduit.ConduitAPIException;
import com.uber.jenkins.phabricator.conduit.Differential;
import com.uber.jenkins.phabricator.conduit.DifferentialClient;
import com.uber.jenkins.phabricator.credentials.ConduitCredentials;
import com.uber.jenkins.phabricator.tasks.ApplyPatchTask;
import com.uber.jenkins.phabricator.tasks.Task;
import com.uber.jenkins.phabricator.utils.CommonUtils;
import com.uber.jenkins.phabricator.utils.Logger;
import hudson.EnvVars;
Expand All @@ -36,9 +38,14 @@
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.IOException;
import java.util.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class PhabricatorBuildWrapper extends BuildWrapper {
private static final String CONDUIT_TAG = "conduit";
private static final String DEFAULT_GIT_PATH = "git";

private final boolean createCommit;
private final boolean applyToMaster;
private final boolean uberDotArcanist;
Expand All @@ -52,6 +59,7 @@ public PhabricatorBuildWrapper(boolean createCommit, boolean applyToMaster, bool
this.showBuildStartedMessage = showBuildStartedMessage;
}

/** {@inheritDoc} */
@Override
public Environment setUp(AbstractBuild build,
Launcher launcher,
Expand All @@ -62,94 +70,70 @@ public Environment setUp(AbstractBuild build,
return this.ignoreBuild(logger, "No environment variables found?!");
}

final String arcPath = this.getArcPath();

final Map<String, String> envAdditions = new HashMap<String, String>();
envAdditions.put(PhabricatorPlugin.WRAP_KEY, "true");
envAdditions.put(PhabricatorPlugin.ARCANIST_PATH, arcPath);

final String conduitToken = this.getConduitToken(build.getParent(), logger);

String diffID = environment.get(PhabricatorPlugin.DIFFERENTIAL_ID_FIELD);
if (CommonUtils.isBlank(diffID)) {
this.addShortText(build);
this.ignoreBuild(logger, "No differential ID found.");
} else {
LauncherFactory starter = new LauncherFactory(launcher, environment, listener.getLogger(), build.getWorkspace());

if (uberDotArcanist) {
int npmCode = starter.launch()
.cmds(Arrays.asList("npm", "install", "uber-dot-arcanist"))
.stdout(logger.getStream())
.join();

if (npmCode != 0) {
logger.warn("uber-dot-arcanist", "Got non-zero exit code installing uber-dot-arcanist from npm: " + npmCode);
}
}

DifferentialClient diffClient = new DifferentialClient(diffID, starter, conduitToken, arcPath);
Differential diff;
try {
diff = new Differential(diffClient.fetchDiff());
diff.decorate(build, this.getPhabricatorURL(build.getParent()));

logger.info("arcanist", "Applying patch for differential");

// Post a silent notification if option is enabled
if (showBuildStartedMessage) {
diffClient.postComment(diff.getRevisionID(false), diff.getBuildStartedMessage(environment));
}
} catch (ArcanistUsageException e) {
logger.warn("arcanist", "Unable to apply patch");
logger.warn("arcanist", e.getMessage());
return null;
}
return new Environment(){};
}

String baseCommit = "origin/master";
if (!applyToMaster) {
baseCommit = diff.getBaseCommit();
}
LauncherFactory starter = new LauncherFactory(launcher, environment, listener.getLogger(), build.getWorkspace());

int resetCode = starter.launch()
.cmds(Arrays.asList("git", "reset", "--hard", baseCommit))
if (uberDotArcanist) {
int npmCode = starter.launch()
.cmds(Arrays.asList("npm", "install", "uber-dot-arcanist"))
.stdout(logger.getStream())
.join();

if (resetCode != 0) {
logger.warn("arcanist", "Got non-zero exit code resetting to base commit " + baseCommit + ": " + resetCode);
if (npmCode != 0) {
logger.warn("uber-dot-arcanist", "Got non-zero exit code installing uber-dot-arcanist from npm: " + npmCode);
}
}

// Clean workspace, otherwise `arc patch` may fail
starter.launch()
.stdout(logger.getStream())
.cmds(Arrays.asList("git", "clean", "-fd", "-f"))
.join();
ConduitAPIClient conduitClient;
try {
conduitClient = getConduitClient(build.getParent(), logger);
} catch (ConduitAPIException e) {
e.printStackTrace(logger.getStream());
logger.warn(CONDUIT_TAG, e.getMessage());
return null;
}

// Update submodules recursively.
starter.launch()
.stdout(logger.getStream())
.cmds(Arrays.asList("git", "submodule", "update", "--init", "--recursive"))
.join();
DifferentialClient diffClient = new DifferentialClient(diffID, conduitClient);
Differential diff;
try {
diff = new Differential(diffClient.fetchDiff());
diff.decorate(build, this.getPhabricatorURL(build.getParent()));

List<String> params = new ArrayList<String>(Arrays.asList("--nobranch", "--diff", diffID));
if (!createCommit) {
params.add("--nocommit");
}
logger.info(CONDUIT_TAG, "Fetching differential from Conduit API");

ArcanistClient arc = new ArcanistClient(
arcPath,
"patch",
null,
conduitToken,
params.toArray(new String[params.size()]));
// Post a silent notification if option is enabled
if (showBuildStartedMessage) {
diffClient.postComment(diff.getRevisionID(false), diff.getBuildStartedMessage(environment));
}
} catch (ConduitAPIException e) {
logger.warn(CONDUIT_TAG, "Unable to apply patch");
logger.warn(CONDUIT_TAG, e.getMessage());
return null;
}

int result = arc.callConduit(starter.launch(), logger.getStream());
String baseCommit = "origin/master";
if (!applyToMaster) {
baseCommit = diff.getBaseCommit();
}

if (result != 0) {
logger.warn("arcanist", "Error applying arc patch; got non-zero exit code " + result);
return null;
}
final String conduitToken = this.getConduitToken(build.getParent(), logger);
Task.Result result = new ApplyPatchTask(
logger, starter, baseCommit, diffID, conduitToken, getArcPath(),
DEFAULT_GIT_PATH, createCommit
).run();

if (result != Task.Result.SUCCESS) {
logger.warn("arcanist", "Error applying arc patch; got non-zero exit code " + result);
return null;
}

return new Environment() {
Expand All @@ -173,6 +157,18 @@ private Environment ignoreBuild(Logger logger, String message) {
return new Environment(){};
}

private ConduitAPIClient getConduitClient(Job owner, Logger logger) throws ConduitAPIException {
ConduitCredentials credentials = getConduitCredentials(owner);
if (credentials == null) {
throw new ConduitAPIException("No credentials configured for conduit");
}
return new ConduitAPIClient(credentials.getUrl(), getConduitToken(owner, logger));
}

private ConduitCredentials getConduitCredentials(Job owner) {
return getDescriptor().getCredentials(owner);
}

/**
* This is used in config.jelly to populate the state of the checkbox
*/
Expand All @@ -197,15 +193,15 @@ public boolean isShowBuildStartedMessage() {
}

public String getPhabricatorURL(Job owner) {
ConduitCredentials credentials = this.getDescriptor().getCredentials(owner);
ConduitCredentials credentials = getConduitCredentials(owner);
if (credentials != null) {
return credentials.getUrl();
}
return this.getDescriptor().getConduitURL();
}

public String getConduitToken(Job owner, Logger logger) {
ConduitCredentials credentials = this.getDescriptor().getCredentials(owner);
ConduitCredentials credentials = getConduitCredentials(owner);
if (credentials != null) {
return credentials.getToken().getPlainText();
}
Expand Down
46 changes: 31 additions & 15 deletions src/main/java/com/uber/jenkins/phabricator/PhabricatorNotifier.java
Expand Up @@ -20,9 +20,7 @@

package com.uber.jenkins.phabricator;

import com.uber.jenkins.phabricator.conduit.ArcanistUsageException;
import com.uber.jenkins.phabricator.conduit.Differential;
import com.uber.jenkins.phabricator.conduit.DifferentialClient;
import com.uber.jenkins.phabricator.conduit.*;
import com.uber.jenkins.phabricator.credentials.ConduitCredentials;
import com.uber.jenkins.phabricator.tasks.NonDifferentialBuildTask;
import com.uber.jenkins.phabricator.tasks.PostCommentTask;
Expand All @@ -48,6 +46,8 @@
import java.io.PrintStream;

public class PhabricatorNotifier extends Notifier {
private static final String UBERALLS_TAG = "uberalls";
private static final String CONDUIT_TAG = "conduit";
// Post a comment on success. Useful for lengthy builds.
private final boolean commentOnSuccess;
private final boolean uberallsEnabled;
Expand Down Expand Up @@ -86,9 +86,6 @@ public final boolean perform(final AbstractBuild<?, ?> build, final Launcher lau
environment.get("GIT_URL"), branch);
final boolean needsDecoration = environment.get(PhabricatorPlugin.WRAP_KEY, null) == null;

final String conduitToken = getConduitToken(build.getParent(), logger);

final String arcPath = environment.get(PhabricatorPlugin.ARCANIST_PATH, "arc");
final boolean uberallsConfigured = !CommonUtils.isBlank(uberalls.getBaseURL());
final String diffID = environment.get(PhabricatorPlugin.DIFFERENTIAL_ID_FIELD);

Expand All @@ -107,14 +104,21 @@ public final boolean perform(final AbstractBuild<?, ?> build, final Launcher lau
return true;
}

LauncherFactory starter = new LauncherFactory(launcher, environment, listener.getLogger(), build.getWorkspace());
ConduitAPIClient conduitClient;
try {
conduitClient = getConduitClient(build.getParent(), logger);
} catch (ConduitAPIException e) {
e.printStackTrace(logger.getStream());
logger.warn(CONDUIT_TAG, e.getMessage());
return false;
}

DifferentialClient diffClient = new DifferentialClient(diffID, starter, conduitToken, arcPath);
DifferentialClient diffClient = new DifferentialClient(diffID, conduitClient);
Differential diff;
try {
diff = new Differential(diffClient.fetchDiff());
} catch (ArcanistUsageException e) {
logger.info("arcanist", "unable to fetch differential");
} catch (ConduitAPIException e) {
logger.info(CONDUIT_TAG, "unable to fetch differential");
return true;
}

Expand All @@ -136,10 +140,10 @@ public final boolean perform(final AbstractBuild<?, ?> build, final Launcher lau
if (uberallsConfigured) {
commenter.processParentCoverage(uberalls.getParentCoverage(diff), diff.getBranch());
} else {
logger.info("uberalls", "no backend configured, skipping...");
logger.info(UBERALLS_TAG, "no backend configured, skipping...");
}
} else {
logger.info("uberalls", "no line coverage found, skipping...");
logger.info(UBERALLS_TAG, "no line coverage found, skipping...");
}

// Add in comments about the build result
Expand All @@ -155,8 +159,8 @@ public final boolean perform(final AbstractBuild<?, ?> build, final Launcher lau
String.format("Error from Harbormaster: %s", result.getString("errorMessage")));
return false;
}
} catch (ArcanistUsageException e) {
logger.info("arcanist", "unable to post to harbormaster");
} catch (ConduitAPIException e) {
logger.info(CONDUIT_TAG, "unable to post to harbormaster");
return true;
}
} else {
Expand Down Expand Up @@ -192,6 +196,14 @@ public final boolean perform(final AbstractBuild<?, ?> build, final Launcher lau
return true;
}

private ConduitAPIClient getConduitClient(Job owner, Logger logger) throws ConduitAPIException {
ConduitCredentials credentials = getConduitCredentials(owner);
if (credentials == null) {
throw new ConduitAPIException("No credentials configured for conduit");
}
return new ConduitAPIClient(credentials.getUrl(), credentials.getToken().getPlainText());
}

private CoverageResult getUberallsCoverage(AbstractBuild<?, ?> build, BuildListener listener) {
if (!build.getResult().isBetterOrEqualTo(Result.UNSTABLE) || !uberallsEnabled) {
return null;
Expand Down Expand Up @@ -235,8 +247,12 @@ public String getCommentFile() {
return commentFile;
}

private ConduitCredentials getConduitCredentials(Job owner) {
return getDescriptor().getCredentials(owner);
}

public String getConduitToken(Job owner, Logger logger) {
ConduitCredentials credentials = this.getDescriptor().getCredentials(owner);
ConduitCredentials credentials = getConduitCredentials(owner);
if (credentials != null) {
return credentials.getToken().getPlainText();
}
Expand Down
Expand Up @@ -27,8 +27,6 @@
import java.io.File;

public class PhabricatorPlugin extends Plugin {
public static final String ARCANIST_PATH = "ARCANIST_PATH";

// Diff ID (not differential ID)
static final String DIFFERENTIAL_ID_FIELD = "DIFF_ID";
// Phabricator object ID (for Harbormaster)
Expand Down

0 comments on commit 5ea8cf4

Please sign in to comment.