]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12629 Scanner Quality Gate check
authorJacek <jacek.poreda@sonarsource.com>
Wed, 30 Oct 2019 13:54:20 +0000 (14:54 +0100)
committerSonarTech <sonartech@sonarsource.com>
Fri, 8 Nov 2019 19:21:12 +0000 (20:21 +0100)
sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/package-info.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ScanProperties.java

diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java
new file mode 100644 (file)
index 0000000..bae3d39
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.scanner.qualitygate;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.EnumSet;
+import java.util.Properties;
+import org.picocontainer.Startable;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.scanner.bootstrap.DefaultScannerWsClient;
+import org.sonar.scanner.scan.ScanProperties;
+import org.sonarqube.ws.Ce;
+import org.sonarqube.ws.Ce.TaskStatus;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.Qualitygates;
+import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Status;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.HttpException;
+import org.sonarqube.ws.client.WsResponse;
+
+public class QualityGateCheck implements Startable {
+
+  private static final Logger LOG = Loggers.get(QualityGateCheck.class);
+  private static final EnumSet<TaskStatus> TASK_TERMINAL_STATUSES = EnumSet.of(TaskStatus.SUCCESS, TaskStatus.FAILED, TaskStatus.CANCELED);
+  private static final int POLLING_INTERVAL_IN_MS = 5000;
+
+  private final DefaultScannerWsClient wsClient;
+  private final ScanProperties properties;
+
+  private long qualityGateTimeoutInMs;
+  private boolean enabled;
+
+  public QualityGateCheck(ScanProperties properties, DefaultScannerWsClient wsClient) {
+    this.wsClient = wsClient;
+    this.properties = properties;
+  }
+
+  @Override
+  public void start() {
+    this.enabled = properties.shouldWaitForQualityGate();
+    this.qualityGateTimeoutInMs = Duration.of(properties.qualityGateWaitTimeout(), ChronoUnit.SECONDS).toMillis();
+  }
+
+  @Override
+  public void stop() {
+    // nothing to do
+  }
+
+  public void await() {
+    if (!enabled) {
+      LOG.debug("Quality Gate Check disabled - skipping");
+      return;
+    }
+
+    String taskId = readTaskIdFromMetaDataFile();
+
+    Ce.Task task = waitForCeTaskToFinish(taskId);
+
+    if (!TaskStatus.SUCCESS.equals(task.getStatus())) {
+      throw MessageException.of(String.format("CE Task finished abnormally with status: %s", task.getStatus().name()));
+    }
+
+    Status qualityGateStatus = getQualityGateStatus(task.getAnalysisId());
+
+    if (Status.OK.equals(qualityGateStatus)) {
+      LOG.info("Quality Gate - OK");
+    } else if (Status.NONE.equals(qualityGateStatus)) {
+      LOG.info("No Quality Gate is associated with the analysis - skipping");
+    } else {
+      LOG.info("Quality Gate - FAILED");
+      throw MessageException.of("Quality Gate - FAILED");
+    }
+  }
+
+  private String readTaskIdFromMetaDataFile() {
+    Path file = properties.metadataFilePath();
+    try (Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
+      Properties metadata = new Properties();
+      metadata.load(reader);
+      return metadata.getProperty("ceTaskId");
+    } catch (IOException e) {
+      throw new IllegalStateException("Unable to read metadata file: " + file, e);
+    }
+  }
+
+  private Ce.Task waitForCeTaskToFinish(String taskId) {
+    GetRequest getTaskResultReq = new GetRequest("api/ce/task")
+      .setMediaType(MediaTypes.PROTOBUF)
+      .setParam("id", taskId);
+
+    long currentTime = 0;
+    while (qualityGateTimeoutInMs > currentTime) {
+      try {
+        WsResponse getTaskResultResponse = wsClient.call(getTaskResultReq).failIfNotSuccessful();
+        Ce.Task task = parseCeTaskResponse(getTaskResultResponse);
+        if (TASK_TERMINAL_STATUSES.contains(task.getStatus())) {
+          return task;
+        }
+        LOG.debug("Received CE task with status {} ", task.getStatus());
+        LOG.info("Waiting {} ms for task to finish...", POLLING_INTERVAL_IN_MS);
+
+        Thread.sleep(POLLING_INTERVAL_IN_MS);
+        currentTime += POLLING_INTERVAL_IN_MS;
+      } catch (HttpException e) {
+        throw MessageException.of(String.format("Failed to get CE Task status - %s", DefaultScannerWsClient.createErrorMessage(e)));
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        throw MessageException.of("Quality Gate Check has been interrupted", e);
+      }
+    }
+    throw MessageException.of("Quality Gate Check timeout exceeded");
+  }
+
+  private static Ce.Task parseCeTaskResponse(WsResponse response) {
+    try (InputStream protobuf = response.contentStream()) {
+      return Ce.TaskResponse.parser().parseFrom(protobuf).getTask();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private Status getQualityGateStatus(String analysisId) {
+    GetRequest getQualityGateReq = new GetRequest("api/qualitygates/project_status")
+      .setMediaType(MediaTypes.PROTOBUF)
+      .setParam("analysisId", analysisId);
+    try {
+      WsResponse getTaskResultResponse = wsClient.call(getQualityGateReq).failIfNotSuccessful();
+      Qualitygates.ProjectStatusResponse.ProjectStatus status = parseQualityGateResponse(getTaskResultResponse);
+      return status.getStatus();
+    } catch (HttpException e) {
+      throw MessageException.of(String.format("Failed to get Quality Gate status - %s", DefaultScannerWsClient.createErrorMessage(e)));
+    }
+  }
+
+  private static Qualitygates.ProjectStatusResponse.ProjectStatus parseQualityGateResponse(WsResponse response) {
+    try (InputStream protobuf = response.contentStream()) {
+      return Qualitygates.ProjectStatusResponse.parser().parseFrom(protobuf).getProjectStatus();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/package-info.java
new file mode 100644 (file)
index 0000000..7e31b3e
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.scanner.qualitygate;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 16f3fec582b60c5886d8b42aef69bc0e5f72223e..5ab47b36755d51425300ea25320cb65d5532cc16 100644 (file)
 package org.sonar.scanner.scan;
 
 import javax.annotation.Nullable;
-import org.sonar.api.SonarEdition;
-import org.sonar.api.SonarRuntime;
-import org.sonar.api.batch.rule.CheckFactory;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.FileMetadata;
 import org.sonar.api.batch.fs.internal.SensorStrategy;
+import org.sonar.api.batch.rule.CheckFactory;
 import org.sonar.api.batch.sensor.issue.internal.DefaultNoSonarFilter;
 import org.sonar.api.resources.Languages;
 import org.sonar.api.resources.ResourceTypes;
@@ -78,6 +76,7 @@ import org.sonar.scanner.notifications.DefaultAnalysisWarnings;
 import org.sonar.scanner.postjob.DefaultPostJobContext;
 import org.sonar.scanner.postjob.PostJobOptimizer;
 import org.sonar.scanner.postjob.PostJobsExecutor;
+import org.sonar.scanner.qualitygate.QualityGateCheck;
 import org.sonar.scanner.report.ActiveRulesPublisher;
 import org.sonar.scanner.report.AnalysisContextReportPublisher;
 import org.sonar.scanner.report.AnalysisWarningsPublisher;
@@ -250,6 +249,9 @@ public class ProjectScanContainer extends ComponentContainer {
       SourcePublisher.class,
       ChangedLinesPublisher.class,
 
+      //QualityGate check
+      QualityGateCheck.class,
+
       // Cpd
       CpdExecutor.class,
       CpdSettings.class,
@@ -353,6 +355,11 @@ public class ProjectScanContainer extends ComponentContainer {
     getComponentByType(CpdExecutor.class).execute();
     getComponentByType(ReportPublisher.class).execute();
 
+    if (properties.shouldWaitForQualityGate()) {
+      LOG.info("------------- Quality Gate wait enabled");
+      getComponentByType(QualityGateCheck.class).await();
+    }
+
     getComponentByType(PostJobsExecutor.class).execute();
 
     if (analysisMode.isMediumTest()) {
index ada80c3054eca50e1cabcac142596ca2c7590b8c..5fff32694df8c1ac1a583daa3c0589cfbb1ca7f5 100644 (file)
@@ -22,9 +22,9 @@ package org.sonar.scanner.scan;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Optional;
+import org.sonar.api.batch.fs.internal.DefaultInputProject;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.MessageException;
-import org.sonar.api.batch.fs.internal.DefaultInputProject;
 
 import static org.sonar.core.config.ScannerProperties.BRANCH_NAME;
 import static org.sonar.core.config.ScannerProperties.ORGANIZATION;
@@ -41,6 +41,8 @@ public class ScanProperties {
   public static final String PRELOAD_FILE_METADATA_KEY = "sonar.preloadFileMetadata";
   public static final String FORCE_RELOAD_KEY = "sonar.scm.forceReloadAll";
   public static final String SCM_REVISION = "sonar.scm.revision";
+  public static final String QUALITY_GATE_WAIT = "sonar.qualitygate.wait";
+  public static final String QUALITY_GATE_TIMEOUT_IN_SEC = "sonar.qualitygate.timeout";
 
   private final Configuration configuration;
   private final DefaultInputProject project;
@@ -83,6 +85,14 @@ public class ScanProperties {
     }
   }
 
+  public boolean shouldWaitForQualityGate() {
+    return configuration.getBoolean(QUALITY_GATE_WAIT).orElse(false);
+  }
+
+  public int qualityGateWaitTimeout() {
+    return configuration.getInt(QUALITY_GATE_TIMEOUT_IN_SEC).orElse(300);
+  }
+
   /**
    * This should be called in the beginning of the analysis to fail fast
    */