Skip to content

Commit 09973f9

Browse files
authored
[JENKINS-76027] Allow Bitbucket build status to be customised (#1101)
Define extension point to allow customisation of build status. Additional data could be set in the optionalData field where all entries will be added to the root object.
1 parent ce95536 commit 09973f9

35 files changed

+1355
-1252
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ work/
1818

1919
# VSCode
2020
.factorypath
21-
META-INF/
21+
META-INF/
22+
/.apt_generated/
23+
/.apt_generated_tests/

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketNotifier.java

Lines changed: 0 additions & 69 deletions
This file was deleted.

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketApi.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424
package com.cloudbees.jenkins.plugins.bitbucket.api;
2525

26+
import com.cloudbees.jenkins.plugins.bitbucket.api.buildstatus.BitbucketBuildStatusNotifier;
2627
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookConfiguration;
2728
import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository;
2829
import com.cloudbees.jenkins.plugins.bitbucket.filesystem.BitbucketSCMFile;
@@ -297,8 +298,10 @@ List<? extends BitbucketRepository> getRepositories(@CheckForNull UserRoleInRepo
297298
* Set the build status for the given commit hash.
298299
*
299300
* @param status the status object to be serialized
301+
* @deprecated Use the appropriate {@link BitbucketBuildStatusNotifier} instead of this
300302
* @throws IOException if there was a network communications error.
301303
*/
304+
@Deprecated(since = "937.0.0", forRemoval = true)
302305
void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOException;
303306

304307
/**
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2025, Falco Nikolas
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package com.cloudbees.jenkins.plugins.bitbucket.api;
25+
26+
import edu.umd.cs.findbugs.annotations.CheckForNull;
27+
import edu.umd.cs.findbugs.annotations.NonNull;
28+
import java.io.IOException;
29+
30+
/**
31+
* The implementation provides an authenticated client for a configured
32+
* Bitbucket endpoint.
33+
*
34+
* @apiNote This interface is intended to be consumed in an extension point.
35+
*
36+
* @author Nikolas Falco
37+
* @since 937.0.0
38+
*/
39+
public interface BitbucketAuthenticatedClient extends AutoCloseable {
40+
41+
/**
42+
* The owner of the repository where register the webhook.
43+
*/
44+
@NonNull
45+
String getRepositoryOwner();
46+
47+
/**
48+
* Name of the repository where register the webhook.
49+
*/
50+
@CheckForNull
51+
String getRepositoryName();
52+
53+
/**
54+
* Perform an HTTP POST to the configured endpoint.
55+
* <p>
56+
* Request will be sent as JSON
57+
*
58+
* @param path to call, it will prepend with the server URL
59+
* @param payload to send
60+
* @return the JSON string of the response
61+
* @throws IOException in case of connection failures
62+
*/
63+
String post(@NonNull String path, @CheckForNull String payload) throws IOException;
64+
65+
/**
66+
* Perform an HTTP PUT to the configured endpoint.
67+
* <p>
68+
* Request will be sent as JSON
69+
*
70+
* @param path to call, it will prepend with the server URL
71+
* @param payload to send
72+
* @return the JSON string of the response
73+
* @throws IOException in case of connection failures
74+
*/
75+
String put(@NonNull String path, @CheckForNull String payload) throws IOException;
76+
77+
/**
78+
* Perform an HTTP DELETE to the configured endpoint.
79+
* <p>
80+
* Request will be sent as JSON
81+
*
82+
* @param path to call, it will prepend with the server URL
83+
* @return the JSON string of the response
84+
* @throws IOException in case of connection failures
85+
*/
86+
String delete(@NonNull String path) throws IOException;
87+
88+
/**
89+
* Perform an HTTP GET to the configured endpoint.
90+
* <p>
91+
* Request will be sent as JSON
92+
*
93+
* @param path to call, it will prepend with the server URL
94+
* @return the JSON string of the response
95+
* @throws IOException in case of connection failures
96+
*/
97+
@NonNull
98+
String get(@NonNull String path) throws IOException;
99+
100+
@Override
101+
void close() throws IOException;
102+
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatus.java

Lines changed: 105 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,20 @@
2323
*/
2424
package com.cloudbees.jenkins.plugins.bitbucket.api;
2525

26+
import com.cloudbees.jenkins.plugins.bitbucket.api.buildstatus.BitbucketBuildStatusCustomizer;
27+
import com.fasterxml.jackson.annotation.JsonAnyGetter;
2628
import com.fasterxml.jackson.annotation.JsonIgnore;
29+
import com.fasterxml.jackson.annotation.JsonValue;
2730
import edu.umd.cs.findbugs.annotations.NonNull;
2831
import edu.umd.cs.findbugs.annotations.Nullable;
32+
import java.util.Collections;
33+
import java.util.HashMap;
34+
import java.util.Map;
35+
import java.util.Objects;
2936
import org.kohsuke.accmod.Restricted;
3037
import org.kohsuke.accmod.restrictions.DoNotUse;
3138

3239
public class BitbucketBuildStatus {
33-
3440
/**
3541
* Enumeration of possible Bitbucket commit notification states
3642
*/
@@ -43,20 +49,18 @@ public enum Status {
4349
CANCELLED("CANCELLED"),
4450
SUCCESSFUL("SUCCESSFUL");
4551

46-
private final String status;
47-
48-
Status(final String status) {
49-
this.status = status;
50-
}
52+
@JsonValue
53+
private final String label;
5154

52-
@Override
53-
public String toString() {
54-
return status;
55+
Status(final String label) {
56+
this.label = label;
5557
}
5658
}
5759

5860
/**
59-
* The commit hash to set the status on
61+
* The commit hash to set the status on.
62+
* <p>
63+
* This is not part of the payload.
6064
*/
6165
@JsonIgnore
6266
private String hash;
@@ -107,9 +111,16 @@ public String toString() {
107111
*/
108112
private int buildNumber;
109113

114+
/**
115+
* A set of new informations.
116+
*/
117+
private Map<String, Object> optionalData;
118+
110119
// Used for marshalling/unmarshalling
111120
@Restricted(DoNotUse.class)
112-
public BitbucketBuildStatus() {}
121+
public BitbucketBuildStatus() {
122+
this.optionalData = new HashMap<>();
123+
}
113124

114125
public BitbucketBuildStatus(String hash,
115126
String description,
@@ -141,7 +152,9 @@ public BitbucketBuildStatus(@NonNull BitbucketBuildStatus other) {
141152
this.name = other.name;
142153
this.refname = other.refname;
143154
this.buildDuration = other.buildDuration;
155+
this.buildNumber = other.buildNumber;
144156
this.parent = other.parent;
157+
this.optionalData = other.optionalData != null ? new HashMap<>(other.optionalData) : new HashMap<>();
145158
}
146159

147160
public String getHash() {
@@ -160,8 +173,8 @@ public void setDescription(String description) {
160173
this.description = description;
161174
}
162175

163-
public String getState() {
164-
return state.toString();
176+
public Status getState() {
177+
return state;
165178
}
166179

167180
public void setState(Status state) {
@@ -223,4 +236,83 @@ public void setParent(String parent) {
223236
public String getParent() {
224237
return parent;
225238
}
239+
240+
/**
241+
* This represent additional informations contributed by
242+
* {@link BitbucketBuildStatusCustomizer}s.
243+
* <p>
244+
* The contents of this map will be added to the root of the sent payload.
245+
* <p>
246+
* For example:
247+
*
248+
* <pre>
249+
* buildStatus.addOptionalData("testResults", new TestResult(1, 2, 3));
250+
* buildStatus.addOptionalData("optX", true);
251+
* </pre>
252+
*
253+
* Will be serialised as:
254+
*
255+
* <pre>
256+
* {
257+
* "description": "The build is in progress..."
258+
* ...
259+
* "testResult": {
260+
* "successful": 5,
261+
* "failed": 2,
262+
* "skipped": 1
263+
* },
264+
* "optX": true
265+
* }
266+
* </pre>
267+
*
268+
* @return an unmodifiable map of extra informations
269+
*/
270+
@JsonAnyGetter
271+
public Map<String, Object> getOptionalData() {
272+
return Collections.unmodifiableMap(optionalData);
273+
}
274+
275+
/**
276+
* Add a new attribute to the payload to send. If the attribute has already
277+
* been valued than it is ignored.
278+
*
279+
* @param attribute attribute of build status, refer to the Bitbucket API
280+
* @param value bean to associate to the given attribute name
281+
* @see <a href="https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commit-statuses/#api-repositories-workspace-repo-slug-commit-commit-statuses-build-post">Cloud REST API</a>
282+
* @see <a href="https://developer.atlassian.com/server/bitbucket/rest/v906/api-group-builds-and-deployments/#api-api-latest-projects-projectkey-repos-repositoryslug-commits-commitid-builds-post">Data Center REST API</a>
283+
*/
284+
public void addOptionalData(String attribute, Object value) {
285+
this.optionalData.putIfAbsent(attribute, value);
286+
}
287+
288+
@Override
289+
public int hashCode() {
290+
return Objects.hash(buildDuration, buildNumber, description, hash, key, name, parent, refname, state, url, optionalData);
291+
}
292+
293+
@Override
294+
public boolean equals(Object obj) {
295+
if (this == obj) {
296+
return true;
297+
}
298+
if (obj == null) {
299+
return false;
300+
}
301+
if (getClass() != obj.getClass()) {
302+
return false;
303+
}
304+
BitbucketBuildStatus other = (BitbucketBuildStatus) obj;
305+
return buildDuration == other.buildDuration
306+
&& buildNumber == other.buildNumber
307+
&& Objects.equals(description, other.description)
308+
&& Objects.equals(hash, other.hash)
309+
&& Objects.equals(key, other.key)
310+
&& Objects.equals(name, other.name)
311+
&& Objects.equals(parent, other.parent)
312+
&& Objects.equals(refname, other.refname)
313+
&& state == other.state
314+
&& Objects.equals(url, other.url)
315+
&& Objects.equals(optionalData, other.optionalData);
316+
}
317+
226318
}

0 commit comments

Comments
 (0)