]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8352 add the scanner properties sonar.analysis.* to payload
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 9 Nov 2016 14:15:32 +0000 (15:15 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 9 Nov 2016 20:37:18 +0000 (21:37 +0100)
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayload.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/failed.json
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/success.json
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadTest/with_analysis_properties.json [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/config/WebhookProperties.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/PostProjectAnalysisTaskTester.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java

index 2429b1236d824285e614a4dfa8f65958aec15c54..ffbcbb132704f9a11c5a65939f76264de0e8ceaf 100644 (file)
@@ -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());
index f01d81d401d75dff1bc290cb4359a5e7c5229044..9ef6879c46f3e233a76ef4d1a648c6102bc134e6 100644 (file)
@@ -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<String, String> 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<String, String> 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();
       }
     };
   }
index ac8224ce0f6a4f677fe3b932b682195288e9ba5c..9e308497d34b74183f79a35e55f1d8d499685c9c 100644 (file)
@@ -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/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 (file)
index 0000000..20ab0ec
--- /dev/null
@@ -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"
+  }
+}
index 6c2fb50476138ef31cd8f191f2a7da80685777f7..740c0487d85b73df4b7e54bc8aa9a5996b3b1ecd 100644 (file)
@@ -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. " +
index 998ca07919a441ab80b5272c155d03e96c35cb3b..41b46e8c90515c853f9dcbc983477450429e15da 100644 (file)
@@ -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.
+   * <br/>
+   * 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
    */
index 6c00a4d3bb1eecc1584a12e1123c0dbafbf78824..911e1ef6df2236c81c201880574a3d4b26fbbcf5 100644 (file)
@@ -561,13 +561,13 @@ public class PostProjectAnalysisTaskTester {
       // prevents instantiation outside PostProjectAnalysisTaskTester
     }
 
+    public ScannerContextBuilder addProperties(Map<String, String> map) {
+      properties.putAll(map);
+      return this;
+    }
+
     public ScannerContext build() {
-      return new ScannerContext() {
-        @Override
-        public Map<String, String> getProperties() {
-          return properties;
-        }
-      };
+      return () -> properties;
     }
   }
 }
index 71788f66d94a855897ca325c7ac28819e187520f..60428aaa7fcfaecb182c64fb25c5239ee933c522 100644 (file)
 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<ScannerReport.ContextProperty> it = from(cache.getAll().entrySet()).transform(new MapEntryToContextPropertyFunction());
-    writer.writeContextProperties(it);
+    MapEntryToContextPropertyFunction transformer = new MapEntryToContextPropertyFunction();
+
+    // properties defined programmatically by plugins
+    Iterable<ScannerReport.ContextProperty> fromCache = from(cache.getAll().entrySet())
+      .transform(transformer);
+
+    // properties that are automatically included to report so that
+    // they can be included to webhook payloads
+    Iterable<ScannerReport.ContextProperty> 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<Map.Entry<String, String>, ScannerReport.ContextProperty> {
     private final ScannerReport.ContextProperty.Builder builder = ScannerReport.ContextProperty.newBuilder();
 
     @Override
-    public ScannerReport.ContextProperty apply(Map.Entry<String, String> input) {
+    public ScannerReport.ContextProperty apply(@Nonnull  Map.Entry<String, String> input) {
       return builder.clear().setKey(input.getKey()).setValue(input.getValue()).build();
     }
   }
index 337f1b7a9244886474368c27e5635a8052474d0a..4743939d4bf6dc6fc3afc104eaea6a4b4a2c69ee 100644 (file)
  */
 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<Iterable<ScannerReport.ContextProperty>>() {
-      @Override
-      protected boolean matchesSafely(Iterable<ScannerReport.ContextProperty> props) {
-        Map<String, ScannerReport.ContextProperty> 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<ScannerReport.ContextProperty> 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<ScannerReport.ContextProperty> expected = Arrays.asList(
+      newContextProperty("sonar.analysis.revision", "ab45b3"),
+      newContextProperty("sonar.analysis.build.number", "B123"));
+    expectWritten(expected);
+  }
+
+  private void expectWritten(List<ScannerReport.ContextProperty> expected) {
     verify(writer).writeContextProperties(argThat(new TypeSafeMatcher<Iterable<ScannerReport.ContextProperty>>() {
       @Override
       protected boolean matchesSafely(Iterable<ScannerReport.ContextProperty> props) {
-        return Iterables.isEmpty(props);
+        List<ScannerReport.ContextProperty> copy = Lists.newArrayList(props);
+        copy.removeAll(expected);
+        return copy.isEmpty();
       }
 
       @Override
@@ -84,11 +96,10 @@ public class ContextPropertiesPublisherTest {
     }));
   }
 
-  private enum ContextPropertyToKey implements Function<ScannerReport.ContextProperty, String> {
-    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();
   }
 }