--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
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;
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;
SourcePublisher.class,
ChangedLinesPublisher.class,
+ //QualityGate check
+ QualityGateCheck.class,
+
// Cpd
CpdExecutor.class,
CpdSettings.class,
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()) {