Documentation - How to include 3rd Party Plugins in a Docker-Setup

Must-share information (formatted with Markdown):

  • which versions are you using
    SonarQube CE 8.9
  • what are you trying to achieve
    Install a 3rd Party Plugin in a Docker Setup
  • what have you tried so far to achieve this
    Searched the documentation for clues

====

Dear all,
after having created a docker setup as documented here: Install the Server | SonarQube Docs i now would like to endulge myself in the possibly hazardous procedure of manually installing a 3rd Party Plugin.

The documentation here: Install a Plugin | SonarQube Docs does describe the steps needed for a “normal” plugin installation (i guess)

  • Am i assuming correctly that i could handle the step of copying the plugin jars inside a custom Dockerfile?
  • I already created such a Dockerfile to inject our custom certs that were needed to talk to the LDAPS.
  • So i would add more COPY commands to that Dockerfile to copy the jars into the target directories, right?

(please excuse my ping to @Tobias_Trabelsi here, i hope i am assuming correctly that he might be quite knowledgeable concerning these container things? :innocent: )

cheers
Daniel

//edit:
P.S.: Maybe it might be helpful to extend the documentation by mentioning how to handle the (manual) installation of 3rd party plugins in a docker based setup

Hi,

Please don’t ping people who aren’t involved in your thread. :slight_smile:

Have you checked the install docs?

I believe this is going to be about setting up your extensions volume & putting the plugin jars there.

 
Ann

Hi Ann, sorry i know.

But i think these things might be of use for Tobias, too. Give and take.

I already mentioned the install docs you qoted in my OP, so obviously i was not able to make the distinction about my question clear enough.

Handling the volumes is not easy (and not part of Install a Plugin | SonarQube Docs) … to get a glimpse of the steps you have to research please take a look at

(most of the answers needed to solve my question are seemingly taken care of there … i quote it here not only because of that, but also to enable you to realize how much of a SEP-Field you make this if you do not integrate helpful hints into your Install a Plugin | SonarQube Docs)

To have the (hopefully working) simplest answer qoted:

Here's a one-liner that copies myfile.txt from current directory to my_volume:
docker run --rm -v $PWD:/source -v my_volume:/dest -w /source alpine cp myfile.txt /dest
  • One cannot easily write to a volume, one has to create a container to manipulate the volume
  • And if i understand the documentation here correctly, contents of a directory that a volume mounts to get copied into that volume … so in the end, maybe my way of prepopulating the plugins in my own Dockerfile might be a pragmatic solution.

I am now even more under the assumption that “the sonar (preferred ) way” of interacting with a volume to manually install a plugin in a docker setup is worth being documented in Install a Plugin | SonarQube Docs to help the next person thinking about this.

cheers
Daniel

Hi @daniel ,

there are a few things here, so let me try to explain.

To install 3rd party plugins, you should in theory be able to use the marketplace. with the named volume mounted to the plugin install directory these installations should persist in case of a change of an image etc.

If you want to do it programatically you can define a additional container that downloads the jars of the plugins you want to have in the named volume (up to you).

if you want to use a local filesystem mount from your docker host, this is also possible since 8.5 where we embed the language analyzer in SQ directly. So since then you can use something like this:

version: "3"

services:
  sonarqube:
    image: sonarqube:8.9-community
    depends_on:
      - db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - /path/to/your/plugins:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    ports:
      - "9000:9000"
  db:
    image: postgres:12
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_logs:
  postgresql:
  postgresql_data:

i am writing compose here, but of cause you can use the respected docker run command as well. The only thing that you need to take care about, would be that the SQ user inside the container has the correct permissions to the docker host path.

hope that helps :slight_smile:

1 Like

Hi Tobias, thank you for taking the time to create your answer!

If i may, i would like to go over your reply, so that you might be able to correct any mistakes in my perception :innocent:

The docker-compose.yml you quoted is the Docker Compose .yml file example from Install the Server | SonarQube Docs with the following one line changed, right?

  • - /path/to/your/plugins:/opt/sonarqube/extensions instead of
  • - sonarqube_extensions:/opt/sonarqube/extensions

which makes the extensions bind-mounted instead of volume mounted. If i understand correctly, then:

  • Using the bind-mount instead of the volume would embed the directories content into the container and the volume would stay empty (probably? :thinking: )

  • to get the plugin(s) into the volume i [c|w]ould

    • run the docker-compose with volumes and install plugins via marketplace (works for CE, but not for DE/EE/DCE because there plugins now have to be installed manually, if i understand correctly) so that they get stored in the mounted volume

    • create a dummy-container and in that process could inject them into the volume

    • create my own Dockerfile/image based on “FROM sonarqube:$tag” where i COPY all plugin-jars to the right directories (because on first mount, any contents of the directory get copied into the volume)

Anything obviously wrong in that summary? :nerd_face:

cheers
Daniel

P.S.: I still would like to suggest that an addition to Install a Plugin | SonarQube Docs would be beneficial to everyone trying to install plugins while using docker. (And even more when not on CE, because then you have to install them manually now, if i understand it correctly)

Hi @daniel ,

yep this is correct :+1:

yep but this is not bad in this case. this used to be a problem before 8.5 but now when the filesystem permissions are correct, SQ will create the required folders.

no the installation via the marketplace is working with CE/DE and EE. only on DCE this is not possible and needs to be done manually. how do you come to this conclusion?

This is also why we don’t document this directly. there are only edge cases where you need to install plugins manually. the majority of the use cases are just click install on the web UI and restart sonarqube (also from the web ui).

Hi @Tobias_Trabelsi and thank you for your detailed answer!

Actually that is my understanding of https://jira.sonarsource.com/browse/MMF-2301 … to quote from there (the whole MMF is interesting, i am just highlighting to focus on my current assumption):

If i combine the consequences of this, i assume something has to be performed manually/automatically to make the plugins available to a volume that SQ in the container uses. (if i bind-mount, the process is more clear. i understand that.)

Only in the CE i can just fire up a compose with volumes only and install via Marketplace that then persists inside the volume, if understood correctly. I would be glad if i missed something there.

Hi @daniel ,

i stand corrected. sorry i was working on something else and did not notice the change. i checked and your understanding is correct.
with this in mind i now better understand where your are coming from and that there is a lack of documentation here. thank you for pointing that out.

can you manage now to install a plugin manually or do you need additional help with the procedure?

1 Like

Hi @Tobias_Trabelsi , thank you for clarification.

I am currently in the process of manufacturing my own internal Dockerfile

I will then find out how well that will work together with the “default” docker-compose.yml + volume-mounts.

TYVM for asking! Will come back here to talk about my findings (and possible followup-questions) after the experiment :nerd_face: :+1:

Daniel,

Were you successful in creating a Dockerfile that will support adding plugins? I’d be very interested, if you can/care to share.

Thanks!

Hi @davewolfusa,

thank you for reminding me of this open wound :slight_smile: … No, actually it now is a rather patched up wound.

Yes, i indeed have found a way to set up my docker compose + a Dockerfile with integrated plugin. Getting that was actually rather easy, setting it up with a fitting docker-compose was more complicated but i managed to get something ready i can work with.

For the Dockerfile i assume following conditions fulfilled in the directory where the Dockerfile resides:

  • Custom ssh certificate keystore in a file named “cacerts.customcertstore”
  • Subfolder named “plugins” which contains the plugin to be installed

Additionally the environment variables for the JDK_KEYSTORE_PASSWORD* entries need to be set/supplied (or you replace those inside the Dockerfile with real passwords, not advisable but more easy to test with)

File: Dockerfile

FROM sonarqube:8.9.0-community

COPY /cacerts.customcertstore /tmp/cacerts

ARG JDK_KEYSTORE_PASSWORD_SRC
ARG JDK_KEYSTORE_PASSWORD_DEST

RUN keytool -importkeystore -srckeystore /tmp/cacerts -destkeystore ${JAVA_HOME}/lib/security/cacerts -srcstorepass $JDK_KEYSTORE_PASSWORD_SRC -deststorepass $JDK_KEYSTORE_PASSWORD_DEST

COPY /plugins/sonar-softvis3d-plugin-1.2.1.jar /opt/sonarqube/extensions/plugins/

RUN chmod -R o+x /opt/sonarqube/extensions/plugins/
RUN chown -R sonarqube:sonarqube /opt/sonarqube/extensions/plugins/

RUN   echo "hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4" > /etc/nsswitch.conf

RUN   echo "sonar.telemetry.enable=false" >> /opt/sonarqube/conf/sonar.properties
RUN   echo "http.nonProxyHosts=firsthostname|192.168.1.11" >> /opt/sonarqube/conf/sonar.properties

cheers
Daniel

P.S.: This setup is definitely not optimized concerning best practices. It is a quick and dirty creation which fulfilled my needs at the time. You can and should adapt it to create something more efficient.

P. P.S.: Tobias @Tobias_Trabelsi , i would be glad if you might be able to add any insight into what might be (dis-)advisable concerning this example. It is just a quick shot, which worked for me. Would definitely be nice to gather some suggestions, if any available. (besides obvious best practices like e.g. “dont put each RUN in its own line” :innocent: )

1 Like

oh, there you go: full idea for the setup goes as follows:

  • create a directory that contains the following docker-compose.yml + the .env file.
  • by redirecting to the .env file you can run multiple stages on the same machine as the data will be managed in the respective volume-names that are managed by docker on its own

file: docker-compose.yml

version: "3"

volumes:
  sonarqube_data:
    name: ${VOLUME_SONARQUBE_DATA}
  sonarqube_extensions:
    name: ${VOLUME_SONARQUBE_EXTENSIONS}
  sonarqube_logs:
    name: ${VOLUME_SONARQUBE_LOGS}
  postgresql:
    name: ${VOLUME_POSTGRESQL}
  postgresql_data:
    name: ${VOLUME_POSTGRESQL_DATA}

services:
  sonarqube:
    image: internaldockerregistry.domain.dns:port/sonarqube-custom:latest
    depends_on:
      - db
    restart: unless-stopped
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
      SONAR_SECURITY_REALM: LDAP
      LDAP_URL: "ldaps://ldapserver.domain.dns:636"
      LDAP_BINDDN: ${ENV_LDAP_BINDDN}
      LDAP_BINDPASSWORD: ${ENV_LDAP_BINDPASSWORD}
      LDAP_STARTTLS: "false"
      LDAP_FOLLOWREFERRALS: "false"
      LDAP_USER_BASEDN: "OU=###,OU=###,DC=domain,DC=dns"
      LDAP_USER_REQUEST: "(&(objectClass=user)(sAMAccountName={login}))"
      LDAP_USER_REALNAMEATTRIBUTE: "displayName"
      LDAP_USER_EMAILATTRIBUTE: "mail"
      LDAP_GROUP_BASEDN: "OU=###,OU=###,DC=domain,DC=dns"
      LDAP_GROUP_REQUEST: "(&(objectClass=group)(member={dn}))"
      SONAR_UPDATECENTER_ACTIVATE: "false"

    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    ports:
      - "9000:9000"
    networks:
      - default
      - reverse_proxy
  db:
    image: postgres:12
    restart: unless-stopped
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

networks:
  reverse_proxy:
    external: true

file: .env

ENV_LDAP_BINDDN="CN=ad_sonarqube_account,OU=###,OU=###,OU=###,DC=domain,DC=dns"
ENV_LDAP_BINDPASSWORD="$3cR37"
VOLUME_SONARQUBE_DATA=sonarqube_data_8.9.0
VOLUME_SONARQUBE_EXTENSIONS=sonarqube_extensions_8.9.0
VOLUME_SONARQUBE_LOGS=sonarqube_logs_8.9.0
VOLUME_POSTGRESQL=postgresql_8.9.0
VOLUME_POSTGRESQL_DATA=postgresql_data_8.9.0

for a second “installation” on same docker-host

  • create second folder
  • copy docker-compose.yml + .env file there
  • change VOLUME_labels in .env so that they reference different volumes
  • change port in docker-compose.yml to something different than 9000
    like for example file: .env
ENV_LDAP_BINDDN="CN=ad_sonarqube_account,OU=###,OU=###,OU=###,DC=domain,DC=dns"
ENV_LDAP_BINDPASSWORD="$3cR37"
VOLUME_SONARQUBE_DATA=sonarqube_data_8.9.0_dev
VOLUME_SONARQUBE_EXTENSIONS=sonarqube_extensions_8.9.0_dev
VOLUME_SONARQUBE_LOGS=sonarqube_logs_8.9.0_dev
VOLUME_POSTGRESQL=postgresql_8.9.0_dev
VOLUME_POSTGRESQL_DATA=postgresql_data_8.9.0_dev

cheers + HTH
Daniel

1 Like

Thank you so much, Daniel, I really appreciate the effort that you’ve put into this project. I hope that SonarQube will update their documentation to cover this since they removed the ability to update the DE and other paid editions.

Hi @daniel ,

i think this is a bit overengineered :sweat_smile:

the most simple solution that i could think of would be to utilize the filesystem of the docker host like this:

mkdir plugins
cd plugins
wget https://github.com/stefanrinderle/softvis3d/releases/download/softvis3d-1.2.1/sonar-softvis3d-plugin-1.2.1.jar
cd ..
chown -R 1000:1000 plugins

and then use the following docker-compose.yaml file to mount the local folder with all your plugins into the container.

version: "3"

services:
  sonarqube:
    image: sonarqube:9.1-developer
    depends_on:
      - db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
    volumes:
      - ./plugins:/opt/sonarqube/extensions/plugins
      - sonarqube_logs:/opt/sonarqube/logs
  db:
    image: postgres:12
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_logs:
  postgresql:
  postgresql_data:

sonarqube will start up and you need to ack the warning about 3rd party plugins and be good to go.

If you don’t want to utilize the file system of the docker host, you can write a init container in your compose as well. This would be an example that uses the init capability that was added to compose with 3.7 (don’t quote me on that… somewhere in the late 3.x spec):

version: "3"

services:

  init:
    image: curlimages/curl:7.79.1
    init: true
    command: ["curl", "-L", "-O", "--output-dir", "/opt/sonarqube/extensions/plugins", "https://github.com/stefanrinderle/softvis3d/releases/download/softvis3d-1.2.1/sonar-softvis3d-plugin-1.2.1.jar"]
    volumes:
      - sonarqube_plugins:/opt/sonarqube/extensions/plugins
  sonarqube:
    image: sonarqube:9.1-developer
    depends_on:
      - db
      - init
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
    volumes:
      - sonarqube_plugins:/opt/sonarqube/extensions/plugins
      - sonarqube_logs:/opt/sonarqube/logs
  db:
    image: postgres:12
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_plugins:
  sonarqube_logs:
  postgresql:
  postgresql_data:

hope that helps to simplify things a little :slight_smile:

Thank you @Tobias_Trabelsi for your insights! I think i definitely need to brush up my knowledge about those init containers (i think i wrote something similar already some time ago, didn’t i :thinking: ahh, nvm)

Concerning the “engineering” part: My decision was based on either editing the compose file or editing the env-file when i want to upgrade while retaining the possibility to down-grade if smth fails.

For example: I just realized today that there are already two minor LTS updates for 8.9. i can copy my compose setup, clone the volumes to new names, edit the .env to point to the new volumes and try out the upgrade.

I could, ofc, do this all in-place in the existing compose setup … but … i perceive that as a bit more messy.

cheers
Daniel

P.S.: As always, if my reasoning doesn’t sound sound, sound a controverse voice, please :nerd_face: :+1:

This is a decision that you made on your personal use case.
maybe as a fyi: when you want to test a upgrade or downgrade from a failed migration you only need a database backup. all other data (not including the 3rd party plugins) can be recovered from this.

anyway this is a little out of context for the original question of which i think is now covered enough :slight_smile:

Hi @Tobias_Trabelsi thank you for your input.

Please excuse the following if i am mistaken! (But:)
My interpretation right now is that this thread has been marked “available for closure” by you without me marking an answer as a Solution.

If that is the case: please unmark it, this is not something i would want to happen with this thread.

It is your (sonarsources) forum, which implies that you (sonarsource) obviously have every right to moderate as you want. You already found a way to “mass-administrate” many issues into discontinuability by forcing closure after someone marked an answer as a reply and seven days have passed. (i myself am not a friend of this forced mute-ing)

Please do not start to do this on purpose without asking for the thread openers agreement. And to repeat: if that is none of your doing, please do take it kindly (take it kindly too, if it is your doing :nerd_face: ) … but please, if you can, see to undo it.

Hi,

A solution was ‘accepted’ by… someone. That started the timer for post closure.

Your OP was about including 3rd-party plugins in an 8.9 SonarQube instance on Docker. There are multiple options for that in this thread:

  • the setup referenced in the docs
  • your own solution
  • Tobias’ counterproposal

In short, the question/request in the OP seems to be fully addressed.

In light of that, would you mind explaining why the thread should remain open?

 
Thx,
Ann

Hi @ganncamp, this is baiting to create the grounds for setting an example why this thread is in need of closure :wink:

I started a different thread already because of this. Might be a bit diluting because there i include a slightly different POV, but most probably our conversation should continue here

To give a short answer here (and repeat my asking for un-setting the solution, which definitely is not the reply i would select): I like to keep my threads open because the forced closing kills community interaction. And sometimes the better answers take longer than this artificial time-frame allows for.