aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacek <jacek.poreda@sonarsource.com>2019-10-30 14:54:20 +0100
committerSonarTech <sonartech@sonarsource.com>2019-11-08 20:21:12 +0100
commit535bc2b8da7e3f5f19c9bea4ab0a3722da10ffa2 (patch)
tree026ee9b3ff5d855c8715a90a18ffdc84e634ef6a
parent615f9bf88a4a1e0fed0818fb8ff01848865ae99a (diff)
downloadsonarqube-535bc2b8da7e3f5f19c9bea4ab0a3722da10ffa2.tar.gz
sonarqube-535bc2b8da7e3f5f19c9bea4ab0a3722da10ffa2.zip
SONAR-12629 Scanner Quality Gate check
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java168
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/package-info.java23
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java13
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ScanProperties.java12
4 files changed, 212 insertions, 4 deletions
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
index 00000000000..bae3d39b634
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java
@@ -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
index 00000000000..7e31b3e2e1f
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/package-info.java
@@ -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;
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
index 16f3fec582b..5ab47b36755 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
@@ -20,12 +20,10 @@
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()) {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ScanProperties.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ScanProperties.java
index ada80c3054e..5fff32694df 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ScanProperties.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ScanProperties.java
@@ -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
*/