Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report pending and completion status to the associated VSTS commit #91

Merged
merged 11 commits into from
Jul 25, 2016
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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change "VSTS" to "Team Services" here?

}
}
}
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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change "VSTS" to "Team Services" here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change "VSTS" to "Team Services" here?

If it's OK with you, I'll make a sweep after merging this and #92.

}
}
}
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