Skip to content

Commit 40dc843

Browse files
authored
Improve error logging from the bitbucket client. This helps to understand what is going wrong (e.g. HTTP 403).
Reduce duplicate code by sharing Apache HTTP client management between bitbucket server and cloud clients.
1 parent d37613f commit 40dc843

File tree

9 files changed

+229
-278
lines changed

9 files changed

+229
-278
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
import hudson.model.Items;
6767
import hudson.model.TaskListener;
6868
import hudson.plugins.git.GitSCM;
69-
import hudson.plugins.git.extensions.GitSCMExtension;
7069
import hudson.scm.SCM;
7170
import hudson.security.AccessControlled;
7271
import hudson.util.FormFillFailure;
@@ -1006,7 +1005,7 @@ public SCM build(SCMHead head, SCMRevision revision) {
10061005

10071006
BitbucketAuthenticator authenticator = authenticator();
10081007
return new BitbucketGitSCMBuilder(this, head, revision, null)
1009-
.withExtension(authenticator == null || sshAuth ? null : new GitClientAuthenticatorExtension(authenticator.getCredentialsForSCM()))
1008+
.withExtension(new GitClientAuthenticatorExtension(authenticator == null || sshAuth ? null : authenticator.getCredentialsForSCM()))
10101009
.withCloneLinks(primaryCloneLinks, mirrorCloneLinks)
10111010
.withTraits(traits)
10121011
.build();

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import hudson.util.FormValidation;
4343
import hudson.util.ListBoxModel;
4444
import jenkins.model.Jenkins;
45-
import jenkins.plugins.git.GitSCMBuilder;
4645
import jenkins.scm.api.SCMSource;
4746
import jenkins.scm.api.trait.SCMBuilder;
4847
import jenkins.scm.api.trait.SCMSourceContext;
@@ -103,9 +102,8 @@ public final String getCredentialsId() {
103102
*/
104103
@Override
105104
protected void decorateBuilder(SCMBuilder<?, ?> builder) {
106-
if (builder instanceof GitSCMBuilder) {
107-
((BitbucketGitSCMBuilder) builder)
108-
.withCredentials(credentialsId, BitbucketRepositoryProtocol.SSH);
105+
if (builder instanceof BitbucketGitSCMBuilder gitBuilder) {
106+
gitBuilder.withCredentials(credentialsId, BitbucketRepositoryProtocol.SSH);
109107
}
110108
}
111109

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

Lines changed: 46 additions & 128 deletions
Large diffs are not rendered by default.

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import java.io.InputStream;
55
import org.apache.http.client.methods.CloseableHttpResponse;
66
import org.apache.http.client.methods.HttpRequestBase;
7-
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
7+
import org.apache.http.conn.HttpClientConnectionManager;
88
import org.apache.http.util.EntityUtils;
99

1010
public class ClosingConnectionInputStream extends InputStream {
@@ -13,16 +13,16 @@ public class ClosingConnectionInputStream extends InputStream {
1313

1414
private final HttpRequestBase method;
1515

16-
private final PoolingHttpClientConnectionManager connectionManager;
16+
private final HttpClientConnectionManager connectionManager;
1717

1818
private final InputStream delegate;
1919

2020
public ClosingConnectionInputStream(final CloseableHttpResponse response, final HttpRequestBase method,
21-
final PoolingHttpClientConnectionManager connectionManager)
21+
final HttpClientConnectionManager connectionmanager)
2222
throws UnsupportedOperationException, IOException {
2323
this.response = response;
2424
this.method = method;
25-
this.connectionManager = connectionManager;
25+
this.connectionManager = connectionmanager;
2626
this.delegate = response.getEntity().getContent();
2727
}
2828

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2016, CloudBees, Inc.
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.internal.api;
25+
26+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRequestException;
27+
import hudson.ProxyConfiguration;
28+
import java.io.IOException;
29+
import java.io.InputStream;
30+
import java.net.InetSocketAddress;
31+
import java.net.Proxy;
32+
import java.nio.charset.StandardCharsets;
33+
import java.util.logging.Logger;
34+
import jenkins.model.Jenkins;
35+
import org.apache.commons.io.IOUtils;
36+
import org.apache.commons.lang.StringUtils;
37+
import org.apache.http.Header;
38+
import org.apache.http.HttpHost;
39+
import org.apache.http.auth.AuthScope;
40+
import org.apache.http.auth.UsernamePasswordCredentials;
41+
import org.apache.http.client.AuthCache;
42+
import org.apache.http.client.CredentialsProvider;
43+
import org.apache.http.client.methods.CloseableHttpResponse;
44+
import org.apache.http.client.protocol.HttpClientContext;
45+
import org.apache.http.impl.auth.BasicScheme;
46+
import org.apache.http.impl.client.BasicAuthCache;
47+
import org.apache.http.impl.client.BasicCredentialsProvider;
48+
import org.apache.http.impl.client.HttpClientBuilder;
49+
import org.kohsuke.accmod.Restricted;
50+
import org.kohsuke.accmod.restrictions.ProtectedExternally;
51+
52+
@Restricted(ProtectedExternally.class)
53+
public class AbstractBitbucketApi {
54+
protected static final int API_RATE_LIMIT_STATUS_CODE = 429;
55+
56+
protected final Logger logger = Logger.getLogger(this.getClass().getName());
57+
protected HttpClientContext context;
58+
59+
60+
protected BitbucketRequestException buildResponseException(CloseableHttpResponse response, String errorMessage) {
61+
String headers = StringUtils.join(response.getAllHeaders(), "\n");
62+
String message = String.format("HTTP request error.%nStatus: %s%nResponse: %s%n%s", response.getStatusLine(), errorMessage, headers);
63+
return new BitbucketRequestException(response.getStatusLine().getStatusCode(), message);
64+
}
65+
66+
protected String getResponseContent(CloseableHttpResponse response) throws IOException {
67+
String content;
68+
long len = response.getEntity().getContentLength();
69+
if (len < 0) {
70+
len = getLenghtFromHeader(response);
71+
}
72+
if (len == 0) {
73+
content = "";
74+
} else {
75+
try (InputStream is = response.getEntity().getContent()) {
76+
content = IOUtils.toString(is, StandardCharsets.UTF_8);
77+
}
78+
}
79+
return content;
80+
}
81+
82+
private long getLenghtFromHeader(CloseableHttpResponse response) {
83+
long len = -1L;
84+
Header[] headers = response.getHeaders("Content-Length");
85+
if (headers != null && headers.length > 0) {
86+
int i = headers.length - 1;
87+
len = -1L;
88+
while (i >= 0) {
89+
Header header = headers[i];
90+
try {
91+
len = Long.parseLong(header.getValue());
92+
break;
93+
} catch (NumberFormatException var5) {
94+
--i;
95+
}
96+
}
97+
}
98+
return len;
99+
}
100+
101+
protected void setClientProxyParams(String host, HttpClientBuilder builder) {
102+
Jenkins jenkins = Jenkins.getInstanceOrNull(); // because unit test
103+
ProxyConfiguration proxyConfig = jenkins != null ? jenkins.proxy : null;
104+
105+
final Proxy proxy;
106+
if (proxyConfig != null) {
107+
proxy = proxyConfig.createProxy(host);
108+
} else {
109+
proxy = Proxy.NO_PROXY;
110+
}
111+
112+
if (proxy != Proxy.NO_PROXY && proxy.type() != Proxy.Type.DIRECT) {
113+
final InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
114+
logger.fine("Jenkins proxy: " + proxy.address());
115+
HttpHost proxyHttpHost = new HttpHost(proxyAddress.getHostName(), proxyAddress.getPort());
116+
builder.setProxy(proxyHttpHost);
117+
String username = proxyConfig.getUserName();
118+
String password = proxyConfig.getSecretPassword().getPlainText();
119+
if (StringUtils.isNotBlank(username)) {
120+
logger.fine("Using proxy authentication (user=" + username + ")");
121+
if (context == null) {
122+
// may have been already set in com.cloudbees.jenkins.plugins.bitbucket.api.credentials.BitbucketUsernamePasswordAuthenticator.configureContext(HttpClientContext, HttpHost)
123+
context = HttpClientContext.create();
124+
}
125+
CredentialsProvider credentialsProvider = context.getCredentialsProvider();
126+
if (credentialsProvider == null) {
127+
credentialsProvider = new BasicCredentialsProvider();
128+
// may have been already set in com.cloudbees.jenkins.plugins.bitbucket.api.credentials.BitbucketUsernamePasswordAuthenticator.configureContext(HttpClientContext, HttpHost)
129+
context.setCredentialsProvider(credentialsProvider);
130+
}
131+
credentialsProvider.setCredentials(new AuthScope(proxyHttpHost), new UsernamePasswordCredentials(username, password));
132+
AuthCache authCache = context.getAuthCache();
133+
if (authCache == null) {
134+
authCache = new BasicAuthCache();
135+
context.setAuthCache(authCache);
136+
}
137+
authCache.put(proxyHttpHost, new BasicScheme());
138+
}
139+
}
140+
}
141+
142+
}

0 commit comments

Comments
 (0)