diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 52560d27c..3c4caf834 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - version: ['10.8.0'] # 9.9 = LTS + version: ['2025.5.0'] edition: ['developer', 'enterprise'] steps: - @@ -66,6 +66,21 @@ jobs: run: | cd sonarqube && ./test.sh --sq-version=${{ matrix.version }} --sq-edition=${{ matrix.edition }} + sonarqube-postgresql: + name: SonarQube PostgreSQL tests + runs-on: ubuntu-22.04 + steps: + - + name: Checkout repository + uses: actions/checkout@v4.2.2 + - + name: Build docker image + run: | + ./.github/workflows/build-docker-image.sh \ + --imagename ods-sonarqube-postgresql \ + --dockerdir sonarqube/docker \ + --dockerfile Dockerfile.database + nexus: name: Nexus tests runs-on: ubuntu-22.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index eba17df8a..c4ff7b824 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Change Cnes report to custom SonarQube report ([#1354](https://github.com/opendevstack/ods-core/pull/1354)) - Adapted Sonarqube server configuration to make projects private and have custom gate ([#1347](https://github.com/opendevstack/ods-core/pull/1347)) - Update Aqua cli to 2022.4.829 ([#1353](https://github.com/opendevstack/ods-core/pull/1353)) +- Update SonarQube and use local image for database ([#1343](https://github.com/opendevstack/ods-core/pull/1343)) ### Fixed diff --git a/Makefile b/Makefile index 1538c8a96..2b95cd5b5 100644 --- a/Makefile +++ b/Makefile @@ -131,13 +131,13 @@ apply-sonarqube-chart: ## Start build of BuildConfig "sonarqube". start-sonarqube-build: - ocp-scripts/start-and-follow-build.sh --namespace $(ODS_NAMESPACE) --build-config sonarqube + ocp-scripts/start-and-follow-build.sh --namespace $(ODS_NAMESPACE) --build-config sonarqube && ocp-scripts/start-and-follow-build.sh --namespace $(ODS_NAMESPACE) --build-config sonarqube-postgresql @echo "Visit $(SONARQUBE_URL)/setup to see if any update actions need to be taken." .PHONY: start-sonarqube-build ## Configure SonarQube service. configure-sonarqube: - cd sonarqube && ./configure.sh --sonarqube=$(SONARQUBE_URL) $(INSECURE_FLAG) + cd sonarqube && ./configure.sh --sonarqube=$(SONARQUBE_URL) --database-config=true $(INSECURE_FLAG) .PHONY: configure-sonarqube @@ -181,7 +181,7 @@ start-opentelemetry-collector-build: # BACKUP ## Create a backup of the current state. -backup: backup-sonarqube backup-ocp-config +backup: backup-ocp-config .PHONY: backup ## Create a backup of OpenShift resources in "ods" namespace. @@ -189,12 +189,6 @@ backup-ocp-config: tailor export --namespace $(ODS_NAMESPACE) > backup_ods.yml .PHONY: backup-ocp-config -## Create a backup of the SonarQube database in backup storage and in the current directory. -backup-sonarqube: - cd sonarqube && ./backup.sh --namespace $(ODS_NAMESPACE) --local-copy=true --backup-dir `pwd` -.PHONY: backup-sonarqube - - # PVC MIGRATION ## Migrate data from one PVC to another. Options: SOURCE_PVC, TARGET_PVC, THREADS (default: 5), CPU_REQUEST (default: 1), MEMORY (default: 2) migrate-pvc-data: diff --git a/configuration-sample/ods-core.env.sample b/configuration-sample/ods-core.env.sample index 2e089afdb..f7e422022 100644 --- a/configuration-sample/ods-core.env.sample +++ b/configuration-sample/ods-core.env.sample @@ -89,7 +89,7 @@ NEXUS_STORAGE_PROVISIONER="ebs.csi.aws.com" # Storage class for Nexus data, for AWS this should be "gp3-csi" NEXUS_STORAGE_CLASS_DATA="gp3-csi" -# Storage class for Nexus backup, for AWS this should be "gp2-encrypted" +# Storage class for Nexus backup, for AWS this should be "csi-aws-vsc" NEXUS_STORAGE_CLASS_BACKUP="csi-aws-vsc" # Nexus snapshot configuration, default to run daily at 2 AM @@ -124,6 +124,8 @@ SONAR_ADMIN_PASSWORD_B64=changeme # Authentication token used by sonar-scanner-cli from Jenkins pipelines. # Do not change the value manually - the token is created and set automatically during "make configure-sonarqube". SONAR_AUTH_TOKEN_B64=changeme +# Web authetification code, needed for liveness Probe +SONAR_WEB_SYSTEMPASSCODE_B64=changeme # Toggle authentication via SAML SONAR_AUTH_SAML='true' @@ -138,14 +140,17 @@ SONAR_SAML_CERTIFICATE_B64=changeme # Image to use for the PostgreSQL database. This needs to be compatible with # your SonarQube version, see https://docs.sonarqube.org/latest/requirements/requirements/. # Take care when upgrading either database or SQ version. -# E.g. registry.redhat.io/rhel9/postgresql-15 -SONAR_DATABASE_IMAGE=docker-registry.default.svc:5000/openshift/postgresql:15 +# E.g. registry.redhat.io/rhel10/postgresql-16 +SONAR_DATABASE_IMAGE=registry.redhat.io/rhel10/postgresql-16 # Connection string for JDBC. Typically this does not need to be changed. SONAR_DATABASE_JDBC_URL=jdbc:postgresql://sonarqube-postgresql:5432/sonarqube # Database name for SonarQube. Typically this does not need to be changed. SONAR_DATABASE_NAME=sonarqube # Password of SonarQube database - should be set to a secure password. SONAR_DATABASE_PASSWORD_B64=changeme +SONAR_DATABASE_SUPER_NAME=super_sonarqube +# Super Password of SonarQube database - should be set to a secure password. +SONAR_DATABASE_SUPER_PASSWORD_B64=changeme # User of SonarQube database. Typically this does not need to be changed. SONAR_DATABASE_USER=sonarqube @@ -157,29 +162,53 @@ SONAR_EDITION=developer # SonarQube version. # See Dockerhub https://hub.docker.com/_/sonarqube/tags # Officially supported is: -# - 10.8.0 -SONAR_VERSION=10.8.0 +# - 2025.5.0 +SONAR_VERSION=2025.5.0 # SonarQube memory and CPU resources -SONARQUBE_CPU_REQUEST=200m -SONARQUBE_MEMORY_REQUEST=2Gi -SONARQUBE_CPU_LIMIT=1 -SONARQUBE_MEMORY_LIMIT=4Gi +SONARQUBE_CPU_REQUEST=300m +SONARQUBE_MEMORY_REQUEST=5Gi +SONARQUBE_CPU_LIMIT=2 +SONARQUBE_MEMORY_LIMIT=5Gi # SonarQube data and backup capacity SONARQUBE_DATA_CAPACITY=2Gi SONARQUBE_EXTENSIONS_CAPACITY=1Gi # SonarQube database memory and CPU resources -SONARQUBE_DB_CPU_REQUEST=100m -SONARQUBE_DB_MEMORY_REQUEST=256Mi -SONARQUBE_DB_CPU_LIMIT=1 +SONARQUBE_DB_CPU_REQUEST=200m +SONARQUBE_DB_MEMORY_REQUEST=512Mi +SONARQUBE_DB_CPU_LIMIT=2 SONARQUBE_DB_MEMORY_LIMIT=512Mi # SonarQube database and backup capacity SONARQUBE_DB_CAPACITY=2Gi SONARQUBE_DB_BACKUP_CAPACITY=1Gi +# SonarQube data storage name +SONARQUBE_DATA_STORAGE_NAME="sonarqube-data-storage" + +# Storage class provisioner for SonarQube data, for AWS this should be "ebs.csi.aws.com" +SONARQUBE_STORAGE_PROVISIONER="" + +# Storage class for SonarQube data, for AWS this should be "gp3-csi" +SONARQUBE_STORAGE_CLASS_DATA="" + +# Storage class provisioner for fast SonarQube storage, for AWS this should be "ebs.csi.aws.com" +SONARQUBE_FAST_STORAGE_PROVISIONER="ebs.csi.aws.com" + +# Storage class for fast SonarQube data, for AWS this should be "gp3-csi" +SONARQUBE_FAST_STORAGE_CLASS_DATA="gp3-csi" + +# Storage class for fast SonarQube backup, for AWS this should be "csi-aws-vsc" +SONARQUBE_FAST_STORAGE_CLASS_BACKUP="csi-aws-vsc" + +# SonarQube backup configuration, default to run daily at 2 AM +SONARQUBE_BACKUP_SCHEDULE="0 2 * * *" + +# SonarQube DB backup TTL in days (default: 30 days) +SONARQUBE_DB_BACKUP_TTL=30 + # SonarQube scan configuration SONAR_SCAN_ENABLED="true" SONAR_SCAN_EXCLUSIONS=".json,.xml,**/__pycache__/**,**/*.pyc,/venv/,/.venv/,/site-packages/,/node_modules/,/dist/,/build/,/out/,/coverage/,/.next/,/.parcel-cache/,/target/,/.gradle/,/.mvn/,/vendor/,/bin/,/obj/,/build/libs/,/.terraform/,/pkg/,/android/,/ios/,/www/,/target/**,/Cargo.lock,/target/,/**/*.class,/**/*.jar,/**/*.war" diff --git a/jenkins/agent-base/Dockerfile.ubi8 b/jenkins/agent-base/Dockerfile.ubi8 index acf75de27..a2ec229bc 100644 --- a/jenkins/agent-base/Dockerfile.ubi8 +++ b/jenkins/agent-base/Dockerfile.ubi8 @@ -2,8 +2,8 @@ FROM quay.io/openshift/origin-jenkins-agent-base SHELL ["/bin/bash", "-o", "pipefail", "-c"] -ENV SONAR_SCANNER_VERSION=6.2.1.4610 \ - SONAR_REPORT_VERSION=1.1 \ +ENV SONAR_SCANNER_VERSION=7.3.0.5189 \ + SONAR_REPORT_VERSION=1.2 \ COSIGN_VERSION=2.4.3 \ TAILOR_VERSION=1.3.4 \ SOPS_VERSION=3.9.0 \ diff --git a/nexus/chart/templates/nexus-snapshot-cronjob.yaml b/nexus/chart/templates/nexus-snapshot-cronjob.yaml index 632129f69..88a3968e1 100644 --- a/nexus/chart/templates/nexus-snapshot-cronjob.yaml +++ b/nexus/chart/templates/nexus-snapshot-cronjob.yaml @@ -1,19 +1,20 @@ apiVersion: batch/v1 kind: CronJob metadata: - name: nexus-volume-snapshot + name: {{ .Values.global.appName }}-volume-snapshot labels: - app: nexus + app: {{ .Values.global.appName }} spec: schedule: "{{ .Values.global.nexusSnapshotSchedule }}" concurrencyPolicy: Forbid + startingDeadlineSeconds: 3600 suspend: false jobTemplate: spec: - ttlSecondsAfterFinished: {{ .Values.global.nexusSnapshotTTL }} + backoffLimit: 0 + ttlSecondsAfterFinished: 604800 template: spec: - backoffLimit: 0 serviceAccountName: ods-edit containers: - name: snapshot-creator @@ -35,27 +36,6 @@ spec: source: persistentVolumeClaimName: {{ .Values.global.nexusStorageName }} EOF - - # Wait for the VolumeSnapshot to become Ready (configurable timeout) - TIMEOUT={{ .Values.global.nexusSnapshotCheckTimeout }} - INTERVAL=30 - elapsed=0 - TIMED_OUT=0 - echo "Waiting for VolumeSnapshot $SNAP_NAME to be ready (timeout: $TIMEOUT seconds)..." - until [ $elapsed -ge $TIMEOUT ]; do - ready=$(oc get volumesnapshot "$SNAP_NAME" -n {{ .Values.global.odsNamespace }} -o jsonpath='{.status.readyToUse}' 2>/dev/null || echo "false") - if [ "$ready" = "true" ]; then - echo "VolumeSnapshot $SNAP_NAME is ready" - break - fi - sleep $INTERVAL - elapsed=$((elapsed + INTERVAL)) - echo " ... waited $elapsed seconds out of $TIMEOUT seconds" - done - if [ $elapsed -ge $TIMEOUT ]; then - echo "Timeout waiting for VolumeSnapshot $SNAP_NAME to be ready" >&2 - exit 1 - fi resources: limits: cpu: '1' @@ -65,5 +45,6 @@ spec: memory: 256Mi imagePullPolicy: IfNotPresent restartPolicy: Never + terminationGracePeriodSeconds: 30 successfulJobsHistoryLimit: 30 failedJobsHistoryLimit: 30 diff --git a/nexus/chart/templates/snapshot-cleanup-cronjob.yaml b/nexus/chart/templates/snapshot-cleanup-cronjob.yaml index ea79d3ee5..252d9f7d2 100644 --- a/nexus/chart/templates/snapshot-cleanup-cronjob.yaml +++ b/nexus/chart/templates/snapshot-cleanup-cronjob.yaml @@ -7,13 +7,14 @@ metadata: spec: schedule: "{{ .Values.global.nexusSnapshotCleanupSchedule }}" concurrencyPolicy: Forbid + startingDeadlineSeconds: 3600 suspend: false jobTemplate: spec: - ttlSecondsAfterFinished: {{ int .Values.global.nexusSnapshotTTL }} + backoffLimit: 0 + ttlSecondsAfterFinished: 604800 template: spec: - backoffLimit: 0 serviceAccountName: ods-edit containers: - name: snapshot-cleaner diff --git a/sonarqube/backup.sh b/sonarqube/backup_old.sh old mode 100755 new mode 100644 similarity index 100% rename from sonarqube/backup.sh rename to sonarqube/backup_old.sh diff --git a/sonarqube/chart/Chart.yaml b/sonarqube/chart/Chart.yaml index 22f52cf97..4a16794fd 100644 --- a/sonarqube/chart/Chart.yaml +++ b/sonarqube/chart/Chart.yaml @@ -15,10 +15,10 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.2 +version: 1.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "10.8.0" +appVersion: "2025.5.0" diff --git a/sonarqube/chart/templates/buildconfig-postgresql.yaml b/sonarqube/chart/templates/buildconfig-postgresql.yaml new file mode 100644 index 000000000..07e6079c2 --- /dev/null +++ b/sonarqube/chart/templates/buildconfig-postgresql.yaml @@ -0,0 +1,40 @@ +apiVersion: build.openshift.io/v1 +kind: BuildConfig +metadata: + labels: + app: {{ .Values.global.appName }} + name: {{ .Values.global.appName }}-postgresql +spec: + failedBuildsHistoryLimit: 5 + nodeSelector: null + output: + to: + kind: ImageStreamTag + name: {{ printf "%s-postgresql:%s" .Values.global.appName .Values.global.odsImageTag }} + postCommit: {} + resources: + limits: + cpu: {{ .Values.buildConfig.cpuLimit }} + memory: {{ .Values.buildConfig.memLimit }} + requests: + cpu: {{ .Values.buildConfig.cpuRequest }} + memory: {{ .Values.buildConfig.memRequest }} + runPolicy: Serial + source: + contextDir: sonarqube/docker + git: + uri: {{ .Values.global.repoBase }}/{{ .Values.global.odsBitBucketProject }}/ods-core.git + ref: {{ .Values.global.odsGitRef }} + sourceSecret: + name: cd-user-token + type: Git + strategy: + type: Docker + dockerStrategy: + dockerfilePath: Dockerfile.database + from: + kind: DockerImage + name: {{ .Values.global.sonarDatabaseImage}} + forcePull: true + noCache: true + successfulBuildsHistoryLimit: 5 diff --git a/sonarqube/chart/templates/buildconfig.yaml b/sonarqube/chart/templates/buildconfig-sonarqube.yaml similarity index 100% rename from sonarqube/chart/templates/buildconfig.yaml rename to sonarqube/chart/templates/buildconfig-sonarqube.yaml diff --git a/sonarqube/chart/templates/cronjob-postgresql-backup.yaml b/sonarqube/chart/templates/cronjob-postgresql-backup.yaml new file mode 100644 index 000000000..7505184e2 --- /dev/null +++ b/sonarqube/chart/templates/cronjob-postgresql-backup.yaml @@ -0,0 +1,161 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ .Values.global.appName }}-postgresql-backup + labels: + app: {{ .Values.global.appName }} +spec: + schedule: "{{ .Values.global.backupSchedule }}" + concurrencyPolicy: Forbid + startingDeadlineSeconds: 3600 + suspend: false + jobTemplate: + spec: + backoffLimit: 0 + ttlSecondsAfterFinished: 604800 + template: + metadata: + annotations: + cluster-autoscaler.kubernetes.io/safe-to-evict: "false" + spec: + serviceAccountName: ods-edit + containers: + - resources: + limits: + cpu: '1' + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi + terminationMessagePath: /dev/termination-log + name: postgresql-backup + command: + - /bin/bash + - '-c' + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: sonarqube-postgresql + key: superuser-password + imagePullPolicy: IfNotPresent + terminationMessagePolicy: File + image: image-registry.openshift-image-registry.svc:5000/{{ .Values.global.odsNamespace }}/{{ .Values.global.appName }}-postgresql:{{ .Values.global.odsImageTag }} + args: + - | + set -e + + # Compute snapshot name + SNAP_NAME="{{ .Values.global.appName }}-postgresql-snapshot-$(date +%Y-%m-%d-%H-%M-%S)" + DB_HOST="{{ .Values.global.appName }}-postgresql" + DB_USER="{{ .Values.global.sonarDatabaseSuperUser }}" + DB_NAME="{{ .Values.global.sonarDatabaseName }}" + ODS_NAMESPACE="{{ .Values.global.odsNamespace }}" + STORAGE_CLASS="{{ .Values.global.sonarqubeFastStorageClassBackup }}" + APP_NAME="{{ .Values.global.appName }}" + + echo "Starting PostgreSQL backup process..." + + # Create the VolumeSnapshot manifest + cat > /tmp/snapshot.yaml < /tmp/psql_output_$$.log 2>&1 & + PSQL_PID=$! + + # Execute commands through the FIFO to maintain single session + exec 3>"$FIFO" + + # Start backup mode + echo "SELECT pg_backup_start('backup', false) AS backup_lsn;" >&3 + sleep 1 + + echo "Creating volume snapshot..." + oc apply -f /tmp/snapshot.yaml + OC_APPLY_EXIT=$? + + if [ $OC_APPLY_EXIT -ne 0 ]; then + echo "ERROR: Failed to create volume snapshot!" + echo "SELECT pg_backup_stop();" >&3 + exec 3>&- + wait $PSQL_PID + rm -f "$FIFO" /tmp/psql_output_$$.log + exit 1 + fi + + echo "Waiting for snapshot to reach Ready status..." + oc wait --for='jsonpath={.status.readyToUse}=true' volumesnapshot/${SNAP_NAME} -n ${ODS_NAMESPACE} --timeout=1800s > /dev/null || true + OC_WAIT_EXIT=$? + + # Stop backup mode (same session) - ALWAYS do this regardless of wait result + echo "Stopping PostgreSQL backup mode..." + echo "SELECT pg_backup_stop() AS backup_end_info;" >&3 + echo "\q" >&3 + sleep 2 + + # Close the FIFO and wait for psql to finish + exec 3>&- + wait $PSQL_PID + PSQL_EXIT=$? + + # Print psql output for verification + echo "PostgreSQL session output:" + cat /tmp/psql_output_$$.log + + # Check if pg_backup_stop was actually executed + if grep -i "backup_end_info" /tmp/psql_output_$$.log > /dev/null; then + echo "✓ PostgreSQL backup mode stopped successfully" + else + echo "⚠ WARNING: Could not confirm pg_backup_stop() execution - check logs above" + fi + + # Check for errors + if grep -i "error" /tmp/psql_output_$$.log > /dev/null; then + echo "ERROR: PostgreSQL error detected!" + rm -f "$FIFO" /tmp/psql_output_$$.log + exit 1 + fi + + if [ $PSQL_EXIT -ne 0 ]; then + echo "ERROR: PostgreSQL session failed with exit code $PSQL_EXIT!" + rm -f "$FIFO" /tmp/psql_output_$$.log + exit 1 + fi + + # Check snapshot wait result AFTER backup is stopped + if [ $OC_WAIT_EXIT -ne 0 ]; then + echo "ERROR: Snapshot did not reach Ready status in time!" + rm -f "$FIFO" /tmp/psql_output_$$.log + exit 1 + fi + + echo "Backup snapshot created successfully: $SNAP_NAME" + rm -f "$FIFO" /tmp/psql_output_$$.log + restartPolicy: Never + terminationGracePeriodSeconds: 30 + successfulJobsHistoryLimit: 30 + failedJobsHistoryLimit: 30 \ No newline at end of file diff --git a/sonarqube/chart/templates/cronjob-sonarqube-data-snapshot.yaml b/sonarqube/chart/templates/cronjob-sonarqube-data-snapshot.yaml new file mode 100644 index 000000000..6f3883104 --- /dev/null +++ b/sonarqube/chart/templates/cronjob-sonarqube-data-snapshot.yaml @@ -0,0 +1,50 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ .Values.global.appName }}-volume-snapshot + labels: + app: {{ .Values.global.appName }} +spec: + schedule: "{{ .Values.global.backupSchedule }}" + concurrencyPolicy: Forbid + startingDeadlineSeconds: 3600 + suspend: false + jobTemplate: + spec: + backoffLimit: 0 + ttlSecondsAfterFinished: 604800 + template: + spec: + serviceAccountName: ods-edit + containers: + - name: snapshot-creator + image: image-registry.openshift-image-registry.svc:5000/openshift/ose-cli:latest + command: + - /bin/sh + - -c + - | + # compute snapshot name so we can check it later + SNAP_NAME="{{ .Values.global.appName }}-snapshot.$(date +%Y-%m-%d.%H-%M-%S)" + cat <&1) + if [ $? -eq 0 ]; then + exit 0 + else + echo "SonarQube response: $RESPONSE" + exit 1 + fi + periodSeconds: 30 successThreshold: 1 - timeoutSeconds: 2 - failureThreshold: 3 + timeoutSeconds: 1 + failureThreshold: 5 readinessProbe: - httpGet: - path: /api/system/status - port: 9000 - scheme: HTTP - periodSeconds: 10 + exec: + command: + - sh + - '-c' + - | + #!/bin/bash + # A Sonarqube container is considered ready if the status is UP, DB_MIGRATION_NEEDED or DB_MIGRATION_RUNNING + # status about migration are added to prevent the node to be kill while SonarQube is upgrading the database. + # The possible values are STARTING, UP, DOWN, RESTARTING, DB_MIGRATION_NEEDED and DB_MIGRATION_RUNNING. + STATUS=$(wget --no-proxy -qO- http://localhost:9000/api/system/status 2>&1) + if echo "$STATUS" | grep -q -e '"status":"UP"' -e '"status":"DB_MIGRATION_NEEDED"' -e '"status":"DB_MIGRATION_RUNNING"'; then + exit 0 + fi + STATUS_VALUE=$(echo "$STATUS" | grep -o '"status":"[^"]*"' | cut -d'"' -f4) + echo "SonarQube status: ${STATUS_VALUE:-unknown}" + exit 1 + periodSeconds: 30 successThreshold: 1 - timeoutSeconds: 2 - failureThreshold: 3 + timeoutSeconds: 1 + failureThreshold: 5 env: - name: SONARQUBE_JDBC_USERNAME valueFrom: @@ -88,6 +108,11 @@ spec: configMapKeyRef: name: {{ .Values.global.appName }} key: database-jdbc-url + - name: SONAR_WEB_SYSTEMPASSCODE + valueFrom: + secretKeyRef: + name: {{ .Values.global.appName }}-app + key: web-system-passcode - name: SONAR_FORCEAUTHENTICATION value: 'true' - name: SONAR_AUTH_SAML_ENABLED @@ -132,7 +157,7 @@ spec: path: /api/system/status port: 9000 scheme: HTTP - timeoutSeconds: 1 + timeoutSeconds: 2 periodSeconds: 10 successThreshold: 1 failureThreshold: 30 diff --git a/sonarqube/chart/templates/imagestream-postgresql.yaml b/sonarqube/chart/templates/imagestream-postgresql.yaml new file mode 100644 index 000000000..70a569576 --- /dev/null +++ b/sonarqube/chart/templates/imagestream-postgresql.yaml @@ -0,0 +1,9 @@ +apiVersion: image.openshift.io/v1 +kind: ImageStream +metadata: + labels: + app: {{ .Values.global.appName }} + name: {{ .Values.global.appName }}-postgresql +spec: + lookupPolicy: + local: false diff --git a/sonarqube/chart/templates/imagestream.yaml b/sonarqube/chart/templates/imagestream-sonarqube.yaml similarity index 100% rename from sonarqube/chart/templates/imagestream.yaml rename to sonarqube/chart/templates/imagestream-sonarqube.yaml diff --git a/sonarqube/chart/templates/pvc-extensions.yaml b/sonarqube/chart/templates/pvc-extensions.yaml index 7057b48dd..f17990586 100644 --- a/sonarqube/chart/templates/pvc-extensions.yaml +++ b/sonarqube/chart/templates/pvc-extensions.yaml @@ -2,7 +2,8 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: annotations: - volume.beta.kubernetes.io/storage-provisioner: {{ .Values.global.storageProvisioner }} + volume.beta.kubernetes.io/storage-provisioner: {{ .Values.global.sonarqubeStorageProvisioner }} + helm.sh/resource-policy: keep finalizers: - kubernetes.io/pvc-protection labels: @@ -14,5 +15,5 @@ spec: resources: requests: storage: {{ .Values.sonarqube.pvcExtensionsCapacity }} - storageClassName: {{ .Values.global.storageClassData }} + storageClassName: {{ .Values.global.sonarqubeStorageClassData }} volumeMode: Filesystem diff --git a/sonarqube/chart/templates/pvc-postgresql-backup.yaml b/sonarqube/chart/templates/pvc-postgresql-backup.yaml deleted file mode 100644 index dbe622a94..000000000 --- a/sonarqube/chart/templates/pvc-postgresql-backup.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - annotations: - volume.beta.kubernetes.io/storage-provisioner: {{ .Values.global.storageProvisioner }} - finalizers: - - kubernetes.io/pvc-protection - labels: - app: {{ .Values.global.appName }} - name: {{ .Values.global.appName }}-postgresql-backup -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.postgresql.pvcDatabaseBackupCapacity }} - storageClassName: {{ .Values.global.storageClassData }} - volumeMode: Filesystem diff --git a/sonarqube/chart/templates/pvc-postgresql.yaml b/sonarqube/chart/templates/pvc-postgresql.yaml index 619fb38bc..d0e20eff1 100644 --- a/sonarqube/chart/templates/pvc-postgresql.yaml +++ b/sonarqube/chart/templates/pvc-postgresql.yaml @@ -2,17 +2,18 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: annotations: - volume.beta.kubernetes.io/storage-provisioner: {{ .Values.global.storageProvisioner }} + volume.beta.kubernetes.io/storage-provisioner: {{ .Values.global.sonarqubeFastStorageProvisioner }} + helm.sh/resource-policy: keep finalizers: - - kubernetes.io/pvc-protection + - kubernetes.io/pvc-protection labels: app: {{ .Values.global.appName }} - name: {{ .Values.global.appName }}-postgresql + name: {{ .Values.global.appName }}-postgresql-data spec: accessModes: - ReadWriteOnce resources: requests: storage: {{ .Values.postgresql.pvcDatabaseCapacity }} - storageClassName: {{ .Values.global.storageClassData }} + storageClassName: {{ .Values.global.sonarqubeFastStorageClassData }} volumeMode: Filesystem diff --git a/sonarqube/chart/templates/pvc-sonar-data.yaml b/sonarqube/chart/templates/pvc-sonar-data.yaml index 10050a59d..bd20dc0de 100644 --- a/sonarqube/chart/templates/pvc-sonar-data.yaml +++ b/sonarqube/chart/templates/pvc-sonar-data.yaml @@ -2,17 +2,18 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: annotations: - volume.beta.kubernetes.io/storage-provisioner: {{ .Values.global.storageProvisioner }} + volume.beta.kubernetes.io/storage-provisioner: {{ .Values.global.sonarqubeFastStorageProvisioner }} + helm.sh/resource-policy: keep finalizers: - kubernetes.io/pvc-protection labels: app: {{ .Values.global.appName }} - name: {{ .Values.global.appName }}-data + name: {{ .Values.global.sonarqubeDataStorageName }} spec: accessModes: - ReadWriteOnce resources: requests: storage: {{ .Values.sonarqube.pvcDataCapacity }} - storageClassName: {{ .Values.global.storageClassData }} + storageClassName: {{ .Values.global.sonarqubeFastStorageClassData }} volumeMode: Filesystem diff --git a/sonarqube/chart/templates/secret.yaml b/sonarqube/chart/templates/secret.yaml index bda720a03..70ee292ef 100644 --- a/sonarqube/chart/templates/secret.yaml +++ b/sonarqube/chart/templates/secret.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: Secret data: admin-password: {{ .Values.global.sonarAdminPasswordB64 }} + web-system-passcode: {{ .Values.global.sonarPasscodeB64 }} {{ if ( eq .Values.sonarqube.sonarAuthSaml "true" )}} saml-provider-id: {{ .Values.sonarqube.sonarAuthSamlProviderIdB64 }} saml-login-url: {{ .Values.sonarqube.sonarAuthSamlLoginUrlB64 }} diff --git a/sonarqube/chart/templates/secrets-postgresql.yaml b/sonarqube/chart/templates/secrets-postgresql.yaml index 23a61a175..e42b352f3 100644 --- a/sonarqube/chart/templates/secrets-postgresql.yaml +++ b/sonarqube/chart/templates/secrets-postgresql.yaml @@ -1,6 +1,7 @@ apiVersion: v1 data: database-password: {{ .Values.global.sonarDatabasePasswordB64 }} + superuser-password: {{ .Values.global.sonarDatabaseSuperPasswordB64 }} kind: Secret metadata: labels: diff --git a/sonarqube/chart/values.yaml.template b/sonarqube/chart/values.yaml.template index 0b4f3a79e..37bff17f6 100644 --- a/sonarqube/chart/values.yaml.template +++ b/sonarqube/chart/values.yaml.template @@ -1,55 +1,66 @@ global: - odsImageTag: $ODS_IMAGE_TAG - repoBase: $REPO_BASE + appDNS: $APP_DNS + appName: 'sonarqube' + backupSchedule: $SONARQUBE_BACKUP_SCHEDULE odsBitBucketProject: $ODS_BITBUCKET_PROJECT odsGitRef: $ODS_GIT_REF - sonarVersion: $SONAR_VERSION - sonarEdition: $SONAR_EDITION + odsImageTag: $ODS_IMAGE_TAG odsNamespace: $ODS_NAMESPACE - appDNS: $APP_DNS - appName: 'sonarqube' - storageProvisioner: $STORAGE_PROVISIONER - storageClassData: $STORAGE_CLASS_DATA - sonarqubeUrl: $SONARQUBE_URL - sonarqubeHost: $SONARQUBE_HOST + registry: $DOCKER_REGISTRY + repoBase: $REPO_BASE + sonarPasscodeB64: $SONAR_WEB_SYSTEMPASSCODE_B64 sonarAdminPasswordB64: $SONAR_ADMIN_PASSWORD_B64 + sonarAdminUsername: $SONAR_ADMIN_USERNAME + sonarDatabaseImage: $SONAR_DATABASE_IMAGE sonarDatabaseJdbcUrl: $SONAR_DATABASE_JDBC_URL sonarDatabaseName: $SONAR_DATABASE_NAME - sonarDatabaseUser: $SONAR_DATABASE_USER - sonarAdminUsername: $SONAR_ADMIN_USERNAME sonarDatabasePasswordB64: $SONAR_DATABASE_PASSWORD_B64 - registry: $DOCKER_REGISTRY - sonarDatabaseImage: $SONAR_DATABASE_IMAGE + sonarDatabaseSuperPasswordB64: $SONAR_DATABASE_SUPER_PASSWORD_B64 + sonarDatabaseUser: $SONAR_DATABASE_USER + sonarDatabaseSuperUser: $SONAR_DATABASE_SUPER_NAME + sonarEdition: $SONAR_EDITION + sonarVersion: $SONAR_VERSION + sonarqubeDataStorageName: $SONARQUBE_DATA_STORAGE_NAME + sonarqubeFastStorageClassBackup: $SONARQUBE_FAST_STORAGE_CLASS_BACKUP + sonarqubeFastStorageClassData: $SONARQUBE_FAST_STORAGE_CLASS_DATA + sonarqubeFastStorageProvisioner: $SONARQUBE_FAST_STORAGE_PROVISIONER + sonarqubeHost: $SONARQUBE_HOST + sonarqubeStorageClassData: $SONARQUBE_STORAGE_CLASS_DATA + sonarqubeStorageProvisioner: $SONARQUBE_STORAGE_PROVISIONER + sonarqubeUrl: $SONARQUBE_URL + storageClassData: $STORAGE_CLASS_DATA + storageProvisioner: $STORAGE_PROVISIONER postgresql: - name: 'sonarqube-postgresql' + backupTTL: $SONARQUBE_DB_BACKUP_TTL + cpuLimit: $SONARQUBE_DB_CPU_LIMIT cpuRequest: $SONARQUBE_DB_CPU_REQUEST - cpuLimit: $SONARQUBE_DB_CPU_LIMIT - memRequest: $SONARQUBE_DB_MEMORY_REQUEST memLimit: $SONARQUBE_DB_MEMORY_LIMIT - pvcDatabaseCapacity: $SONARQUBE_DB_CAPACITY + memRequest: $SONARQUBE_DB_MEMORY_REQUEST + name: 'sonarqube-postgresql' pvcDatabaseBackupCapacity: $SONARQUBE_DB_BACKUP_CAPACITY + pvcDatabaseCapacity: $SONARQUBE_DB_CAPACITY sonarqube: - cpuRequest: $SONARQUBE_CPU_REQUEST cpuLimit: $SONARQUBE_CPU_LIMIT - memRequest: $SONARQUBE_MEMORY_REQUEST + cpuRequest: $SONARQUBE_CPU_REQUEST memLimit: $SONARQUBE_MEMORY_LIMIT + memRequest: $SONARQUBE_MEMORY_REQUEST pvcDataCapacity: $SONARQUBE_DATA_CAPACITY pvcExtensionsCapacity: $SONARQUBE_EXTENSIONS_CAPACITY sonarAuthSaml: $SONAR_AUTH_SAML sonarAuthSamlApplicationId: $SONAR_SAML_APPLICATION_ID - sonarAuthSamlProviderIdB64: $SONAR_SAML_PROVIDER_ID_B64 + sonarAuthSamlCertficateB64: $SONAR_SAML_CERTIFICATE_B64 sonarAuthSamlLoginUrlB64: $SONAR_SAML_LOGIN_URL_B64 + sonarAuthSamlProviderIdB64: $SONAR_SAML_PROVIDER_ID_B64 sonarAuthSamlServerBaseUrl: $SONARQUBE_URL - sonarAuthSamlCertficateB64: $SONAR_SAML_CERTIFICATE_B64 buildConfig: cpuRequest: 200m cpuLimit: 1 memRequest: 1Gi memLimit: 2Gi scan: + sonarAlertEmails: $SONAR_SCAN_ALERT_EMAILS sonarEnabled: $SONAR_SCAN_ENABLED sonarExclusions: $SONAR_SCAN_EXCLUSIONS sonarNexusRepository: $SONAR_SCAN_NEXUS_REPOSITORY - sonarAlertEmails: $SONAR_SCAN_ALERT_EMAILS sonarProjectsPrivate: $SONAR_SCAN_PROJECTS_PRIVATE sonarQubeAccount: $SONAR_SCAN_ACCOUNT diff --git a/sonarqube/configure.sh b/sonarqube/configure.sh index a31c8531e..5da3b879a 100755 --- a/sonarqube/configure.sh +++ b/sonarqube/configure.sh @@ -5,6 +5,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ODS_CORE_DIR=${SCRIPT_DIR%/*} ODS_CONFIGURATION_DIR="${ODS_CORE_DIR}/../ods-configuration" +# Source the configuration file to load all environment variables +if [ -f "${ODS_CONFIGURATION_DIR}/ods-core.env" ]; then + set +u + # shellcheck source=/dev/null + source "${ODS_CONFIGURATION_DIR}/ods-core.env" + set -u +fi + echo_done(){ echo -e "\033[92mDONE\033[39m: $1" } @@ -38,6 +46,7 @@ SONARQUBE_URL="" INSECURE="" CONFIGURATION_LOCATED="" VALUES_WRITTEN_TO_CONFIG="" +DATABASE_CONFIG="" function usage { printf "Setup SonarQube.\n\n" @@ -52,6 +61,7 @@ function usage { printf "\t-p|--pipeline-user\tName of Jenkins pipeline user (defaults to 'cd_user')\n" printf "\t-t|--token-name\t\tName of SonarQube user token (defaults to 'ods-jenkins-shared-library')\n" printf "\t-w|--write-to-config\tIf token/password should be written to ods-core.env\n" + printf "\t-d|--database-config\tConfigure PostgreSQL, create/add super user" } while [[ "$#" -gt 0 ]]; do @@ -80,6 +90,9 @@ while [[ "$#" -gt 0 ]]; do -s|--sonarqube) SONARQUBE_URL="$2"; shift;; -s=*|--sonarqube=*) SONARQUBE_URL="${1#*=}";; + -d|--database-config) DATABASE_CONFIG="$2"; shift;; + -d=*|--database-config*) DATABASE_CONFIG="${1#*=}";; + *) echo_error "Unknown parameter passed: $1"; exit 1;; esac; shift; done @@ -237,18 +250,13 @@ fi sampleToken=$(grep SONAR_AUTH_TOKEN_B64 "${ODS_CORE_DIR}/configuration-sample/ods-core.env.sample" | cut -d "=" -f 2-) configuredToken=$(grep SONAR_AUTH_TOKEN_B64 "${ODS_CONFIGURATION_DIR}/ods-core.env" | cut -d "=" -f 2- | base64 --decode) -authTokenVerified="" +tokenMatch="" if [ "${configuredToken}" == "${sampleToken}" ]; then echo_info "Auth token in ods-core.env is the sample value." -else - echo_info "Checking if login with token from ods.core.env is possible ..." - if curl ${INSECURE} -sSf --user "${configuredToken}": "${SONARQUBE_URL}/api/user_tokens/search?login=cd_user" > /dev/null; then - echo_info "Configured token for '${PIPELINE_USER_NAME}' verified." - authTokenVerified="y" - fi + tokenMatch="y" fi -if [ -z "${authTokenVerified}" ]; then +if [ "${tokenMatch}" == "y" ]; then echo_info "Creating token for '${PIPELINE_USER_NAME}' ..." encodedTokenName="$(uriencode "${TOKEN_NAME}")" tokenResponse=$(curl ${INSECURE} -X POST -sSf --user "${ADMIN_USER_NAME}:${ADMIN_USER_PASSWORD}" \ @@ -424,4 +432,64 @@ else fi fi +if [ "${DATABASE_CONFIG}" = "true" ]; then + # Grant PostgreSQL backup privileges to database user + # Uses oc exec to execute commands in the SonarQube PostgreSQL pod + echo_info "Configuring PostgreSQL backup privileges..." + + # Get values from environment variables + oc_namespace="${ODS_NAMESPACE}" + db_user="${SONAR_DATABASE_SUPER_NAME}" + db_name="${SONAR_DATABASE_NAME}" + db_password_b64="${SONAR_DATABASE_SUPER_PASSWORD_B64}" + + if [ -z "${oc_namespace}" ] || [ -z "${db_user}" ] || [ -z "${db_name}" ]; then + echo_warn "Skipping PostgreSQL backup privileges configuration - missing environment variables" + echo_info "Required: ODS_NAMESPACE, SONAR_DATABASE_SUPER_NAME, SONAR_DATABASE_NAME" + else + # Decode the base64 password + db_password=$(echo "${db_password_b64}" | base64 -d) + + # Fetch the PostgreSQL pod name dynamically + echo_info "Fetching SonarQube PostgreSQL pod in namespace '${oc_namespace}'..." + db_pod_name=$(oc get pods -n "${oc_namespace}" -l name=sonarqube-postgresql -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "${db_pod_name}" ]; then + echo_warn "Could not find PostgreSQL pod in namespace '${oc_namespace}' with label 'name=sonarqube-postgresql'" + else + echo_info "Found PostgreSQL pod: ${db_pod_name}" + echo_info "Granting PostgreSQL backup privileges to user '${db_user}' in pod '${db_pod_name}'..." + + # pg_backup_start() and pg_backup_stop() are superuser-only functions + # They cannot be granted via GRANT statements, the user must be a SUPERUSER + echo_info "Making user '${db_user}' a SUPERUSER to enable backup operations..." + + # Use oc exec to run psql command inside the PostgreSQL pod + # Connect as postgres user (superuser) to create/alter the backup user + # No password needed as we're running as root via oc exec inside the pod + psql_command=" + DO \$\$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${db_user}') THEN + CREATE USER \"${db_user}\" WITH PASSWORD '${db_password}' SUPERUSER; + RAISE NOTICE 'User ${db_user} created as SUPERUSER.'; + ELSE + ALTER USER \"${db_user}\" PASSWORD '${db_password}' SUPERUSER; + RAISE NOTICE 'User ${db_user} updated to SUPERUSER.'; + END IF; + END + \$\$; + " + + if oc exec -n "${oc_namespace}" "${db_pod_name}" -- \ + psql -U postgres -d "${db_name}" -c "${psql_command}"; then + echo_info "User '${db_user}' configured as SUPERUSER." + echo_done "PostgreSQL backup privileges configuration completed." + else + echo_warn "Failed to configure '${db_user}' as SUPERUSER." + fi + fi + fi +fi + echo_done "SonarQube configured." diff --git a/sonarqube/docker/Dockerfile b/sonarqube/docker/Dockerfile index 267d342cd..05e49dcb6 100644 --- a/sonarqube/docker/Dockerfile +++ b/sonarqube/docker/Dockerfile @@ -25,7 +25,6 @@ RUN mkdir -p /opt/configuration/sonarqube/plugins # Language plugins not bundled ADD https://github.com/Inform-Software/sonar-groovy/releases/download/1.8/sonar-groovy-plugin-1.8.jar /opt/configuration/sonarqube/plugins/ ADD https://github.com/Merck/sonar-r-plugin/releases/download/0.2.2/sonar-r-plugin-0.2.2.jar /opt/configuration/sonarqube/plugins/ -ADD https://github.com/elegoff/sonar-rust/releases/download/v0.2.5/community-rust-plugin-0.2.5.jar /opt/configuration/sonarqube/plugins/ COPY run.sh $SONARQUBE_HOME/bin/ diff --git a/sonarqube/docker/Dockerfile.database b/sonarqube/docker/Dockerfile.database new file mode 100644 index 000000000..8a820abd8 --- /dev/null +++ b/sonarqube/docker/Dockerfile.database @@ -0,0 +1,13 @@ +FROM public.ecr.aws/docker/library/postgres:16-alpine + +USER root + +# Install oc client tool +RUN if command -v apk &> /dev/null; then \ + apk add --no-cache curl && \ + curl -L https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/openshift-client-linux.tar.gz | tar xz -C /usr/local/bin; \ + elif command -v yum &> /dev/null; then \ + curl -L https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/openshift-client-linux.tar.gz | tar xz -C /usr/local/bin; \ + fi + +USER postgres \ No newline at end of file diff --git a/sonarqube/test.sh b/sonarqube/test.sh index da6c3cc8e..b10627c24 100755 --- a/sonarqube/test.sh +++ b/sonarqube/test.sh @@ -6,14 +6,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ODS_CORE_DIR=${SCRIPT_DIR%/*} ODS_CONFIGURATION_DIR="${ODS_CORE_DIR}/../ods-configuration" -SONAR_VERSION=10.8.0 +SONAR_VERSION=2025.5.0 SONAR_EDITION="developer" function usage { printf "Test SonarQube setup.\n\n" printf "\t-h|--help\t\tPrint usage\n" printf "\t-v|--verbose\t\tEnable verbose mode\n" - printf "\t-s|--sq-version\t\tSonarQube version, e.g. '10.8.0' (defaults to %s)\n" "${SONAR_VERSION}" + printf "\t-s|--sq-version\t\tSonarQube version, e.g. '2025.5.0' (defaults to %s)\n" "${SONAR_VERSION}" printf "\t-e|--sq-edition\t\tSonarQube edition, e.g. 'community' or 'enterprise' (defaults to %s)\n" "${SONAR_EDITION}" printf "\t-i|--insecure\t\tAllow insecure server connections when using SSL\n" printf "\t--verify\t\tSkips setup of local docker container and instead checks existing sonarqube setup based on ods-core.env\n" @@ -190,8 +190,7 @@ case $SONAR_EDITION in community | developer | enterprise | datacenter) expectedPlugins=("groovy:1.8" - "r:0.2.2" - "communityrust:0.2.5" ) + "r:0.2.2") ;; *)