Skip to content

Commit

Permalink
#334: Add ability to collect Bitbucket HTTP debug logs
Browse files Browse the repository at this point in the history
When Bitbucket Pull Request decoration fails due to an error from Bitbucket, diagnosing the cause of the failure can be challenging due to the lack of logs. This change therefore adds an interceptor to the HTTP client used in Bitbucket decoration which logs HTTP request headers and body content to the Sonarqube logs at debug level.
  • Loading branch information
mc1arke committed Jun 9, 2021
1 parent b3c9f54 commit 41e3ee3
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 50 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ dependencies {
runtime 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7'
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
compile 'org.javassist:javassist:3.27.0-GA'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.cloud.BitbucketCloudConfiguration;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.BitbucketServerConfiguration;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.alm.setting.ALM;
import org.sonar.db.alm.setting.AlmSettingDto;
import org.sonar.db.alm.setting.ProjectAlmSettingDto;
Expand All @@ -33,9 +37,9 @@ private BitbucketClientFactory() {

public static BitbucketClient createClient(AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) {
if (almSettingDto.getAlm() == ALM.BITBUCKET_CLOUD) {
return new BitbucketCloudClient(new BitbucketCloudConfiguration(almSettingDto.getAppId(), projectAlmSettingDto.getAlmRepo(), almSettingDto.getClientId(), almSettingDto.getClientSecret()), createObjectMapper());
return new BitbucketCloudClient(new BitbucketCloudConfiguration(almSettingDto.getAppId(), projectAlmSettingDto.getAlmRepo(), almSettingDto.getClientId(), almSettingDto.getClientSecret()), createObjectMapper(), createBaseClientBuilder());
} else {
return new BitbucketServerClient(new BitbucketServerConfiguration(projectAlmSettingDto.getAlmRepo(), projectAlmSettingDto.getAlmSlug(), almSettingDto.getUrl(), almSettingDto.getPersonalAccessToken()), createObjectMapper());
return new BitbucketServerClient(new BitbucketServerConfiguration(projectAlmSettingDto.getAlmRepo(), projectAlmSettingDto.getAlmSlug(), almSettingDto.getUrl(), almSettingDto.getPersonalAccessToken()), createObjectMapper(), createBaseClientBuilder());
}
}

Expand All @@ -44,4 +48,11 @@ private static ObjectMapper createObjectMapper() {
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}

private static OkHttpClient.Builder createBaseClientBuilder() {
Logger logger = Loggers.get(BitbucketClientFactory.class);
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(logger::debug);
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient.Builder().addInterceptor(httpLoggingInterceptor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static java.lang.String.format;
Expand All @@ -63,17 +62,15 @@ public class BitbucketCloudClient implements BitbucketClient {
private static final String LINK_TEXT = "Go to SonarQube";

private final ObjectMapper objectMapper;
private final String bearerToken;
private final Supplier<OkHttpClient.Builder> okHttpClientBuilderSupplier;
private final OkHttpClient okHttpClient;

public BitbucketCloudClient(BitbucketCloudConfiguration config, ObjectMapper objectMapper) {
this(config, objectMapper, OkHttpClient.Builder::new);
public BitbucketCloudClient(BitbucketCloudConfiguration config, ObjectMapper objectMapper, OkHttpClient.Builder baseClientBuilder) {
this(objectMapper, createAuthorisingClient(baseClientBuilder, negotiateBearerToken(config, objectMapper, baseClientBuilder.build())));
}

BitbucketCloudClient(BitbucketCloudConfiguration config, ObjectMapper objectMapper, Supplier<OkHttpClient.Builder> okHttpClientBuilderSupplier) {
BitbucketCloudClient(ObjectMapper objectMapper, OkHttpClient okHttpClient) {
this.objectMapper = objectMapper;
this.okHttpClientBuilderSupplier = okHttpClientBuilderSupplier;
this.bearerToken = negotiateBearerToken(config, objectMapper, okHttpClientBuilderSupplier.get().build());
this.okHttpClient = okHttpClient;
}

private static String negotiateBearerToken(BitbucketCloudConfiguration bitbucketCloudConfiguration, ObjectMapper objectMapper, OkHttpClient okHttpClient) {
Expand Down Expand Up @@ -142,7 +139,7 @@ public void uploadAnnotations(String project, String repository, String commit,
LOGGER.info("Creating annotations on bitbucket cloud");
LOGGER.debug("Create annotations: " + objectMapper.writeValueAsString(annotations));

try (Response response = getClient().newCall(req).execute()) {
try (Response response = okHttpClient.newCall(req).execute()) {
validate(response);
}
}
Expand All @@ -166,7 +163,7 @@ public void uploadReport(String project, String repository, String commit, CodeI
LOGGER.info("Create report on bitbucket cloud: " + targetUrl);
LOGGER.debug("Create report: " + body);

try (Response response = getClient().newCall(req).execute()) {
try (Response response = okHttpClient.newCall(req).execute()) {
validate(response);
}
}
Expand Down Expand Up @@ -199,14 +196,13 @@ void deleteExistingReport(String project, String repository, String commit) thro

LOGGER.info("Deleting existing reports on bitbucket cloud");

try (Response response = getClient().newCall(req).execute()) {
try (Response response = okHttpClient.newCall(req).execute()) {
// we dont need to validate the output here since most of the time this call will just return a 404
}
}

private OkHttpClient getClient() {
return okHttpClientBuilderSupplier.get()
.addInterceptor(chain -> {
private static OkHttpClient createAuthorisingClient(OkHttpClient.Builder baseClientBuilder, String bearerToken) {
return baseClientBuilder.addInterceptor(chain -> {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", format("Bearer %s", bearerToken))
.addHeader("Accept", APPLICATION_JSON_MEDIA_TYPE.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.CreateReportRequest;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.ErrorResponse;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.ServerProperties;
import com.google.common.annotations.VisibleForTesting;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
Expand Down Expand Up @@ -61,10 +60,16 @@ public class BitbucketServerClient implements BitbucketClient {

private final BitbucketServerConfiguration config;
private final ObjectMapper objectMapper;
private final OkHttpClient okHttpClient;

public BitbucketServerClient(BitbucketServerConfiguration config, ObjectMapper objectMapper) {
public BitbucketServerClient(BitbucketServerConfiguration config, ObjectMapper objectMapper, OkHttpClient.Builder baseClientBuilder) {
this(config, objectMapper, createAuthorisingClient(baseClientBuilder, config));
}

BitbucketServerClient(BitbucketServerConfiguration config, ObjectMapper objectMapper, OkHttpClient okHttpClient) {
this.config = config;
this.objectMapper = objectMapper;
this.okHttpClient = okHttpClient;
}

@Override
Expand Down Expand Up @@ -97,7 +102,7 @@ public void deleteAnnotations(String project, String repository, String commit)
.delete()
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", config.getUrl(), project, repository, commit, REPORT_KEY))
.build();
try (Response response = getClient().newCall(req).execute()) {
try (Response response = okHttpClient.newCall(req).execute()) {
validate(response);
}
}
Expand All @@ -113,7 +118,7 @@ public void uploadAnnotations(String project, String repository, String commit,
.post(RequestBody.create(objectMapper.writeValueAsString(request), APPLICATION_JSON_MEDIA_TYPE))
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", config.getUrl(), project, repository, commit, REPORT_KEY))
.build();
try (Response response = getClient().newCall(req).execute()) {
try (Response response = okHttpClient.newCall(req).execute()) {
validate(response);
}
}
Expand All @@ -131,7 +136,7 @@ public void uploadReport(String project, String repository, String commit, CodeI
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s", config.getUrl(), project, repository, commit, REPORT_KEY))
.build();

try (Response response = getClient().newCall(req).execute()) {
try (Response response = okHttpClient.newCall(req).execute()) {
validate(response);
}
}
Expand Down Expand Up @@ -174,7 +179,7 @@ public ServerProperties getServerProperties() throws IOException {
.get()
.url(format("%s/rest/api/1.0/application-properties", config.getUrl()))
.build();
try (Response response = getClient().newCall(req).execute()) {
try (Response response = okHttpClient.newCall(req).execute()) {
validate(response);

return objectMapper.reader().forType(ServerProperties.class)
Expand All @@ -184,10 +189,8 @@ public ServerProperties getServerProperties() throws IOException {
}
}

@VisibleForTesting
OkHttpClient getClient() {
return new OkHttpClient.Builder()
.addInterceptor(chain -> {
private static OkHttpClient createAuthorisingClient(OkHttpClient.Builder clientBuilder, BitbucketServerConfiguration config) {
return clientBuilder.addInterceptor(chain -> {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", format("Bearer %s", config.getPersonalAccessToken()))
.addHeader("Accept", APPLICATION_JSON_MEDIA_TYPE.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsAnnotation;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsReport;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.DataValue;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.cloud.BitbucketCloudConfiguration;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.cloud.CloudAnnotation;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.cloud.CloudCreateReportRequest;
import com.google.common.collect.Sets;
Expand All @@ -32,7 +31,6 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.times;
Expand All @@ -49,24 +47,10 @@ public class BitbucketCloudClientUnitTest {
private OkHttpClient client;

@Before
public void before() throws IOException {
BitbucketCloudConfiguration config = new BitbucketCloudConfiguration("clientId", "secret", "repository", "project");
OkHttpClient.Builder builder = mock(OkHttpClient.Builder.class);
when(builder.build()).thenReturn(client);
when(builder.addInterceptor(any())).thenReturn(builder);
public void before() {
Call call = mock(Call.class);
Response response = mock(Response.class);
ResponseBody responseBody = mock(ResponseBody.class);
when(response.body()).thenReturn(responseBody);
String token = "dummyToken";
when(responseBody.string()).thenReturn(token);
when(mapper.readValue(token, BitbucketCloudClient.AuthToken.class)).thenReturn(new BitbucketCloudClient.AuthToken("accessToken"));

when(client.newCall(any())).thenReturn(call);
when(call.execute()).thenReturn(response);

underTest = new BitbucketCloudClient(config, mapper, () -> builder);
reset(client);
underTest = new BitbucketCloudClient(mapper, client);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,7 @@ public class BitbucketServerClientUnitTest {
public void before() {
BitbucketServerConfiguration
config = new BitbucketServerConfiguration("repo", "slug", "https://my-server.org", "token");
underTest = new BitbucketServerClient(config, mapper) {
@Override
OkHttpClient getClient() {
return client;
}
};
underTest = new BitbucketServerClient(config, mapper, client);
}

@Test
Expand Down

0 comments on commit 41e3ee3

Please sign in to comment.