Skip to content

Commit 7686eb7

Browse files
authored
[JENKINS-75157] Bitbucket client fail to retrieve resource with HTTP 404 for bitbucket server (#970) (#973)
Remove the HttpHost parameter when calling in Apache client as the request could be different than the host configured in jenkins, for example in Bitbucket Server request using mirror link. Rethrow the FileNotFoundException in executeMethod instead of encapsulate into other exception to respect the SCMFile interface. Rewrote the BitbucketSCMFile logic to not assume the SCMFile.type but resolve at runtime when needed.
1 parent 7645694 commit 7686eb7

File tree

13 files changed

+296
-142
lines changed

13 files changed

+296
-142
lines changed

pom.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,6 @@
9090
<dependency>
9191
<groupId>org.jenkins-ci.plugins</groupId>
9292
<artifactId>git</artifactId>
93-
<!-- TODO: remove when in bom -->
94-
<version>5.7.0</version>
9593
</dependency>
9694
<dependency>
9795
<groupId>org.jenkins-ci.plugins</groupId>

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,17 +302,30 @@ List<? extends BitbucketRepository> getRepositories(@CheckForNull UserRoleInRepo
302302
* @throws IOException if there was a network communications error.
303303
* @throws InterruptedException if interrupted while waiting on remote communications.
304304
*/
305+
@NonNull
305306
@Restricted(NoExternalUse.class)
306307
Iterable<SCMFile> getDirectoryContent(BitbucketSCMFile parent) throws IOException, InterruptedException;
307308

308309
/**
309310
* Return an input stream for the given file.
310311
*
311-
* @param file and instance of SCM file
312+
* @param file an instance of SCM file
312313
* @return the stream of the given {@link SCMFile}
313314
* @throws IOException if there was a network communications error.
314315
* @throws InterruptedException if interrupted while waiting on remote communications.
315316
*/
316317
@Restricted(NoExternalUse.class)
317318
InputStream getFileContent(BitbucketSCMFile file) throws IOException, InterruptedException;
319+
320+
/**
321+
* Return the metadata for the given file.
322+
*
323+
* @param file an instance of SCM file
324+
* @return a {@link SCMFile} file with updated the metadata
325+
* @throws IOException if there was a network communications error.
326+
* @throws InterruptedException if interrupted while waiting on remote communications.
327+
*/
328+
@NonNull
329+
@Restricted(NoExternalUse.class)
330+
SCMFile getFile(@NonNull BitbucketSCMFile file) throws IOException, InterruptedException;
318331
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import com.fasterxml.jackson.core.type.TypeReference;
5656
import edu.umd.cs.findbugs.annotations.CheckForNull;
5757
import edu.umd.cs.findbugs.annotations.NonNull;
58+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
5859
import hudson.Util;
5960
import java.awt.image.BufferedImage;
6061
import java.io.BufferedInputStream;
@@ -836,33 +837,45 @@ public Iterable<SCMFile> getDirectoryContent(final BitbucketSCMFile parent) thro
836837
.set("path", parent.getPath())
837838
.expand();
838839
List<SCMFile> result = new ArrayList<>();
839-
String response = getRequest(url);
840-
BitbucketCloudPage<BitbucketRepositorySource> page = JsonParser.mapper.readValue(response,
841-
new TypeReference<BitbucketCloudPage<BitbucketRepositorySource>>(){});
842840

843-
for(BitbucketRepositorySource source:page.getValues()){
844-
result.add(source.toBitbucketScmFile(parent));
845-
}
841+
String pageURL = url;
842+
BitbucketCloudPage<BitbucketRepositorySource> page;
843+
do {
844+
String response = getRequest(pageURL);
845+
page = JsonParser.mapper.readValue(response, new TypeReference<BitbucketCloudPage<BitbucketRepositorySource>>(){});
846846

847-
while (!page.isLastPage()){
848-
response = getRequest(page.getNext());
849-
page = JsonParser.mapper.readValue(response,
850-
new TypeReference<BitbucketCloudPage<BitbucketRepositorySource>>(){});
851-
for(BitbucketRepositorySource source:page.getValues()){
852-
result.add(source.toBitbucketScmFile(parent));
847+
for(BitbucketRepositorySource source : page.getValues()){
848+
result.add(source.toBitbucketSCMFile(parent));
853849
}
854-
}
850+
pageURL = page.getNext();
851+
} while (!page.isLastPage());
855852
return result;
856853
}
857854

858855
@Override
859-
public InputStream getFileContent(BitbucketSCMFile file) throws IOException, InterruptedException {
860-
String url = UriTemplate.fromTemplate(REPO_URL_TEMPLATE + "/src{/branchOrHash,path}")
856+
public InputStream getFileContent(@NonNull BitbucketSCMFile file) throws IOException, InterruptedException {
857+
String url = UriTemplate.fromTemplate(REPO_URL_TEMPLATE + "/src{/branchOrHash,path}{?at}")
861858
.set("owner", owner)
862859
.set("repo", repositoryName)
863860
.set("branchOrHash", file.getHash())
864861
.set("path", file.getPath())
862+
.set("at", file.getRef())
865863
.expand();
866864
return getRequestAsInputStream(url);
867865
}
866+
867+
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
868+
@NonNull
869+
@Override
870+
public SCMFile getFile(@NonNull BitbucketSCMFile file) throws IOException, InterruptedException {
871+
String url = UriTemplate.fromTemplate(REPO_URL_TEMPLATE + "/src{/branchOrHash,path}?format=meta")
872+
.set("owner", owner)
873+
.set("repo", repositoryName)
874+
.set("branchOrHash", file.getHash() != null ? file.getHash() : file.getRef())
875+
.set("path", file.getPath())
876+
.expand();
877+
String response = getRequest(url);
878+
BitbucketRepositorySource src = JsonParser.mapper.readValue(response, BitbucketRepositorySource.class);
879+
return src.toBitbucketSCMFile((BitbucketSCMFile) file.parent());
880+
}
868881
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/repository/BitbucketRepositorySource.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.fasterxml.jackson.annotation.JsonCreator;
2929
import com.fasterxml.jackson.annotation.JsonIgnore;
3030
import com.fasterxml.jackson.annotation.JsonProperty;
31+
import edu.umd.cs.findbugs.annotations.NonNull;
3132
import java.util.List;
3233
import java.util.Map;
3334
import jenkins.scm.api.SCMFile;
@@ -68,23 +69,25 @@ public String getHash() {
6869

6970
@JsonIgnore
7071
public boolean isDirectory() {
71-
return type.equals("commit_directory");
72+
return "commit_directory".equals(type);
7273
}
7374

74-
public BitbucketSCMFile toBitbucketScmFile(BitbucketSCMFile parent){
75+
@NonNull
76+
public BitbucketSCMFile toBitbucketSCMFile(BitbucketSCMFile parent) {
7577
SCMFile.Type fileType;
76-
if(isDirectory()){
78+
if (isDirectory()) {
7779
fileType = SCMFile.Type.DIRECTORY;
7880
} else {
7981
fileType = SCMFile.Type.REGULAR_FILE;
80-
for(String attribute: getAttributes()){
81-
if(attribute.equals("link")){
82+
for (String attribute : getAttributes()) {
83+
if ("link".equals(attribute)) {
8284
fileType = SCMFile.Type.LINK;
83-
} else if(attribute.equals("subrepository")){
85+
} else if ("subrepository".equals(attribute)) {
8486
fileType = SCMFile.Type.OTHER; // sub-module or sub-repo
8587
}
8688
}
8789
}
8890
return new BitbucketSCMFile(parent, path, fileType, hash);
8991
}
92+
9093
}

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

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,19 @@
2525
package com.cloudbees.jenkins.plugins.bitbucket.filesystem;
2626

2727
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
28+
import edu.umd.cs.findbugs.annotations.CheckForNull;
2829
import edu.umd.cs.findbugs.annotations.NonNull;
2930
import java.io.IOException;
3031
import java.io.InputStream;
32+
import java.util.Collections;
3133
import jenkins.scm.api.SCMFile;
3234

33-
public class BitbucketSCMFile extends SCMFile {
35+
public class BitbucketSCMFile extends SCMFile {
3436

3537
private final BitbucketApi api;
3638
private String ref;
3739
private final String hash;
40+
private boolean resolved;
3841

3942
public String getRef() {
4043
return ref;
@@ -44,34 +47,22 @@ public void setRef(String ref) {
4447
this.ref = ref;
4548
}
4649

47-
@Deprecated
48-
public BitbucketSCMFile(BitbucketSCMFileSystem bitBucketSCMFileSystem,
49-
BitbucketApi api,
50-
String ref) {
51-
this(bitBucketSCMFileSystem, api, ref, null);
52-
}
53-
54-
public BitbucketSCMFile(BitbucketSCMFileSystem bitBucketSCMFileSystem,
55-
BitbucketApi api,
56-
String ref, String hash) {
57-
super();
58-
type(Type.DIRECTORY);
50+
public BitbucketSCMFile(BitbucketApi api, String ref, String hash) {
5951
this.api = api;
6052
this.ref = ref;
6153
this.hash = hash;
54+
this.resolved = false;
6255
}
6356

64-
@Deprecated
65-
public BitbucketSCMFile(@NonNull BitbucketSCMFile parent, String name, Type type) {
66-
this(parent, name, type, null);
67-
}
68-
69-
public BitbucketSCMFile(@NonNull BitbucketSCMFile parent, String name, Type type, String hash) {
57+
public BitbucketSCMFile(BitbucketSCMFile parent, String name, @CheckForNull Type type, String hash) {
7058
super(parent, name);
7159
this.api = parent.api;
7260
this.ref = parent.ref;
7361
this.hash = hash;
74-
type(type);
62+
if (type != null) {
63+
type(type);
64+
}
65+
this.resolved = type != null;
7566
}
7667

7768
public String getHash() {
@@ -80,40 +71,48 @@ public String getHash() {
8071

8172
@Override
8273
@NonNull
83-
public Iterable<SCMFile> children() throws IOException,
84-
InterruptedException {
74+
public Iterable<SCMFile> children() throws IOException, InterruptedException {
8575
if (this.isDirectory()) {
8676
return api.getDirectoryContent(this);
8777
} else {
88-
throw new IOException("Cannot get children from a regular file");
78+
// respect the interface javadoc
79+
return Collections.emptyList();
8980
}
9081
}
9182

9283
@Override
9384
@NonNull
9485
public InputStream content() throws IOException, InterruptedException {
95-
if (this.isDirectory()) {
96-
throw new IOException("Cannot get raw content from a directory");
97-
} else {
86+
if (this.isFile()) {
9887
return api.getFileContent(this);
88+
} else {
89+
throw new IOException("Cannot get raw content from a directory");
9990
}
10091
}
10192

10293
@Override
10394
public long lastModified() throws IOException, InterruptedException {
104-
// TODO: Return valid value when Tag support is implemented
105-
return 0;
95+
return 0L;
10696
}
10797

10898
@Override
10999
@NonNull
110100
protected SCMFile newChild(String name, boolean assumeIsDirectory) {
111-
return new BitbucketSCMFile(this, name, assumeIsDirectory?Type.DIRECTORY:Type.REGULAR_FILE, hash);
101+
return new BitbucketSCMFile(this, name, null, hash);
112102
}
113103

114104
@Override
115105
@NonNull
116106
protected Type type() throws IOException, InterruptedException {
107+
if (!resolved) {
108+
try {
109+
SCMFile metadata = api.getFile(this);
110+
type(metadata.getType());
111+
} catch(IOException e) {
112+
type(Type.NONEXISTENT);
113+
}
114+
resolved = true;
115+
}
117116
return this.getType();
118117
}
119118

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

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@
4343
import hudson.Util;
4444
import hudson.model.Item;
4545
import hudson.model.Queue;
46-
import hudson.model.queue.Tasks;
4746
import hudson.scm.SCM;
4847
import hudson.scm.SCMDescriptor;
4948
import hudson.security.ACL;
5049
import java.io.IOException;
50+
import java.lang.annotation.Inherited;
5151
import jenkins.authentication.tokens.api.AuthenticationTokens;
5252
import jenkins.scm.api.SCMFile;
5353
import jenkins.scm.api.SCMFileSystem;
@@ -69,21 +69,18 @@ protected BitbucketSCMFileSystem(BitbucketApi api, String ref, SCMRevision rev)
6969
}
7070

7171
/**
72-
* Return timestamp of last commit or of tag if its annotated tag.
73-
*
74-
* @return timestamp of last commit or of tag if its annotated tag
72+
* {@link Inherited}
7573
*/
7674
@Override
7775
public long lastModified() throws IOException {
78-
// TODO figure out how to implement this
7976
return 0L;
8077
}
8178

8279
@NonNull
8380
@Override
8481
public SCMFile getRoot() {
8582
SCMRevision revision = getRevision();
86-
return new BitbucketSCMFile(this, api, ref, revision == null ? null : revision.toString());
83+
return new BitbucketSCMFile(api, ref, revision == null ? null : revision.toString());
8784
}
8885

8986
@Extension
@@ -116,22 +113,24 @@ public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull
116113
}
117114

118115
private static StandardCredentials lookupScanCredentials(@CheckForNull Item context,
119-
@CheckForNull String scanCredentialsId, String serverUrl) {
120-
if (Util.fixEmpty(scanCredentialsId) == null) {
116+
@CheckForNull String scanCredentialsId,
117+
String serverURL) {
118+
scanCredentialsId = Util.fixEmpty(scanCredentialsId);
119+
if (scanCredentialsId == null) {
121120
return null;
122121
} else {
123122
return CredentialsMatchers.firstOrNull(
124-
CredentialsProvider.lookupCredentials(
123+
CredentialsProvider.lookupCredentialsInItem(
125124
StandardCredentials.class,
126125
context,
127-
context instanceof Queue.Task
128-
? Tasks.getDefaultAuthenticationOf((Queue.Task) context)
129-
: ACL.SYSTEM,
130-
URIRequirementBuilder.fromUri(serverUrl).build()
126+
context instanceof Queue.Task task
127+
? task.getDefaultAuthentication2()
128+
: ACL.SYSTEM2,
129+
URIRequirementBuilder.fromUri(serverURL).build()
131130
),
132131
CredentialsMatchers.allOf(
133132
CredentialsMatchers.withId(scanCredentialsId),
134-
AuthenticationTokens.matcher(BitbucketAuthenticator.authenticationContext(serverUrl))
133+
AuthenticationTokens.matcher(BitbucketAuthenticator.authenticationContext(serverURL))
135134
)
136135
);
137136
}

0 commit comments

Comments
 (0)