Add workflow step that updates the GitLab commit status depending on the build status (fixes #262)
This commit is contained in:
parent
099fe76dd3
commit
eb1abb2e2c
|
@ -123,6 +123,12 @@ If you plan to use forked repositories, you will need to enable the GitLab CI in
|
|||
**Note:** You do not need to select any "Trigger Events" as the Web Hook for Merge Request Events will alert Jenkins.
|
||||
|
||||
* GitLab 8.1 has implemented a commit status api. To enable add the post-build step ``Publish build status to GitLab commit (GitLab 8.1+ required)`` to the job.
|
||||
For pipeline jobs surround your build step with the gitlabCommitStatus step like this:
|
||||
```
|
||||
gitlabCommitStatus {
|
||||
<script that builds your project>
|
||||
}
|
||||
```
|
||||
* Configure access to GitLab as described above in "Configure access to GitLab" (the account needs at least developer permissions to post commit statuses)
|
||||
|
||||
## Forked repositories
|
||||
|
|
7
pom.xml
7
pom.xml
|
@ -7,7 +7,7 @@
|
|||
</parent>
|
||||
|
||||
<properties>
|
||||
<jenkins.version>1.609.1</jenkins.version>
|
||||
<jenkins.version>1.609.3</jenkins.version>
|
||||
<hpi-plugin.version>1.115</hpi-plugin.version>
|
||||
<jenkins-test-harness.version>${jenkins.version}</jenkins-test-harness.version>
|
||||
<findbugs.failOnError>false</findbugs.failOnError>
|
||||
|
@ -161,6 +161,11 @@
|
|||
<artifactId>git-client</artifactId>
|
||||
<version>1.19.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-step-api</artifactId>
|
||||
<version>1.15</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
|
|
|
@ -1,40 +1,26 @@
|
|||
package com.dabsquared.gitlabjenkins.publisher;
|
||||
|
||||
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
|
||||
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
|
||||
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
|
||||
import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
|
||||
import com.dabsquared.gitlabjenkins.util.ProjectIdUtil;
|
||||
import hudson.EnvVars;
|
||||
import com.dabsquared.gitlabjenkins.util.CommitStatusUpdater;
|
||||
import hudson.Extension;
|
||||
import hudson.Launcher;
|
||||
import hudson.model.AbstractBuild;
|
||||
import hudson.model.AbstractProject;
|
||||
import hudson.model.BuildListener;
|
||||
import hudson.model.Result;
|
||||
import hudson.plugins.git.util.BuildData;
|
||||
import hudson.tasks.BuildStepDescriptor;
|
||||
import hudson.tasks.BuildStepMonitor;
|
||||
import hudson.tasks.Notifier;
|
||||
import hudson.tasks.Publisher;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.kohsuke.stapler.DataBoundConstructor;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author Robin Müller
|
||||
*/
|
||||
public class GitLabCommitStatusPublisher extends Notifier {
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(GitLabCommitStatusPublisher.class.getName());
|
||||
|
||||
@DataBoundConstructor
|
||||
public GitLabCommitStatusPublisher() { }
|
||||
|
||||
|
@ -44,89 +30,23 @@ public class GitLabCommitStatusPublisher extends Notifier {
|
|||
|
||||
@Override
|
||||
public boolean prebuild(AbstractBuild<?, ?> build, BuildListener listener) {
|
||||
String commitHash = getBuildRevision(build);
|
||||
updateCommitStatus(build, listener, BuildState.running, commitHash, getBuildUrl(build));
|
||||
CommitStatusUpdater.updateCommitStatus(build, listener, BuildState.running);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
|
||||
String commitHash = getBuildRevision(build);
|
||||
String buildUrl = getBuildUrl(build);
|
||||
Result buildResult = build.getResult();
|
||||
if (buildResult == Result.SUCCESS) {
|
||||
updateCommitStatus(build, listener, BuildState.success, commitHash, buildUrl);
|
||||
CommitStatusUpdater.updateCommitStatus(build, listener, BuildState.success);
|
||||
} else if (buildResult == Result.ABORTED) {
|
||||
updateCommitStatus(build, listener, BuildState.canceled, commitHash, buildUrl);
|
||||
CommitStatusUpdater.updateCommitStatus(build, listener, BuildState.canceled);
|
||||
} else {
|
||||
updateCommitStatus(build, listener, BuildState.failed, commitHash, buildUrl);
|
||||
CommitStatusUpdater.updateCommitStatus(build, listener, BuildState.failed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getBuildRevision(AbstractBuild<?, ?> build) {
|
||||
return build.getAction(BuildData.class).getLastBuiltRevision().getSha1String();
|
||||
}
|
||||
|
||||
private void updateCommitStatus(AbstractBuild<?, ?> build, BuildListener listener, BuildState state, String commitHash, String buildUrl) {
|
||||
try {
|
||||
for (String gitlabProjectId : retrieveGitlabProjectIds(build, build.getEnvironment(listener))) {
|
||||
try {
|
||||
GitLabApi client = getClient(build);
|
||||
if (client == null) {
|
||||
listener.getLogger().println("No GitLab connection configured");
|
||||
} else if (existsCommit(client, gitlabProjectId, commitHash)) {
|
||||
client.changeBuildStatus(gitlabProjectId, commitHash, state, getBuildBranch(build), "jenkins", buildUrl, null);
|
||||
}
|
||||
} catch (WebApplicationException e) {
|
||||
listener.getLogger().printf("Failed to update Gitlab commit status for project '%s': %s%n", gitlabProjectId, e.getMessage());
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to update Gitlab commit status for project '%s'", gitlabProjectId), e);
|
||||
}
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
listener.getLogger().printf("Failed to update Gitlab commit status: %s%n", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean existsCommit(GitLabApi client, String gitlabProjectId, String commitHash) {
|
||||
try {
|
||||
client.headCommit(gitlabProjectId, commitHash);
|
||||
return true;
|
||||
} catch (NotFoundException e) {
|
||||
LOGGER.log(Level.FINE, String.format("Project (%s) and commit (%s) combination not found", gitlabProjectId, commitHash));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String getBuildBranch(AbstractBuild<?, ?> build) {
|
||||
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
|
||||
return cause == null ? null : cause.getData().getSourceBranch();
|
||||
}
|
||||
|
||||
private String getBuildUrl(AbstractBuild<?, ?> build) {
|
||||
return Jenkins.getInstance().getRootUrl() + build.getUrl();
|
||||
}
|
||||
|
||||
private GitLabApi getClient(AbstractBuild<?, ?> build) {
|
||||
GitLabConnectionProperty connectionProperty = build.getProject().getProperty(GitLabConnectionProperty.class);
|
||||
if (connectionProperty != null) {
|
||||
return connectionProperty.getClient();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<String> retrieveGitlabProjectIds(AbstractBuild<?, ?> build, EnvVars environment) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String remoteUrl : build.getAction(BuildData.class).getRemoteUrls()) {
|
||||
try {
|
||||
result.add(ProjectIdUtil.retrieveProjectId(environment.expand(remoteUrl)));
|
||||
} catch (ProjectIdUtil.ProjectIdResolutionException e) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Extension
|
||||
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
|
||||
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
package com.dabsquared.gitlabjenkins.util;
|
||||
|
||||
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
|
||||
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
|
||||
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
|
||||
import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
|
||||
import hudson.EnvVars;
|
||||
import hudson.model.Run;
|
||||
import hudson.model.TaskListener;
|
||||
import hudson.plugins.git.util.BuildData;
|
||||
import jenkins.model.Jenkins;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author Robin Müller
|
||||
*/
|
||||
public class CommitStatusUpdater {
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(CommitStatusUpdater.class.getName());
|
||||
|
||||
public static void updateCommitStatus(Run<?, ?> build, TaskListener listener, BuildState state) {
|
||||
String commitHash = getBuildRevision(build);
|
||||
String buildUrl = getBuildUrl(build);
|
||||
try {
|
||||
for (String gitlabProjectId : retrieveGitlabProjectIds(build, build.getEnvironment(listener))) {
|
||||
try {
|
||||
GitLabApi client = getClient(build);
|
||||
if (client == null) {
|
||||
println(listener, "No GitLab connection configured");
|
||||
} else if (existsCommit(client, gitlabProjectId, commitHash)) {
|
||||
client.changeBuildStatus(gitlabProjectId, commitHash, state, getBuildBranch(build), "jenkins", buildUrl, null);
|
||||
}
|
||||
} catch (WebApplicationException e) {
|
||||
printf(listener, "Failed to update Gitlab commit status for project '%s': %s%n", gitlabProjectId, e.getMessage());
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to update Gitlab commit status for project '%s'", gitlabProjectId), e);
|
||||
}
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
printf(listener, "Failed to update Gitlab commit status: %s%n", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void println(TaskListener listener, String message) {
|
||||
if (listener == null) {
|
||||
LOGGER.log(Level.FINE, "failed to print message {0} due to null TaskListener", message);
|
||||
} else {
|
||||
listener.getLogger().println(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printf(TaskListener listener, String message, Object... args) {
|
||||
if (listener == null) {
|
||||
LOGGER.log(Level.FINE, "failed to print message {0} due to null TaskListener", String.format(message, args));
|
||||
} else {
|
||||
listener.getLogger().printf(message, args);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getBuildRevision(Run<?, ?> build) {
|
||||
return build.getAction(BuildData.class).getLastBuiltRevision().getSha1String();
|
||||
}
|
||||
|
||||
private static boolean existsCommit(GitLabApi client, String gitlabProjectId, String commitHash) {
|
||||
try {
|
||||
client.headCommit(gitlabProjectId, commitHash);
|
||||
return true;
|
||||
} catch (NotFoundException e) {
|
||||
LOGGER.log(Level.FINE, String.format("Project (%s) and commit (%s) combination not found", gitlabProjectId, commitHash));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getBuildBranch(Run<?, ?> build) {
|
||||
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
|
||||
return cause == null ? null : cause.getData().getSourceBranch();
|
||||
}
|
||||
|
||||
private static String getBuildUrl(Run<?, ?> build) {
|
||||
return Jenkins.getInstance().getRootUrl() + build.getUrl();
|
||||
}
|
||||
|
||||
private static GitLabApi getClient(Run<?, ?> build) {
|
||||
GitLabConnectionProperty connectionProperty = build.getParent().getProperty(GitLabConnectionProperty.class);
|
||||
if (connectionProperty != null) {
|
||||
return connectionProperty.getClient();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<String> retrieveGitlabProjectIds(Run<?, ?> build, EnvVars environment) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String remoteUrl : build.getAction(BuildData.class).getRemoteUrls()) {
|
||||
try {
|
||||
result.add(ProjectIdUtil.retrieveProjectId(environment.expand(remoteUrl)));
|
||||
} catch (ProjectIdUtil.ProjectIdResolutionException e) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package com.dabsquared.gitlabjenkins.workflow;
|
||||
|
||||
import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState;
|
||||
import com.dabsquared.gitlabjenkins.util.CommitStatusUpdater;
|
||||
import hudson.Extension;
|
||||
import hudson.model.Run;
|
||||
import hudson.model.TaskListener;
|
||||
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
|
||||
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
|
||||
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
|
||||
import org.jenkinsci.plugins.workflow.steps.BodyExecution;
|
||||
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
|
||||
import org.jenkinsci.plugins.workflow.steps.StepContext;
|
||||
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
|
||||
import org.kohsuke.stapler.DataBoundConstructor;
|
||||
import org.kohsuke.stapler.export.ExportedBean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:robin.mueller@1und1.de">Robin Müller</a>
|
||||
*/
|
||||
@ExportedBean
|
||||
public class GitLabCommitStatusStep extends AbstractStepImpl {
|
||||
|
||||
@DataBoundConstructor
|
||||
public GitLabCommitStatusStep() { }
|
||||
|
||||
public static class Execution extends AbstractStepExecutionImpl {
|
||||
private static final long serialVersionUID = 1;
|
||||
|
||||
@StepContextParameter
|
||||
private transient Run<?, ?> run;
|
||||
|
||||
private BodyExecution body;
|
||||
|
||||
@Override
|
||||
public boolean start() throws Exception {
|
||||
body = getContext().newBodyInvoker()
|
||||
.withCallback(new BodyExecutionCallback() {
|
||||
@Override
|
||||
public void onStart(StepContext context) {
|
||||
CommitStatusUpdater.updateCommitStatus(run, getTaskListener(context), BuildState.running);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(StepContext context, Object result) {
|
||||
CommitStatusUpdater.updateCommitStatus(run, getTaskListener(context), BuildState.success);
|
||||
context.onSuccess(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(StepContext context, Throwable t) {
|
||||
CommitStatusUpdater.updateCommitStatus(run, getTaskListener(context), BuildState.failed);
|
||||
context.onFailure(t);
|
||||
}
|
||||
})
|
||||
.start();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(@Nonnull Throwable cause) throws Exception {
|
||||
// should be no need to do anything special (but verify in JENKINS-26148)
|
||||
if (body != null) {
|
||||
CommitStatusUpdater.updateCommitStatus(run, null, BuildState.canceled);
|
||||
body.cancel(cause);
|
||||
}
|
||||
}
|
||||
|
||||
private TaskListener getTaskListener(StepContext context) {
|
||||
if (!context.isReady()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return context.get(TaskListener.class);
|
||||
} catch (Exception x) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Extension
|
||||
public static final class DescriptorImpl extends AbstractStepDescriptorImpl {
|
||||
public DescriptorImpl() {
|
||||
super(Execution.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Update the commit status in GitLab depending on the build status";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFunctionName() {
|
||||
return "gitlabCommitStatus";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean takesImplicitBlockArgument() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue