Skip to content

Instantly share code, notes, and snippets.

@szpq0i
Created October 5, 2018 10:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save szpq0i/0029e0ccccf7af57555eb7400fb8c477 to your computer and use it in GitHub Desktop.
Save szpq0i/0029e0ccccf7af57555eb7400fb8c477 to your computer and use it in GitHub Desktop.
UpsourcePlugin
/*
* Copyright 2000-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.upsource.sonar;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.InstantiationStrategy;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.postjob.PostJob;
import org.sonar.api.batch.postjob.PostJobContext;
import org.sonar.api.batch.postjob.PostJobDescriptor;
import org.sonar.api.batch.postjob.issue.PostJobIssue;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.config.Settings;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicInteger;
import java.io.FileWriter;
import java.io.IOException;
@BatchSide
@InstantiationStrategy(InstantiationStrategy.PER_TASK)
public class PushIssuesJob implements PostJob {
private static final Logger LOGGER = Loggers.get(PushIssuesJob.class);
private final Settings settings;
public PushIssuesJob(Settings settings) {
this.settings = settings;
}
@Override
public void describe(PostJobDescriptor descriptor) {
descriptor.name("Push issues to Upsource");
}
@Override
public void execute(PostJobContext context) {
final String upsourceUrl = settings.getString(UpsourcePlugin.UPSOURCE_SERVER);
if (upsourceUrl == null || upsourceUrl.isEmpty()) {
LOGGER.error("Cannot post issues to Upsource: key " + UpsourcePlugin.UPSOURCE_SERVER + " is not defined");
return;
}
final String upsourceToken = settings.getString(UpsourcePlugin.UPSOURCE_TOKEN);
final String upsourceProject = settings.getString(UpsourcePlugin.UPSOURCE_PROJECT);
if (upsourceProject == null || upsourceProject.isEmpty()) {
LOGGER.error("Cannot post issues to Upsource: key " + UpsourcePlugin.UPSOURCE_PROJECT + " is not defined");
return;
}
final String upsourceRevision = settings.getString(UpsourcePlugin.UPSOURCE_REVISION);
if (upsourceRevision == null || upsourceRevision.isEmpty()) {
LOGGER.error("Cannot post issues to Upsource: key " + UpsourcePlugin.UPSOURCE_REVISION + " is not defined");
return;
}
final String upsourceProjectPath = settings.getString(UpsourcePlugin.UPSOURCE_PROJECT_PATH);
final String sonarProjectKey = settings.getString("sonar.projectKey");
final String sonarSources = settings.getString("sonar.sources");
// issues are not accessible when the mode "issues" is not enabled
// with the scanner property "sonar.analysis.mode=issues"
try {
final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
final Element root = (Element) document.appendChild(document.createElement("issues"));
root.setAttribute("version", "1");
// all open issues
final AtomicInteger issuesCount = new AtomicInteger(0);
final Iterable<PostJobIssue> issues;
try {
issues = context.issues();
} catch (Throwable ex) {
if (context.analysisMode().isIssues())
throw ex;
LOGGER.info("Cannot load issues: " + ex.getMessage());
return;
}
for (PostJobIssue issue : issues) {
if (issue.inputComponent() != null && issue.inputComponent().isFile()) {
final Integer issueLine = issue.line();
final String message = issue.message();
final Severity severity = issue.severity();
final String filePath = fileKeyToFilePath((InputFile) issue.inputComponent(), upsourceProjectPath);
if (filePath == null) {
LOGGER.warn("Cannot parse input file '" + issue.inputComponent() + "'");
continue;
}
final Element issueElement = (Element) root.appendChild(document.createElement("issue"));
appendChild(document, issueElement, "file", filePath);
appendChild(document, issueElement, "line", issueLine == null ? "0" : Integer.toString(issueLine));
appendChild(document, issueElement, "message", removeAbsolutePaths(message));
appendChild(document, issueElement, "severity", severity.name());
issuesCount.incrementAndGet();
}
}
if (issuesCount.get() == 0) {
LOGGER.warn("No issues found to post to Upsource");
return;
}
final String result = documentToString(document);
final String upsourceInspectionsPath = settings.getString(UpsourcePlugin.UPSOURCE_INSPECTIONS_PATH);
if (upsourceInspectionsPath != null) {
String fileName = String.format("%s/results-%s.xml", upsourceInspectionsPath, upsourceRevision);
try (FileWriter writer = new FileWriter(fileName)) {
writer.write(result);
LOGGER.info("Saving inspections to: " + fileName);
} catch (IOException e) {
LOGGER.error("An error occurred while saving inspection results", e);
}
}
final URI uri = URI.create(suffix(upsourceUrl, "/") + "~inspections/" + upsourceProject + "/" + upsourceRevision + "/sonar");
try {
LOGGER.info("Posting " + issuesCount.get() + " issues to " + uri.toString());
HttpURLConnection conn = (HttpURLConnection) uri.toURL().openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
if (upsourceToken != null && !upsourceToken.isEmpty()) {
conn.setRequestProperty("Authorization", "Basic " + upsourceToken);
}
conn.setRequestProperty("Content-Type", "application/xml; charset=UTF-8");
try {
conn.connect();
try (OutputStream os = conn.getOutputStream()) {
os.write(result.getBytes());
}
final int responseCode = conn.getResponseCode();
LOGGER.info("Posted " + issuesCount.get() + " issues to " + uri.toString() + ". Response code: " + responseCode);
} finally {
conn.disconnect();
}
} catch (Throwable e) {
throw new Error("Posting " + issuesCount.get() + " issues to " + uri.toString(), e);
}
} catch (Throwable e) {
LOGGER.error("Posting issues to Upsource", e);
}
}
private static String removeAbsolutePaths(String message) {
if (message == null)
return "";
String currentDirectory = Paths.get(System.getProperty("user.dir")).toAbsolutePath().toString();
if (message.contains(currentDirectory)) {
StringBuilder builder = new StringBuilder(message);
int index;
while ((index = builder.indexOf(currentDirectory)) >= 0)
builder.replace(index, index + currentDirectory.length(), "");
return builder.toString();
}
return message;
}
private static String suffix(String str, String suffix) {
return str.endsWith(suffix) ? str : (str + suffix);
}
private static String prefix(String prefix, String str) {
return str.startsWith(prefix) ? str : (prefix + str);
}
private static String toVcsPath(final String projectPath, final String filePath) {
if (projectPath == null || projectPath.isEmpty()) {
return prefix("/", filePath);
}
return prefix("/", suffix(projectPath, "/")) + prefix("/", filePath).substring(1);
}
private static String fileKeyToFilePath(final InputFile file, final String projectPath) {
Path currentDirectory = Paths.get(System.getProperty("user.dir"));
Path relativeFilePath = currentDirectory.relativize(file.path());
return toVcsPath(projectPath, relativeFilePath.toString().replace('\\', '/'));
}
private static void appendChild(final Document document, final Element root, final String name, final String value) {
final Node node = root.appendChild(document.createElement(name));
node.appendChild(document.createTextNode(value));
}
private static String documentToString(final Document document) {
final DOMImplementationLS domImplementation = (DOMImplementationLS) document.getImplementation();
final LSSerializer lsSerializer = domImplementation.createLSSerializer();
lsSerializer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);
lsSerializer.getDomConfig().setParameter("xml-declaration", Boolean.FALSE);
lsSerializer.setNewLine("\n");
return lsSerializer.writeToString(document);
}
}
/*
* Copyright 2000-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.upsource.sonar;
import org.sonar.api.*;
@Properties({
@Property(
key = UpsourcePlugin.UPSOURCE_SERVER,
defaultValue = "",
name = "Upsource server URL",
description = "",
global = true),
@Property(
key = UpsourcePlugin.UPSOURCE_TOKEN,
name = "Upsource project integration token",
description = "",
global = false,
type = PropertyType.PASSWORD),
@Property(
key = UpsourcePlugin.UPSOURCE_PROJECT,
name = "Upsource project ID",
description = "",
project = true,
global = false),
@Property(
key = UpsourcePlugin.UPSOURCE_PROJECT_PATH,
name = "Path to the project in version control (if not in the root)",
description = "",
project = true,
global = false),
@Property(
key = UpsourcePlugin.UPSOURCE_REVISION,
name = "VCS revision ID",
description = "",
project = true,
module = false,
global = false),
@Property(
key = UpsourcePlugin.UPSOURCE_INSPECTIONS_PATH,
name = "Path to directory with inspections file (intended to be used for debug purposes)",
description = "",
project = true,
global = false)})
public class UpsourcePlugin implements Plugin {
public static final String UPSOURCE_SERVER = "sonar.upsource.url";
public static final String UPSOURCE_PROJECT = "sonar.upsource.project";
public static final String UPSOURCE_PROJECT_PATH = "sonar.upsource.projectpath";
public static final String UPSOURCE_REVISION = "sonar.upsource.revision";
public static final String UPSOURCE_TOKEN = "sonar.upsource.token";
public static final String UPSOURCE_INSPECTIONS_PATH = "sonar.upsource.inspectionspath";
@Override
public void define(Context context) {
context.addExtension(PushIssuesJob.class);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment