All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.batch.report.ReportPublisher Maven / Gradle / Ivy

There is a newer version: 25.1.0.102122
Show newest version
/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact 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.batch.report;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.io.Files;
import com.squareup.okhttp.HttpUrl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.commons.io.FileUtils;
import org.picocontainer.Startable;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.config.Settings;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.TempFolder;
import org.sonar.api.utils.ZipUtils;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.batch.analysis.DefaultAnalysisMode;
import org.sonar.batch.bootstrap.BatchWsClient;
import org.sonar.batch.scan.ImmutableProjectReactor;
import org.sonar.scanner.protocol.output.ScannerReportWriter;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.WsCe;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsResponse;

import static org.apache.commons.lang.StringUtils.trimToEmpty;
import static org.sonar.core.util.FileUtils.deleteQuietly;

@BatchSide
public class ReportPublisher implements Startable {

  private static final Logger LOG = Loggers.get(ReportPublisher.class);

  public static final String KEEP_REPORT_PROP_KEY = "sonar.batch.keepReport";
  public static final String VERBOSE_KEY = "sonar.verbose";
  public static final String METADATA_DUMP_FILENAME = "report-task.txt";

  private final Settings settings;
  private final BatchWsClient wsClient;
  private final AnalysisContextReportPublisher contextPublisher;
  private final ImmutableProjectReactor projectReactor;
  private final DefaultAnalysisMode analysisMode;
  private final TempFolder temp;
  private final ReportPublisherStep[] publishers;

  private File reportDir;
  private ScannerReportWriter writer;

  public ReportPublisher(Settings settings, BatchWsClient wsClient, AnalysisContextReportPublisher contextPublisher,
    ImmutableProjectReactor projectReactor, DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) {
    this.settings = settings;
    this.wsClient = wsClient;
    this.contextPublisher = contextPublisher;
    this.projectReactor = projectReactor;
    this.analysisMode = analysisMode;
    this.temp = temp;
    this.publishers = publishers;
  }

  @Override
  public void start() {
    reportDir = new File(projectReactor.getRoot().getWorkDir(), "batch-report");
    writer = new ScannerReportWriter(reportDir);
    contextPublisher.init(writer);

    if (!analysisMode.isIssues() && !analysisMode.isMediumTest()) {
      String publicUrl = publicUrl();
      if (HttpUrl.parse(publicUrl) == null) {
        throw MessageException.of("Failed to parse public URL set in SonarQube server: " + publicUrl);
      }
    }
  }

  @Override
  public void stop() {
    if (!shouldKeepReport()) {
      deleteQuietly(reportDir);
    }
  }

  public File getReportDir() {
    return reportDir;
  }

  public ScannerReportWriter getWriter() {
    return writer;
  }

  public void execute() {
    // If this is a issues mode analysis then we should not upload reports
    String taskId = null;
    if (!analysisMode.isIssues()) {
      File report = generateReportFile();
      if (shouldKeepReport()) {
        LOG.info("Analysis report generated in " + reportDir);
      }
      if (!analysisMode.isMediumTest()) {
        taskId = upload(report);
      }
    }
    logSuccess(taskId);
  }

  private boolean shouldKeepReport() {
    return settings.getBoolean(KEEP_REPORT_PROP_KEY) || settings.getBoolean(VERBOSE_KEY);
  }

  private File generateReportFile() {
    try {
      long startTime = System.currentTimeMillis();
      for (ReportPublisherStep publisher : publishers) {
        publisher.publish(writer);
      }
      long stopTime = System.currentTimeMillis();
      LOG.info("Analysis report generated in {}ms, dir size={}", stopTime - startTime, FileUtils.byteCountToDisplaySize(FileUtils.sizeOfDirectory(reportDir)));

      startTime = System.currentTimeMillis();
      File reportZip = temp.newFile("batch-report", ".zip");
      ZipUtils.zipDir(reportDir, reportZip);
      stopTime = System.currentTimeMillis();
      LOG.info("Analysis reports compressed in {}ms, zip size={}", stopTime - startTime, FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(reportZip)));
      return reportZip;
    } catch (IOException e) {
      throw new IllegalStateException("Unable to prepare analysis report", e);
    }
  }

  /**
   * Uploads the report file to server and returns the generated task id
   */
  @VisibleForTesting
  String upload(File report) {
    LOG.debug("Upload report");
    long startTime = System.currentTimeMillis();
    ProjectDefinition projectDefinition = projectReactor.getRoot();
    PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, report);
    PostRequest post = new PostRequest("api/ce/submit")
      .setMediaType(MediaTypes.PROTOBUF)
      .setParam("projectKey", projectDefinition.getKey())
      .setParam("projectName", projectDefinition.getName())
      .setParam("projectBranch", projectDefinition.getBranch())
      .setPart("report", filePart);
    WsResponse response = wsClient.call(post).failIfNotSuccessful();
    try (InputStream protobuf = response.contentStream()) {
      return WsCe.SubmitResponse.parser().parseFrom(protobuf).getTaskId();
    } catch (Exception e) {
      throw Throwables.propagate(e);
    } finally {
      long stopTime = System.currentTimeMillis();
      LOG.info("Analysis report uploaded in " + (stopTime - startTime) + "ms");
    }
  }

  @VisibleForTesting
  void logSuccess(@Nullable String taskId) {
    if (taskId == null) {
      LOG.info("ANALYSIS SUCCESSFUL");
    } else {
      String publicUrl = publicUrl();
      HttpUrl httpUrl = HttpUrl.parse(publicUrl);

      Map metadata = new LinkedHashMap<>();
      String effectiveKey = projectReactor.getRoot().getKeyWithBranch();
      metadata.put("projectKey", effectiveKey);
      metadata.put("serverUrl", publicUrl);

      URL dashboardUrl = httpUrl.newBuilder()
        .addPathSegment("dashboard").addPathSegment("index").addPathSegment(effectiveKey)
        .build()
        .url();
      metadata.put("dashboardUrl", dashboardUrl.toExternalForm());

      URL taskUrl = HttpUrl.parse(publicUrl).newBuilder()
        .addPathSegment("api").addPathSegment("ce").addPathSegment("task")
        .addQueryParameter("id", taskId)
        .build()
        .url();
      metadata.put("ceTaskId", taskId);
      metadata.put("ceTaskUrl", taskUrl.toExternalForm());

      LOG.info("ANALYSIS SUCCESSFUL, you can browse {}", dashboardUrl);
      LOG.info("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report");
      LOG.info("More about the report processing at {}", taskUrl);

      dumpMetadata(metadata);
    }
  }

  private void dumpMetadata(Map metadata) {
    File file = new File(projectReactor.getRoot().getWorkDir(), METADATA_DUMP_FILENAME);
    try (Writer output = Files.newWriter(file, StandardCharsets.UTF_8)) {
      for (Map.Entry entry : metadata.entrySet()) {
        output.write(entry.getKey());
        output.write("=");
        output.write(entry.getValue());
        output.write("\n");
      }

      LOG.debug("Report metadata written to {}", file);
    } catch (IOException e) {
      throw new IllegalStateException("Unable to dump " + file, e);
    }
  }

  /**
   * The public URL is optionally configured on server. If not, then the regular URL is returned.
   * See https://jira.sonarsource.com/browse/SONAR-4239
   */
  private String publicUrl() {
    String baseUrl = trimToEmpty(settings.getString(CoreProperties.SERVER_BASE_URL));
    if (baseUrl.isEmpty()) {
      // If server base URL was not configured in Sonar server then is is better to take URL configured on batch side
      baseUrl = wsClient.baseUrl();
    }
    return baseUrl.replaceAll("(/)+$", "");
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy