Scan C# code with the sonarscanner docker image (without using msbuild and begin/end calls)?

server version: 8.3.1.34397
scanner version: 4.4.0.2170

I’m using a docker image to build my .NET project, run unit tests and run end-to-end tests. I’m using a multi stage docker image with different targets, so I’m executing the image once for build, once for unit tests and so on.

I got this working with sonarscanner, but I’m not satisfied with the result. Now a big part of my docker file is required just to make sonarqube work - install java, install the sonar scanner, call begin, call end, …


FROM mcr.microsoft.com/dotnet/core/aspnet:3.1.2-buster-slim AS base
WORKDIR /app
EXPOSE 80


FROM mcr.microsoft.com/dotnet/core/sdk:3.1.200-buster AS build

# Versionsnummer - wird für den Sonarqube Bericht benötigt
ARG version=
# Token für Sonarqube Anmeldung
ARG sonar_login=

ENV no_proxy=tfs
ENV http_proxy=http://192.168.100.101:8080
ENV https_proxy=http://192.168.100.101:8080


# Java installieren (für Sonarqube)
RUN apt-get update && \
    apt-get install -y openjdk-11-jre-headless && \
    apt-get clean;

RUN java --version

# Reportgenerator für Code Coverage Reports installieren
RUN dotnet tool install dotnet-reportgenerator-globaltool --version 4.5.2 --tool-path /tools

# Sonarqube
RUN dotnet tool install dotnet-sonarscanner --tool-path /tools

WORKDIR /src
RUN /tools/dotnet-sonarscanner begin \
    /k:re-spike-kubernetes-beispiel-api \
    /v:$version \
    /d:sonar.login=$sonar_login \
    /d:sonar.host.url=https://sonarqube.my-company.de \
    /d:sonar.verbose=true \
    /d:sonar.cs.vstest.reportsPaths=/testresults/test_results.xml \
    /d:sonar.cs.opencover.reportsPaths=/testresults/coverage/coverage.opencover.xml

COPY Beispiel.Api/Beispiel.Api.csproj Beispiel.Api/
COPY Beispiel.Api.Tests/Beispiel.Api.Tests.csproj Beispiel.Api.Tests/
COPY Beispiel.Api.EndToEndTests/Beispiel.Api.EndToEndTests.csproj Beispiel.Api.EndToEndTests/
COPY Beispiel.Api.sln .
RUN dotnet restore

COPY Beispiel.Api Beispiel.Api/

RUN dotnet build -c Release -o /app/build


FROM build AS test

WORKDIR /src
COPY run-tests.sh .
COPY Beispiel.Api.Tests Beispiel.Api.Tests/

RUN ls -laR .sonarqube

RUN ["chmod", "+x", "/src/run-tests.sh"]
ENTRYPOINT ["/src/run-tests.sh"]


FROM build as e2e
WORKDIR /src
COPY Beispiel.Api.EndToEndTests Beispiel.Api.EndToEndTests/
CMD dotnet test -c Release -o /app/build --results-directory /testresults --logger "trx;LogFileName=e2e_results.xml"


FROM build as sonar
ARG sonar_login=
ENV sonar_login=$sonar_login

CMD /tools/dotnet-sonarscanner end /d:sonar.login=$sonar_login

FROM build AS publish
RUN dotnet publish -c Release -o /app/publish


FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

ENTRYPOINT ["dotnet", "Beispiel.Api.dll"]

This feels like a lot of steps required just to invoke static source analysis. I need to be able to explain this Dockerfile to lots of C# devs who never used docker before, and sonarqube makes this a lot harder than it should be.

So I also tried moving sonarqube out of my Dockerfile. I wanted to invoke sonarqube simply by running the Sonarscanner Docker image:

docker run --rm  -e SONAR_HOST_URL="https://sonarqube.my-company.de/" -v %CD%:/src -v %CD:/usr/src sonarsource/sonar-scanner-cli  -Dproject.settings=/src/sonar-project.properties

this is my sonar-project.properties:

sonar.host.url=https://sonarqube.my-company.de
sonar.projectKey=re-spike-kubernetes-beispiel-api2
sonar.login=...my login key...
sonar.sources=.
sonar.exclusions=testresults/**
sonar.coverage.exclusions=testresults/**
sonar.cs.vstest.reportsPaths=testresults/test_results.xml
sonar.cs.opencover.reportsPaths=testresults/coverage/coverage.opencover.xml
sonar.verbose=true
sonar.projectBaseDir=/src
sonar.scanAllFiles=true

(I’ll later also pass in a volume with the test results files, I don’t have that in this example yet)

This approach would be much simpler and not require me to make any modifications to my Dockerfile or hook up stuff with my build process. It would allow me to have a clean separation between building my code and analyzing my code.

However, while the scanner runs and it looks like it would be analyzing my code, it does not detect any bugs/smells/issues. All metrics for the project are “0”. I can see the source code in Sonarqube, but it does not have any syntax highlighting.

(I can provide a log file for the scan if that helps, I did not want to paste it here because of the log size)

Is there any way to get the “single call to docker run” approach working? I don’t want to make my Dockerfile way more complex than necessary just to get it to work with Sonarqube.

What is so special about analyzing csharp code that it requires this complex begin / end + msbuild integration setup?

Regards,
Mathias

Hi @oocx and welcome to our community. While I cannot answer to the question about docker, I can answer about the .NET analysis.

Our .NET analyzer is a Roslyn analyzer, so it acts like a plugin during the compilation (i.e. build). Normally you can install Roslyn analyzers as Nugets (or from within the IDE for specific projects) or reference them directly in the PROJ files, however that’s not an out-of-the-box experience - you need to modify your project configuration manually.

Moreover, the analysis needs to take into account the SonarQube configuration - custom Quality Profiles or project-level settings configured in SQ. And this needs to be done before the build (and analysis) begins.

To fix these two problems (inject the analyzers before the build and take into account SonarQube configuration), the BEGIN step is used which:

  • downloads the analyzers from SQ/SC and installs them locally in a cache
  • creates a .ruleset file based on the Quality Profiles of SonarQube
  • puts a target file in the ImportBefore folder which tells MSBuild to use the sonar-dotnet analyzers (from the local cache) and passes the rule configuration to the analysis

Then during the build our Roslyn analyzers get invoked by the Roslyn compiler framework and create the analysis output, which during the END step is read and sent to SonarQube. Also, the END step does the clean-up of the target file added in the BEGIN step. The END step actually invokes the common scanner-cli which is used by all our plugins to communicate with SonarQube.