/** * Author: Gerardo Viedma (gviedma@theserverlabs.com) * The following Groovy script collects Sonar Metrics for a Hudson job, * sending an informative email with the results. * The following Hudson parameters are supported: * * projectName: Project name that will appear in emails sent from Hudson. * sonarProjectId: Internal project ID used by Sonar. * sonarUrl: URL for the Sonar server. * emailRecipients: email addresses for recipients of Sonar metrics summary. * rulesComplianceThreshold: minimum percentage of rule compliance for validating a build. A value of false means this metric will not be enforced. * blockerThreshold: maximum number of blocker violations for validating a build. A value of false means this metric will not be enforced. * criticalThreshold: maximum number of critical violations for validating a build. A value of false means this metric will not be enforced. * majorThreshold: maximum number of major violations for validating a build. A value of false means this metric will not be enforced. * codeCoverageThreshold: minimum percentage of code coverage for unit tests for validating a build. A value of false means this metric will not be enforced. * testSuccessThreshold: minimum percentage of successful unit tests for validating a build. A value of false means this metric will not be enforced. * violationsThreshold: maximum number of violations of all type for validating a build. A value of false means this metric will not be enforced. * **/ import javax.mail.internet.MimeMessage import javax.mail.Message import javax.mail.internet.InternetAddress import javax.mail.Transport MAX_THRESHOLD_TYPE = 'MAX' MIN_THRESHOLD_TYPE = 'MIN' // definiciones de métricas de sonar relevantes metricDefinitions = [ 'violations_density':[name:'Rules Compliance', thresholdName:'rulesComplianceThreshold', thresholdType:MIN_THRESHOLD_TYPE], 'violations':[name:'Total Violations', thresholdName:'violationsThreshold', thresholdType:MAX_THRESHOLD_TYPE], 'blocker_violations':[name:'Blocker Violations', thresholdName:'blockerThreshold', thresholdType:MAX_THRESHOLD_TYPE], 'critical_violations':[name:'Critical Violations', thresholdName:'criticalThreshold', thresholdType:MAX_THRESHOLD_TYPE], 'major_violations':[name:'Major Violations', thresholdName:'majorThreshold', thresholdType:MAX_THRESHOLD_TYPE], 'coverage':[name:'Code Coverage', thresholdName:'codeCoverageThreshold', thresholdType:MIN_THRESHOLD_TYPE], 'test_success_density':[name:'Test Success', thresholdName:'testSuccessThreshold', thresholdType:MIN_THRESHOLD_TYPE] ] def safeParse(value) { try { return Double.parseDouble(value) } catch (e) { return -1 } } def getNameForMetric(metricId) { metricDefinitions.find { it.key == metricId }.value.name } 88 def getThresholdTypeForMetric(metricId) { metricDefinitions.find { it.key == metricId }.value.thresholdType } def prettyPrintFailures(failedMetrics, isHtml) { newline = '\n' if (isHtml) newline = '
' prettyText = '' failedMetrics.each { metricId, error -> metricName = getNameForMetric(metricId) prettyText += "The following metric failed ${metricName}:${newline}${error}${newline}${newline}" } return prettyText } def prettyPrintMetrics(sonarMetrics, isHtml) { newline = '\n' if (isHtml) newline = '
' prettyText = '' sonarMetrics.each { metricId, value -> metricName = getNameForMetric(metricId) prettyText += "${metricName}: ${value}${newline}" } return prettyText } def addSonarMetric(metricsMap, sonarMetricId, sonarResource) { metric = sonarResource.msr.find { it.key == sonarMetricId } metricsMap[sonarMetricId] = safeParse(metric.val.text()) } def getSonarXml(sonarUrl, sonarProjectId) { // get sonar metrics through REST interface metricsParam = metricDefinitions.keySet().join(',') sonarUrl = "${sonarUrl}/api/resources?resource=${sonarProjectId}&format=xml&metrics=${metricsParam}" return sonarUrl.toURL().text } def getSonarMetrics(sonarUrl, sonarProjectId) { sonarMetrics = [:] sonarXml = getSonarXml(sonarUrl, sonarProjectId) // parse XML resources = new XmlSlurper().parseText(sonarXml) projectResource = resources.resource[0] metricDefinitions.each { addSonarMetric(sonarMetrics, it.key, projectResource) } return sonarMetrics } def verifyMetric(metricId, sonarMetrics, sonarThresholds, failedMetrics) { metricValue = sonarMetrics[metricId] thresholdValue = sonarThresholds[metricId] thresholdType = getThresholdTypeForMetric(metricId) if (thresholdValue != -1) { if (thresholdType == MAX_THRESHOLD_TYPE) { if (metricValue > thresholdValue) failedMetrics[metricId] = "The metric has a value ${metricValue} greater than the maximum threshold of ${thresholdValue}." } else if (thresholdType == MIN_THRESHOLD_TYPE) { if (metricValue < thresholdValue) failedMetrics[metricId] = "The metric has a value ${metricValue} less than the minimum threshold of ${thresholdValue}." } else { failedMetrics[metricId] = "An invalid configuration was detected for ${metricValue}." } } } def getSonarThresholds() { sonarThresholds = [:] metricDefinitions.each { metricId, metricDefinition -> sonarThresholds[metricId] = safeParse(manager.build.getBuildVariables()[metricDefinition.thresholdName]) } return sonarThresholds } def verifyMetrics(sonarMetrics) { failedMetrics = [:] sonarThresholds = getSonarThresholds() metricDefinitions.each { metricId, metricDefinition -> verifyMetric(metricId, sonarMetrics, sonarThresholds, failedMetrics) } return failedMetrics } def sendMail(session, subject, from, to, body) { msg = new MimeMessage(session) msg.setSubject(subject) msg.setSentDate(new Date()) msg.setFrom(InternetAddress.parse(from, false)[0]) msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false)) msg.setText(body) Transport.send(msg) } def sendSuccessMail(sonarUrl, projectName, emailRecipients, sonarMetrics) { body = """ The Sonar metrics for ${projectName} were processed correctly. ${sonarUrl} The key metrics analyzed are summarized below: ${prettyPrintMetrics(sonarMetrics, false)} Regards, Your Hudson Job """ mailSession = hudson.tasks.Mailer.descriptor().createSession() mailSubject = "Sonar metrics for ${projectName} (PASS)" mailFrom = 'hudson@rockwellcollins.com' sendMail(mailSession, mailSubject, mailFrom, emailRecipients, body) } def sendFailureMail(sonarUrl, projectName, emailRecipients, sonarMetrics, failedMetrics) { body = """ The Sonar metrics for ${projectName} were processed correctly. ${sonarUrl} The key metrics analyzed are summarized below: ${prettyPrintMetrics(sonarMetrics, false)} The following quality problems were detected: ${prettyPrintFailures(failedMetrics, false)} Regards, Your Hudson Job """ mailSession = hudson.tasks.Mailer.descriptor().createSession() mailSubject = "Sonar metrics for ${projectName} (FAIL)" mailFrom = 'hudson@example.com' sendMail(mailSession, mailSubject, mailFrom, emailRecipients, body) } def doSuccess(sonarUrl, projectName, emailRecipients, sonarMetrics) { manager.addShortText("pass",'white','green', '1px', 'gray') manager.createSummary("gear2.gif").appendText("The following quality metrics were approved:

${prettyPrintMetrics(sonarMetrics, true)}", false) // send email summary sendSuccessMail(sonarUrl, projectName, emailRecipients, sonarMetrics) } def doFailure(projectName, emailRecipients, sonarMetrics, failedMetrics) { manager.addWarningBadge("There were ${failedMetrics.size()} quality metric violations") manager.addShortText("fail",'white','red', '1px', 'gray') summary = manager.createSummary("warning.gif") summary.appendText("The following quality metrics were processsed:

${prettyPrintMetrics(sonarMetrics, true)}", false) summary.appendText("
The following quality metrics failed:

${prettyPrintFailures(failedMetrics, true)}", false, false, false, "red") // send email summary sendFailureMail(projectName, emailRecipients, sonarMetrics, failedMetrics) manager.buildFailure() } // main script starts here buildVars = manager.build.getBuildVariables() // configuration variables projectName = buildVars['projectName'] emailRecipients = buildVars['emailRecipients'] sonarProjectId = buildVars['sonarProjectId'] sonarUrl = buildVars['sonarUrl'] // process sonar metrics sonarMetrics = getSonarMetrics(sonarUrl, sonarProjectId) // verify thresholds failedMetrics = verifyMetrics(sonarMetrics) if (failedMetrics) { doFailure("${sonarUrl}/dashboard/index/1", projectName, emailRecipients, sonarMetrics, failedMetrics) } else { doSuccess("${sonarUrl}/dashboard/index/1", projectName, emailRecipients, sonarMetrics) }