Skip to content

Commit

Permalink
#348: Add support for Sonarqube 8.9.0
Browse files Browse the repository at this point in the history
Sonarqube 8.9 packages all the core code and dependencies as a single fat JAR, which means plugins can no longer add code to the classpath by dropping JARs into the `lib/common` directory as this directory does not exist. To allow the plugin's classes to be visible to the Compute Engine and Web class-loaders, a Java Agent has been introduced that intentionally leaks the plugin classes to the Sonarqube application, thereby allowing the Core Extension Loaders to continue to see and initialise the relevant extension classes, thereby allowing them to load the plugin's implementation classes into Sonarqube's dependency management system. The Docker files have been updated to remove the copying of the plugin to the legacy directory, and to pass additional environment variable so the agent options are added to the relevant Sonarqube component's command-line arguments.

Similarly, the fall-through logic in `PlatformEditionProvider` that had allowed the plugin to force the Compute Engine to believe it was running in Sonarqube developer edition has been removed, with the edition now being hard-coded to `COMMUNITY`, without any way of over-riding it. To overcome this, the agent that's being used to expose the plugin classes to Sonarqube's class-loaders is also altering the `PlatformEditionProvider` class definition during class-loading to force it to return `DEVELOPER` when running inside Compute Engine, so that the branch enforcement checks in Compute Engine pass.

As the `GetBinding` action has been moved into Sonarqube Community Edition's core from Developer edition, it no longer needs to be provided by this plugin, and has been removed to prevent is causing conflicts on application startup.
  • Loading branch information
mc1arke committed May 11, 2021
1 parent 22d4722 commit a6b6db3
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 553 deletions.
4 changes: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ SONARQUBE_VERSION=latest
# The name of the Dockerfile to run. 'Dockerfile' is building locally, 'release.Dockerfile' if building the release image
DOCKERFILE=Dockerfile

# The version of the plugin to include in the image; only relevant if 'release.Dockerfile' is being used
PLUGIN_VERSION=1.7.0
# The version of the plugin to include in the image
PLUGIN_VERSION=1.8.0-SNAPSHOT
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ WORKDIR /home/build/project
RUN ./gradlew build -x test

FROM sonarqube:${SONARQUBE_VERSION}
COPY --from=builder --chown=sonarqube:sonarqube /home/build/project/build/libs/sonarqube-community-branch-plugin-*.jar /opt/sonarqube/lib/common/
COPY --from=builder --chown=sonarqube:sonarqube /home/build/project/build/libs/sonarqube-community-branch-plugin-*.jar /opt/sonarqube/extensions/plugins/

ARG PLUGIN_VERSION
ENV PLUGIN_VERSION=${PLUGIN_VERSION}
ENV SONAR_WEB_JAVAADDITIONALOPTS="-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-${PLUGIN_VERSION}.jar=web"
ENV SONAR_CE_JAVAADDITIONALOPTS="-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-${PLUGIN_VERSION}.jar=ce"
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,21 @@ SonarQube Version | Plugin Version
The plugin is intended to support the [features and parameters specified in the SonarQube documentation](https://docs.sonarqube.org/latest/branches/overview/).

# Installation
Either build the project or [download a compatible release version of the plugin JAR](https://github.com/mc1arke/sonarqube-community-branch-plugin/releases). Copy the plugin JAR file to the `extensions/plugins/` **and** the `lib/common/` directories of your SonarQube instance and restart SonarQube.

## Manual Install
__Please ensure you follow the installation instructions for the version of the plugin you're installing by looking at the README on the relevant release tag.__

Either build the project or [download a compatible release version of the plugin JAR](https://github.com/mc1arke/sonarqube-community-branch-plugin/releases).

1. Copy the plugin JAR file to the `extensions/plugins/` directory of your SonarQube instance
2. Add `-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-${version}.jar=web` to the `sonar.web.javaAdditionalOptions` property in your Sonarqube installation's `config/sonar.properties` file, e.g. `sonar.web.javaAdditionalOpts=-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-1.8.0.jar=web`
3. Add `-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-${version}.jar=ce` to the `sonar.ce.javaAdditionalOptions` property in your Sonarqube installation's `config/sonar.properties` file, e.g. `sonar.ce.javaAdditionalOpts=-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-1.8.0.jar=ce`
4. Start Sonarqube, and accept the warning about using third-party plugins

## Docker
The plugin is distributed in the [mc1arke/sonarqube-with-community-branch-plugin](https://hub.docker.com/r/mc1arke/sonarqube-with-community-branch-plugin) Docker image, with the image versions matching the up-stream Sonarqube image version.

__Note:__ If you're setting the `SONAR_WEB_JAVAADDITIONALOPTS` or `SONAR_CE_JAVAADDITIONALOPTS` environment variables in your container launch then you'll need to add the `javaagent` configuration to your overrides to match what's in the provided Dockerfile.

# Configuration
## Global configuration
Expand Down
9 changes: 6 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ repositories {
}
}

def sonarqubeVersion = '8.7.0.41497'
def sonarqubeVersion = '8.9.0.43852'
def sonarqubeLibDir = "${projectDir}/sonarqube-lib"
def sonarLibraries = "${sonarqubeLibDir}/sonarqube-${sonarqubeVersion}/lib"

Expand All @@ -64,6 +64,7 @@ dependencies {
compile files('lib/nodes-0.5.0.jar')
runtime 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7'
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
compile 'org.javassist:javassist:3.27.0-GA'
}


Expand Down Expand Up @@ -92,7 +93,9 @@ jar {
'Plugin-IssueTrackerUrl': 'https://github.com/mc1arke/sonarqube-community-branch-plugin/issues',
'Plugin-Key': 'communityBranchPlugin',
'Plugin-Class': 'com.github.mc1arke.sonarqube.plugin.CommunityBranchPluginBootstrap',
'Plugin-Name': 'Community Branch Plugin'
'Plugin-Name': 'Community Branch Plugin',
'Premain-Class': 'com.github.mc1arke.sonarqube.plugin.CommunityBranchAgent',
'Can-Retransform-Classes': 'true'
}
}

Expand Down Expand Up @@ -132,4 +135,4 @@ jacocoTestReport {

plugins.withType(JacocoPlugin) {
tasks["test"].finalizedBy 'jacocoTestReport'
}
}
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ services:
networks:
- sonarnet
environment:
- SONARQUBE_JDBC_URL=jdbc:postgresql://db:5432/sonar
- SONARQUBE_JDBC_USERNAME=sonar
- SONARQUBE_JDBC_PASSWORD=sonar
- SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonar
- SONAR_JDBC_USERNAME=sonar
- SONAR_JDBC_PASSWORD=sonar
volumes:
- sonarqube_conf:/opt/sonarqube/conf
- sonarqube_data:/opt/sonarqube/data
Expand Down
4 changes: 3 additions & 1 deletion release.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ FROM sonarqube:${SONARQUBE_VERSION}
ARG PLUGIN_VERSION
ENV PLUGIN_VERSION=${PLUGIN_VERSION}

ADD --chown=sonarqube:sonarqube https://github.com/mc1arke/sonarqube-community-branch-plugin/releases/download/${PLUGIN_VERSION}/sonarqube-community-branch-plugin-${PLUGIN_VERSION}.jar /opt/sonarqube/lib/common/
ADD --chown=sonarqube:sonarqube https://github.com/mc1arke/sonarqube-community-branch-plugin/releases/download/${PLUGIN_VERSION}/sonarqube-community-branch-plugin-${PLUGIN_VERSION}.jar /opt/sonarqube/extensions/plugins/

ENV SONAR_WEB_JAVAADDITIONALOPTS="-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-${PLUGIN_VERSION}.jar=web"
ENV SONAR_CE_JAVAADDITIONALOPTS="-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-${PLUGIN_VERSION}.jar=ce"
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (C) 2021 Michael Clarke
*
* 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 com.github.mc1arke.sonarqube.plugin;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;

public final class CommunityBranchAgent {

private static final Logger LOGGER = Loggers.get(CommunityBranchAgent.class);

private CommunityBranchAgent() {
super();
}

public static void premain(String args, Instrumentation instrumentation) throws UnmodifiableClassException, ClassNotFoundException {
LOGGER.info("Loading agent");

if (!"ce".equals(args) && !"web".equals(args)) {
throw new IllegalArgumentException("Invalid/missing agent argument");
}

if ("ce".equals(args)) {
redefineEdition(instrumentation);
}
}

private static void redefineEdition(Instrumentation instrumentation) throws ClassNotFoundException, UnmodifiableClassException {
String targetClassName = "org.sonar.core.platform.PlatformEditionProvider";

instrumentation.addTransformer(new ClassFileTransformer() {

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] byteCode) {

String finalTargetClassName = targetClassName.replace(".", "/");

if (!className.equals(finalTargetClassName)) {
return byteCode;
}

LOGGER.debug("Transforming class " + targetClassName);
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(targetClassName);
CtMethod m = cc.getDeclaredMethod("get");
m.setBody("return java.util.Optional.of(org.sonar.core.platform.EditionProvider.Edition.DEVELOPER);");

byteCode = cc.toBytecode();
cc.detach();
} catch (NotFoundException | CannotCompileException | IOException e) {
LOGGER.error("Could not transform class, will use default class definition", e);
}

return byteCode;
}

});

instrumentation.retransformClasses(Class.forName(targetClassName));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/
package com.github.mc1arke.sonarqube.plugin;

import com.github.mc1arke.sonarqube.plugin.ce.CommunityBranchEditionProvider;
import com.github.mc1arke.sonarqube.plugin.ce.CommunityReportAnalysisComponentProvider;
import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchConfigurationLoader;
import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchParamsValidator;
Expand All @@ -27,14 +26,13 @@
import com.github.mc1arke.sonarqube.plugin.scanner.ScannerPullRequestPropertySensor;
import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchFeatureExtension;
import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchSupportDelegate;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.CountBindingAction;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.CreateBitbucketCloudAction;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.DeleteBindingAction;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetAzureBindingAction;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetBitbucketBindingAction;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetBitbucketCloudBindingAction;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetGithubBindingAction;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetGitlabBindingAction;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.CreateBitbucketCloudAction;
import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.UpdateBitbucketCloudAction;
import org.sonar.api.CoreProperties;
import org.sonar.api.Plugin;
Expand All @@ -60,11 +58,9 @@ public String getName() {
@Override
public void load(CoreExtension.Context context) {
if (SonarQubeSide.COMPUTE_ENGINE == context.getRuntime().getSonarQubeSide()) {
context.addExtensions(CommunityReportAnalysisComponentProvider.class, CommunityBranchEditionProvider.class);
context.addExtensions(CommunityReportAnalysisComponentProvider.class);
} else if (SonarQubeSide.SERVER == context.getRuntime().getSonarQubeSide()) {
context.addExtensions(CommunityBranchFeatureExtension.class, CommunityBranchSupportDelegate.class,

CountBindingAction.class,
DeleteBindingAction.class,
SetGithubBindingAction.class,
SetAzureBindingAction.class,
Expand Down

This file was deleted.

This file was deleted.

Loading

0 comments on commit a6b6db3

Please sign in to comment.