From 10adc5fddc913aea5b43a57dbdce1adffa55b5b6 Mon Sep 17 00:00:00 2001 From: Kawika Avilla Date: Mon, 14 Mar 2022 20:33:06 +0000 Subject: [PATCH] [Build][Test] OS and OSD improvements plus support for Playground While using a child folder 'Playground' in Jenkins I had to do a number of changes to support a successful test run. I also included the ability to skip publishing the notification not to spam channels from builds from for example `Playground`. Also ability to skip building docker because from what I could tell it was publishing docker images from `Playground` pipelines as well. Finally, some cleanup to integ tests to use the input manifest to container image. Also, make integ tests run in docker container for OpenSearch. Issues resolved: https://github.com/opensearch-project/opensearch-build/issues/1688 https://github.com/opensearch-project/opensearch-build/issues/1687 https://github.com/opensearch-project/opensearch-build/issues/1686 Signed-off-by: Kawika Avilla --- .../distribution-build.jenkinsfile | 104 +++++++++++------- .../integ-test.jenkinsfile | 39 +++++-- .../opensearch/distribution-build.jenkinsfile | 91 ++++++++++----- jenkins/opensearch/integ-test.jenkinsfile | 30 ++++- .../TestCreateTestResultsMessage.groovy | 31 ++++++ .../jobs/CreateTestResultsMessage_Jenkinsfile | 24 ++++ .../CreateTestResultsMessage_Jenkinsfile.txt | 20 ++++ .../CreateTestResultsMessageLibTester.groovy | 41 +++++++ vars/createTestResultsMessage.groovy | 14 +++ 9 files changed, 313 insertions(+), 81 deletions(-) create mode 100644 tests/jenkins/TestCreateTestResultsMessage.groovy create mode 100644 tests/jenkins/jobs/CreateTestResultsMessage_Jenkinsfile create mode 100644 tests/jenkins/jobs/CreateTestResultsMessage_Jenkinsfile.txt create mode 100644 tests/jenkins/lib-testers/CreateTestResultsMessageLibTester.groovy create mode 100644 vars/createTestResultsMessage.groovy diff --git a/jenkins/opensearch-dashboards/distribution-build.jenkinsfile b/jenkins/opensearch-dashboards/distribution-build.jenkinsfile index 8d1d680e09..3ac5044da0 100644 --- a/jenkins/opensearch-dashboards/distribution-build.jenkinsfile +++ b/jenkins/opensearch-dashboards/distribution-build.jenkinsfile @@ -9,8 +9,8 @@ pipeline { agent none environment { AGENT_X64 = 'Jenkins-Agent-al2-x64-c54xlarge-Docker-Host' - AGENT_ARM64 = 'Jenkins-Agent-al2-arm64-c6g4xlarge-Docker-Host' - INTEG_TEST_JOB_NAME = 'integ-test-opensearch-dashboards' + AGENT_ARM64 = 'Jenkins-Agent-al2-arm64-c6g4xlarge-Docker-Host' + DEFAULT_INTEG_TEST_JOB_NAME = 'integ-test-opensearch-dashboards' } parameters { string( @@ -23,8 +23,33 @@ pipeline { description: 'Test manifest under the manifests folder, e.g. 2.0.0/opensearch-dashboards-2.0.0-test.yml.', trim: true ) + string( + name: 'INTEG_TEST_JOB_NAME', + description: "Name of integration test job that will be triggered, e.g. Playground/integ-test-opensearch-dashboards.", + defaultValue: "integ-test-opensearch-dashboards", + trim: true + ) + booleanParam( + name: 'BUILD_DOCKER', + description: 'Build docker image or not.', + defaultValue: true + ) + booleanParam( + name: 'PUBLISH_NOTIFICATION', + description: 'Publish the status of this build job or not.', + defaultValue: true + ) } stages { + stage('verify-parameters') { + steps { + script { + env.INTEG_TEST_JOB_NAME = INTEG_TEST_JOB_NAME != '' ? + INTEG_TEST_JOB_NAME : + DEFAULT_INTEG_TEST_JOB_NAME + } + } + } stage('detect docker image + args') { agent { docker { @@ -65,22 +90,20 @@ pipeline { echo "artifactUrl (x64): ${artifactUrl}" def integTestResults = - build job: INTEG_TEST_JOB_NAME, + build job: env.INTEG_TEST_JOB_NAME, propagate: false, wait: true, parameters: [ + string(name: 'INPUT_MANIFEST', value: INPUT_MANIFEST), string(name: 'TEST_MANIFEST', value: TEST_MANIFEST), string(name: 'BUILD_MANIFEST_URL', value: buildManifestUrl), - string(name: 'AGENT_LABEL', value: AGENT_X64), - string(name: 'CONTAINER_IMAGE', value: getTestDocker(testManifest: "manifests/${TEST_MANIFEST}")?.image ?: dockerAgent.image) + string(name: 'AGENT_LABEL', value: AGENT_X64) ] - String status = integTestResults.getResult() - String icon = status == 'SUCCESS' ? ':white_check_mark:' : ':warning:' - lib.jenkins.Messages.new(this).add( - STAGE_NAME, - lib.jenkins.Messages.new(this).get([STAGE_NAME]) + - "\nInteg Tests (x64): ${icon} ${status} ${integTestResults.getAbsoluteUrl()}" + createTestResultsMessage( + testType: "Integ Tests (x64)", + status: integTestResults.getResult(), + absoluteUrl: integTestResults.getAbsoluteUrl() ) } } @@ -141,22 +164,20 @@ pipeline { echo "artifactUrl (arm64): ${artifactUrl}" def integTestResults = - build job: INTEG_TEST_JOB_NAME, + build job: env.INTEG_TEST_JOB_NAME, propagate: false, wait: true, parameters: [ + string(name: 'INPUT_MANIFEST', value: INPUT_MANIFEST), string(name: 'TEST_MANIFEST', value: TEST_MANIFEST), string(name: 'BUILD_MANIFEST_URL', value: buildManifestUrl), - string(name: 'AGENT_LABEL', value: AGENT_ARM64), - string(name: 'CONTAINER_IMAGE', value: getTestDocker(testManifest: "manifests/${TEST_MANIFEST}")?.image ?: dockerAgent.image) + string(name: 'AGENT_LABEL', value: AGENT_ARM64) ] - String status = integTestResults.getResult() - String icon = status == 'SUCCESS' ? ':white_check_mark:' : ':warning:' - lib.jenkins.Messages.new(this).add( - STAGE_NAME, - lib.jenkins.Messages.new(this).get([STAGE_NAME]) + - "\nInteg Tests (arm64): ${icon} ${status} ${integTestResults.getAbsoluteUrl()}" + createTestResultsMessage( + testType: "Integ Tests (arm64)", + status: integTestResults.getResult(), + absoluteUrl: integTestResults.getAbsoluteUrl() ) } } @@ -171,6 +192,10 @@ pipeline { } } stage('docker build') { + when { + beforeAgent true + equals expected: true, actual: BUILD_DOCKER + } steps { node(AGENT_X64) { script { @@ -191,19 +216,20 @@ pipeline { success { node(AGENT_X64) { script { - def stashed = lib.jenkins.Messages.new(this).get([ - 'build-and-test-linux-x64', - 'build-archive-linux-arm64', - 'assemble-archive-and-test-linux-arm64' - ]) + if (params.PUBLISH_NOTIFICATION) { + def stashed = lib.jenkins.Messages.new(this).get([ + 'build-and-test-linux-x64', + 'assemble-archive-and-test-linux-arm64' + ]) - publishNotification( - icon: ':white_check_mark:', - message: 'Successful Build', - extra: stashed, - credentialsId: 'BUILD_NOTICE_WEBHOOK', - manifest: "${INPUT_MANIFEST}" - ) + publishNotification( + icon: ':white_check_mark:', + message: 'Successful Build', + extra: stashed, + credentialsId: 'BUILD_NOTICE_WEBHOOK', + manifest: "${INPUT_MANIFEST}" + ) + } postCleanup() } @@ -212,12 +238,14 @@ pipeline { failure { node(AGENT_X64) { script { - publishNotification( - icon: ':warning:', - message: 'Failed Build', - credentialsId: 'BUILD_NOTICE_WEBHOOK', - manifest: "${INPUT_MANIFEST}" - ) + if (params.PUBLISH_NOTIFICATION) { + publishNotification( + icon: ':warning:', + message: 'Failed Build', + credentialsId: 'BUILD_NOTICE_WEBHOOK', + manifest: "${INPUT_MANIFEST}" + ) + } postCleanup() } diff --git a/jenkins/opensearch-dashboards/integ-test.jenkinsfile b/jenkins/opensearch-dashboards/integ-test.jenkinsfile index 6b9bb440c3..712a80fde3 100644 --- a/jenkins/opensearch-dashboards/integ-test.jenkinsfile +++ b/jenkins/opensearch-dashboards/integ-test.jenkinsfile @@ -7,9 +7,14 @@ pipeline { agent none environment { BUILD_MANIFEST = "build-manifest.yml" - BUILD_JOB_NAME = 'distribution-build-opensearch-dashboards' + DEFAULT_BUILD_JOB_NAME = "distribution-build-opensearch-dashboards" } parameters { + string( + name: 'INPUT_MANIFEST', + description: 'Input manifest under the manifests folder, e.g. 2.0.0/opensearch-dashboards-2.0.0.yml.', + trim: true + ) string( name: 'TEST_MANIFEST', description: 'Test manifest under the manifests folder, e.g. 2.0.0/opensearch-dashboards-2.0.0-test.yml.', @@ -25,11 +30,6 @@ pipeline { description: 'The agent label where the tests should be executed, e.g. Jenkins-Agent-al2-x64-c54xlarge-Docker-Host.', trim: true ) - string( - name: 'CONTAINER_IMAGE', - description: 'The container image running on the agent where the tests should be executed, e.g. opensearchstaging/ci-runner:centos7-x64-arm64-jdkmulti-node10.24.1-cypress6.9.1-20211028.', - trim: true - ) } stages { stage('verify-parameters') { @@ -39,10 +39,24 @@ pipeline { currentBuild.result = 'ABORTED' error("Integration Tests failed to start. Missing parameter: AGENT_LABEL.") } - if (CONTAINER_IMAGE == '') { - currentBuild.result = 'ABORTED' - error("Integration Tests failed to start. Missing parameter: CONTAINER_IMAGE.") - } + env.BUILD_JOB_NAME = currentBuild.upstreamBuilds ? + currentBuild.upstreamBuilds[0].fullProjectName : + env.DEFAULT_BUILD_JOB_NAME + } + } + } + stage('detect docker image + args') { + agent { + docker { + label 'Jenkins-Agent-al2-x64-c54xlarge-Docker-Host' + image 'opensearchstaging/ci-runner:centos7-x64-arm64-jdkmulti-node10.24.1-cypress6.9.1-20211028' + alwaysPull true + } + } + steps { + script { + currentBuild.description = "$INPUT_MANIFEST" + dockerAgent = detectDockerAgent() } } } @@ -50,7 +64,8 @@ pipeline { agent { docker { label AGENT_LABEL - image CONTAINER_IMAGE + image dockerAgent.image + args dockerAgent.args alwaysPull true } } @@ -66,7 +81,7 @@ pipeline { echo "BUILD_ID: ${BUILD_ID}" runIntegTestScript( - jobName: BUILD_JOB_NAME, + jobName: env.BUILD_JOB_NAME, buildManifest: BUILD_MANIFEST, testManifest: "manifests/${TEST_MANIFEST}", buildId: BUILD_ID diff --git a/jenkins/opensearch/distribution-build.jenkinsfile b/jenkins/opensearch/distribution-build.jenkinsfile index 602c2dc2fd..4630b78cc4 100644 --- a/jenkins/opensearch/distribution-build.jenkinsfile +++ b/jenkins/opensearch/distribution-build.jenkinsfile @@ -5,7 +5,7 @@ pipeline { environment { AGENT_X64 = 'Jenkins-Agent-al2-x64-c54xlarge-Docker-Host' AGENT_ARM64 = 'Jenkins-Agent-al2-arm64-c6g4xlarge-Docker-Host' - INTEG_TEST_JOB_NAME = 'integ-test' + DEFAULT_INTEG_TEST_JOB_NAME = 'integ-test' } parameters { string( @@ -18,8 +18,33 @@ pipeline { description: 'Test manifest under the manifests folder, e.g. 2.0.0/opensearch-2.0.0-test.yml.', trim: true ) + string( + name: 'INTEG_TEST_JOB_NAME', + description: "Name of integration test job that will be triggered, e.g. Playground/integ-test.", + defaultValue: "integ-test", + trim: true + ) + booleanParam( + name: 'BUILD_DOCKER', + description: 'Build docker image or not.', + defaultValue: true + ) + booleanParam( + name: 'PUBLISH_NOTIFICATION', + description: 'Publish the status of this build job or not.', + defaultValue: true + ) } stages { + stage('verify-parameters') { + steps { + script { + env.INTEG_TEST_JOB_NAME = INTEG_TEST_JOB_NAME != '' ? + INTEG_TEST_JOB_NAME : + DEFAULT_INTEG_TEST_JOB_NAME + } + } + } stage('detect docker image + args') { agent { docker { @@ -123,23 +148,22 @@ pipeline { echo "buildManifestUrl (x64): ${buildManifestUrl}" echo "artifactUrl (x64): ${artifactUrl}" - + def integTestResults = - build job: INTEG_TEST_JOB_NAME, + build job: env.INTEG_TEST_JOB_NAME, propagate: false, wait: true, parameters: [ + string(name: 'INPUT_MANIFEST', value: INPUT_MANIFEST), string(name: 'TEST_MANIFEST', value: TEST_MANIFEST), string(name: 'BUILD_MANIFEST_URL', value: buildManifestUrl), string(name: 'AGENT_LABEL', value: AGENT_X64) ] - String status = integTestResults.getResult() - String icon = status == 'SUCCESS' ? ':white_check_mark:' : ':warning:' - lib.jenkins.Messages.new(this).add( - STAGE_NAME, - lib.jenkins.Messages.new(this).get([STAGE_NAME]) + - "\nInteg Tests (x64): ${icon} ${status} ${integTestResults.getAbsoluteUrl()}" + createTestResultsMessage( + testType: "Integ Tests (x64)", + status: integTestResults.getResult(), + absoluteUrl: integTestResults.getAbsoluteUrl() ) } } @@ -171,21 +195,20 @@ pipeline { echo "artifactUrl (arm64): ${artifactUrl}" def integTestResults = - build job: INTEG_TEST_JOB_NAME, + build job: env.INTEG_TEST_JOB_NAME, propagate: false, wait: true, parameters: [ + string(name: 'INPUT_MANIFEST', value: INPUT_MANIFEST), string(name: 'TEST_MANIFEST', value: TEST_MANIFEST), string(name: 'BUILD_MANIFEST_URL', value: buildManifestUrl), string(name: 'AGENT_LABEL', value: AGENT_ARM64) ] - String status = integTestResults.getResult() - String icon = status == 'SUCCESS' ? ':white_check_mark:' : ':warning:' - lib.jenkins.Messages.new(this).add( - STAGE_NAME, - lib.jenkins.Messages.new(this).get([STAGE_NAME]) + - "\nInteg Tests (arm64): ${icon} ${status} ${integTestResults.getAbsoluteUrl()}" + createTestResultsMessage( + testType: "Integ Tests (arm64)", + status: integTestResults.getResult(), + absoluteUrl: integTestResults.getAbsoluteUrl() ) } } @@ -198,6 +221,10 @@ pipeline { } } stage('docker build') { + when { + beforeAgent: true + equals expected: true, actual: BUILD_DOCKER + } steps { node('Jenkins-Agent-al2-x64-c54xlarge-Docker-Host') { script { @@ -219,15 +246,17 @@ pipeline { success { node(AGENT_X64) { script { - def stashed = lib.jenkins.Messages.new(this).get(['build-and-test-x64', 'build-and-test-arm64']) + if (params.PUBLISH_NOTIFICATION) { + def stashed = lib.jenkins.Messages.new(this).get(['build-and-test-x64', 'build-and-test-arm64']) - publishNotification( - icon: ':white_check_mark:', - message: 'Successful Build', - extra: stashed, - credentialsId: 'BUILD_NOTICE_WEBHOOK', - manifest: "${INPUT_MANIFEST}" - ) + publishNotification( + icon: ':white_check_mark:', + message: 'Successful Build', + extra: stashed, + credentialsId: 'BUILD_NOTICE_WEBHOOK', + manifest: "${INPUT_MANIFEST}" + ) + } postCleanup() } @@ -236,12 +265,14 @@ pipeline { failure { node(AGENT_X64) { script { - publishNotification( - icon: ':warning:', - message: 'Failed Build', - credentialsId: 'BUILD_NOTICE_WEBHOOK', - manifest: "${INPUT_MANIFEST}" - ) + if (params.PUBLISH_NOTIFICATION) { + publishNotification( + icon: ':warning:', + message: 'Failed Build', + credentialsId: 'BUILD_NOTICE_WEBHOOK', + manifest: "${INPUT_MANIFEST}" + ) + } postCleanup() } diff --git a/jenkins/opensearch/integ-test.jenkinsfile b/jenkins/opensearch/integ-test.jenkinsfile index 6902787b81..67ee074952 100644 --- a/jenkins/opensearch/integ-test.jenkinsfile +++ b/jenkins/opensearch/integ-test.jenkinsfile @@ -4,12 +4,18 @@ pipeline { agent none environment { BUILD_MANIFEST = "build-manifest.yml" + DEFAULT_BUILD_JOB_NAME = "distribution-build-opensearch" } tools { jdk "JDK14" maven "maven-3.8.2" } parameters { + string( + name: 'INPUT_MANIFEST', + description: 'Input manifest under the manifests folder, e.g. 2.0.0/opensearch-2.0.0.yml.', + trim: true + ) string( name: 'TEST_MANIFEST', description: 'Test manifest under the manifests folder, e.g. 2.0.0/opensearch-2.0.0-test.yml.', @@ -34,13 +40,34 @@ pipeline { currentBuild.result = 'ABORTED' error("Integration Tests failed to start. Missing parameter: AGENT_LABEL.") } + env.BUILD_JOB_NAME = currentBuild.upstreamBuilds ? + currentBuild.upstreamBuilds[0].fullProjectName : + env.DEFAULT_BUILD_JOB_NAME + } + } + } + stage('detect docker image + args') { + agent { + docker { + label 'Jenkins-Agent-al2-x64-c54xlarge-Docker-Host' + image 'opensearchstaging/ci-runner:centos7-x64-arm64-jdkmulti-node10.24.1-cypress6.9.1-20211028' + alwaysPull true + } + } + steps { + script { + currentBuild.description = "$INPUT_MANIFEST" + dockerAgent = detectDockerAgent() } } } stage('integ-test') { agent { - node { + docker { label AGENT_LABEL + image dockerAgent.image + args dockerAgent.args + alwaysPull true } } steps { @@ -55,6 +82,7 @@ pipeline { echo "BUILD_ID: ${BUILD_ID}" runIntegTestScript( + jobName: env.BUILD_JOB_NAME, buildManifest: BUILD_MANIFEST, testManifest: "manifests/${TEST_MANIFEST}", buildId: BUILD_ID diff --git a/tests/jenkins/TestCreateTestResultsMessage.groovy b/tests/jenkins/TestCreateTestResultsMessage.groovy new file mode 100644 index 0000000000..81f9a1078f --- /dev/null +++ b/tests/jenkins/TestCreateTestResultsMessage.groovy @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +import jenkins.tests.BuildPipelineTest +import org.junit.Before +import org.junit.Test + + +class TestCreateTestResultsMessage extends BuildPipelineTest { + @Before + void setUp() { + this.registerLibTester( + new CreateTestResultsMessageLibTester( + 'Integ Tests (x64)', + 'SUCCESS', + 'dummy-test.com/test-results' + ) + ) + super.setUp() + } + + @Test + public void TestCreateTestResultsMessage() { + super.testPipeline("tests/jenkins/jobs/CreateTestResultsMessage_Jenkinsfile") + } +} diff --git a/tests/jenkins/jobs/CreateTestResultsMessage_Jenkinsfile b/tests/jenkins/jobs/CreateTestResultsMessage_Jenkinsfile new file mode 100644 index 0000000000..17f4002214 --- /dev/null +++ b/tests/jenkins/jobs/CreateTestResultsMessage_Jenkinsfile @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +pipeline { + agent none + stages { + stage('stage') { + steps { + script { + createTestResultsMessage( + testType: 'Integ Tests (x64)', + status: 'SUCCESS', + absoluteUrl: 'dummy-test.com/test-results' + ) + } + } + } + } +} \ No newline at end of file diff --git a/tests/jenkins/jobs/CreateTestResultsMessage_Jenkinsfile.txt b/tests/jenkins/jobs/CreateTestResultsMessage_Jenkinsfile.txt new file mode 100644 index 0000000000..45d59757d0 --- /dev/null +++ b/tests/jenkins/jobs/CreateTestResultsMessage_Jenkinsfile.txt @@ -0,0 +1,20 @@ + CreateTestResultsMessage_Jenkinsfile.run() + CreateTestResultsMessage_Jenkinsfile.pipeline(groovy.lang.Closure) + CreateTestResultsMessage_Jenkinsfile.echo(Executing on agent [label:none]) + CreateTestResultsMessage_Jenkinsfile.stage(stage, groovy.lang.Closure) + CreateTestResultsMessage_Jenkinsfile.script(groovy.lang.Closure) + CreateTestResultsMessage_Jenkinsfile.createTestResultsMessage({testType=Integ Tests (x64), status=SUCCESS, absoluteUrl=dummy-test.com/test-results}) + createTestResultsMessage.legacySCM(groovy.lang.Closure) + createTestResultsMessage.library({identifier=jenkins@20211123, retriever=null}) + Messages.asBoolean() + Messages.asBoolean() + Messages.get([stage]) + createTestResultsMessage.unstash({name=messages-stage}) + createTestResultsMessage.findFiles({excludes=, glob=messages/*}) + createTestResultsMessage.dir(messages, groovy.lang.Closure) + createTestResultsMessage.deleteDir() + Messages.add(stage, +Integ Tests (x64): :white_check_mark: SUCCESS dummy-test.com/test-results) + createTestResultsMessage.writeFile({file=messages/stage.msg, text= +Integ Tests (x64): :white_check_mark: SUCCESS dummy-test.com/test-results}) + createTestResultsMessage.stash({includes=messages/*, name=messages-stage}) diff --git a/tests/jenkins/lib-testers/CreateTestResultsMessageLibTester.groovy b/tests/jenkins/lib-testers/CreateTestResultsMessageLibTester.groovy new file mode 100644 index 0000000000..874bf05356 --- /dev/null +++ b/tests/jenkins/lib-testers/CreateTestResultsMessageLibTester.groovy @@ -0,0 +1,41 @@ +import static org.hamcrest.CoreMatchers.notNullValue +import static org.hamcrest.MatcherAssert.assertThat + + +class CreateTestResultsMessageLibTester extends LibFunctionTester { + + private String testType + private String status + private String absoluteUrl + + public CreateTestResultsMessageLibTester( + String testType, + String status, + String absoluteUrl + ){ + this.testType = testType + this.status = status + this.absoluteUrl = absoluteUrl + } + + void configure(helper, binding) { + binding.setVariable('STAGE_NAME', 'stage') + helper.registerAllowedMethod('findFiles', [Map.class], null) + } + + void parameterInvariantsAssertions(call) { + assertThat(call.args.testType.first(), notNullValue()) + assertThat(call.args.status.first(), notNullValue()) + assertThat(call.args.absoluteUrl.first(), notNullValue()) + } + + boolean expectedParametersMatcher(call) { + return call.args.testType.first().toString().equals(this.testType) + && call.args.status.first().toString().equals(this.status) + && call.args.absoluteUrl.first().toString().equals(this.absoluteUrl) + } + + String libFunctionName() { + return 'createTestResultsMessage' + } +} diff --git a/vars/createTestResultsMessage.groovy b/vars/createTestResultsMessage.groovy new file mode 100644 index 0000000000..00093e9b9e --- /dev/null +++ b/vars/createTestResultsMessage.groovy @@ -0,0 +1,14 @@ +def call(Map args = [:]) { + def lib = library(identifier: "jenkins@20211123", retriever: legacySCM(scm)) + + String testType = args.testType + String status = args.status + String absoluteUrl = args.absoluteUrl + String icon = status == 'SUCCESS' ? ':white_check_mark:' : ':warning:' + + lib.jenkins.Messages.new(this).add( + "${STAGE_NAME}", + lib.jenkins.Messages.new(this).get(["${STAGE_NAME}"]) + + "\n${testType}: ${icon} ${status} ${absoluteUrl}" + ) +} \ No newline at end of file