Jenkinsfile 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. #!/usr/bin/env groovy
  2. /*
  3. * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  4. * its licensors.
  5. *
  6. * For complete copyright and license terms please see the LICENSE at the root of this
  7. * distribution (the "License"). All use of this software is governed by the License,
  8. * or, if provided, by the license below or the license accompanying this file. Do not
  9. * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  10. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. *
  12. */
  13. PIPELINE_CONFIG_FILE = 'AutomatedReview/lumberyard.json'
  14. INCREMENTAL_BUILD_SCRIPT_PATH = 'scripts/build/bootstrap/incremental_build_util.py'
  15. EMPTY_JSON = readJSON text: '{}'
  16. def pipelineProperties = []
  17. def pipelineParameters = [
  18. // Build/clean Parameters
  19. // The CLEAN_OUTPUT_DIRECTORY is used by ci_build scripts. Creating the parameter here passes it as an environment variable to jobs and is consumed that way
  20. booleanParam(defaultValue: false, description: 'Deletes the contents of the output directory before building. This will cause a \"clean\" build', name: 'CLEAN_OUTPUT_DIRECTORY'),
  21. booleanParam(defaultValue: false, description: 'Deletes the contents of the workspace and forces a complete pull.', name: 'CLEAN_WORKSPACE'),
  22. booleanParam(defaultValue: false, description: 'Recreates the volume used for the workspace. The volume will be created out of a snapshot taken from main.', name: 'RECREATE_VOLUME'),
  23. string(defaultValue: '', description: 'Filters and overrides the list of jobs to run for each of the below platforms (comma-separated). Can\'t be used during a pull request.', name: 'JOB_LIST_OVERRIDE'),
  24. // Pull Request Parameters
  25. string(defaultValue: '', description: '', name: 'DESTINATION_BRANCH'),
  26. string(defaultValue: '', description: '', name: 'DESTINATION_COMMIT'),
  27. string(defaultValue: '', description: '', name: 'PULL_REQUEST_ID'),
  28. string(defaultValue: '', description: '', name: 'REPOSITORY_NAME'),
  29. string(defaultValue: '', description: '', name: 'SOURCE_BRANCH'),
  30. string(defaultValue: '', description: '', name: 'SOURCE_COMMIT')
  31. ]
  32. def palSh(cmd, lbl = '', winSlashReplacement = true) {
  33. if (env.IS_UNIX) {
  34. sh label: lbl,
  35. script: cmd
  36. } else if (winSlashReplacement) {
  37. bat label: lbl,
  38. script: cmd.replace('/','\\')
  39. } else {
  40. bat label: lbl,
  41. script: cmd
  42. }
  43. }
  44. def palMkdir(path) {
  45. if (env.IS_UNIX) {
  46. sh label: "Making directories ${path}",
  47. script: "mkdir -p ${path}"
  48. } else {
  49. def win_path = path.replace('/','\\')
  50. bat label: "Making directories ${win_path}",
  51. script: "mkdir ${win_path}."
  52. }
  53. }
  54. def palRm(path) {
  55. if (env.IS_UNIX) {
  56. sh label: "Removing ${path}",
  57. script: "rm ${path}"
  58. } else {
  59. def win_path = path.replace('/','\\')
  60. bat label: "Removing ${win_path}",
  61. script: "del ${win_path}"
  62. }
  63. }
  64. def palRmDir(path) {
  65. if (env.IS_UNIX) {
  66. sh label: "Removing ${path}",
  67. script: "rm -rf ${path}"
  68. } else {
  69. def win_path = path.replace('/','\\')
  70. bat label: "Removing ${win_path}",
  71. script: "rd /s /q ${win_path}"
  72. }
  73. }
  74. def IsJobEnabled(buildTypeMap, pipelineName, platformName) {
  75. def job_list_override = params.JOB_LIST_OVERRIDE.tokenize(',')
  76. if(params.PULL_REQUEST_ID) { // dont allow pull requests to filter platforms/jobs
  77. if(buildTypeMap.value.TAGS) {
  78. return buildTypeMap.value.TAGS.contains(pipelineName)
  79. }
  80. } else if (!job_list_override.isEmpty()) {
  81. return params[platformName] && job_list_override.contains(buildTypeMap.key);
  82. } else {
  83. if (params[platformName]) {
  84. if(buildTypeMap.value.TAGS) {
  85. return buildTypeMap.value.TAGS.contains(pipelineName)
  86. }
  87. }
  88. }
  89. return false
  90. }
  91. def GetRunningPipelineName(JENKINS_JOB_NAME) {
  92. // If the job name has an underscore
  93. def job_parts = JENKINS_JOB_NAME.tokenize('/')[0].tokenize('_')
  94. if (job_parts.size() > 1) {
  95. return job_parts[job_parts.size()-1]
  96. }
  97. return 'default'
  98. }
  99. @NonCPS
  100. def RegexMatcher(str, regex) {
  101. def matcher = (str =~ regex)
  102. return matcher ? matcher.group(1) : null
  103. }
  104. def LoadPipelineConfig(String pipelineName, String branchName) {
  105. echo 'Loading pipeline config'
  106. PullFilesFromGit(PIPELINE_CONFIG_FILE, branchName)
  107. def pipelineConfig = {}
  108. pipelineConfig = readJSON file: PIPELINE_CONFIG_FILE
  109. pipelineConfig.platforms = EMPTY_JSON
  110. // Load the pipeline configs per platform
  111. pipelineConfig.PIPELINE_CONFIGS.each { pipeline_config ->
  112. def platform_regex = pipeline_config.replace('.','\\.').replace('*', '(.*)')
  113. if (!env.IS_UNIX) {
  114. platform_regex = platform_regex.replace('/','\\\\')
  115. }
  116. echo "Downloading platform pipeline configs ${pipeline_config}"
  117. PullFilesFromGit(pipeline_config, branchName)
  118. echo "Searching platform pipeline configs in ${pipeline_config} using ${platform_regex}"
  119. for (pipeline_config_path in findFiles(glob: pipeline_config)) {
  120. echo "\tFound platform pipeline config ${pipeline_config_path}"
  121. def platform = RegexMatcher(pipeline_config_path, platform_regex)
  122. if(platform) {
  123. pipelineConfig.platforms[platform] = EMPTY_JSON
  124. pipelineConfig.platforms[platform].PIPELINE_ENV = readJSON file: pipeline_config_path.toString()
  125. }
  126. }
  127. }
  128. // Load the build configs
  129. pipelineConfig.BUILD_CONFIGS.each { build_config ->
  130. def platform_regex = build_config.replace('.','\\.').replace('*', '(.*)')
  131. if (!env.IS_UNIX) {
  132. platform_regex = platform_regex.replace('/','\\\\')
  133. }
  134. echo "Downloading configs ${build_config}"
  135. PullFilesFromGit(build_config, branchName)
  136. echo "Searching configs in ${build_config} using ${platform_regex}"
  137. for (build_config_path in findFiles(glob: build_config)) {
  138. echo "\tFound config ${build_config_path}"
  139. def platform = RegexMatcher(build_config_path, platform_regex)
  140. if(platform) {
  141. pipelineConfig.platforms[platform].build_types = readJSON file: build_config_path.toString()
  142. }
  143. }
  144. }
  145. return pipelineConfig
  146. }
  147. def GetPipelineRegion() {
  148. def gitUrl = scm.getUserRemoteConfigs()[0].getUrl()
  149. def gitUrlList = gitUrl.tokenize('.') as String[]
  150. def pipelineRegion = gitUrlList[1]
  151. return pipelineRegion
  152. }
  153. def GetBuildEnvVars(Map platformEnv, Map buildTypeEnv, String pipelineName) {
  154. def envVarMap = [:]
  155. platformPipelineEnv = platformEnv['ENV'] ?: [:]
  156. platformPipelineEnv.each { var ->
  157. envVarMap[var.key] = var.value
  158. }
  159. platformEnvOverride = platformEnv['PIPELINE_ENV_OVERRIDE'] ?: [:]
  160. platformPipelineEnvOverride = platformEnvOverride[pipelineName] ?: [:]
  161. platformPipelineEnvOverride.each { var ->
  162. envVarMap[var.key] = var.value
  163. }
  164. buildTypeEnv.each { var ->
  165. // This may override the above one if there is an entry defined by the job
  166. envVarMap[var.key] = var.value
  167. }
  168. return envVarMap
  169. }
  170. def GetEnvStringList(Map envVarMap) {
  171. def strList = []
  172. envVarMap.each { var ->
  173. strList.add("${var.key}=${var.value}")
  174. }
  175. return strList
  176. }
  177. // Pulls/downloads files from the repo through codecommit. Despite Glob matching is NOT supported, '*' is supported
  178. // as a folder or filename (not a portion, it has to be the whole folder or filename)
  179. def PullFilesFromGit(String filenamePath, String branchName, boolean failIfNotFound = true, String repositoryName = env.DEFAULT_REPOSITORY_NAME) {
  180. echo "PullFilesFromGit filenamePath=${filenamePath} branchName=${branchName}"
  181. def folderPathParts = filenamePath.tokenize('/')
  182. def filename = folderPathParts[folderPathParts.size()-1]
  183. folderPathParts.remove(folderPathParts.size()-1) // remove the filename
  184. def folderPath = folderPathParts.join('/')
  185. if (folderPath.contains('*')) {
  186. def currentPath = ''
  187. for (int i = 0; i < folderPathParts.size(); i++) {
  188. if (folderPathParts[i] == '*') {
  189. palMkdir(currentPath)
  190. retry(3) { palSh("aws codecommit get-folder --repository-name ${repositoryName} --commit-specifier ${branchName} --folder-path ${currentPath} > ${currentPath}/.codecommit", "GetFolder ${currentPath}") }
  191. def folderInfo = readJSON file: "${currentPath}/.codecommit"
  192. folderInfo.subFolders.each { folder ->
  193. def newSubPath = currentPath + '/' + folder.relativePath
  194. for (int j = i+1; j < folderPathParts.size(); j++) {
  195. newSubPath = newSubPath + '/' + folderPathParts[j]
  196. }
  197. newSubPath = newSubPath + '/' + filename
  198. PullFilesFromGit(newSubPath, branchName, false, repositoryName)
  199. }
  200. palRm("${currentPath}/.codecommit")
  201. }
  202. if (i == 0) {
  203. currentPath = folderPathParts[i]
  204. } else {
  205. currentPath = currentPath + '/' + folderPathParts[i]
  206. }
  207. }
  208. } else if (filename.contains('*')) {
  209. palMkdir(folderPath)
  210. retry(3) { palSh("aws codecommit get-folder --repository-name ${repositoryName} --commit-specifier ${branchName} --folder-path ${folderPath} > ${folderPath}/.codecommit", "GetFolder ${folderPath}") }
  211. def folderInfo = readJSON file: "${folderPath}/.codecommit"
  212. folderInfo.files.each { file ->
  213. PullFilesFromGit("${folderPath}/${filename}", branchName, false, repositoryName)
  214. }
  215. palRm("${folderPath}/.codecommit")
  216. } else {
  217. def errorFile = "${folderPath}/error.txt"
  218. palMkdir(folderPath)
  219. retry(3) {
  220. try {
  221. if(env.IS_UNIX) {
  222. sh label: "Downloading ${filenamePath}",
  223. script: "aws codecommit get-file --repository-name ${repositoryName} --commit-specifier ${branchName} --file-path ${filenamePath} --query fileContent --output text 2>${errorFile} > ${filenamePath}_encoded"
  224. sh label: 'Decoding',
  225. script: "base64 --decode ${filenamePath}_encoded > ${filenamePath}"
  226. } else {
  227. errorFile = errorFile.replace('/','\\')
  228. win_filenamePath = filenamePath.replace('/', '\\')
  229. bat label: "Downloading ${win_filenamePath}",
  230. script: "aws codecommit get-file --repository-name ${repositoryName} --commit-specifier ${branchName} --file-path ${filenamePath} --query fileContent --output text 2>${errorFile} > ${win_filenamePath}_encoded"
  231. bat label: 'Decoding',
  232. script: "certutil -decode ${win_filenamePath}_encoded ${win_filenamePath}"
  233. }
  234. palRm("${filenamePath}_encoded")
  235. } catch (Exception ex) {
  236. def error = ''
  237. if(fileExists(errorFile)) {
  238. error = readFile errorFile
  239. }
  240. if (!error || !(!failIfNotFound && error.contains('FileDoesNotExistException'))) {
  241. palRm("${errorFile} ${filenamePath}.encoded ${filenamePath}")
  242. throw new Exception("Could not get file: ${filenamePath}, ex: ${ex}, stderr: ${error}")
  243. }
  244. }
  245. palRm(errorFile)
  246. }
  247. }
  248. }
  249. def CheckoutRepo(boolean disableSubmodules = false) {
  250. if(fileExists('.git')) {
  251. // If the repository after checkout is locked, likely we took a snapshot while git was running,
  252. // to leave the repo in a usable state, garbagecollect. This also helps in situations where
  253. def indexLockFile = '.git/index.lock'
  254. if(fileExists(indexLockFile)) {
  255. palSh('git gc', 'Git GarbageCollect')
  256. }
  257. if(fileExists(indexLockFile)) { // if it is still there, remove it
  258. palRm(indexLockFile, 'Remove index.lock')
  259. }
  260. palSh('git remote prune origin', 'Git reset')
  261. }
  262. def random = new Random()
  263. def retryAttempt = 0
  264. retry(5) {
  265. if (retryAttempt > 0) {
  266. sleep random.nextInt(60 * retryAttempt) // Stagger checkouts to prevent HTTP 429 (Too Many Requests) response from CodeCommit
  267. }
  268. retryAttempt = retryAttempt + 1
  269. if(params.PULL_REQUEST_ID) {
  270. // This is a pull request build. Perform merge with destination branch before building.
  271. checkout scm: [
  272. $class: 'GitSCM',
  273. branches: scm.branches,
  274. extensions: [
  275. [$class: 'PreBuildMerge', options: [mergeRemote: 'origin', mergeTarget: params.DESTINATION_BRANCH]],
  276. [$class: 'SubmoduleOption', disableSubmodules: disableSubmodules, recursiveSubmodules: true],
  277. [$class: 'CheckoutOption', timeout: 60]
  278. ],
  279. userRemoteConfigs: scm.userRemoteConfigs
  280. ]
  281. } else {
  282. checkout scm: [
  283. $class: 'GitSCM',
  284. branches: scm.branches,
  285. extensions: [
  286. [$class: 'SubmoduleOption', disableSubmodules: disableSubmodules, recursiveSubmodules: true],
  287. [$class: 'CheckoutOption', timeout: 60]
  288. ],
  289. userRemoteConfigs: scm.userRemoteConfigs
  290. ]
  291. }
  292. }
  293. // CHANGE_ID is used by some scripts to identify uniquely the current change (usually metric jobs)
  294. palSh('git rev-parse HEAD > commitid', 'Getting commit id')
  295. env.CHANGE_ID = readFile file: 'commitid'
  296. env.CHANGE_ID = env.CHANGE_ID.trim()
  297. palRm('commitid')
  298. }
  299. def PreBuildCommonSteps(String pipeline, String branchName, String platform, String buildType, String workspace, boolean mount = true, boolean disableSubmodules = false) {
  300. echo 'Starting pre-build common steps...'
  301. if (mount) {
  302. unstash name: 'incremental_build_script'
  303. def pythonCmd = ''
  304. if(env.IS_UNIX) pythonCmd = 'sudo -E python -u '
  305. else pythonCmd = 'python -u '
  306. if(params.RECREATE_VOLUME) {
  307. palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action delete --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Deleting volume')
  308. }
  309. timeout(5) {
  310. palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action mount --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Mounting volume')
  311. }
  312. if(env.IS_UNIX) {
  313. sh label: 'Setting volume\'s ownership',
  314. script: """
  315. if sudo test ! -d "${workspace}"; then
  316. sudo mkdir -p ${workspace}
  317. cd ${workspace}/..
  318. sudo chown -R lybuilder:root .
  319. fi
  320. """
  321. }
  322. }
  323. if(params.CLEAN_WORKSPACE) {
  324. if(fileExists(workspace)) {
  325. palRmDir(workspace)
  326. }
  327. }
  328. dir(workspace) {
  329. CheckoutRepo(disableSubmodules)
  330. // Get python
  331. if(env.IS_UNIX) {
  332. sh label: 'Getting python',
  333. script: 'python/get_python.sh'
  334. } else {
  335. bat label: 'Getting python',
  336. script: 'python/get_python.bat'
  337. }
  338. }
  339. }
  340. def Build(Map options, String platform, String type, String workspace) {
  341. def command = "${options.BUILD_ENTRY_POINT} --platform ${platform} --type ${type}"
  342. dir(workspace) {
  343. if (env.IS_UNIX) {
  344. sh label: "Running ${platform} ${type}",
  345. script: "${options.PYTHON_DIR}/python.sh -u ${command}"
  346. } else {
  347. bat label: "Running ${platform} ${type}",
  348. script: "${options.PYTHON_DIR}/python.cmd -u ${command}".replace('/','\\')
  349. }
  350. }
  351. }
  352. def PostBuildCommonSteps(String workspace, boolean mount = true) {
  353. echo 'Starting post-build common steps...'
  354. if(params.PULL_REQUEST_ID) {
  355. dir(workspace) {
  356. if(fileExists('.git')) {
  357. palSh('git reset --hard HEAD', 'Discard PR merge, git reset')
  358. }
  359. }
  360. }
  361. if (mount) {
  362. def pythonCmd = ''
  363. if(env.IS_UNIX) pythonCmd = 'sudo -E python -u '
  364. else pythonCmd = 'python -u '
  365. try {
  366. timeout(5) {
  367. palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action unmount", 'Unmounting volume')
  368. }
  369. } catch (Exception e) {
  370. echo "Unmount script error ${e}"
  371. }
  372. }
  373. }
  374. def CreateSetupStage(String pipelineName, String branchName, String platformName, String jobName, Map environmentVars) {
  375. return {
  376. stage("Setup") {
  377. PreBuildCommonSteps(pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME'])
  378. }
  379. }
  380. }
  381. def CreateBuildStage(Map pipelineConfig, String platformName, String jobName, Map environmentVars) {
  382. return {
  383. stage("${jobName}") {
  384. Build(pipelineConfig, platformName, jobName, environmentVars['WORKSPACE'])
  385. }
  386. }
  387. }
  388. def CreateTeardownStage(Map environmentVars) {
  389. return {
  390. stage("Teardown") {
  391. PostBuildCommonSteps(environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME'])
  392. }
  393. }
  394. }
  395. def pipelineName = ''
  396. def pipelineRegion = ''
  397. def branchName = ''
  398. def pipelineConfig = {}
  399. // Start Pipeline
  400. try {
  401. stage('Setup Pipeline') {
  402. node('controller') {
  403. def envVarList = []
  404. if(isUnix()) {
  405. envVarList.add('IS_UNIX=1')
  406. }
  407. withEnv(envVarList) {
  408. timestamps {
  409. pipelineName = GetRunningPipelineName(env.JOB_NAME) // env.JOB_NAME is the name of the job given by Jenkins
  410. pipelineRegion = GetPipelineRegion()
  411. if(env.BRANCH_NAME) {
  412. branchName = env.BRANCH_NAME
  413. } else {
  414. branchName = scm.branches[0].name // for non-multibranch pipelines
  415. env.BRANCH_NAME = branchName // so scripts that read this environment have it (e.g. incremental_build_util.py)
  416. }
  417. pipelineProperties.add(disableConcurrentBuilds())
  418. echo "Running \"${pipelineName}\" for \"${branchName}\", region: \"${pipelineRegion}\"..."
  419. // Load configs
  420. pipelineConfig = LoadPipelineConfig(pipelineName, branchName)
  421. // Add each platform as a parameter that the user can disable if needed
  422. pipelineConfig.platforms.each { platform ->
  423. pipelineParameters.add(booleanParam(defaultValue: true, description: '', name: platform.key))
  424. }
  425. pipelineProperties.add(parameters(pipelineParameters))
  426. properties(pipelineProperties)
  427. // Stash the INCREMENTAL_BUILD_SCRIPT_PATH since all nodes will use it
  428. PullFilesFromGit(INCREMENTAL_BUILD_SCRIPT_PATH, branchName)
  429. stash name: 'incremental_build_script',
  430. includes: INCREMENTAL_BUILD_SCRIPT_PATH
  431. }
  432. }
  433. }
  434. }
  435. if(env.BUILD_NUMBER == '1') {
  436. // Exit pipeline early on the intial build. This allows Jenkins to load the pipeline for the branch and enables users
  437. // to select build parameters on their first actual build. See https://issues.jenkins.io/browse/JENKINS-41929
  438. currentBuild.result = 'SUCCESS'
  439. return
  440. }
  441. // Build and Post-Build Testing Stage
  442. def buildConfigs = [:]
  443. // Platform Builds run on EC2
  444. pipelineConfig.platforms.each { platform ->
  445. platform.value.build_types.each { build_job ->
  446. if (IsJobEnabled(build_job, pipelineName, platform.key)) { // User can filter jobs, jobs are tagged by pipeline
  447. def envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName)
  448. envVars['JOB_NAME'] = "${branchName}_${platform.key}_${build_job.key}" // backwards compatibility, some scripts rely on this
  449. def nodeLabel = envVars['NODE_LABEL']
  450. buildConfigs["${platform.key} [${build_job.key}]"] = {
  451. node("${nodeLabel}-${pipelineRegion}") {
  452. if(isUnix()) { // Has to happen inside a node
  453. envVars['IS_UNIX'] = 1
  454. }
  455. withEnv(GetEnvStringList(envVars)) {
  456. timeout(time: envVars['TIMEOUT'], unit: 'MINUTES', activity: true) {
  457. try {
  458. CreateSetupStage(pipelineName, branchName, platform.key, build_job.key, envVars).call()
  459. if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages
  460. build_job.value.steps.each { build_step ->
  461. CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call()
  462. }
  463. } else {
  464. CreateBuildStage(pipelineConfig, platform.key, build_job.key, envVars).call()
  465. }
  466. }
  467. catch(Exception e) {
  468. // https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Result.java
  469. // {SUCCESS,UNSTABLE,FAILURE,NOT_BUILT,ABORTED}
  470. def currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE'
  471. if (currentResult == 'FAILURE') {
  472. currentBuild.result = 'FAILURE'
  473. error "FAILURE: ${e}"
  474. } else if (currentResult == 'UNSTABLE') {
  475. currentBuild.result = 'UNSTABLE'
  476. unstable(message: "UNSTABLE: ${e}")
  477. }
  478. }
  479. finally {
  480. CreateTeardownStage(envVars).call()
  481. }
  482. }
  483. }
  484. }
  485. }
  486. }
  487. }
  488. }
  489. timestamps {
  490. stage('Build') {
  491. parallel buildConfigs // Run parallel builds
  492. }
  493. echo 'All builds successful'
  494. }
  495. }
  496. catch(Exception e) {
  497. error "Exception: ${e}"
  498. }
  499. finally {
  500. try {
  501. if(env.SNS_TOPIC) {
  502. snsPublish(
  503. topicArn: env.SNS_TOPIC,
  504. subject:'Build Result',
  505. message:"${currentBuild.currentResult}:${params.REPOSITORY_NAME}:${params.SOURCE_BRANCH}:${params.SOURCE_COMMIT}:${params.DESTINATION_COMMIT}:${params.PULL_REQUEST_ID}:${BUILD_URL}:${params.RECREATE_VOLUME}:${params.CLEAN_OUTPUT_DIRECTORY}"
  506. )
  507. }
  508. step([
  509. $class: 'Mailer',
  510. notifyEveryUnstableBuild: true,
  511. sendToIndividuals: true,
  512. recipients: emailextrecipients([
  513. [$class: 'CulpritsRecipientProvider'],
  514. [$class: 'RequesterRecipientProvider']
  515. ])
  516. ])
  517. } catch(Exception e) {
  518. }
  519. }