From bcd0f171c81a84d9c21ca00816194f35a3279fac Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Wed, 9 Nov 2016 15:15:32 +0100 Subject: [PATCH] SONAR-8352 add the scanner properties sonar.analysis.* to payload --- .../webhook/WebhookPayload.java | 13 ++++ .../webhook/WebhookPayloadTest.java | 63 ++++++++++++---- .../webhook/WebhookPostTaskTest.java | 2 + .../webhook/WebhookPayloadTest/failed.json | 2 + .../webhook/WebhookPayloadTest/success.json | 2 + .../with_analysis_properties.json | 19 +++++ .../sonar/core/config/WebhookProperties.java | 6 ++ .../sonar/api/batch/sensor/SensorContext.java | 4 ++ .../PostProjectAnalysisTaskTester.java | 12 ++-- .../report/ContextPropertiesPublisher.java | 26 +++++-- .../ContextPropertiesPublisherTest.java | 71 +++++++++++-------- 11 files changed, 165 insertions(+), 55 deletions(-) create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/with_analysis_properties.json diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayload.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayload.java index 2429b1236d8..ffbcbb13270 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayload.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayload.java @@ -29,9 +29,11 @@ import org.sonar.api.ce.posttask.CeTask; import org.sonar.api.ce.posttask.PostProjectAnalysisTask; import org.sonar.api.ce.posttask.Project; import org.sonar.api.ce.posttask.QualityGate; +import org.sonar.api.ce.posttask.ScannerContext; import org.sonar.api.utils.text.JsonWriter; import static java.util.Objects.requireNonNull; +import static org.sonar.core.config.WebhookProperties.ANALYSIS_PROPERTY_PREFIX; @Immutable public class WebhookPayload { @@ -63,10 +65,21 @@ public class WebhookPayload { } writeProject(analysis, writer, analysis.getProject()); writeQualityGate(writer, analysis.getQualityGate()); + writeAnalysisProperties(writer, analysis.getScannerContext()); writer.endObject().close(); return new WebhookPayload(analysis.getProject().getKey(), string.toString()); } + private static void writeAnalysisProperties(JsonWriter writer, ScannerContext scannerContext) { + writer.name("properties"); + writer.beginObject(); + scannerContext.getProperties().entrySet() + .stream() + .filter(prop -> prop.getKey().startsWith(ANALYSIS_PROPERTY_PREFIX)) + .forEach(prop -> writer.prop(prop.getKey(), prop.getValue())); + writer.endObject(); + } + private static void writeTask(JsonWriter writer, CeTask ceTask) { writer.prop("taskId", ceTask.getId()); writer.prop("status", ceTask.getStatus().toString()); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest.java index f01d81d401d..9ef6879c46f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest.java @@ -19,7 +19,9 @@ */ package org.sonar.server.computation.task.projectanalysis.webhook; +import com.google.common.collect.ImmutableMap; import java.util.Date; +import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; import org.junit.Test; @@ -29,6 +31,7 @@ import org.sonar.api.ce.posttask.Project; import org.sonar.api.ce.posttask.QualityGate; import org.sonar.api.ce.posttask.ScannerContext; +import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newCeTaskBuilder; import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newConditionBuilder; @@ -59,24 +62,13 @@ public class WebhookPayloadTest { .setErrorThreshold("70.0") .build(QualityGate.EvaluationStatus.WARN, "74.0")) .build(); - PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate); + PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate, emptyMap()); WebhookPayload payload = WebhookPayload.from(analysis); assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); assertJson(payload.toJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/success.json")); } - @Test - public void create_payload_for_failed_analysis() { - CeTask ceTask = newCeTaskBuilder().setStatus(CeTask.Status.FAILED).setId("#1").build(); - PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(ceTask, null); - - WebhookPayload payload = WebhookPayload.from(analysis); - - assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); - assertJson(payload.toJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/failed.json")); - } - @Test public void create_payload_with_gate_conditions_without_value() { CeTask task = newCeTaskBuilder() @@ -94,14 +86,53 @@ public class WebhookPayloadTest { .setErrorThreshold("70.0") .buildNoValue()) .build(); - PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate); + PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate, emptyMap()); WebhookPayload payload = WebhookPayload.from(analysis); assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); assertJson(payload.toJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/gate_condition_without_value.json")); } - private static PostProjectAnalysisTask.ProjectAnalysis newAnalysis(CeTask task, @Nullable QualityGate gate) { + @Test + public void create_payload_with_analysis_properties() { + CeTask task = newCeTaskBuilder() + .setStatus(CeTask.Status.SUCCESS) + .setId("#1") + .build(); + QualityGate gate = newQualityGateBuilder() + .setId("G1") + .setName("Gate One") + .setStatus(QualityGate.Status.WARN) + .build(); + Map scannerProperties = ImmutableMap.of( + "sonar.analysis.revision", "ab45d24", + "sonar.analysis.buildNumber", "B123", + "not.prefixed.with.sonar.analysis", "should be ignored", + "foo", "should be ignored too" + ); + PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate, scannerProperties); + + WebhookPayload payload = WebhookPayload.from(analysis); + assertJson(payload.toJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/with_analysis_properties.json")); + assertThat(payload.toJson()) + .doesNotContain("not.prefixed.with.sonar.analysis") + .doesNotContain("foo") + .doesNotContain("should be ignored"); + } + + @Test + public void create_payload_for_failed_analysis() { + CeTask ceTask = newCeTaskBuilder().setStatus(CeTask.Status.FAILED).setId("#1").build(); + PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(ceTask, null, emptyMap()); + + WebhookPayload payload = WebhookPayload.from(analysis); + + assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); + assertJson(payload.toJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/failed.json")); + } + + private static PostProjectAnalysisTask.ProjectAnalysis newAnalysis(CeTask task, @Nullable QualityGate gate, + Map scannerProperties) { return new PostProjectAnalysisTask.ProjectAnalysis() { @Override public CeTask getCeTask() { @@ -134,7 +165,9 @@ public class WebhookPayloadTest { @Override public ScannerContext getScannerContext() { - return newScannerContextBuilder().build(); + return newScannerContextBuilder() + .addProperties(scannerProperties) + .build(); } }; } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java index ac8224ce0f6..9e308497d34 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java @@ -35,6 +35,7 @@ import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolde import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newCeTaskBuilder; import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newProjectBuilder; +import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newScannerContextBuilder; import static org.sonar.server.computation.task.projectanalysis.component.ReportComponent.DUMB_PROJECT; public class WebhookPostTaskTest { @@ -102,6 +103,7 @@ public class WebhookPostTaskTest { .setKey("P1") .setName("Project One") .build()) + .withScannerContext(newScannerContextBuilder().build()) .execute(); } } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/failed.json b/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/failed.json index 358c1f055ae..1d67df6251a 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/failed.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/failed.json @@ -4,5 +4,7 @@ "project": { "key": "P1", "name": "Project One" + }, + "properties": { } } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/success.json b/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/success.json index fdb7f8eb112..c2a4b01dd7b 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/success.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/success.json @@ -20,5 +20,7 @@ "warningThreshold": "75.0" } ] + }, + "properties": { } } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/with_analysis_properties.json b/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/with_analysis_properties.json new file mode 100644 index 00000000000..20ab0ec3769 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/with_analysis_properties.json @@ -0,0 +1,19 @@ +{ + "taskId": "#1", + "status": "SUCCESS", + "analysedAt": "2017-07-14T04:40:00+0200", + "project": { + "key": "P1", + "name": "Project One" + }, + "qualityGate": { + "name": "Gate One", + "status": "WARN", + "conditions": [ + ] + }, + "properties": { + "sonar.analysis.revision": "ab45d24", + "sonar.analysis.buildNumber": "B123" + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/config/WebhookProperties.java b/sonar-core/src/main/java/org/sonar/core/config/WebhookProperties.java index 6c2fb504761..740c0487d85 100644 --- a/sonar-core/src/main/java/org/sonar/core/config/WebhookProperties.java +++ b/sonar-core/src/main/java/org/sonar/core/config/WebhookProperties.java @@ -32,6 +32,12 @@ public class WebhookProperties { public static final String PROJECT_KEY = "sonar.webhooks.project"; public static final String NAME_FIELD = "name"; public static final String URL_FIELD = "url"; + + /** + * Prefix of the properties to be automatically exported from scanner to payload + */ + public static final String ANALYSIS_PROPERTY_PREFIX = "sonar.analysis."; + private static final String CATEGORY = "webhooks"; private static final String DESCRIPTION = "Webhooks are used to notify external services when a project analysis is done. " + "A HTTP POST request including a JSON payload is sent to each of the provided URLs. " + diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java index 998ca07919a..41b46e8c905 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java @@ -147,7 +147,11 @@ public interface SensorContext { /** * Add a property to the scanner context. This context is available * in Compute Engine when processing the report. + *
+ * The properties starting with {@code "sonar.analysis."} are included to the + * payload of webhooks. * + * @throws IllegalArgumentException if key or value parameter is null * @see org.sonar.api.ce.posttask.PostProjectAnalysisTask.ProjectAnalysis#getScannerContext() * @since 6.1 */ diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/PostProjectAnalysisTaskTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/PostProjectAnalysisTaskTester.java index 6c00a4d3bb1..911e1ef6df2 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/PostProjectAnalysisTaskTester.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/PostProjectAnalysisTaskTester.java @@ -561,13 +561,13 @@ public class PostProjectAnalysisTaskTester { // prevents instantiation outside PostProjectAnalysisTaskTester } + public ScannerContextBuilder addProperties(Map map) { + properties.putAll(map); + return this; + } + public ScannerContext build() { - return new ScannerContext() { - @Override - public Map getProperties() { - return properties; - } - }; + return () -> properties; } } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java index 71788f66d94..60428aaa7fc 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java @@ -20,31 +20,49 @@ package org.sonar.scanner.report; import com.google.common.base.Function; +import com.google.common.collect.Iterables; import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.api.config.Settings; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReportWriter; import org.sonar.scanner.repository.ContextPropertiesCache; import static com.google.common.collect.FluentIterable.from; +import static org.sonar.core.config.WebhookProperties.ANALYSIS_PROPERTY_PREFIX; public class ContextPropertiesPublisher implements ReportPublisherStep { + private final ContextPropertiesCache cache; + private final Settings settings; - public ContextPropertiesPublisher(ContextPropertiesCache cache) { + public ContextPropertiesPublisher(ContextPropertiesCache cache, Settings settings) { this.cache = cache; + this.settings = settings; } @Override public void publish(ScannerReportWriter writer) { - Iterable it = from(cache.getAll().entrySet()).transform(new MapEntryToContextPropertyFunction()); - writer.writeContextProperties(it); + MapEntryToContextPropertyFunction transformer = new MapEntryToContextPropertyFunction(); + + // properties defined programmatically by plugins + Iterable fromCache = from(cache.getAll().entrySet()) + .transform(transformer); + + // properties that are automatically included to report so that + // they can be included to webhook payloads + Iterable fromSettings = from(settings.getProperties().entrySet()) + .filter(e -> e.getKey().startsWith(ANALYSIS_PROPERTY_PREFIX)) + .transform(transformer); + + writer.writeContextProperties(Iterables.concat(fromCache, fromSettings)); } private static final class MapEntryToContextPropertyFunction implements Function, ScannerReport.ContextProperty> { private final ScannerReport.ContextProperty.Builder builder = ScannerReport.ContextProperty.newBuilder(); @Override - public ScannerReport.ContextProperty apply(Map.Entry input) { + public ScannerReport.ContextProperty apply(@Nonnull Map.Entry input) { return builder.clear().setKey(input.getKey()).setValue(input.getValue()).build(); } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java index 337f1b7a924..4743939d4bf 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java @@ -19,20 +19,21 @@ */ package org.sonar.scanner.report; -import com.google.common.base.Function; -import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; -import java.util.Map; -import javax.annotation.Nonnull; +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.List; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.scanner.repository.ContextPropertiesCache; +import org.sonar.api.config.MapSettings; +import org.sonar.api.config.Settings; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.repository.ContextPropertiesCache; +import static java.util.Collections.emptyList; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -41,41 +42,52 @@ public class ContextPropertiesPublisherTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - ContextPropertiesCache cache = new ContextPropertiesCache(); - ContextPropertiesPublisher underTest = new ContextPropertiesPublisher(cache); + private ScannerReportWriter writer = mock(ScannerReportWriter.class); + private ContextPropertiesCache cache = new ContextPropertiesCache(); + private Settings settings = new MapSettings(); + private ContextPropertiesPublisher underTest = new ContextPropertiesPublisher(cache, settings); @Test public void publish_writes_properties_to_report() { cache.put("foo1", "bar1"); cache.put("foo2", "bar2"); - ScannerReportWriter writer = mock(ScannerReportWriter.class); underTest.publish(writer); - verify(writer).writeContextProperties(argThat(new TypeSafeMatcher>() { - @Override - protected boolean matchesSafely(Iterable props) { - Map map = Maps.uniqueIndex(props, ContextPropertyToKey.INSTANCE); - return map.size() == 2 && - map.get("foo1").getValue().equals("bar1") && - map.get("foo2").getValue().equals("bar2"); - } - - @Override - public void describeTo(Description description) { - } - })); + List expected = Arrays.asList( + newContextProperty("foo1", "bar1"), + newContextProperty("foo2", "bar2")); + expectWritten(expected); } @Test public void publish_writes_no_properties_to_report() { - ScannerReportWriter writer = mock(ScannerReportWriter.class); underTest.publish(writer); + expectWritten(emptyList()); + } + + @Test + public void publish_settings_prefixed_with_sonar_analysis_for_webhooks() { + settings.setProperty("foo", "should not be exported"); + settings.setProperty("sonar.analysis.revision", "ab45b3"); + settings.setProperty("sonar.analysis.build.number", "B123"); + + underTest.publish(writer); + + List expected = Arrays.asList( + newContextProperty("sonar.analysis.revision", "ab45b3"), + newContextProperty("sonar.analysis.build.number", "B123")); + expectWritten(expected); + } + + private void expectWritten(List expected) { verify(writer).writeContextProperties(argThat(new TypeSafeMatcher>() { @Override protected boolean matchesSafely(Iterable props) { - return Iterables.isEmpty(props); + List copy = Lists.newArrayList(props); + copy.removeAll(expected); + return copy.isEmpty(); } @Override @@ -84,11 +96,10 @@ public class ContextPropertiesPublisherTest { })); } - private enum ContextPropertyToKey implements Function { - INSTANCE; - @Override - public String apply(@Nonnull ScannerReport.ContextProperty input) { - return input.getKey(); - } + private static ScannerReport.ContextProperty newContextProperty(String key, String value) { + return ScannerReport.ContextProperty.newBuilder() + .setKey(key) + .setValue(value) + .build(); } } -- 2.39.5