github.com/jaylevin/jenkins-library@v1.230.4/vars/mailSendNotification.groovy (about)

     1  import static com.sap.piper.Prerequisites.checkScript
     2  
     3  import com.sap.piper.ConfigurationHelper
     4  import com.sap.piper.GenerateDocumentation
     5  import com.sap.piper.Utils
     6  import groovy.text.GStringTemplateEngine
     7  import groovy.transform.Field
     8  
     9  @Field String STEP_NAME = getClass().getName()
    10  @Field Set GENERAL_CONFIG_KEYS = [
    11      /**
    12       * Only if `notifyCulprits` is set:
    13       * Credentials if the repository is protected.
    14       * @possibleValues Jenkins credentials id
    15       */
    16      'gitSshKeyCredentialsId'
    17  ]
    18  @Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
    19      /**
    20       * Set the build result used to determine the mail template.
    21       * default `currentBuild.result`
    22       */
    23      'buildResult',
    24      /**
    25       * Only if `notifyCulprits` is set:
    26       * Defines a dedicated git commitId for culprit retrieval.
    27       * default `commonPipelineEnvironment.getGitCommitId()`
    28       */
    29      'gitCommitId',
    30      /**
    31       * Only if `notifyCulprits` is set:
    32       * Repository url used to retrieve culprit information.
    33       * default `commonPipelineEnvironment.getGitSshUrl()`
    34       */
    35      'gitUrl',
    36      /**
    37       * defines if the console log file should be attached to the notification mail.
    38       * @possibleValues `true`, `false`
    39       */
    40      'notificationAttachment',
    41      /**
    42       * A space-separated list of recipients that always get the notification.
    43       */
    44      'notificationRecipients',
    45      /**
    46       * Notify all committers since the last successful build.
    47       * @possibleValues `true`, `false`
    48       */
    49      'notifyCulprits',
    50      /**
    51       * Number of log line which are included in the email body.
    52       */
    53      'numLogLinesInBody',
    54      /**
    55       * The project name used in the email subject.
    56       * default `currentBuild.fullProjectName`
    57       */
    58      'projectName',
    59      /**
    60       * Needs to be set to `true` if step is used outside of a node context, e.g. post actions in a declarative pipeline script.
    61       * default `false`
    62       * @possibleValues `true`, `false`
    63       */
    64      'wrapInNode'
    65  ])
    66  @Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
    67  
    68  /**
    69   * Sends notifications to all potential culprits of a current or previous build failure and to fixed list of recipients.
    70   * It will attach the current build log to the email.
    71   *
    72   * Notifications are sent in following cases:
    73   *
    74   * * current build failed or is unstable
    75   * * current build is successful and previous build failed or was unstable
    76   */
    77  @GenerateDocumentation
    78  void call(Map parameters = [:]) {
    79      handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters, allowBuildFailure: true) {
    80          def script = checkScript(this, parameters) ?: this
    81          String stageName = parameters.stageName ?: env.STAGE_NAME
    82  
    83          // load default & individual configuration
    84          Map config = ConfigurationHelper.newInstance(this)
    85              .loadStepDefaults([:], stageName)
    86              .mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
    87              .mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
    88              .mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS)
    89              .mixin(
    90                  projectName: script.currentBuild.fullProjectName,
    91                  displayName: script.currentBuild.displayName,
    92                  buildResult: script.currentBuild.result,
    93                  gitUrl: script.commonPipelineEnvironment.getGitSshUrl(),
    94                  gitCommitId: script.commonPipelineEnvironment.getGitCommitId()
    95              )
    96              .mixin(parameters, PARAMETER_KEYS)
    97              .use()
    98  
    99          new Utils().pushToSWA([step: STEP_NAME], config)
   100  
   101          //this takes care that terminated builds due to milestone-locking do not cause an error
   102          if (script.commonPipelineEnvironment.getBuildResult() == 'ABORTED') return
   103  
   104          def subject = "${config.buildResult}: Build ${config.projectName} ${config.displayName}"
   105          def log = ''
   106          def mailTemplate
   107          if (config.buildResult == 'UNSTABLE' || config.buildResult == 'FAILURE'){
   108              mailTemplate = 'com.sap.piper/templates/mailFailure.html'
   109              log = script.currentBuild.rawBuild.getLog(config.numLogLinesInBody).join('\n')
   110          }else if(hasRecovered(config.buildResult, script.currentBuild)){
   111              mailTemplate = 'com.sap.piper/templates/mailRecover.html'
   112              subject += ' is back to normal'
   113          }
   114          if(mailTemplate){
   115              def mailContent = GStringTemplateEngine.newInstance().createTemplate(libraryResource(mailTemplate)).make([env: env, log: log]).toString()
   116              def recipientList = ''
   117              if(config.notifyCulprits){
   118                  if (!config.gitUrl) {
   119                      echo "[${STEP_NAME}] no gitUrl available, -> exiting without sending mails"
   120                      return
   121                  }
   122                  recipientList += getCulpritCommitters(config, script.currentBuild)
   123              }
   124              if(config.notificationRecipients)
   125                  recipientList +=  " ${config.notificationRecipients}"
   126              emailext(
   127                  mimeType: 'text/html',
   128                  subject: subject.trim(),
   129                  body: mailContent,
   130                  to: recipientList.trim(),
   131                  recipientProviders: [requestor()],
   132                  attachLog: config.notificationAttachment
   133              )
   134          }
   135      }
   136  }
   137  
   138  def getNumberOfCommits(buildList){
   139      def numCommits = 0
   140      if(buildList != null)
   141          for(actBuild in buildList) {
   142              def changeLogSets = actBuild.getChangeSets()
   143              if(changeLogSets != null)
   144                  for(changeLogSet in changeLogSets)
   145                      for(change in changeLogSet)
   146                          numCommits++
   147          }
   148      return numCommits
   149  }
   150  
   151  def getCulpritCommitters(config, currentBuild) {
   152      def recipients
   153      def buildList = []
   154      def build = currentBuild
   155  
   156      if (build != null) {
   157          // At least add the current build
   158          buildList.add(build)
   159  
   160          // Now collect FAILED or ABORTED ones
   161          build = build.getPreviousBuild()
   162          while (build != null) {
   163              if (build.getResult() != 'SUCCESS') {
   164                  buildList.add(build)
   165              } else {
   166                  break
   167              }
   168              build = build.getPreviousBuild()
   169          }
   170      }
   171      def numberOfCommits = getNumberOfCommits(buildList)
   172      if(config.wrapInNode){
   173          node(){
   174              try{
   175                  recipients = getCulprits(config, env.BRANCH_NAME, numberOfCommits)
   176              }finally{
   177                  deleteDir()
   178              }
   179          }
   180      }else{
   181          try{
   182              recipients = getCulprits(config, env.BRANCH_NAME, numberOfCommits)
   183          }finally{
   184              deleteDir()
   185          }
   186      }
   187      echo "[${STEP_NAME}] last ${numberOfCommits} commits revealed following responsibles ${recipients}"
   188      return recipients
   189  }
   190  
   191  def getCulprits(config, branch, numberOfCommits) {
   192      try {
   193          if (branch?.startsWith('PR-')) {
   194              //special GitHub Pull Request handling
   195              deleteDir()
   196              sshagent(
   197                  credentials: [config.gitSshKeyCredentialsId],
   198                  ignoreMissing: true
   199              ) {
   200                  def pullRequestID = branch.replaceAll('PR-', '')
   201                  def localBranchName = "pr" + pullRequestID
   202                  sh """git init
   203      git fetch ${config.gitUrl} pull/${pullRequestID}/head:${localBranchName} > /dev/null 2>&1
   204      git checkout -f ${localBranchName} > /dev/null 2>&1
   205      """
   206              }
   207          } else {
   208              //standard git/GitHub handling
   209              if (config.gitCommitId) {
   210                  deleteDir()
   211                  sshagent(
   212                      credentials: [config.gitSshKeyCredentialsId],
   213                      ignoreMissing: true
   214                  ) {
   215                      sh """git clone ${config.gitUrl} .
   216      git checkout ${config.gitCommitId} > /dev/null 2>&1"""
   217                  }
   218              } else {
   219                  def retCode = sh(returnStatus: true, script: 'git log > /dev/null 2>&1')
   220                  if (retCode != 0) {
   221                      echo "[${STEP_NAME}] No git context available to retrieve culprits"
   222                      return ''
   223                  }
   224              }
   225          }
   226  
   227          def recipients = sh(returnStdout: true, script: "git log -${numberOfCommits} --first-parent --pretty=format:'%ae %ce'")
   228          return getDistinctRecipients(recipients)
   229      } catch(err) {
   230          echo "[${STEP_NAME}] Culprit retrieval from git failed with '${err.getMessage()}'. Please make sure to configure gitSshKeyCredentialsId. So far, only fixed list of recipients is used."
   231          return ''
   232      }
   233  }
   234  
   235  def getDistinctRecipients(recipients){
   236      def result
   237      def recipientAddresses = recipients.split()
   238      def knownAddresses = new HashSet<String>()
   239      if(recipientAddresses != null) {
   240          for(address in recipientAddresses) {
   241              address = address.trim()
   242              if(address
   243                  && address.contains("@")
   244                  && !address.contains("noreply")
   245                  && !knownAddresses.contains(address)) {
   246                  knownAddresses.add(address)
   247              }
   248          }
   249          result = knownAddresses.join(" ")
   250      }
   251      return result
   252  }
   253  
   254  def hasRecovered(buildResult, currentBuild){
   255      return buildResult == 'SUCCESS' && currentBuild.getPreviousBuild()?.result != 'SUCCESS'
   256  }