Skip to content

Commit

Permalink
Merge pull request #91 from jenkinsci/report_status_to_vsts_steps
Browse files Browse the repository at this point in the history
Report pending and completion status to the associated TFS/Team Services commit
  • Loading branch information
olivierdagenais authored Jul 25, 2016
2 parents bf18655 + b3c7fdf commit aa88059
Show file tree
Hide file tree
Showing 13 changed files with 577 additions and 1 deletion.
35 changes: 35 additions & 0 deletions src/main/java/hudson/plugins/tfs/VstsCollectionConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.cloudbees.plugins.credentials.domains.HostnameRequirement;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
Expand All @@ -27,6 +28,7 @@
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;

public class VstsCollectionConfiguration extends AbstractDescribableImpl<VstsCollectionConfiguration> {
Expand Down Expand Up @@ -160,4 +162,37 @@ static List<StandardUsernamePasswordCredentials> findCredentials(final String ho
);
return matches;
}

static StandardUsernamePasswordCredentials findCredentialsById(final String credentialsId) {
final Jenkins jenkins = Jenkins.getInstance();
final List<StandardUsernamePasswordCredentials> matches =
CredentialsProvider.lookupCredentials(
StandardUsernamePasswordCredentials.class,
jenkins,
ACL.SYSTEM,
Collections.<DomainRequirement>emptyList()
);
final CredentialsMatcher matcher = CredentialsMatchers.withId(credentialsId);
final StandardUsernamePasswordCredentials result = CredentialsMatchers.firstOrNull(matches, matcher);
return result;
}

// TODO: we'll probably also want findCredentialsForGitRepo, where we match part of the URL path
public static StandardUsernamePasswordCredentials findCredentialsForCollection(final URI collectionUri) {
final VstsPluginGlobalConfig config = VstsPluginGlobalConfig.get();
// TODO: consider using a different data structure to speed up this look-up
final List<VstsCollectionConfiguration> pairs = config.getCollectionConfigurations();
for (final VstsCollectionConfiguration pair : pairs) {
final String candidateCollectionUrlString = pair.getCollectionUrl();
final URI candidateCollectionUri = URI.create(candidateCollectionUrlString);
if (UriHelper.areSame(candidateCollectionUri, collectionUri)) {
final String credentialsId = pair.credentialsId;
if (credentialsId != null) {
return findCredentialsById(credentialsId);
}
return null;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package hudson.plugins.tfs;

import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.tfs.util.VstsStatus;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import jenkins.tasks.SimpleBuildStep;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;
import java.io.IOException;

/**
* A _Post-Build Action_ that reports the completion status of an associated build to VSTS.
*/
public class VstsCompletedStatusPostBuildAction extends Notifier implements SimpleBuildStep {

@DataBoundConstructor
public VstsCompletedStatusPostBuildAction() {

}

@Override
public void perform(
@Nonnull final Run<?, ?> run,
@Nonnull final FilePath workspace,
@Nonnull final Launcher launcher,
@Nonnull final TaskListener listener
) throws InterruptedException, IOException {
try {
VstsStatus.createFromRun(run);
}
catch (final Exception e) {
e.printStackTrace(listener.error("Error while trying to update completion status in VSTS"));
}
}

@Override
public BuildStepMonitor getRequiredMonitorService() {
// we don't need the outcome of any previous builds for this step
return BuildStepMonitor.NONE;
}

@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {

@Override
public boolean isApplicable(final Class<? extends AbstractProject> jobType) {
return true;
}

@Override
public String getDisplayName() {
return "Set completion status for VSTS commit or pull request";
}
}
}
56 changes: 56 additions & 0 deletions src/main/java/hudson/plugins/tfs/VstsPendingStatusBuildStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package hudson.plugins.tfs;

import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.tfs.util.VstsStatus;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import jenkins.tasks.SimpleBuildStep;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;
import java.io.IOException;

/**
* A _Build Step_ that reports the status of an associated build as "Pending" to VSTS.
*/
public class VstsPendingStatusBuildStep extends Builder implements SimpleBuildStep {

@DataBoundConstructor
public VstsPendingStatusBuildStep() {

}

@Override
public void perform(
@Nonnull final Run<?, ?> run,
@Nonnull final FilePath workspace,
@Nonnull final Launcher launcher,
@Nonnull final TaskListener listener
) throws InterruptedException, IOException {
try {
VstsStatus.createFromRun(run);
}
catch (final Exception e) {
e.printStackTrace(listener.error("Error while trying to update pending status in VSTS"));
}
}

@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {

@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}

@Override
public String getDisplayName() {
return "Set pending status for VSTS commit or pull request";
}
}
}
9 changes: 9 additions & 0 deletions src/main/java/hudson/plugins/tfs/VstsPluginGlobalConfig.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package hudson.plugins.tfs;

import hudson.Extension;
import hudson.ExtensionList;
import jenkins.model.GlobalConfiguration;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.ObjectUtils;
import org.kohsuke.stapler.StaplerRequest;

import java.util.ArrayList;
Expand All @@ -17,6 +19,7 @@
public class VstsPluginGlobalConfig extends GlobalConfiguration {

private static final Logger LOGGER = Logger.getLogger(VstsPluginGlobalConfig.class.getName());
private static final VstsPluginGlobalConfig DEFAULT_CONFIG = new VstsPluginGlobalConfig();

private List<VstsCollectionConfiguration> collectionConfigurations = new ArrayList<VstsCollectionConfiguration>();

Expand All @@ -29,6 +32,12 @@ public VstsPluginGlobalConfig(final List<VstsCollectionConfiguration> collection
this.collectionConfigurations = collectionConfigurations;
}

public static VstsPluginGlobalConfig get() {
final ExtensionList<GlobalConfiguration> configurationExtensions = all();
final VstsPluginGlobalConfig config = configurationExtensions.get(VstsPluginGlobalConfig.class);
final VstsPluginGlobalConfig result = ObjectUtils.defaultIfNull(config, DEFAULT_CONFIG);
return result;
}

public List<VstsCollectionConfiguration> getCollectionConfigurations() {
return collectionConfigurations;
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/hudson/plugins/tfs/model/GitStatusState.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package hudson.plugins.tfs.model;

import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

public enum GitStatusState {

NotSet(0),
Expand All @@ -9,6 +13,26 @@ public enum GitStatusState {
Error(4),
;

public static final Map<String, GitStatusState> CASE_INSENSITIVE_LOOKUP;

static {
final Map<String, GitStatusState> map = new TreeMap<String, GitStatusState>(String.CASE_INSENSITIVE_ORDER);
for (final GitStatusState value : GitStatusState.values()) {
map.put(value.name(), value);
}
CASE_INSENSITIVE_LOOKUP = Collections.unmodifiableMap(map);
}

public static GitStatusState caseInsensitiveValueOf(final String name) {
if (name == null) {
throw new NullPointerException("Name is null");
}
if (!CASE_INSENSITIVE_LOOKUP.containsKey(name)) {
throw new IllegalArgumentException("No enum constant " + name);
}
return CASE_INSENSITIVE_LOOKUP.get(name);
}

private final int value;

GitStatusState(final int value) {
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/hudson/plugins/tfs/model/GitStatusStateMorpher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package hudson.plugins.tfs.model;

import net.sf.ezmorph.MorphException;
import net.sf.ezmorph.ObjectMorpher;

public class GitStatusStateMorpher implements ObjectMorpher {

public static final GitStatusStateMorpher INSTANCE = new GitStatusStateMorpher();

private GitStatusStateMorpher() {

}

@Override
public Object morph(final Object value) {
if (value == null) {
return null;
}

if (!supports(value.getClass())) {
throw new MorphException(value.getClass() + " is not supported");
}

final String s = value.toString();
return GitStatusState.caseInsensitiveValueOf(s);
}

@Override
public Class morphsTo() {
return GitStatusState.class;
}

@Override
public boolean supports(Class clazz) {
return String.class.isAssignableFrom(clazz);
}
}
39 changes: 39 additions & 0 deletions src/main/java/hudson/plugins/tfs/model/VstsGitStatus.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
package hudson.plugins.tfs.model;

import hudson.model.Job;
import hudson.model.Result;
import hudson.model.Run;
import net.sf.json.JSONObject;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class VstsGitStatus {

private static final Map<Result, GitStatusState> RESULT_TO_STATE;

static {
final Map<Result, GitStatusState> resultToStatus = new HashMap<Result, GitStatusState>();
resultToStatus.put(Result.SUCCESS, GitStatusState.Succeeded);
resultToStatus.put(Result.UNSTABLE, GitStatusState.Failed);
resultToStatus.put(Result.FAILURE, GitStatusState.Failed);
resultToStatus.put(Result.NOT_BUILT, GitStatusState.Error);
resultToStatus.put(Result.ABORTED, GitStatusState.Error);
RESULT_TO_STATE = Collections.unmodifiableMap(resultToStatus);
}

public GitStatusState state;
public String description;
public String targetUrl;
public GitStatusContext context;

public static VstsGitStatus fromRun(@Nonnull final Run<?, ?> run) {
final VstsGitStatus status = new VstsGitStatus();
final Result result = run.getResult();
if (result == null) {
status.state = GitStatusState.Pending;
status.description = status.state.toString();
}
else {
status.state = RESULT_TO_STATE.get(result);
status.description = result.toString();
}
status.targetUrl = run.getAbsoluteUrl();
final Job<?, ?> project = run.getParent();
final String runDisplayName = run.getDisplayName();
final String projectDisplayName = project.getDisplayName();
status.context = new GitStatusContext(runDisplayName, projectDisplayName);
return status;
}

public String toJson() {
final JSONObject jsonObject = JSONObject.fromObject(this);
final String result = jsonObject.toString();
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/hudson/plugins/tfs/util/StringHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ public static boolean endsWithIgnoreCase(final String haystack, final String nee
return haystack.regionMatches(true, toffset, needle, 0, nl);
}

public static boolean equal(final String a, final String b) {
return innerEqual(a, b, false);
}

public static boolean equalIgnoringCase(final String a, final String b) {
return innerEqual(a, b, true);
}

static boolean innerEqual(final String a, final String b, final boolean ignoreCase) {
if (a == null) {
return b == null;
}
if (b == null) {
return false;
}
final int length = a.length();
if (length != b.length()) {
return false;
}
return a.regionMatches(ignoreCase, 0, b, 0, length);
}

public static String determineContentTypeWithoutCharset(final String contentType) {
if (contentType == null) {
return null;
Expand Down
Loading

0 comments on commit aa88059

Please sign in to comment.