Skip to content

Commit

Permalink
Support for BitBucket CodeInsights format
Browse files Browse the repository at this point in the history
Adding bitbucket codeinsight export format
  • Loading branch information
martinschaef authored Jul 8, 2022
1 parent 8855a9b commit 7519bae
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 6 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/cicd-demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ jobs:
- name: Download CodeGuru Reviewer CLI
shell: bash
env:
VERSION: 0.1.0
run: curl -OL "https://github.com/martinschaef/aws-codeguru-cli/releases/download/$VERSION/aws-codeguru-cli.zip"
VERSION: 0.2.1
run: curl -OL "https://github.com/aws/aws-codeguru-cli/releases/download/$VERSION/aws-codeguru-cli.zip"
- run: unzip aws-codeguru-cli.zip

# Run CodeGuru Reviewer on the current project and use the --fail-on-recommendations option to fail
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/self-test-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
jq -r '.runs[0].results[] | {ruleId: .ruleId, firstFile: .locations[0].physicalLocation.artifactLocation.uri, firstLine: .locations[0].physicalLocation.region.startLine} | join(",")' \
code-guru/recommendations.sarif.json | sort >> summary-of-results.csv
cat summary-of-results.csv
- name: Validate Findings ${{ matrix.os }}
if: steps.iam-role.outcome == 'success'
shell: bash
Expand Down Expand Up @@ -136,7 +136,7 @@ jobs:
jq -r '.runs[0].results[] | {ruleId: .ruleId, firstFile: .locations[0].physicalLocation.artifactLocation.uri, firstLine: .locations[0].physicalLocation.region.startLine} | join(",")' \
code-guru/recommendations.sarif.json | sort >> summary-of-results.csv
cat summary-of-results.csv
- name: Validate Findings ${{ matrix.os }}
if: steps.iam-role.outcome == 'success'
shell: bash
Expand Down
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ repositories {

defaultTasks 'clean', 'check', 'installDist'

version = '0.1.0'
version = '0.2.1'
jar.archiveName = "${jar.baseName}.${jar.extension}"
distZip.archiveName = "${jar.baseName}.zip"

Expand Down Expand Up @@ -59,6 +59,8 @@ dependencies {

implementation 'com.google.code.findbugs:jsr305:3.0.2'

implementation("com.google.guava:guava:31.1-jre")

compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'

Expand Down
1 change: 1 addition & 0 deletions lombok.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lombok.anyConstructor.addConstructorProperties=true
14 changes: 14 additions & 0 deletions src/main/java/com/amazonaws/gurureviewercli/Main.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.amazonaws.gurureviewercli;


import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
Expand All @@ -16,6 +17,7 @@
import lombok.val;
import org.beryx.textio.TextIO;
import org.beryx.textio.system.SystemTextTerminal;
import org.eclipse.jgit.util.FileUtils;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
Expand All @@ -36,6 +38,7 @@
import com.amazonaws.gurureviewercli.model.GitMetaData;
import com.amazonaws.gurureviewercli.model.ScanMetaData;
import com.amazonaws.gurureviewercli.model.configfile.CustomConfiguration;
import com.amazonaws.gurureviewercli.util.CodeInsightExport;
import com.amazonaws.gurureviewercli.util.Log;
import com.amazonaws.gurureviewercli.util.RecommendationPrinter;
import com.amazonaws.gurureviewercli.util.RecommendationsFilter;
Expand Down Expand Up @@ -68,6 +71,10 @@ public class Main {
required = false)
private boolean failOnRecommendations;

@Parameter(names = {"--bitbucket-code-insights"},
description = "Output directory for Bitbucket insights report and annotation files.",
required = false)
private String bitbucketCodeInsightsDirectory;
@Parameter(names = {"--root-dir", "-r"},
description = "The root directory of the project that should be analyzed.",
required = true)
Expand Down Expand Up @@ -165,6 +172,13 @@ public static void main(String[] argv) {
}
ResultsAdapter.saveResults(outputPath, results, scanMetaData);
Log.info("Analysis finished.");

if (main.bitbucketCodeInsightsDirectory != null) {
val bitBucketDir = new File(main.bitbucketCodeInsightsDirectory).getCanonicalFile();
FileUtils.mkdirs(bitBucketDir, true);
CodeInsightExport.report(results, scanMetaData, bitBucketDir.toPath());
}

if (main.failOnRecommendations && !results.isEmpty()) {
RecommendationPrinter.print(results);
Log.error("Exiting with code 5 because %d recommendations were found and --fail-on-recommendations"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class Recommendation {
private String severity;

@Data
static final class RuleMetadata {
public static final class RuleMetadata {
private String ruleId;
private String ruleName;
private String shortDescription;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.amazonaws.gurureviewercli.model.bitbucket;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;

/**
* Bitbucket CodeInsight annotation.
* See https://developer.atlassian.com/cloud/bitbucket/rest/api-group-reports/#api-group-reports
*
* Example
* {
* "external_id": "CodeGuruReviewer-02-annotation002",
* "title": "Bug report",
* "annotation_type": "BUG",
* "summary": "This line might introduce a bug.",
* "severity": "MEDIUM",
* "path": "my-service/src/main/java/com/myCompany/mysystem/logic/Helper.java",
* "line": 13
* }
*/
@Log4j2
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CodeInsightsAnnotation {

private String title;

@JsonProperty("external_id")
private String externalId;

@JsonProperty("annotation_type")
private String annotationType;

private String path;

private long line;

private String summary;

private String severity;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.amazonaws.gurureviewercli.model.bitbucket;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;

/**
* Bitbucket CodeInsight report format.
* See https://developer.atlassian.com/cloud/bitbucket/rest/api-group-reports/#api-group-reports
* Example:
* {
* "title": "Amazon CodeGuru Reviewer Scan Report",
* "details": "Some more text.",
* "report_type": "SECURITY",
* "reporter": "Amazon CodeGuru Reviewer",
* "link": "http://www.CodeGuruReviewer.com/reports/001",
* "result": "FAILED",
* "data": [
* {
* "title": "Duration (seconds)",
* "type": "DURATION",
* "value": 14
* },
* {
* "title": "Safe to merge?",
* "type": "BOOLEAN",
* "value": false
* }
* ]
* }
*/
@Log4j2
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CodeInsightsReport {

private String title;

private String details;

private String result;

private String link;

private List<CodeInsightsReportData> data;

@JsonProperty("reporter")
private String reporter;

@JsonProperty("report_type")
private final String reportType = "SECURITY";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.amazonaws.gurureviewercli.model.bitbucket;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;

/**
* Bitbucket CodeInsight report data.
* See https://developer.atlassian.com/cloud/bitbucket/rest/api-group-reports/#api-group-reports
*/
@Log4j2
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CodeInsightsReportData {

private String title;

private String type;

private Object value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.amazonaws.gurureviewercli.util;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.val;
import software.amazon.awssdk.services.codegurureviewer.model.RecommendationSummary;
import software.amazon.awssdk.services.codegurureviewer.model.Severity;

import com.amazonaws.gurureviewercli.model.ScanMetaData;
import com.amazonaws.gurureviewercli.model.bitbucket.CodeInsightsAnnotation;
import com.amazonaws.gurureviewercli.model.bitbucket.CodeInsightsReport;

/**
* Export Report and Annotations file for BitBucket CodeInsights.
*/
public final class CodeInsightExport {
private static final String REPORT_FILE_NAME = "report.json";
private static final String ANNOTATIONS_FILE_NAME = "annotations.json";

private static final JsonMapper JSON_MAPPER =
JsonMapper.builder()
.serializationInclusion(JsonInclude.Include.NON_ABSENT)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
.build();

public static void report(final Collection<RecommendationSummary> recommendations,
final ScanMetaData scanMetaData,
final Path outputDir) throws IOException {
val reportTitle = "CodeGuru Reviewer report";
val url = String.format("https://console.aws.amazon.com/codeguru/reviewer?region=%s#/codereviews/details/%s",
scanMetaData.getRegion(), scanMetaData.getCodeReviewArn());
val report = CodeInsightsReport.builder()
.title(reportTitle)
.reporter("CodeGuru Reviewer CLI")
.details(String.format("CodeGuru Reviewer reported %d recommendations",
recommendations.size()))
.result(recommendations.isEmpty() ? "PASSED" : "FAILED")
.link(url)
.data(new ArrayList<>())
.build();

val annotations = recommendations.stream().map(r -> convert(r, reportTitle))
.collect(Collectors.toList());

JSON_MAPPER.writeValue(outputDir.resolve(REPORT_FILE_NAME).toFile(), report);
JSON_MAPPER.writeValue(outputDir.resolve(ANNOTATIONS_FILE_NAME).toFile(), annotations);
}

private static CodeInsightsAnnotation convert(final RecommendationSummary recommendation,
final String reportTitle) {
String description = recommendation.recommendationCategoryAsString();
if (recommendation.ruleMetadata() != null) {
description = recommendation.ruleMetadata().shortDescription();
}

return CodeInsightsAnnotation.builder()
.title(reportTitle)
.externalId(recommendation.recommendationId())
.path(recommendation.filePath())
.line(recommendation.startLine())
.summary(description)
.annotationType("Vulnerability".toUpperCase())
.severity(convertSeverity(recommendation.severity()))
.build();
}

private static String convertSeverity(Severity guruSeverity) {
if (guruSeverity != null) {
return guruSeverity.toString().toUpperCase(); // Bitbucket uses the same severity levels as CodeGuru.
}
return "Unknown";
}

}
7 changes: 7 additions & 0 deletions src/main/java/com/amazonaws/gurureviewercli/util/Log.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.amazonaws.gurureviewercli.util;

import java.io.PrintWriter;
import java.io.StringWriter;

import org.beryx.textio.TextTerminal;
import org.beryx.textio.system.SystemTextTerminal;

Expand Down Expand Up @@ -50,6 +53,10 @@ public static void awsUrl(final String format, final Object... args) {

public static void error(final Throwable t) {
terminal.println(TEXT_RED + t.getMessage() + TEXT_RESET);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
terminal.println(sw.toString());
}

private Log() {
Expand Down
Loading

0 comments on commit 7519bae

Please sign in to comment.