github.com/verrazzano/verrazzano@v1.7.1/ci/JenkinsfileTestTrigger (about)

     1  // Copyright (c) 2020, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  import groovy.transform.Field
     5  
     6  @Field
     7  def SUSPECT_LIST = ""
     8  
     9  pipeline {
    10      options {
    11          timeout(time: 5, unit: 'HOURS')
    12          skipDefaultCheckout true
    13      }
    14  
    15      agent {
    16         docker {
    17              image "${RUNNER_DOCKER_IMAGE}"
    18              args "${RUNNER_DOCKER_ARGS}"
    19              registryUrl "${RUNNER_DOCKER_REGISTRY_URL}"
    20              registryCredentialsId 'ocir-pull-and-push-account'
    21              label "pipeline-job-large"
    22          }
    23      }
    24  
    25      // Use cases:
    26      //        1) (automatic) master, release* will trigger this job by default on successful runs and supply GIT_COMMIT_TO_USE with the exact GIT commit to use for all testing (same as the upstream build)
    27      //        2) (automatic) branch builds with TRIGGER_FULL_TESTS enabled will trigger this job by default on successful runs and supply GIT_COMMIT_TO_USE with the exact GIT commit to use for all testing (same as the upstream build)
    28      //        3) (manual) any branch should be able to call this “trigger” job with a commit that was previously built and has a valid GIT commit hash. In this case the manual job
    29      //           must be started from the desired branch using Build with Parameters, the GIT_COMMIT_TO_USE must supply the GIT commit hash from the previous build, and VERRAZZANO_OPERATOR_IMAGE must NOT BE SPECIFIED or be NONE
    30      //           This allows one to rerun the tests without rerunning the upstream build (ie: if intermittent test issue occurred)
    31      //        4) (manual) any branch should be able to call this “trigger” job with the current head of their branch, pointing to a previously built operator image. This is useful if you are adding/fixing test cases where the
    32      //           operator image was built already (from master, or your branch) and only want to run the tests using it without running the full build. This is not a super likely situation (more likely individual test jobs
    33      //           will be manually kicked off rather than all of them). To accomplish this, specify GIT_COMMIT_TO_USE=NONE, and VERRAZZANO_OPERATOR_IMAGE=image-to-use
    34      parameters {
    35          string (name: 'GIT_COMMIT_TO_USE',
    36                          defaultValue: 'NONE',
    37                          description: 'This is the full git commit hash from the source build to be used for all jobs. A full pipeline specifies a valid commit hash here. NONE can be used for manually triggered jobs, however even for those a commit hash value is preferred to be supplied',
    38                          trim: true)
    39          choice (description: 'Predefined config permutations for Verrazzano installation used by upgrade path pipeline. Prod profile is the default profile for NONE', name: 'VZ_INSTALL_CONFIG',
    40                  choices: ["NONE", "dev-kind-persistence"])
    41          string (name: 'VERRAZZANO_OPERATOR_IMAGE',
    42                          defaultValue: 'NONE',
    43                          description: 'This is for manually testing only where someone needs to use a specific operator image, otherwise the default value of NONE is used',
    44                          trim: true)
    45          string (name: 'WILDCARD_DNS_DOMAIN',
    46                          defaultValue: 'nip.io',
    47                          description: 'This is the wildcard DNS domain',
    48                          trim: true)
    49          string (name: 'TAGGED_TESTS',
    50                  defaultValue: '',
    51                  description: 'A comma separated list of build tags for tests that should be executed (e.g. unstable_test). Default:',
    52                  trim: true)
    53          string (name: 'INCLUDED_TESTS',
    54                  defaultValue: '.*',
    55                  description: 'A regex matching any fully qualified test file that should be executed (e.g. examples/helidon/). Default: .*',
    56                  trim: true)
    57          string (name: 'EXCLUDED_TESTS',
    58                  defaultValue: '_excluded_test',
    59                  description: 'A regex matching any fully qualified test file that should not be executed (e.g. multicluster/|_excluded_test). Default: _excluded_test',
    60                  trim: true)
    61          string (name: 'CONSOLE_REPO_BRANCH',
    62                  defaultValue: '',
    63                  description: 'The branch to check out after cloning the console repository.',
    64                  trim: true)
    65          booleanParam (description: 'This will FORCE a stable commit update without running any tests. This is normally ONLY used for debugging, it can be used to force a manual value into object storage', name: 'FORCE_STABLE_COMMIT_UPDATE', defaultValue: false)
    66      }
    67  
    68      environment {
    69          CLEAN_BRANCH_NAME = "${env.BRANCH_NAME.replace("/", "%2F")}"
    70          GOPATH = '/home/opc/go'
    71          GO_REPO_PATH = "${GOPATH}/src/github.com/verrazzano"
    72          OCI_CLI_AUTH="instance_principal"
    73          OCI_OS_NAMESPACE = credentials('oci-os-namespace')
    74          PROMETHEUS_GW_URL = credentials('prometheus-dev-url')
    75          SERVICE_KEY = credentials('PAGERDUTY_SERVICE_KEY')
    76      }
    77  
    78      stages {
    79          stage('Clean workspace and checkout') {
    80              steps {
    81                  sh """
    82                      echo "${NODE_LABELS}"
    83                  """
    84  
    85                  // REVIEW: I'm not sure that we actually need to fetch the sources here, but I'm doing here as it was easier
    86                  // to test working with the SCM checkout settings starting from this job. We should be able to trigger this job
    87                  // with parameters directly (ie: based on a previous build), in that situation doing this gives us a single point
    88                  // to ensure the commit matches what was intended before triggering a bunch of downstream jobs that will
    89                  // all fail if it wasn't correct. So we may want to keep it here unless there is a compelling reason not to do so.
    90                  // I haven't looked at the executor resource usage yet in all of this, so it may be that could have constraints for
    91                  // using flyweight executors (still need to look at that)
    92                  script {
    93                      if (params.GIT_COMMIT_TO_USE == "NONE") {
    94                          echo "Specific GIT commit was not specified, use current head"
    95                          def scmInfo = checkout([
    96                              $class: 'GitSCM',
    97                              branches: [[name: env.BRANCH_NAME]],
    98                              doGenerateSubmoduleConfigurations: false,
    99                              extensions: [],
   100                              submoduleCfg: [],
   101                              userRemoteConfigs: [[url: env.SCM_VERRAZZANO_GIT_URL]]])
   102                          env.GIT_COMMIT = scmInfo.GIT_COMMIT
   103                          env.GIT_BRANCH = scmInfo.GIT_BRANCH
   104                      } else {
   105                          echo "SCM checkout of ${params.GIT_COMMIT_TO_USE}"
   106                          def scmInfo = checkout([
   107                              $class: 'GitSCM',
   108                              branches: [[name: params.GIT_COMMIT_TO_USE]],
   109                              doGenerateSubmoduleConfigurations: false,
   110                              extensions: [],
   111                              submoduleCfg: [],
   112                              userRemoteConfigs: [[url: env.SCM_VERRAZZANO_GIT_URL]]])
   113                          env.GIT_COMMIT = scmInfo.GIT_COMMIT
   114                          env.GIT_BRANCH = scmInfo.GIT_BRANCH
   115                          // If the commit we were handed is not what the SCM says we are using, fail
   116                          if (!env.GIT_COMMIT.equals(params.GIT_COMMIT_TO_USE)) {
   117                              echo "SCM didn't checkout the commit we expected. Expected: ${params.GIT_COMMIT_TO_USE}, Found: ${scmInfo.GIT_COMMIT}"
   118                              exit 1
   119                          }
   120                      }
   121                      echo "SCM checkout of ${env.GIT_BRANCH} at ${env.GIT_COMMIT}"
   122                  }
   123  
   124                  script {
   125                      echo "Generate git tags and save it to tags.txt file in the workspace"
   126                      sh """
   127                          cd ${workspace}
   128                          git tag | awk '/v1[.]/' >  tags.txt
   129                      """
   130                      def props = readProperties file: '.verrazzano-development-version'
   131                      VERRAZZANO_DEV_VERSION = props['verrazzano-development-version']
   132                      LATEST_RELEASE_VERSION = sh(returnStdout: true, script: "go run  ${WORKSPACE}/ci/tools/derive_upgrade_version.go ${workspace} latest-version-for-branch ${VERRAZZANO_DEV_VERSION}").trim()
   133                      TIMESTAMP = sh(returnStdout: true, script: "date +%Y%m%d%H%M%S").trim()
   134                      SHORT_COMMIT_HASH = sh(returnStdout: true, script: "git rev-parse --short=8 HEAD").trim()
   135                      // update the description with some meaningful info
   136                      currentBuild.description = SHORT_COMMIT_HASH + " : " + env.GIT_COMMIT + " : " + params.GIT_COMMIT_TO_USE
   137                      def currentCommitHash = env.GIT_COMMIT
   138                      def commitList = getCommitList()
   139                      withCredentials([file(credentialsId: 'jenkins-to-slack-users', variable: 'JENKINS_TO_SLACK_JSON')]) {
   140                          def userMappings = readJSON file: JENKINS_TO_SLACK_JSON
   141                          SUSPECT_LIST = getSuspectList(commitList, userMappings)
   142                          echo "Suspect list: ${SUSPECT_LIST}"
   143                      }
   144                  }
   145              }
   146          }
   147  
   148          stage ('Kick off parallel tests') {
   149              when {
   150                  allOf {
   151                      not { buildingTag() }
   152                      expression {params.FORCE_STABLE_COMMIT_UPDATE == false}
   153                  }
   154              }
   155              parallel {
   156                  stage('Multi Cluster Tests') {
   157                      steps {
   158                          retry(count: JOB_PROMOTION_RETRIES) {
   159                              script {
   160                                  build job: "/verrazzano-multi-cluster-acceptance-tests/${CLEAN_BRANCH_NAME}",
   161                                      parameters: [
   162                                          string(name: 'GIT_COMMIT_TO_USE', value: env.GIT_COMMIT),
   163                                          string(name: 'VERRAZZANO_OPERATOR_IMAGE', value: params.VERRAZZANO_OPERATOR_IMAGE),
   164                                          string(name: 'WILDCARD_DNS_DOMAIN', value: params.WILDCARD_DNS_DOMAIN),
   165                                          string(name: 'TAGGED_TESTS', value: params.TAGGED_TESTS),
   166                                          string(name: 'INCLUDED_TESTS', value: params.INCLUDED_TESTS),
   167                                          string(name: 'EXCLUDED_TESTS', value: params.EXCLUDED_TESTS),
   168                                          string(name: 'CONSOLE_REPO_BRANCH', value: params.CONSOLE_REPO_BRANCH)
   169                                      ], wait: true
   170                              }
   171                          }
   172                      }
   173                  }
   174                  stage('Upgrade Path Tests with External ES') {
   175                      steps {
   176                          retry(count: JOB_PROMOTION_RETRIES) {
   177                              script {
   178                                  def latestRelease = LATEST_RELEASE_VERSION
   179                                  build job: "/verrazzano-upgrade-path-tests/${CLEAN_BRANCH_NAME}",
   180                                      parameters: [
   181                                          string(name: 'GIT_COMMIT_TO_USE', value: env.GIT_COMMIT),
   182                                          booleanParam(name: 'EXTERNAL_ELASTICSEARCH', value: true),
   183                                          string(name: 'VERSION_FOR_INSTALL', value: latestRelease),
   184                                          string(name: 'VZ_INSTALL_CONFIG', value: params.VZ_INSTALL_CONFIG),
   185                                          string(name: 'VERRAZZANO_OPERATOR_IMAGE', value: params.VERRAZZANO_OPERATOR_IMAGE),
   186                                          string(name: 'WILDCARD_DNS_DOMAIN', value: params.WILDCARD_DNS_DOMAIN),
   187                                          string(name: 'TAGGED_TESTS', value: params.TAGGED_TESTS),
   188                                          string(name: 'INCLUDED_TESTS', value: params.INCLUDED_TESTS),
   189                                          string(name: 'EXCLUDED_TESTS', value: params.EXCLUDED_TESTS),
   190                                          string(name: 'CONSOLE_REPO_BRANCH', value: params.CONSOLE_REPO_BRANCH),
   191                                      ], wait: true
   192                              }
   193                          }
   194                      }
   195                  }
   196                  stage('Uninstall Tests') {
   197                      steps {
   198                          retry(count: JOB_PROMOTION_RETRIES) {
   199                              script {
   200                                  build job: "/verrazzano-uninstall-test/${CLEAN_BRANCH_NAME}",
   201                                      parameters: [
   202                                          string(name: 'GIT_COMMIT_TO_USE', value: env.GIT_COMMIT),
   203                                          string(name: 'VERRAZZANO_OPERATOR_IMAGE', value: params.VERRAZZANO_OPERATOR_IMAGE),
   204                                          string(name: 'WILDCARD_DNS_DOMAIN', value: params.WILDCARD_DNS_DOMAIN),
   205                                          string(name: 'TAGGED_TESTS', value: params.TAGGED_TESTS),
   206                                          string(name: 'INCLUDED_TESTS', value: params.INCLUDED_TESTS),
   207                                          string(name: 'EXCLUDED_TESTS', value: params.EXCLUDED_TESTS),
   208                                          string(name: 'CONSOLE_REPO_BRANCH', value: params.CONSOLE_REPO_BRANCH)
   209                                      ], wait: true
   210                              }
   211                          }
   212                      }
   213                  }
   214                  stage('OCI DNS tests') {
   215                      steps {
   216                          retry(count: JOB_PROMOTION_RETRIES) {
   217                              script {
   218                                  build job: "/verrazzano-new-oci-dns-acceptance-tests/${CLEAN_BRANCH_NAME}",
   219                                      parameters: [
   220                                          string(name: 'GIT_COMMIT_TO_USE', value: env.GIT_COMMIT),
   221                                          string(name: 'VERRAZZANO_OPERATOR_IMAGE', value: params.VERRAZZANO_OPERATOR_IMAGE),
   222                                          booleanParam(name: 'CREATE_CLUSTER_USE_CALICO', value: true),
   223                                          string(name: 'TAGGED_TESTS', value: params.TAGGED_TESTS),
   224                                          string(name: 'INCLUDED_TESTS', value: params.INCLUDED_TESTS),
   225                                          string(name: 'EXCLUDED_TESTS', value: params.EXCLUDED_TESTS),
   226                                      ], wait: true
   227                              }
   228                          }
   229                      }
   230                  }
   231                  stage('Kind No Istio Injection Tests') {
   232                      steps {
   233                          retry(count: JOB_PROMOTION_RETRIES) {
   234                              script {
   235                                  build job: "/verrazzano-no-injection-tests/${CLEAN_BRANCH_NAME}",
   236                                      parameters: [
   237                                          string(name: 'KUBERNETES_CLUSTER_VERSION', value: '1.27'),
   238                                          string(name: 'GIT_COMMIT_TO_USE', value: env.GIT_COMMIT),
   239                                          string(name: 'VERRAZZANO_OPERATOR_IMAGE', value: params.VERRAZZANO_OPERATOR_IMAGE),
   240                                          string(name: 'WILDCARD_DNS_DOMAIN', value: params.WILDCARD_DNS_DOMAIN),
   241                                          string(name: 'TAGGED_TESTS', value: params.TAGGED_TESTS),
   242                                          string(name: 'INCLUDED_TESTS', value: params.INCLUDED_TESTS),
   243                                          string(name: 'EXCLUDED_TESTS', value: params.EXCLUDED_TESTS),
   244                                          booleanParam(name: 'RUN_COHERENCE_TESTS', value: true),
   245                                          string(name: 'CONSOLE_REPO_BRANCH', value: params.CONSOLE_REPO_BRANCH)
   246                                      ], wait: true
   247                              }
   248                          }
   249                      }
   250                  }
   251                  stage('Upgrade Path Tests') {
   252                      steps {
   253                          retry(count: JOB_PROMOTION_RETRIES) {
   254                              script {
   255                                  def latestRelease = LATEST_RELEASE_VERSION
   256                                  echo "Printing latest release version: ${latestRelease}"
   257                                  build job: "/verrazzano-upgrade-path-tests/${CLEAN_BRANCH_NAME}",
   258                                      parameters: [
   259                                          string(name: 'GIT_COMMIT_TO_USE', value: env.GIT_COMMIT),
   260                                          string(name: 'VERSION_FOR_INSTALL', value: latestRelease),
   261                                          string(name: 'VZ_INSTALL_CONFIG', value: params.VZ_INSTALL_CONFIG),
   262                                          string(name: 'VERRAZZANO_OPERATOR_IMAGE', value: params.VERRAZZANO_OPERATOR_IMAGE),
   263                                          string(name: 'WILDCARD_DNS_DOMAIN', value: params.WILDCARD_DNS_DOMAIN),
   264                                          string(name: 'TAGGED_TESTS', value: params.TAGGED_TESTS),
   265                                          string(name: 'INCLUDED_TESTS', value: params.INCLUDED_TESTS),
   266                                          string(name: 'EXCLUDED_TESTS', value: params.EXCLUDED_TESTS),
   267                                          string(name: 'CONSOLE_REPO_BRANCH', value: params.CONSOLE_REPO_BRANCH),
   268                                      ], wait: true
   269                              }
   270                          }
   271                      }
   272                  }
   273              }
   274          }
   275      }
   276      post {
   277          success {
   278              // WARNING: Only do this object storage update here, do not copy to other jobs
   279              storePipelineArtifacts()
   280          }
   281          failure {
   282              script {
   283                  failedOrAborted()
   284              }
   285          }
   286          aborted {
   287              script {
   288                  failedOrAborted()
   289              }
   290          }
   291      }
   292  }
   293  
   294  def failedOrAborted() {
   295      if (env.JOB_NAME == "verrazzano-push-triggered-acceptance-tests/master" || env.JOB_NAME ==~ "verrazzano-push-triggered-acceptance-tests/release-1.*") {
   296          if (isPagerDutyEnabled()) {
   297              pagerduty(resolve: false, serviceKey: "$SERVICE_KEY", incDescription: "Verrazzano: ${env.JOB_NAME} - Failed", incDetails: "Job Failed - \"${env.JOB_NAME}\" build: ${env.BUILD_NUMBER}\n\nView the log at:\n ${env.BUILD_URL}\n\nBlue Ocean:\n${env.RUN_DISPLAY_URL}")
   298          }
   299          slackSend ( channel: "$SLACK_ALERT_CHANNEL", message: "Job Failed - \"${env.JOB_NAME}\" build: ${env.BUILD_NUMBER}\n\nView the log at:\n ${env.BUILD_URL}\n\nBlue Ocean:\n${env.RUN_DISPLAY_URL}\n\nSuspects:\n${SUSPECT_LIST}" )
   300      }
   301  }
   302  
   303  def isPagerDutyEnabled() {
   304      // this controls whether PD alerts are enabled
   305      if (NOTIFY_PAGERDUTY_TRIGGERED_FAILURES.equals("true")) {
   306          echo "Pager-Duty notifications enabled via global override setting"
   307          return true
   308      }
   309      return false
   310  }
   311  
   312  // Called in final post success block of pipeline
   313  // WARNING: Only do this object storage update here, do not copy to other jobs
   314  def storePipelineArtifacts() {
   315      script {
   316          // If this was clean, record the commit in object store so the periodic test jobs can run against that rather than the head of the branch
   317          // NOTE: Normally master and release-* branches are the only ones doing this, but when we need to test out pipeline changes we can make use
   318          // as well
   319          LAST_STABLE_COMMIT=getLastStableCommit()
   320          IS_ANCESTOR=isAncestor()
   321          if (LAST_STABLE_COMMIT == null || IS_ANCESTOR) {
   322              sh """
   323                          echo "git-commit=${env.GIT_COMMIT}" > $WORKSPACE/last-stable-commit.txt
   324                          if [ "true" == "${params.FORCE_STABLE_COMMIT_UPDATE}" ]; then
   325                              echo "forced-by-job=${env.BUILD_URL}" >> $WORKSPACE/last-stable-commit.txt
   326                          fi
   327                          cat $WORKSPACE/last-stable-commit.txt
   328                      """
   329              putLastStableCommit()
   330          }
   331      }
   332  }
   333  
   334  // Sets Job Display Name based on last-stable-commit.txt put status
   335  def putLastStableCommit() {
   336      putLastStableCommitCommandOutput = sh (
   337                                                 label: "Put last stable commit text file",
   338                                                 script: "oci --region us-phoenix-1 os object put --force --namespace ${OCI_OS_NAMESPACE} -bn ${OCI_OS_BUCKET} --name ${CLEAN_BRANCH_NAME}/last-stable-commit.txt --file $WORKSPACE/last-stable-commit.txt 2>&1 || true",
   339                                                 returnStdout: true
   340                                                 ).trim()
   341      echo "command out: ${putLastStableCommitCommandOutput}"
   342      if (putLastStableCommitCommandOutput.length() > 0) {
   343          if (putLastStableCommitCommandOutput =~ /(.*)status(.*)\d{1,4}(.*)/) {
   344              currentBuild.displayName = "${currentBuild.displayName} : Put Last Stable Commit Failed"
   345              slackSend ( channel: "$SLACK_ALERT_CHANNEL", message: "Job Failed - \"${env.JOB_NAME}\" build: ${env.BUILD_NUMBER}\n\nView the log at:\n ${env.BUILD_URL}\n\nBlue Ocean:\n${env.RUN_DISPLAY_URL}\n\nFailed to put last-stable-commit.txt in Object Store\n" )
   346          }
   347      }
   348  }
   349  
   350  // Returns true if current commit is ancestor of last stable commit
   351  def isAncestor() {
   352     LAST_STABLE_COMMIT=getLastStableCommit()
   353     ANCESTOR=''
   354     if (LAST_STABLE_COMMIT != null) {
   355         ANCESTOR = sh (
   356                            label: "Check for Ancestry",
   357                            script: "git rev-list ${env.GIT_COMMIT} | grep \$(git rev-parse $LAST_STABLE_COMMIT) 2>&1 || true",
   358                            returnStdout: true
   359                            ).trim()
   360     }
   361     if (ANCESTOR.length() > 0) {
   362         return true
   363     } else {
   364         return false
   365     }
   366  }
   367  
   368  // Returns the last stable commit for the branch, or null if the commit file does not exist yet.
   369  // - fails the pipeline if any error other than 404 is returned by the OCI CLI
   370  def getLastStableCommit() {
   371      lastStableCommitCommandOutput = sh (
   372          label: "Get last stable commit text file",
   373          script: "oci --region us-phoenix-1 os object get --namespace ${OCI_OS_NAMESPACE} -bn ${OCI_OS_BUCKET} --name ${CLEAN_BRANCH_NAME}/last-stable-commit.txt --file ${WORKSPACE}/stored-stable-commit.txt 2>&1 || true",
   374          returnStdout: true
   375          ).trim()
   376      echo "command out: ${lastStableCommitCommandOutput}"
   377      if (lastStableCommitCommandOutput.length() > 0) {
   378          // We can get warning messages here as well even when the command succeeded, so be more precise on the checking
   379          if (lastStableCommitCommandOutput =~ /(.*)status(.*)\d{1,4}(.*)/) {
   380              // If we think we had a status: NNN, we ignore 404 and fail for others
   381              assert lastStableCommitCommandOutput =~ /(.*)status(.*)404(.*)/ : "An unexpected error occurred getting last stable commit from ObjectStore: ${lastStableCommitCommandOutput}"
   382          } else {
   383              // If we got here, we have some message that may or may not be an error. If we don't see the file, we assume it was an error
   384              sh """
   385                  if [ ! -f $WORKSPACE/stored-stable-commit.txt ]; then
   386                      echo "An unexpected error occurred getting last stable commit from ObjectStore: ${lastStableCommitCommandOutput}"
   387                      exit 1
   388                  fi
   389              """
   390          }
   391      }
   392      // Get the commit ID for the last stable commit
   393      def lastStableCommitProps = readProperties file: "$WORKSPACE/stored-stable-commit.txt"
   394      return lastStableCommitProps['git-commit']
   395  }
   396  
   397  // Called in Stage Clean workspace and checkout steps
   398  @NonCPS
   399  def getCommitList() {
   400      echo "Checking for change sets"
   401      def commitList = []
   402      def changeSets = currentBuild.changeSets
   403      for (int i = 0; i < changeSets.size(); i++) {
   404          echo "get commits from change set"
   405          def commits = changeSets[i].items
   406          for (int j = 0; j < commits.length; j++) {
   407              def commit = commits[j]
   408              def id = commit.commitId
   409              echo "Add commit id: ${id}"
   410              commitList.add(id)
   411          }
   412      }
   413      return commitList
   414  }
   415  
   416  def trimIfGithubNoreplyUser(userIn) {
   417      if (userIn == null) {
   418          echo "Not a github noreply user, not trimming: ${userIn}"
   419          return userIn
   420      }
   421      if (userIn.matches(".*\\+.*@users.noreply.github.com.*")) {
   422          def userOut = userIn.substring(userIn.indexOf("+") + 1, userIn.indexOf("@"))
   423          return userOut;
   424      }
   425      if (userIn.matches(".*<.*@users.noreply.github.com.*")) {
   426          def userOut = userIn.substring(userIn.indexOf("<") + 1, userIn.indexOf("@"))
   427          return userOut;
   428      }
   429      if (userIn.matches(".*@users.noreply.github.com")) {
   430          def userOut = userIn.substring(0, userIn.indexOf("@"))
   431          return userOut;
   432      }
   433      echo "Not a github noreply user, not trimming: ${userIn}"
   434      return userIn
   435  }
   436  
   437  def getSuspectList(commitList, userMappings) {
   438      def retValue = ""
   439      def suspectList = []
   440      if (commitList == null || commitList.size() == 0) {
   441          echo "No commits to form suspect list"
   442      } else {
   443          for (int i = 0; i < commitList.size(); i++) {
   444              def id = commitList[i]
   445              try {
   446                  def gitAuthor = sh(
   447                      script: "git log --format='%ae' '$id^!'",
   448                      returnStdout: true
   449                  ).trim()
   450                  if (gitAuthor != null) {
   451                      def author = trimIfGithubNoreplyUser(gitAuthor)
   452                      echo "DEBUG: author: ${gitAuthor}, ${author}, id: ${id}"
   453                      if (userMappings.containsKey(author)) {
   454                          def slackUser = userMappings.get(author)
   455                          if (!suspectList.contains(slackUser)) {
   456                              echo "Added ${slackUser} as suspect"
   457                              retValue += " ${slackUser}"
   458                              suspectList.add(slackUser)
   459                          }
   460                      } else {
   461                          // If we don't have a name mapping use the commit.author, at least we can easily tell if the mapping gets dated
   462                          if (!suspectList.contains(author)) {
   463                              echo "Added ${author} as suspect"
   464                              retValue += " ${author}"
   465                              suspectList.add(author)
   466                          }
   467                      }
   468                  } else {
   469                      echo "No author returned from git"
   470                  }
   471              } catch (Exception e) {
   472                  echo "INFO: Problem processing commit ${id}, skipping commit: " + e.toString()
   473              }
   474          }
   475      }
   476      def startedByUser = "";
   477      def causes = currentBuild.getBuildCauses()
   478      echo "causes: " + causes.toString()
   479      for (cause in causes) {
   480          def causeString = cause.toString()
   481          echo "current cause: " + causeString
   482          def causeInfo = readJSON text: causeString
   483          if (causeInfo.userId != null) {
   484              startedByUser = causeInfo.userId
   485          }
   486      }
   487  
   488      if (startedByUser.length() > 0) {
   489          echo "Build was started by a user, adding them to the suspect notification list: ${startedByUser}"
   490          def author = trimIfGithubNoreplyUser(startedByUser)
   491          echo "DEBUG: author: ${startedByUser}, ${author}"
   492          if (userMappings.containsKey(author)) {
   493              def slackUser = userMappings.get(author)
   494              if (!suspectList.contains(slackUser)) {
   495                  echo "Added ${slackUser} as suspect"
   496                  retValue += " ${slackUser}"
   497                  suspectList.add(slackUser)
   498              }
   499          } else {
   500              // If we don't have a name mapping use the commit.author, at least we can easily tell if the mapping gets dated
   501              if (!suspectList.contains(author)) {
   502                 echo "Added ${author} as suspect"
   503                 retValue += " ${author}"
   504                 suspectList.add(author)
   505              }
   506          }
   507      } else {
   508          echo "Build not started by a user, not adding to notification list"
   509      }
   510      echo "returning suspect list: ${retValue}"
   511      return retValue
   512  }