diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index 6532ab1e3f..0d13168427 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -7,12 +7,14 @@ pipeline { REPO = 'apm-agent-nodejs' BASE_DIR = "src/github.com/elastic/${env.REPO}" PIPELINE_LOG_LEVEL='INFO' - NOTIFY_TO = credentials('notify-to') JOB_GCS_BUCKET = credentials('gcs-bucket') GITHUB_CHECK_ITS_NAME = 'Integration Tests' ITS_PIPELINE = 'apm-integration-tests-selector-mbp/main' OPBEANS_REPO = 'opbeans-node' GITHUB_CHECK = 'true' + RELEASE_URL_MESSAGE = "()" + SLACK_CHANNEL = '#apm-agent-node' + NOTIFY_TO = 'build-apm+apm-agent-nodejs@elastic.co' } options { timeout(time: 3, unit: 'HOURS') @@ -274,6 +276,9 @@ pipeline { beforeAgent true tag pattern: 'v\\d+\\.\\d+\\.\\d+', comparator: 'REGEXP' } + environment { + SUFFIX_ARN_FILE = 'arn-file.md' + } stages { stage('Opbeans') { environment { @@ -294,6 +299,61 @@ pipeline { } } } + stage('Dist') { + steps { + withGithubNotify(context: "Dist") { + setEnvVar('ELASTIC_LAYER_NAME', "elastic-apm-node${getVersion()}") + setEnvVar('RELEASE_NOTES_URL', getReleaseNotesUrl()) + deleteDir() + unstash 'source' + withNodeJSEnv(version: 'v14.17.5'){ + dir("${BASE_DIR}"){ + cmd(label: 'make dist', script: 'make -C .ci dist') + } + } + } + } + } + stage('Publish') { + steps { + withGithubNotify(context: "Publish") { + withGoEnv(){ + withAWSEnv(secret: 'secret/observability-team/ci/service-account/apm-aws-lambda', forceInstallation: true, version: '2.4.10') { + dir("${BASE_DIR}"){ + cmd(label: 'make publish-in-all-aws-regions', script: 'make -C .ci publish-in-all-aws-regions') + cmd(label: 'make create-arn-file', script: 'make -C .ci create-arn-file') + } + } + } + } + } + post { + always { + archiveArtifacts(allowEmptyArchive: true, artifacts: "${BASE_DIR}/build/aws") + } + } + } + stage('Release Notes') { + steps { + withGhEnv(forceInstallation: true, version: '2.4.0') { + dir("${BASE_DIR}"){ + cmd(label: 'make release-notes', script: 'make -C .ci release-notes') + } + } + } + } + } + post { + success { + whenTrue(isTag()) { + notifyStatus(slackStatus: 'good', subject: "[${env.REPO}] Release *${env.TAG_NAME}* published", body: "Build: (<${env.RUN_DISPLAY_URL}|here>)\nRelease URL: ${env.RELEASE_URL_MESSAGE}") + } + } + failure { + whenTrue(isTag()) { + notifyStatus(slackStatus: 'warning', subject: "[${env.REPO}] Release *${env.TAG_NAME}* could not be published.", body: "Build: (<${env.RUN_DISPLAY_URL}|here>)") + } + } } } /** @@ -505,3 +565,39 @@ def grabWorkerIP(){ } return linuxIp } + +/** +* Transform TAG releases from v{major}.{minor}.{patch} to +* ver-{major}-{minor}-{patch}. e.g: given v1.2.3 then +* -ver-1-2-3. +*/ +def getVersion() { + if (env.BRANCH_NAME?.trim() && env.BRANCH_NAME.startsWith('v')) { + return env.BRANCH_NAME.replaceAll('v', '-ver-').replaceAll('\\.', '-') + } + return '' +} + +/** +* Calculate the elastic.co release notes URL given the TAG release. Otherwise +* it returns the default current URL. +*/ +def getReleaseNotesUrl() { + def baseUrl = 'https://www.elastic.co/guide/en/apm/agent/nodejs/current' + if (env.BRANCH_NAME?.trim() && env.BRANCH_NAME.startsWith('v')) { + def version = env.BRANCH_NAME.replaceAll('v', '') + def parts = version.split('\\.') + def major = parts[0] + return "${baseUrl}/release-notes-${major}.x.html#release-notes-${version}" + } + return baseUrl +} + +def notifyStatus(def args = [:]) { + releaseNotification(slackChannel: "${env.SLACK_CHANNEL}", + slackColor: args.slackStatus, + slackCredentialsId: 'jenkins-slack-integration-token', + to: "${env.NOTIFY_TO}", + subject: args.subject, + body: args.body) +} diff --git a/.ci/Makefile b/.ci/Makefile new file mode 100644 index 0000000000..8794ee6c42 --- /dev/null +++ b/.ci/Makefile @@ -0,0 +1,107 @@ +SHELL = /bin/bash -eo pipefail + +AWS_FOLDER = ../build/aws + +export AWS_FOLDER + +build: + rm -rf $(AWS_FOLDER) || true + mkdir -p $(AWS_FOLDER) + cd $(AWS_FOLDER); \ + npm init -y; \ + npm install --global-style https://github.com/elastic/apm-agent-nodejs#$(BRANCH_NAME); \ + mkdir nodejs; \ + mv node_modules nodejs +env: + env +dist: validate-branch-name build + rm -f $(BRANCH_NAME).zip || true + cd $(AWS_FOLDER) ; \ + zip -r $(BRANCH_NAME).zip nodejs + +# List all the AWS regions +get-all-aws-regions: + @mkdir -p $(AWS_FOLDER) + @aws \ + ec2 \ + describe-regions \ + --region us-east-1 \ + --output json \ + --no-cli-pager \ + | jq -r '.Regions[].RegionName' > $(AWS_FOLDER)/.regions + +# Publish the given LAYER in all the AWS regions +publish-in-all-aws-regions: validate-layer-name get-all-aws-regions + @mkdir -p $(AWS_FOLDER) + @while read AWS_DEFAULT_REGION; do \ + echo "publish '$(ELASTIC_LAYER_NAME)' in $${AWS_DEFAULT_REGION}"; \ + AWS_DEFAULT_REGION="$${AWS_DEFAULT_REGION}" ELASTIC_LAYER_NAME=$(ELASTIC_LAYER_NAME) $(MAKE) publish > $(AWS_FOLDER)/$${AWS_DEFAULT_REGION}.publish; \ + AWS_DEFAULT_REGION="$${AWS_DEFAULT_REGION}" ELASTIC_LAYER_NAME=$(ELASTIC_LAYER_NAME) $(MAKE) grant-public-layer-access; \ + done < $(AWS_FOLDER)/.regions + +# Publish the given LAYER in the given AWS region +publish: validate-layer-name validate-aws-default-region + @aws lambda \ + --output json \ + publish-layer-version \ + --layer-name "$(ELASTIC_LAYER_NAME)" \ + --description "AWS Lambda Extension Layer for the Elastic APM Node.js Agent" \ + --license "Apache-2.0" \ + --compatible-runtimes nodejs14.x nodejs12.x nodejs10.x \ + --zip-file "fileb://./$(AWS_FOLDER)/$(BRANCH_NAME).zip" + +# Grant public access to the given LAYER in the given AWS region +grant-public-layer-access: validate-layer-name validate-aws-default-region + @echo "[debug] $(ELASTIC_LAYER_NAME) with version: $$($(MAKE) -s --no-print-directory get-version)" + @aws lambda \ + --output json \ + add-layer-version-permission \ + --layer-name "$(ELASTIC_LAYER_NAME)" \ + --action lambda:GetLayerVersion \ + --principal '*' \ + --statement-id "$(ELASTIC_LAYER_NAME)" \ + --version-number $$($(MAKE) -s --no-print-directory get-version) > $(AWS_FOLDER)/$(AWS_DEFAULT_REGION).public + +# Get the ARN Version for the AWS_REGIONS +# NOTE: jq -r .Version "$(AWS_FOLDER)/$(AWS_DEFAULT_REGION)" fails in the CI +# with 'parse error: Invalid numeric literal at line 1, column 5' +get-version: validate-aws-default-region + @grep '"Version"' "$(AWS_FOLDER)/$(AWS_DEFAULT_REGION).publish" | cut -d":" -f2 | sed 's/ //g' | cut -d"," -f1 + +# Generate the file with the ARN entries +create-arn-file: validate-suffix-arn-file validate-release-notes-url + @./scripts/create-arn-table.sh + +release-notes: validate-branch-name validate-suffix-arn-file + @gh release list + @gh \ + release \ + create $(BRANCH_NAME) \ + --title '$(BRANCH_NAME)' \ + --notes-file $(AWS_FOLDER)/$(SUFFIX_ARN_FILE) \ + $(AWS_FOLDER)/$(BRANCH_NAME).zip + +validate-branch-name: +ifndef BRANCH_NAME + $(error BRANCH_NAME is undefined) +endif + +validate-suffix-arn-file: +ifndef SUFFIX_ARN_FILE + $(error SUFFIX_ARN_FILE is undefined) +endif + +validate-layer-name: +ifndef ELASTIC_LAYER_NAME + $(error ELASTIC_LAYER_NAME is undefined) +endif + +validate-aws-default-region: +ifndef AWS_DEFAULT_REGION + $(error AWS_DEFAULT_REGION is undefined) +endif + +validate-release-notes-url: +ifndef RELEASE_NOTES_URL + $(error RELEASE_NOTES_URL is undefined) +endif diff --git a/.ci/scripts/create-arn-table.sh b/.ci/scripts/create-arn-table.sh new file mode 100755 index 0000000000..f654d1f85b --- /dev/null +++ b/.ci/scripts/create-arn-table.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -o pipefail + +# +# Create the AWS ARN table given the below environment variables: +# +# - AWS_FOLDER - that's the location of the publish-layer-version output for each region. +# - SUFFIX_ARN_FILE - that's the output file to be stored in the AWS_FOLDER. +# - RELEASE_NOTES_URL - that's the URL for the changelog in the elastic.co +# + +ARN_FILE=${SUFFIX_ARN_FILE} + +{ + echo "For more information, please see the [changelog](${RELEASE_NOTES_URL})." + echo '' + echo "### Elastic APM Node.js agent layer ARNs" + echo '' + echo '|Region|ARN|' + echo '|------|---|' +} > "${AWS_FOLDER}/${ARN_FILE}" + + +for f in "${AWS_FOLDER}"/*.publish; do + LAYER_VERSION_ARN=$(grep '"LayerVersionArn"' "${f}" | cut -d":" -f2- | sed 's/ //g' | sed 's/"//g' | cut -d"," -f1) + FILENAME=$(basename /"${f}" .publish) + echo "INFO: create-arn-table ARN(${LAYER_VERSION_ARN}):region(${FILENAME}))" + echo "|${FILENAME}|${LAYER_VERSION_ARN}|" >> "${AWS_FOLDER}/${ARN_FILE}" +done + +echo '' >> "${AWS_FOLDER}/${ARN_FILE}"