Skip to content

Commit be322bd

Browse files
author
Thomas Gorka
authored
Auto notify tuleap about current build status (#195)
* Auto notify tuleap about current build status part of: [story #18320](https://tuleap.net/plugins/tracker/?aid=18320) native support of pull requests in tuleap branch source jenkins plugin 1. First, checkout the last version of tuleap-api-plugin 2. run `mvn install` in this repository 3. Open your IDE, open the tuleap-git-branch-source-plugin 4. Edit pom.xml and replace the version of tuleap-api-plugin with the snapshot version built at step 2 5. Refresh the dependencies 6. Now run the plugin 1. Go to a repository of your choice on your Tuleap instance 2. make a pullrequest containing a Jenkinsfile like this: ``` pipeline { agent any stages { stage('Build') { steps { echo 'Building..' sh "sleep 10s" } } stage('Test') { steps { echo 'Testing..' } } stage('Deploy') { steps { echo 'Deploying....' } } } } ``` 3. Open the pullrequest UI -> CI status: Unknown 4. Go to your repository on Jenkins and click the [Scan multibranch pipeline now] 5. Once the build is started, refresh the PR UI. You should see CI status: Pending 6. Once the build is complete, refresh the UI once again -> CI Status: success * Cleanup contribution * Take comments into account
1 parent 8b79b33 commit be322bd

File tree

5 files changed

+303
-1
lines changed

5 files changed

+303
-1
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
<dependency>
124124
<groupId>io.jenkins.plugins</groupId>
125125
<artifactId>tuleap-api</artifactId>
126-
<version>2.1.3</version>
126+
<version>2.2.0</version>
127127
</dependency>
128128
<dependency>
129129
<groupId>org.jenkins-ci.plugins</groupId>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.jenkinsci.plugins.tuleap_git_branch_source;
2+
3+
import hudson.model.Run;
4+
import hudson.plugins.git.util.BuildData;
5+
import io.jenkins.plugins.tuleap_api.client.GitApi;
6+
import io.jenkins.plugins.tuleap_api.client.internals.entities.TuleapBuildStatus;
7+
import io.jenkins.plugins.tuleap_credentials.TuleapAccessToken;
8+
9+
import jenkins.scm.api.SCMSource;
10+
import org.jenkinsci.plugins.tuleap_git_branch_source.config.TuleapConnector;
11+
import org.jetbrains.annotations.Nullable;
12+
13+
import java.io.PrintStream;
14+
15+
public class TuleapPipelineStatusNotifier {
16+
private final GitApi gitApi;
17+
18+
public TuleapPipelineStatusNotifier(GitApi gitApi) {
19+
this.gitApi = gitApi;
20+
}
21+
22+
@Nullable
23+
private TuleapSCMSource getTuleapSCMSource(Run<?, ?> build) {
24+
final SCMSource source = SCMSource.SourceByItem.findSource(build.getParent());
25+
if (!(source instanceof TuleapSCMSource)) {
26+
return null;
27+
}
28+
return (TuleapSCMSource) source;
29+
}
30+
31+
public void sendBuildStatusToTuleap(Run<?, ?> build, PrintStream logger, TuleapBuildStatus status) {
32+
final TuleapSCMSource source = getTuleapSCMSource(build);
33+
if (source == null) {
34+
// Not a TuleapSCMSource, abort
35+
return;
36+
}
37+
38+
final TuleapAccessToken token = getAccessKey(source);
39+
if (token == null) {
40+
throw new RuntimeException(
41+
"Access key for project not found. Please check your project configuration."
42+
);
43+
}
44+
45+
final BuildData gitData = build.getAction(BuildData.class);
46+
if (gitData == null) {
47+
throw new RuntimeException(
48+
"Failed to retrieve Git Data. Please check the configuration."
49+
);
50+
}
51+
52+
final int repository_id = source.getTuleapGitRepository().getId();
53+
logger.printf(
54+
"Notifying Tuleap about build status: %s",
55+
status.toString()
56+
);
57+
58+
this.gitApi.sendBuildStatus(
59+
Integer.toString(repository_id),
60+
gitData.lastBuild.getSHA1().name(),
61+
status,
62+
token
63+
);
64+
}
65+
66+
@Nullable
67+
private TuleapAccessToken getAccessKey(TuleapSCMSource source) {
68+
return TuleapConnector.lookupScanCredentials(
69+
source.getOwner(),
70+
source.getApiBaseUri(),
71+
source.getCredentialsId()
72+
);
73+
}
74+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.jenkinsci.plugins.tuleap_git_branch_source;
2+
3+
import com.google.inject.Guice;
4+
import com.google.inject.Injector;
5+
import edu.umd.cs.findbugs.annotations.NonNull;
6+
import hudson.Extension;
7+
import hudson.FilePath;
8+
import hudson.model.Result;
9+
import hudson.model.Run;
10+
import hudson.model.TaskListener;
11+
import hudson.model.listeners.RunListener;
12+
import hudson.model.listeners.SCMListener;
13+
import hudson.scm.SCM;
14+
import hudson.scm.SCMRevisionState;
15+
import io.jenkins.plugins.tuleap_api.client.GitApi;
16+
import io.jenkins.plugins.tuleap_api.client.TuleapApiGuiceModule;
17+
import io.jenkins.plugins.tuleap_api.client.internals.entities.TuleapBuildStatus;
18+
19+
import java.io.File;
20+
import java.util.logging.Level;
21+
import java.util.logging.Logger;
22+
23+
public class TuleapPipelineWatcher {
24+
private static final Logger LOGGER = Logger
25+
.getLogger(TuleapPipelineWatcher.class.getName());
26+
27+
private static TuleapPipelineStatusNotifier getNotifier() {
28+
final Injector injector = Guice.createInjector(new TuleapApiGuiceModule());
29+
return new TuleapPipelineStatusNotifier(
30+
injector.getInstance(GitApi.class)
31+
);
32+
}
33+
34+
@Extension
35+
public static class TuleapJobCheckOutListener extends SCMListener {
36+
@Override
37+
public void onCheckout(
38+
Run<?, ?> build,
39+
SCM scm,
40+
FilePath workspace,
41+
TaskListener listener,
42+
File changelogFile,
43+
SCMRevisionState pollingBaseline
44+
) {
45+
LOGGER.log(Level.INFO, String.format("Tuleap build: Checkout > %s", build.getFullDisplayName()));
46+
getNotifier().sendBuildStatusToTuleap(build, listener.getLogger(), TuleapBuildStatus.pending);
47+
}
48+
}
49+
50+
@Extension
51+
public static class TuleapJobCompletedListener extends RunListener<Run<?, ?>> {
52+
@Override
53+
public void onCompleted(Run<?, ?> build, @NonNull TaskListener listener) {
54+
LOGGER.log(Level.INFO, String.format("Tuleap build: Complete > %s", build.getFullDisplayName()));
55+
56+
final Result buildResult = build.getResult();
57+
final TuleapBuildStatus status = (buildResult == Result.SUCCESS) ? TuleapBuildStatus.success : TuleapBuildStatus.failure;
58+
59+
getNotifier().sendBuildStatusToTuleap(build, listener.getLogger(), status);
60+
}
61+
}
62+
}

src/main/java/org/jenkinsci/plugins/tuleap_git_branch_source/TuleapSCMSource.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import edu.umd.cs.findbugs.annotations.CheckForNull;
44
import edu.umd.cs.findbugs.annotations.NonNull;
5+
import edu.umd.cs.findbugs.annotations.Nullable;
56
import hudson.Extension;
67
import hudson.Util;
78
import hudson.model.Action;
@@ -277,6 +278,10 @@ public String getGitBaseUri() {
277278
return TuleapConfiguration.get().getGitBaseUrl();
278279
}
279280

281+
public TuleapGitRepository getTuleapGitRepository() {
282+
return this.repository;
283+
}
284+
280285
@Symbol("Tuleap")
281286
@Extension
282287
public static class DescriptorImpl extends SCMSourceDescriptor {
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package org.jenkinsci.plugins.tuleap_git_branch_source;
2+
3+
import hudson.model.FreeStyleBuild;
4+
import hudson.model.FreeStyleProject;
5+
import hudson.plugins.git.util.Build;
6+
import hudson.plugins.git.util.BuildData;
7+
import io.jenkins.plugins.tuleap_api.client.GitApi;
8+
import io.jenkins.plugins.tuleap_api.client.internals.entities.TuleapBuildStatus;
9+
import io.jenkins.plugins.tuleap_api.deprecated_client.api.TuleapGitRepository;
10+
import io.jenkins.plugins.tuleap_credentials.TuleapAccessToken;
11+
import jenkins.scm.api.SCMSource;
12+
import org.eclipse.jgit.lib.ObjectId;
13+
import org.jenkinsci.plugins.tuleap_git_branch_source.config.TuleapConnector;
14+
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
15+
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
16+
import org.junit.After;
17+
import org.junit.Before;
18+
import org.junit.Test;
19+
import org.mockito.MockedStatic;
20+
import org.mockito.Mockito;
21+
22+
import java.io.PrintStream;
23+
24+
import static org.mockito.Mockito.*;
25+
26+
public class TuleapPipelineStatusNotifierTest {
27+
private GitApi gitApi;
28+
private TuleapPipelineStatusNotifier notifier;
29+
private TuleapAccessToken accessKey;
30+
private MockedStatic<SCMSource.SourceByItem> sourceByItem;
31+
private MockedStatic<TuleapConnector> tuleapConnector;
32+
33+
@Before
34+
public void setUp() {
35+
this.gitApi = mock(GitApi.class);
36+
this.accessKey = mock(TuleapAccessToken.class);
37+
this.sourceByItem = Mockito.mockStatic(SCMSource.SourceByItem.class);
38+
this.tuleapConnector = Mockito.mockStatic(TuleapConnector.class);
39+
40+
this.notifier = new TuleapPipelineStatusNotifier(this.gitApi);
41+
}
42+
43+
@After
44+
public void tearDown() {
45+
this.sourceByItem.close();
46+
this.tuleapConnector.close();
47+
}
48+
49+
@Test
50+
public void testItDoesNotNotifyWhenItIsNotATuleapSCMBuild() {
51+
final FreeStyleBuild build = mock(FreeStyleBuild.class);
52+
final SCMSource source = mock(SCMSource.class);
53+
final PrintStream logger = mock(PrintStream.class);
54+
final FreeStyleProject freestyleProject = mock(FreeStyleProject.class);
55+
56+
when(build.getParent()).thenReturn(freestyleProject);
57+
58+
this.sourceByItem.when(() -> SCMSource.SourceByItem.findSource(freestyleProject)).thenReturn(source);
59+
60+
verify(this.gitApi, never()).sendBuildStatus(
61+
"5",
62+
"aeiouy123456",
63+
TuleapBuildStatus.success,
64+
this.accessKey
65+
);
66+
67+
this.notifier.sendBuildStatusToTuleap(build, logger, TuleapBuildStatus.success);
68+
}
69+
70+
@Test(expected = RuntimeException.class)
71+
public void testItThrowsAnExceptionWhenAccessKeyNotFound() {
72+
final WorkflowRun build = mock(WorkflowRun.class);
73+
final TuleapSCMSource source = mock(TuleapSCMSource.class);
74+
final PrintStream logger = mock(PrintStream.class);
75+
final WorkflowJob workflowJob = mock(WorkflowJob.class);
76+
77+
when(build.getParent()).thenReturn(workflowJob);
78+
79+
this.sourceByItem.when(() -> SCMSource.SourceByItem.findSource(workflowJob)).thenReturn(source);
80+
this.tuleapConnector.when(() -> TuleapConnector.lookupScanCredentials(
81+
Mockito.any(),
82+
Mockito.any(),
83+
Mockito.any()
84+
)).thenReturn(null);
85+
86+
verify(this.gitApi, never()).sendBuildStatus(
87+
"5",
88+
"aeiouy123456",
89+
TuleapBuildStatus.success,
90+
this.accessKey
91+
);
92+
93+
this.notifier.sendBuildStatusToTuleap(build, logger, TuleapBuildStatus.success);
94+
}
95+
96+
@Test(expected = RuntimeException.class)
97+
public void testItThrowsAnExceptionWhenGitDataNotFound() {
98+
final WorkflowRun build = mock(WorkflowRun.class);
99+
final TuleapSCMSource source = mock(TuleapSCMSource.class);
100+
final PrintStream logger = mock(PrintStream.class);
101+
final WorkflowJob workflowJob = mock(WorkflowJob.class);
102+
final TuleapAccessToken accessKey = mock(TuleapAccessToken.class);
103+
104+
when(build.getParent()).thenReturn(workflowJob);
105+
when(build.getAction(BuildData.class)).thenReturn(null);
106+
107+
this.sourceByItem.when(() -> SCMSource.SourceByItem.findSource(workflowJob)).thenReturn(source);
108+
this.tuleapConnector.when(() -> TuleapConnector.lookupScanCredentials(
109+
Mockito.any(),
110+
Mockito.any(),
111+
Mockito.any()
112+
)).thenReturn(accessKey);
113+
114+
verify(this.gitApi, never()).sendBuildStatus(
115+
"5",
116+
"aeiouy123456",
117+
TuleapBuildStatus.success,
118+
this.accessKey
119+
);
120+
121+
this.notifier.sendBuildStatusToTuleap(build, logger, TuleapBuildStatus.success);
122+
}
123+
124+
@Test
125+
public void testItNotifiesTuleap() {
126+
final WorkflowRun build = mock(WorkflowRun.class);
127+
final TuleapSCMSource source = mock(TuleapSCMSource.class);
128+
final PrintStream logger = mock(PrintStream.class);
129+
final WorkflowJob workflowJob = mock(WorkflowJob.class);
130+
final TuleapAccessToken accessKey = mock(TuleapAccessToken.class);
131+
final BuildData gitData = mock(BuildData.class);
132+
final ObjectId sha1 = mock(ObjectId.class);
133+
final TuleapGitRepository repository = new TuleapGitRepository();
134+
final Build lastBuild = mock(Build.class);
135+
136+
repository.setId(5);
137+
gitData.lastBuild = lastBuild;
138+
139+
when(build.getParent()).thenReturn(workflowJob);
140+
when(build.getAction(BuildData.class)).thenReturn(gitData);
141+
when(lastBuild.getSHA1()).thenReturn(sha1);
142+
when(sha1.name()).thenReturn("aeiouy123465");
143+
when(source.getTuleapGitRepository()).thenReturn(repository);
144+
145+
this.sourceByItem.when(() -> SCMSource.SourceByItem.findSource(workflowJob)).thenReturn(source);
146+
this.tuleapConnector.when(() -> TuleapConnector.lookupScanCredentials(
147+
Mockito.any(),
148+
Mockito.any(),
149+
Mockito.any()
150+
)).thenReturn(accessKey);
151+
152+
verify(this.gitApi, atMostOnce()).sendBuildStatus(
153+
"5",
154+
"aeiouy123456",
155+
TuleapBuildStatus.success,
156+
accessKey
157+
);
158+
159+
this.notifier.sendBuildStatusToTuleap(build, logger, TuleapBuildStatus.success);
160+
}
161+
}

0 commit comments

Comments
 (0)