diff options
author | Jacek <jacek.poreda@sonarsource.com> | 2019-10-30 14:54:20 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-11-08 20:21:12 +0100 |
commit | 535bc2b8da7e3f5f19c9bea4ab0a3722da10ffa2 (patch) | |
tree | 026ee9b3ff5d855c8715a90a18ffdc84e634ef6a | |
parent | 615f9bf88a4a1e0fed0818fb8ff01848865ae99a (diff) | |
download | sonarqube-535bc2b8da7e3f5f19c9bea4ab0a3722da10ffa2.tar.gz sonarqube-535bc2b8da7e3f5f19c9bea4ab0a3722da10ffa2.zip |
SONAR-12629 Scanner Quality Gate check
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 */ |