Integración de SonarQube con Jenkins Pipeline mediante API REST

Introducción En este capítulo se explica cómo configurar calidad de código personalizada desde la primera ejecución del pipeilne utilizando las API REST de SonarQube. Se abordará la creación de proyectos, asignación de perfiles de calidad y configuración de quality gates mediante programación.

Documentación oficial de API: http://192.168.1.134:9000/web_api

  1. Biblioteca de funciones para SonarQube API Crear un archivo Groovy con las funciones necesarias para interactuar con SonarQube. Nota: actualizar la dirección del servidor y las credenciales según corresponda.
package org.devops.qa


// Cliente HTTP genérico
class SonarClient {
    
    def httpRequest(method, endpoint, payload) {
        def serverUrl = "http://192.168.1.134:9000/api"
        
        def response = httpRequest authentication: 'sonar-admin',
                httpMode: method, 
                contentType: "APPLICATION_JSON",
                consoleLogResponseBody: true,
                ignoreSslErrors: true, 
                requestBody: payload,
                url: "${serverUrl}/${endpoint}"
        
        return response
    }
}

// Obtener estado del quality gate
def fetchGateStatus(projectKey) {
    def client = new SonarClient()
    def endpoint = "project_branches/list?project=${projectKey}"
    def rawResponse = client.httpRequest("GET", endpoint, '')
    
    def data = readJSON text: """${rawResponse.content}"""
    def status = data["branches"][0]["status"]["qualityGateStatus"]
    
    return status
}

// Obtener estado con soporte para branches
def fetchGateStatus(projectKey, branchName) {
    def client = new SonarClient()
    def endpoint = "qualitygates/project_status?projectKey=${projectKey}&branch=${branchName}"
    def rawResponse = client.httpRequest("GET", endpoint, '')
    
    def data = readJSON text: """${rawResponse.content}"""
    def status = data["projectStatus"]["status"]
    
    return status
}

// Verificar existencia de proyecto
def checkProjectExists(projectKey) {
    def client = new SonarClient()
    def endpoint = "projects/search?projects=${projectKey}"
    def rawResponse = client.httpRequest("GET", endpoint, '')
    
    def data = readJSON text: """${rawResponse.content}"""
    def total = data["paging"]["total"]
    
    return total.toString() != "0"
}

// Crear nuevo proyecto
def initializeProject(projectKey) {
    def client = new SonarClient()
    def endpoint = "projects/create?name=${projectKey}&project=${projectKey}"
    def response = client.httpRequest("POST", endpoint, '')
    println(response)
}

// Asignar perfil de calidad
def assignQualityProfile(projectKey, language, profileName) {
    def client = new SonarClient()
    def endpoint = "qualityprofiles/add_project?language=${language}&project=${projectKey}&qualityProfile=${profileName}"
    def response = client.httpRequest("POST", endpoint, '')
    println(response)
}

// Obtener ID del quality gate
def retrieveGateId(gateName) {
    def client = new SonarClient()
    def endpoint = "qualitygates/show?name=${gateName}"
    def rawResponse = client.httpRequest("GET", endpoint, '')
    
    def data = readJSON text: """${rawResponse.content}"""
    def gateId = data["id"]
    
    return gateId
}

// Configurar quality gate para proyecto
def setProjectGate(projectKey, gateName) {
    def gateId = retrieveGateId(gateName)
    def client = new SonarClient()
    def endpoint = "qualitygates/select?gateId=${gateId}&projectKey=${projectKey}"
    def response = client.httpRequest("POST", endpoint, '')
    println(response)
}

  1. Pipeline Jenkins Ejemplo de Jenkinsfile que integra las funciones anteriores. Nota: el nombre del quality profile debe adaptarse al entorno.
#!groovy

@Library('jenkinslibrary@master') _

// Instanciar bibliotecas compartidas
def builder = new org.devops.build()
def publisher = new org.devops.deploy()
def utils = new org.devops.tools()
def vcs = new org.devops.gitlab()
def notifier = new org.devops.toemail()
def scanner = new org.devops.sonarqube()
def qaClient = new org.devops.qa.SonarClient()


// Parámetros del pipeline
String sourceUrl = "${env.srcUrl}"
String branchRef = "${env.branchName}"
String buildType = "${env.buildType}"
String buildScript = "${env.buildShell}"


// Manejo de trigger desde Gitlab
if("${runOpts}" == "GitlabPush"){
    branchRef = branchRef - "refs/heads/"
    currentBuild.description = "Ejecutado por ${userName} ${branchRef}"
    vcs.updateCommitStatus(projectId, commitSha, "running")
}


pipeline {
    agent any

    stages {
        stage("Obtener fuentes"){
            steps{
                script{
                    println("${branchRef}")
                    utils.log("Obteniendo código fuente", "green")
                    checkout scmGit(
                        branches: [[name: "${branchRef}"]], 
                        extensions: [], 
                        userRemoteConfigs: [[
                            credentialsId: 'e7054d0e-275e-48ca-8188-e69da2faffb8', 
                            url: "${sourceUrl}"
                        ]]
                    )
                }
            }
        }
        
        stage("Compilar"){
            steps{
                script {
                    utils.log("Ejecutando compilación", "blue")
                    builder.compile(buildType, buildScript)
                }
            }
        }
        
        stage("Análisis de calidad"){
            steps {
                script{
                    utils.log("Verificando proyecto en SonarQube", "green")
                    def exists = qaClient.checkProjectExists("${JOB_NAME}")
                    println(exists)
                    
                    if (!exists){
                        println("Proyecto ${JOB_NAME} no existe, creando...")
                        qaClient.initializeProject("${JOB_NAME}")
                    } else {
                        println("Proyecto ${JOB_NAME} ya está registrado")
                    }                    
                    
                    utils.log("Asignando perfil de calidad", "green")
                    def qualityProfile = "demo-java"
                    qaClient.assignQualityProfile("${JOB_NAME}", "java", qualityProfile)
                
                    utils.log("Configurando quality gate", "green")
                    qaClient.setProjectGate("${JOB_NAME}", qualityProfile)
                
                    utils.log("Ejecutando análisis estático", "green")
                    scanner.analyze("test", "${JOB_NAME}", "${JOB_NAME}", "src")
                    
                    sleep 30
                    
                    utils.log("Obteniendo resultados", "green")
                    def gateStatus = qaClient.fetchGateStatus("${JOB_NAME}")
                    
                    println(gateStatus)
                    if (gateStatus.toString() == "ERROR"){
                        notifier.send("¡Calidad de código inaceptable!", userEmail)
                        error "Quality gate falló"
                    } else {
                        println(gateStatus)
                    }                    
                }
            }
        }
    }
    
    post {
        always{
            script{
               println("Ejecución finalizada")
            }
        }
        
        success{
            script{
                println("Pipeline exitoso")
                vcs.updateCommitStatus(projectId, commitSha, "success")
                notifier.send("Pipeline completado exitosamente", userEmail)
            }
        }
        
        aborted{
            script{
                println("Pipeline cancelado")
                vcs.updateCommitStatus(projectId, commitSha, "canceled")
            }
        }
        
        failure{
            script{
                println("Pipeline fallido")
                vcs.updateCommitStatus(projectId, commitSha, "failed")
                notifier.send("Pipeline fallido", userEmail)
            }
        }
    }
}

  1. Verificación Para validar el funcionamiento correcto, eliminar primero el proyecto de SonarQube si ya existe, y posteriormente ejecutar el pipeline para observar la creación automática y cnofiguración.

Etiquetas: Jenkins Pipeline sonarqube quality-gate Groovy

Publicado el 7-13 10:01