Skip to content

Commit 156beeb

Browse files
authored
Fix the timestamp in BitbucketTagSCMHead valued with 0 in NativeServer webhook processor instead of the annotated tag timestamp or using the referred commit timestamp. (#991)
This is archived perforing a remote call to get missing details in the event payload, unfortunately there are no real example to implement a different strategy.
1 parent c94a34a commit 156beeb

File tree

7 files changed

+191
-15
lines changed

7 files changed

+191
-15
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ public class BitbucketTagSCMHead extends GitTagSCMHead implements TagSCMHead {
3838
private static final long serialVersionUID = 1L;
3939

4040
/**
41-
* Constructor.
41+
* Default constructor.
4242
*
4343
* @param tagName the tag name
44-
* @param timestamp the timestamp of tag
44+
* @param timestamp the timestamp of annotated tag or the commit is referring to
4545
*/
4646
public BitbucketTagSCMHead(@NonNull String tagName, long timestamp) {
4747
super(tagName, timestamp);

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/filesystem/BitbucketSCMFileSystem.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,7 @@ public SCMFileSystem build(@NonNull SCMSource source, @NonNull SCMHead head, @Ch
145145
String owner = src.getRepoOwner();
146146
String repository = src.getRepository();
147147
String serverUrl = src.getServerUrl();
148-
StandardCredentials credentials;
149-
credentials = lookupScanCredentials(src.getOwner(), credentialsId, serverUrl);
148+
StandardCredentials credentials = lookupScanCredentials(src.getOwner(), credentialsId, serverUrl);
150149

151150
BitbucketAuthenticator authenticator = AuthenticationTokens.convert(BitbucketAuthenticator.authenticationContext(serverUrl), credentials);
152151

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/ServerPushEvent.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@
2929
import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead;
3030
import com.cloudbees.jenkins.plugins.bitbucket.PullRequestSCMHead;
3131
import com.cloudbees.jenkins.plugins.bitbucket.PullRequestSCMRevision;
32+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
33+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApiFactory;
34+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
35+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit;
3236
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest;
37+
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentials;
3338
import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerAPIClient;
3439
import com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest.BitbucketServerPullRequest;
3540
import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepository;
3641
import com.cloudbees.jenkins.plugins.bitbucket.server.events.NativeServerChange;
42+
import com.cloudbees.plugins.credentials.common.StandardCredentials;
3743
import com.google.common.base.Ascii;
3844
import edu.umd.cs.findbugs.annotations.CheckForNull;
3945
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -48,6 +54,7 @@
4854
import java.util.Set;
4955
import java.util.logging.Level;
5056
import java.util.logging.Logger;
57+
import jenkins.authentication.tokens.api.AuthenticationTokens;
5158
import jenkins.plugins.git.AbstractGitSCMSource;
5259
import jenkins.scm.api.SCMEvent;
5360
import jenkins.scm.api.SCMHead;
@@ -138,7 +145,19 @@ private void addBranchesAndTags(BitbucketSCMSource src, Map<SCMHead, SCMRevision
138145
: new AbstractGitSCMSource.SCMRevisionImpl(head, change.getToHash());
139146
result.put(head, revision);
140147
} else if ("TAG".equals(refType)) {
141-
SCMHead head = new BitbucketTagSCMHead(change.getRef().getDisplayId(), 0);
148+
String tagName = change.getRef().getDisplayId();
149+
// FIXME slow workaround until a real example of server tag payload has been provided
150+
// I expect in the payload there is also the referred commit.
151+
long tagTimestamp;
152+
try (BitbucketApi client = getClient(src)) {
153+
// BitbucketBranch tag = client.getTag(tagName); // requires two API call and does not return the tag timestamp
154+
BitbucketCommit tag = client.resolveCommit(change.getFromHash());
155+
tagTimestamp = tag != null ? tag.getDateMillis() : 0;
156+
} catch (InterruptedException | IOException e) {
157+
LOGGER.log(Level.SEVERE, "Fail to retrive the timestamp for tag event {0}", tagName);
158+
tagTimestamp = 0;
159+
}
160+
SCMHead head = new BitbucketTagSCMHead(tagName, tagTimestamp);
142161
final SCMRevision revision = getType() == SCMEvent.Type.REMOVED ? null
143162
: new AbstractGitSCMSource.SCMRevisionImpl(head, change.getToHash());
144163
result.put(head, revision);
@@ -149,6 +168,19 @@ private void addBranchesAndTags(BitbucketSCMSource src, Map<SCMHead, SCMRevision
149168
}
150169
}
151170

171+
protected BitbucketApi getClient(BitbucketSCMSource src) {
172+
String serverURL = src.getServerUrl();
173+
174+
StandardCredentials credentials = BitbucketCredentials.lookupCredentials(
175+
serverURL,
176+
src.getOwner(),
177+
src.getCredentialsId(),
178+
StandardCredentials.class
179+
);
180+
BitbucketAuthenticator authenticator = AuthenticationTokens.convert(BitbucketAuthenticator.authenticationContext(serverURL), credentials);
181+
return BitbucketApiFactory.newInstance(serverURL, authenticator, src.getRepoOwner(), null, src.getRepository());
182+
}
183+
152184
private void addPullRequests(BitbucketSCMSource src, Map<SCMHead, SCMRevision> result) throws InterruptedException {
153185
if (getType() != SCMEvent.Type.UPDATED) {
154186
return; // adds/deletes won't be handled here

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketIntegrationClientFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
2828
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils;
29+
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation;
2930
import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerAPIClient;
3031
import java.io.FileNotFoundException;
3132
import java.io.IOException;
@@ -91,7 +92,7 @@ public static class BitbucketServerIntegrationClient extends BitbucketServerAPIC
9192
private final IRequestAudit audit;
9293

9394
private BitbucketServerIntegrationClient(String payloadRootPath, String baseURL, String owner, String repositoryName) {
94-
super(baseURL, owner, repositoryName, mock(BitbucketAuthenticator.class), false);
95+
super(baseURL, owner, repositoryName, mock(BitbucketAuthenticator.class), false, BitbucketServerWebhookImplementation.NATIVE);
9596

9697
if (payloadRootPath == null) {
9798
this.payloadRootPath = PAYLOAD_RESOURCE_ROOTPATH;

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/filesystem/BitbucketSCMFileTest.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,19 @@
2828
import java.io.FileNotFoundException;
2929
import jenkins.scm.api.SCMFile;
3030
import jenkins.scm.api.SCMFile.Type;
31-
import org.junit.jupiter.api.BeforeAll;
3231
import org.junit.jupiter.api.Test;
3332
import org.jvnet.hudson.test.Issue;
3433
import org.jvnet.hudson.test.JenkinsRule;
35-
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
3634

3735
import static org.assertj.core.api.Assertions.assertThat;
3836
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3937
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4038

41-
@WithJenkins
4239
class BitbucketSCMFileTest {
4340

4441
@SuppressWarnings("unused")
4542
private static JenkinsRule j;
4643

47-
@BeforeAll
48-
static void init(JenkinsRule rule) {
49-
j = rule;
50-
}
51-
5244
@Issue("JENKINS-75157")
5345
@Test
5446
void verify_content_throws_FileNotFoundException_when_file_does_not_exists() {

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPushHookProcessorTest.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
*/
2424
package com.cloudbees.jenkins.plugins.bitbucket.hooks;
2525

26+
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketMockApiFactory;
2627
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource;
28+
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead;
2729
import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead;
30+
import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketIntegrationClientFactory;
2831
import hudson.scm.SCM;
2932
import java.io.IOException;
3033
import java.io.InputStream;
@@ -39,13 +42,15 @@
3942
import org.junit.jupiter.api.BeforeEach;
4043
import org.junit.jupiter.api.Test;
4144
import org.jvnet.hudson.test.Issue;
45+
import org.jvnet.hudson.test.JenkinsRule;
46+
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
4247

4348
import static org.assertj.core.api.Assertions.assertThat;
4449
import static org.mockito.Mockito.mock;
4550

4651
class NativeServerPushHookProcessorTest {
4752

48-
private static final String SERVER_URL = "http://localhost:7990/";
53+
private static final String SERVER_URL = "http://localhost:7990";
4954
private static final String MIRROR_ID = "ABCD-1234-EFGH-5678";
5055
private NativeServerPushHookProcessor sut;
5156
private SCMHeadEvent<?> scmEvent;
@@ -80,6 +85,27 @@ void test_mirror_sync_changes() throws Exception {
8085
.isEqualTo(new BranchSCMHead("main"));
8186
}
8287

88+
@WithJenkins
89+
@Test
90+
void test_tag_timestamp(JenkinsRule rule) throws Exception {
91+
sut.process(HookEventType.SERVER_REFS_CHANGED, loadResource("native/tagPayload.json"), BitbucketType.SERVER, "origin", SERVER_URL);
92+
assertThat(scmEvent)
93+
.isInstanceOf(ServerPushEvent.class)
94+
.isNotNull();
95+
96+
BitbucketSCMSource scmSource = new BitbucketSCMSource("amuniz", "test-repos");
97+
scmSource.setServerUrl(SERVER_URL);
98+
99+
BitbucketMockApiFactory.add(SERVER_URL, BitbucketIntegrationClientFactory.getApiMockClient(SERVER_URL));
100+
101+
Map<SCMHead, SCMRevision> result = scmEvent.heads(scmSource);
102+
assertThat(result.keySet())
103+
.hasSize(1)
104+
.first()
105+
.usingRecursiveComparison()
106+
.isEqualTo(new BitbucketTagSCMHead("v0.0.0", 1537538991000L));
107+
}
108+
83109
@Test
84110
@Issue("JENKINS-55927")
85111
void test_mirror_sync_reflimitexceeed() throws Exception {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
{
2+
"actor": {
3+
"active": true,
4+
"displayName": "Antonio Muniz",
5+
"emailAddress": "amuniz@acme.com",
6+
"id": 2,
7+
"links": {
8+
"self": [
9+
{
10+
"href": "http://localhost:7990/users/amuniz"
11+
}
12+
]
13+
},
14+
"name": "amuniz",
15+
"slug": "amuniz",
16+
"type": "NORMAL"
17+
},
18+
"changes": [
19+
{
20+
"fromHash": "fb522a6f08c7c7df337312e4e65ec1b57710672e",
21+
"ref": {
22+
"displayId": "v0.0.0",
23+
"id": "refs/tags/v0.0.0",
24+
"type": "TAG"
25+
},
26+
"refId": "refs/heads/main",
27+
"toHash": "ad64eb8f05364f825e8557a7a672cc576baffb9a",
28+
"type": "ADD"
29+
}
30+
],
31+
"commits": [
32+
{
33+
"author": {
34+
"active": true,
35+
"displayName": "Antonio Muniz",
36+
"emailAddress": "amuniz@acme.com",
37+
"id": 2,
38+
"links": {
39+
"self": [
40+
{
41+
"href": "http://localhost:7990/users/amuniz"
42+
}
43+
]
44+
},
45+
"name": "amuniz",
46+
"slug": "amuniz",
47+
"type": "NORMAL"
48+
},
49+
"authorTimestamp": 1537538991000,
50+
"committer": {
51+
"active": true,
52+
"displayName": "Antonio Muniz",
53+
"emailAddress": "amuniz@acme.com",
54+
"id": 2,
55+
"links": {
56+
"self": [
57+
{
58+
"href": "http://localhost:7990/users/amuniz"
59+
}
60+
]
61+
},
62+
"name": "amuniz",
63+
"slug": "amuniz",
64+
"type": "NORMAL"
65+
},
66+
"committerTimestamp": 1537538991000,
67+
"displayId": "9fdd7b96d3f",
68+
"id": "9fdd7b96d3f5c276d0b9e0bf38c879eb112d889a",
69+
"message": "source-2: Wed 13 Nov 2024 21:54:36 AEST",
70+
"parents": [
71+
{
72+
"displayId": "ae995d7a370",
73+
"id": "ae995d7a37069d0988462a9c92828971e8a42b5d"
74+
}
75+
]
76+
}
77+
],
78+
"date": "2024-11-27T01:34:45+0000",
79+
"eventKey": "repo:refs_changed",
80+
"repository": {
81+
"archived": false,
82+
"forkable": true,
83+
"hierarchyId": "050ab73089457a2ae375",
84+
"id": 1,
85+
"links": {
86+
"clone": [
87+
{
88+
"href": "ssh://git@localhost:7999/amuniz/test-repos.git",
89+
"name": "ssh"
90+
},
91+
{
92+
"href": "http://localhost:7990/scm/amuniz/test-repos.git",
93+
"name": "http"
94+
}
95+
],
96+
"self": [
97+
{
98+
"href": "http://localhost:7990/projects/AMUNIZ/repos/test-repos/browse"
99+
}
100+
]
101+
},
102+
"name": "test-repos",
103+
"project": {
104+
"id": 1,
105+
"key": "AMUNIZ",
106+
"links": {
107+
"self": [
108+
{
109+
"href": "http://localhost:7990/projects/AMUNIZ"
110+
}
111+
]
112+
},
113+
"name": "prj",
114+
"public": false,
115+
"type": "NORMAL"
116+
},
117+
"public": false,
118+
"scmId": "git",
119+
"slug": "test-repos",
120+
"state": "AVAILABLE",
121+
"statusMessage": "Available"
122+
},
123+
"toCommit": {
124+
"_comment": "NOT real payload!"
125+
}
126+
}

0 commit comments

Comments
 (0)