# ========================================================================================= # Copyright (C) 2021 Orange & contributors # # This program is free software; you can redistribute it and/or modify it under the terms # of the GNU Lesser General Public License as published by the Free Software Foundation; # either version 3 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License along with this # program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth # Floor, Boston, MA 02110-1301, USA. # ========================================================================================= # default workflow rules: Merge Request pipelines spec: inputs: image: description: The Docker image used to run semantic-release default: registry.hub.docker.com/library/node:lts-slim version: description: The [semantic-release](https://www.npmjs.com/package/semantic-release) version to use default: latest branches-ref: description: Regular expression pattern matching branches from which releases should happen (should match your [semantic-release configuration](https://semantic-release.gitbook.io/semantic-release/usage/configuration#branches)) default: /^(master|main)$/ exec-version: description: The [@semantic-release/exec](https://www.npmjs.com/package/@semantic-release/exec) version to use default: latest config-dir: description: directory containing your [semantic-release configuration](https://semantic-release.gitbook.io/semantic-release/usage/configuration#configuration-file) default: . tag-format: description: 'For generated `.releaserc` file only. [tagFormat semantic-release option](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#tagformat)e. :warning: don''t forget to double the `$` character so it is not interpreted by GitLab.' default: $${version} required-plugins-file: description: Full path to `semrel-required-plugins.txt` file _(relative to `$CI_PROJECT_DIR`)_ default: semrel-required-plugins.txt release-disabled: description: Disable semantic-release type: boolean default: false changelog-enabled: description: Add the [@semantic-release/changelog](https://github.com/semantic-release/changelog) plugin which will commit a changelog file in the repository. type: boolean default: false changelog-file: description: '[changelogFile @semantic-release/changelog option](https://github.com/semantic-release/changelog#options).' default: CHANGELOG.md changelog-title: description: '[changelogTitle @semantic-release/changelog option](https://github.com/semantic-release/changelog#options). You might want to use markdown format (for example `# MyApp Changelog`).' default: '' dry-run: description: For generated `.releaserc` file only. Activate the [dryRun semantic-release option](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#dryrun) if present. type: boolean default: false auto-release-enabled: description: When set the job start automatically. When not set (default), the job is manual. type: boolean default: false hooks-dir: description: Hook scripts folder. default: . commit-message: description: '[message @semantic-release/git option](https://github.com/semantic-release/git#message)' default: '' commit-spec: description: "Commit specification `preset` (possible values: `angular`, `codemirror`, `ember`, `eslint`, `express`, `jquery`, `jshint`, `conventionalcommits`). The default is `angular`." options: - angular - codemirror - conventionalcommits - ember - eslint - express - jquery - jshint default: 'angular' info-on: description: Define on which branch(es) the job shall be run options: - '' - prod - branches-ref - protected - all default: '' --- workflow: rules: # prevent MR pipeline originating from production or integration branch(es) - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ $PROD_REF || $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ $INTEG_REF' when: never # on non-prod, non-integration branches: prefer MR pipeline over branch pipeline - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF' when: never - if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*tag(,[^],]*)*\]/" && $CI_COMMIT_TAG' when: never - if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*branch(,[^],]*)*\]/" && $CI_COMMIT_BRANCH' when: never - if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*mr(,[^],]*)*\]/" && $CI_MERGE_REQUEST_ID' when: never - if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*default(,[^],]*)*\]/" && $CI_COMMIT_REF_NAME =~ $CI_DEFAULT_BRANCH' when: never - if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*prod(,[^],]*)*\]/" && $CI_COMMIT_REF_NAME =~ $PROD_REF' when: never - if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*integ(,[^],]*)*\]/" && $CI_COMMIT_REF_NAME =~ $INTEG_REF' when: never - if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*dev(,[^],]*)*\]/" && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF' when: never - when: always variables: # variabilized tracking image TBC_TRACKING_IMAGE: registry.gitlab.com/to-be-continuous/tools/tracking:master # Default Docker image (use a public image - can be overridden) SEMREL_IMAGE: $[[ inputs.image ]] SEMREL_HOOKS_DIR: $[[ inputs.hooks-dir ]] SEMREL_TAG_FORMAT: $[[ inputs.tag-format ]] SEMREL_REQUIRED_PLUGINS_FILE: $[[ inputs.required-plugins-file ]] # undocumented (for internal use only) SEMREL_VERIFY_CONDITIONS_CMD: "verify-conditions.sh" SEMREL_VERIFY_RELEASE_CMD: "verify-release.sh" SEMREL_PREPARE_CMD: "prepare.sh" SEMREL_PUBLISH_CMD: "publish.sh" SEMREL_SUCCESS_CMD: "success.sh" SEMREL_FAIL_CMD: "fail.sh" SEMREL_VERSION: $[[ inputs.version ]] SEMREL_EXEC_VERSION: $[[ inputs.exec-version ]] SEMREL_CONFIG_DIR: $[[ inputs.config-dir ]] SEMREL_CHANGELOG_ENABLED: $[[ inputs.changelog-enabled ]] SEMREL_CHANGELOG_FILE: $[[ inputs.changelog-file ]] SEMREL_CHANGELOG_TITLE: $[[ inputs.changelog-title ]] SEMREL_DRY_RUN: $[[ inputs.dry-run ]] SEMREL_AUTO_RELEASE_ENABLED: $[[ inputs.auto-release-enabled ]] SEMREL_COMMIT_MESSAGE: $[[ inputs.commit-message ]] SEMREL_RELEASE_DISABLED: $[[ inputs.release-disabled ]] SEMREL_INFO_ON: $[[ inputs.info-on ]] SEMREL_COMMIT_SPEC: $[[ inputs.commit-spec ]] # default production ref name (pattern) PROD_REF: /^(master|main)$/ SEMREL_BRANCHES_REF: $[[ inputs.branches-ref ]] stages: - build - test - package-build - package-test - infra - deploy - acceptance - publish - infra-prod - production .semrel-scripts: &semrel-scripts | # BEGSCRIPT set -e function log_info() { echo -e "[\\e[1;94mINFO\\e[0m] $*" } function log_warn() { echo -e "[\\e[1;93mWARN\\e[0m] $*" } function log_error() { echo -e "[\\e[1;91mERROR\\e[0m] $*" } function fail() { log_error "$*" exit 1 } function assert_defined() { if [[ -z "$1" ]] then log_error "$2" exit 1 fi } function install_ca_certs() { certs=$1 if [[ -z "$certs" ]] then return fi # import in system if echo "$certs" >> /etc/ssl/certs/ca-certificates.crt then log_info "CA certificates imported in \\e[33;1m/etc/ssl/certs/ca-certificates.crt\\e[0m" fi if echo "$certs" >> /etc/ssl/cert.pem then log_info "CA certificates imported in \\e[33;1m/etc/ssl/cert.pem\\e[0m" fi # configure for npm echo "$certs" > /tmp/custom-ca.pem export NODE_EXTRA_CA_CERTS=/tmp/custom-ca.pem # import in Java keystore (if keytool command found) if command -v keytool > /dev/null then # shellcheck disable=SC2046 javahome=${JAVA_HOME:-$(dirname $(readlink -f $(command -v java)))/..} # shellcheck disable=SC2086 keystore=${JAVA_KEYSTORE_PATH:-$(ls -1 $javahome/jre/lib/security/cacerts 2>/dev/null || ls -1 $javahome/lib/security/cacerts 2>/dev/null || echo "")} if [[ -f "$keystore" ]] then storepass=${JAVA_KEYSTORE_PASSWORD:-changeit} nb_certs=$(echo "$certs" | grep -c 'END CERTIFICATE') log_info "importing $nb_certs certificates in Java keystore \\e[33;1m$keystore\\e[0m..." for idx in $(seq 0 $((nb_certs - 1))) do # TODO: use keytool option -trustcacerts ? if echo "$certs" | awk "n==$idx { print }; /END CERTIFICATE/ { n++ }" | keytool -noprompt -import -alias "imported CA Cert $idx" -keystore "$keystore" -storepass "$storepass" then log_info "... CA certificate [$idx] successfully imported" else log_warn "... Failed importing CA certificate [$idx]: abort" return fi done else log_warn "Java keystore \\e[33;1m$keystore\\e[0m not found: could not import CA certificates" fi fi } function unscope_variables() { _scoped_vars=$(env | awk -F '=' "/^scoped__[a-zA-Z0-9_]+=/ {print \$1}" | sort) if [[ -z "$_scoped_vars" ]]; then return; fi log_info "Processing scoped variables..." for _scoped_var in $_scoped_vars do _fields=${_scoped_var//__/:} _condition=$(echo "$_fields" | cut -d: -f3) case "$_condition" in if) _not="";; ifnot) _not=1;; *) log_warn "... unrecognized condition \\e[1;91m$_condition\\e[0m in \\e[33;1m${_scoped_var}\\e[0m" continue ;; esac _target_var=$(echo "$_fields" | cut -d: -f2) _cond_var=$(echo "$_fields" | cut -d: -f4) _cond_val=$(eval echo "\$${_cond_var}") _test_op=$(echo "$_fields" | cut -d: -f5) case "$_test_op" in defined) if [[ -z "$_not" ]] && [[ -z "$_cond_val" ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" ]]; then continue; fi ;; equals|startswith|endswith|contains|in|equals_ic|startswith_ic|endswith_ic|contains_ic|in_ic) # comparison operator # sluggify actual value _cond_val=$(echo "$_cond_val" | tr '[:punct:]' '_') # retrieve comparison value _cmp_val_prefix="scoped__${_target_var}__${_condition}__${_cond_var}__${_test_op}__" _cmp_val=${_scoped_var#"$_cmp_val_prefix"} # manage 'ignore case' if [[ "$_test_op" == *_ic ]] then # lowercase everything _cond_val=$(echo "$_cond_val" | tr '[:upper:]' '[:lower:]') _cmp_val=$(echo "$_cmp_val" | tr '[:upper:]' '[:lower:]') fi case "$_test_op" in equals*) if [[ -z "$_not" ]] && [[ "$_cond_val" != "$_cmp_val" ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == "$_cmp_val" ]]; then continue; fi ;; startswith*) if [[ -z "$_not" ]] && [[ "$_cond_val" != "$_cmp_val"* ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == "$_cmp_val"* ]]; then continue; fi ;; endswith*) if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val" ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val" ]]; then continue; fi ;; contains*) if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val"* ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val"* ]]; then continue; fi ;; in*) if [[ -z "$_not" ]] && [[ "__${_cmp_val}__" != *"__${_cond_val}__"* ]]; then continue; elif [[ "$_not" ]] && [[ "__${_cmp_val}__" == *"__${_cond_val}__"* ]]; then continue; fi ;; esac ;; *) log_warn "... unrecognized test operator \\e[1;91m${_test_op}\\e[0m in \\e[33;1m${_scoped_var}\\e[0m" continue ;; esac # matches _val=$(eval echo "\$${_target_var}") log_info "... apply \\e[32m${_target_var}\\e[0m from \\e[32m\$${_scoped_var}\\e[0m${_val:+ (\\e[33;1moverwrite\\e[0m)}" _val=$(eval echo "\$${_scoped_var}") export "${_target_var}"="${_val}" done log_info "... done" } # evaluate and export a secret # - $1: secret variable name function eval_secret() { name=$1 value=$(eval echo "\$${name}") case "$value" in @b64@*) decoded=$(mktemp) errors=$(mktemp) if echo "$value" | cut -c6- | base64 -d > "${decoded}" 2> "${errors}" then # shellcheck disable=SC2086 export ${name}="$(cat ${decoded})" log_info "Successfully decoded base64 secret \\e[33;1m${name}\\e[0m" else fail "Failed decoding base64 secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")" fi ;; @hex@*) decoded=$(mktemp) errors=$(mktemp) if echo "$value" | cut -c6- | sed 's/\([0-9A-F]\{2\}\)/\\\\x\1/gI' | xargs printf > "${decoded}" 2> "${errors}" then # shellcheck disable=SC2086 export ${name}="$(cat ${decoded})" log_info "Successfully decoded hexadecimal secret \\e[33;1m${name}\\e[0m" else fail "Failed decoding hexadecimal secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")" fi ;; @url@*) url=$(echo "$value" | cut -c6-) if command -v curl > /dev/null then decoded=$(mktemp) errors=$(mktemp) if curl -s -S -f --connect-timeout 5 -o "${decoded}" "$url" 2> "${errors}" then # shellcheck disable=SC2086 export ${name}="$(cat ${decoded})" log_info "Successfully curl'd secret \\e[33;1m${name}\\e[0m" else log_warn "Failed getting secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")" fi elif command -v wget > /dev/null then decoded=$(mktemp) errors=$(mktemp) if wget -T 5 -O "${decoded}" "$url" 2> "${errors}" then # shellcheck disable=SC2086 export ${name}="$(cat ${decoded})" log_info "Successfully wget'd secret \\e[33;1m${name}\\e[0m" else log_warn "Failed getting secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")" fi elif command -v node > /dev/null then decoded=$(mktemp) errors=$(mktemp) if node -e "const fs=require('fs');function dlFile(url,file,maxRedir=5){return new Promise((resolve,reject)=>{let redirCount=0;const req=require(url.split(':')[0]).get(url,res=>{res.statusCode>=300&&res.statusCode<400&&res.headers.location&&redirCount<maxRedir?(redirCount++,console.log('Follow redirect ('+redirCount+'): '+res.headers.location),dlFile(res.headers.location,file,maxRedir).then(resolve).catch(reject)):200===res.statusCode?(res.pipe(fs.createWriteStream(file)).on('finish',()=>resolve()),res.on('error',reject)):reject(new Error('HTTP error: '+res.statusCode))});req.on('error',reject)})}dlFile('$url','$decoded').then(()=>{console.log('Download complete'),process.exit(0)}).catch(e=>{console.error('Error:',e),process.exit(1)});" 2> "${errors}" then # shellcheck disable=SC2086 export ${name}="$(cat ${decoded})" log_info "Successfully dl'd secret \\e[33;1m${name}\\e[0m" else log_warn "Failed getting secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")" fi else log_warn "Couldn't get secret \\e[33;1m${name}\\e[0m: no http client found" fi ;; esac } function eval_all_secrets() { encoded_vars=$(env | grep -v '^scoped__' | awk -F '=' '/^[a-zA-Z0-9_]*=@(b64|hex|url)@/ {print $1}') for var in $encoded_vars do eval_secret "$var" done } function download_file() { if command -v wget &> /dev/null then wget "$1" -O "$2" elif command -v curl &> /dev/null then curl -sfL "$1" -o "$2" elif command -v node &> /dev/null then node -e "const fs=require('fs');function dlFile(url,file,maxRedir=5){return new Promise((resolve,reject)=>{let redirCount=0;const req=require(url.split(':')[0]).get(url,res=>{res.statusCode>=300&&res.statusCode<400&&res.headers.location&&redirCount<maxRedir?(redirCount++,console.log('Follow redirect ('+redirCount+'): '+res.headers.location),dlFile(res.headers.location,file,maxRedir).then(resolve).catch(reject)):200===res.statusCode?(res.pipe(fs.createWriteStream(file)).on('finish',()=>resolve()),res.on('error',reject)):reject(new Error('HTTP error: '+res.statusCode))});req.on('error',reject)})}dlFile('$1','$2').then(()=>{console.log('Download complete'),process.exit(0)}).catch(e=>{console.error('Error:',e),process.exit(1)});" else fail "wget, curl or node required" fi } function github_get_latest_version() { if command -v curl &> /dev/null then curl -sSf -I "https://github.com/$1/releases/latest" | awk -F '/' -v RS='\r\n' '/location:/ {print $NF}' elif command -v node &> /dev/null then node -e "const https=require('https'); const options={hostname:'github.com', path:'/$1/releases/latest', method:'HEAD'}; https.request(options, (res) => {tokens=res.headers.location.split('/'); console.log(tokens[tokens.length-1]); res.req.destroy()}).end();" else fail "curl or node required" fi } function maybe_install_packages() { if command -v apt-get > /dev/null then # Debian if ! dpkg --status "$@" > /dev/null then apt-get update apt-get install --no-install-recommends --yes --quiet "$@" fi elif command -v apk > /dev/null then # Alpine if ! apk info --installed "$@" > /dev/null then apk add --no-cache "$@" fi else log_error "... didn't find any supported package manager to install $*" exit 1 fi } function extract_release_config_from_package_json() { package_json="./package.json" if [[ -f "${package_json}" ]]; then release_config=$(node -pe "JSON.stringify(require('${package_json}').release, null, 2)") case "$release_config" in "undefined"|"null") release_config="" ;; esac echo "$release_config" fi } function prepare_semantic_release() { git config --global --add safe.directory "$(pwd)" if [[ -f ".releaserc" ]]; then log_info "\\e[33;1m.releaserc\\e[0m file found" semrelConfigFile=".releaserc" elif [[ -f ".releaserc.yml" ]]; then log_info "\\e[33;1m.releaserc.yml\\e[0m file found" semrelConfigFile=".releaserc.yml" elif [[ -f ".releaserc.yaml" ]]; then log_info "\\e[33;1m.releaserc.yaml\\e[0m file found" semrelConfigFile=".releaserc.yaml" elif [[ -f ".releaserc.json" ]]; then log_info "\\e[33;1m.releaserc.json\\e[0m file found" semrelConfigFile=".releaserc.json" elif [[ -f ".releaserc.js" ]]; then log_info "\\e[33;1m.releaserc.js\\e[0m file found" semrelConfigFile=".releaserc.js" else releaseConfig="$(extract_release_config_from_package_json)" if [[ -n "${releaseConfig}" ]]; then log_info "release configuration found in \\e[33;1mpackage.json\\e[0m file" # exporting release configuration in dedicated file for required plugins installation semrelConfigFile=".release_config_from_package_json" echo "${releaseConfig}" > "${semrelConfigFile}" else log_info "semantic release configuration file not found, generating default \\e[33;1m.releaserc\\e[0m" semrelConfigFile=".releaserc" if [[ -n "$TRACE" ]]; then debug="true" else debug="false" fi commitPresetConfig=$(generate_commit_preset_conf) changelogPluginConfig=$(generate_changelog_plugin_conf) execPluginConfig=$(generate_exec_plugin_conf) gitPluginConfig=$(generate_git_plugin_conf) { echo "debug: ${debug}" echo "" echo "tagFormat: '${SEMREL_TAG_FORMAT}'" echo "" echo "plugins: " echo " - - '@semantic-release/commit-analyzer'" echo "${commitPresetConfig}" echo " - - '@semantic-release/release-notes-generator'" echo "${commitPresetConfig}" echo " - '@semantic-release/gitlab'" echo "${changelogPluginConfig}" echo "${execPluginConfig}" echo "${gitPluginConfig}" echo "" echo "branches:" echo " - '${CI_COMMIT_REF_NAME}'" } > "${semrelConfigFile}" cat "${semrelConfigFile}" fi fi } function install_semantic_release_plugins() { log_info "installing required plugins" # shellcheck disable=SC2046 if [[ -f "${SEMREL_REQUIRED_PLUGINS_FILE}" ]]; then while IFS= read -r line || [[ -n "$line" ]] do required_plugins="${required_plugins} $line" done <<< $(cat "${SEMREL_REQUIRED_PLUGINS_FILE}") fi # shellcheck disable=SC2046 while IFS= read -r line || [[ -n "$line" ]] do plugin=$(echo "$line" | cut -d\" -f2) required_plugins="${required_plugins} $plugin" done <<< $(yq eval ".plugins[]" "${semrelConfigFile}" -o=json --indent 0) # shellcheck disable=SC2086 npm install --global "semantic-release@${SEMREL_VERSION}" ${required_plugins} if [[ ! -f "${SEMREL_REQUIRED_PLUGINS_FILE}" && -n "${SEMREL_COMMIT_SPEC}" ]]; then case "$SEMREL_COMMIT_SPEC" in cc) SEMREL_COMMIT_SPEC=conventionalcommits ;; esac npm install --global "conventional-changelog-$SEMREL_COMMIT_SPEC" fi if [[ -n "$TRACE" ]]; then if [[ -f "./package.json" ]]; then log_info "Installed devDependencies..." npm pkg get devDependencies fi log_info "Globally installed packages..." npm list --global fi } # this script console output is inserted in generated file: DO NOT ADD LOGS function generate_commit_preset_conf() { if [[ -n "${SEMREL_COMMIT_SPEC}" ]]; then if [[ "${SEMREL_COMMIT_SPEC}" == "cc" ]]; then conventionalCommits="conventionalcommits" fi echo " - preset: '${conventionalCommits:-$SEMREL_COMMIT_SPEC}'" fi } # this script console output is inserted in generated file: DO NOT ADD LOGS function generate_changelog_plugin_conf() { if [[ "${SEMREL_CHANGELOG_ENABLED}" = "true" ]]; then if [[ -n "${SEMREL_CHANGELOG_FILE}" ]] || [[ -n "${SEMREL_CHANGELOG_TITLE}" ]]; then if [[ -n "${SEMREL_CHANGELOG_FILE}" ]]; then changeLogConfig="changelogFile: '${SEMREL_CHANGELOG_FILE}'" fi if [[ -n "${SEMREL_CHANGELOG_TITLE}" ]]; then changeLogConfig=$(echo -e "${changeLogConfig:+${changeLogConfig}\n }changelogTitle: '${SEMREL_CHANGELOG_TITLE}'") fi echo " - - '@semantic-release/changelog'" echo " - ${changeLogConfig}" else echo " - '@semantic-release/changelog'" fi else echo "" fi } # this script console output is inserted in generated file: DO NOT ADD LOGS function generate_git_plugin_conf() { # git plugin has default changelog file as asset by default so # we need to add it explicitly if the user configured a custom changelogFile echo " - - '@semantic-release/git'" if [[ "${SEMREL_CHANGELOG_ENABLED}" = "true" ]] && [[ -n "${SEMREL_CHANGELOG_FILE}" ]]; then echo " - assets:" echo " - '${SEMREL_CHANGELOG_FILE}'" echo " - 'package.json'" echo " - 'package-lock.json'" echo " - 'npm-shrinkwrap.json'" if [[ -n "${SEMREL_COMMIT_MESSAGE}" ]]; then echo " message: \"${SEMREL_COMMIT_MESSAGE}\"" fi else if [[ -n "${SEMREL_COMMIT_MESSAGE}" ]]; then echo " - message: \"${SEMREL_COMMIT_MESSAGE}\"" fi fi } # this script console output is inserted in generated file: DO NOT ADD LOGS function generate_exec_plugin_conf() { scriptsConfig="" tabs=" - " scriptPath=${SEMREL_HOOKS_DIR}/${SEMREL_VERIFY_CONDITIONS_CMD} if [[ -f "${scriptPath}" ]]; then chmod +x "${scriptPath}" scriptsConfig="${tabs}verifyConditionsCmd: '${scriptPath}'" tabs=" " fi scriptPath=${SEMREL_HOOKS_DIR}/${SEMREL_VERIFY_RELEASE_CMD} if [[ -f "${scriptPath}" ]]; then chmod +x "${scriptPath}" scriptsConfig=$(echo -e "${scriptsConfig}\n${tabs}verifyReleaseCmd: '\"${scriptPath}\" \"\${lastRelease.version}\" \"\${nextRelease.version}\" \"\${nextRelease.type}\"'") tabs=" " fi scriptPath=${SEMREL_HOOKS_DIR}/${SEMREL_PREPARE_CMD} if [[ -f "${scriptPath}" ]]; then chmod +x "${scriptPath}" scriptsConfig=$(echo -e "${scriptsConfig}\n${tabs}prepareCmd: '\"${scriptPath}\" \"\${lastRelease.version}\" \"\${nextRelease.version}\" \"\${nextRelease.type}\"'") tabs=" " fi scriptPath=${SEMREL_HOOKS_DIR}/${SEMREL_PUBLISH_CMD} if [[ -f "${scriptPath}" ]]; then chmod +x "${scriptPath}" scriptsConfig=$(echo -e "${scriptsConfig}\n${tabs}publishCmd: '\"${scriptPath}\" \"\${nextRelease.version}\" \"\${options.branch}\" \"\${commits.length}\" \"\${Date.now()}\"'") tabs=" " fi scriptPath=${SEMREL_HOOKS_DIR}/${SEMREL_SUCCESS_CMD} if [[ -f "${scriptPath}" ]]; then chmod +x "${scriptPath}" scriptsConfig=$(echo -e "${scriptsConfig}\n${tabs}successCmd: '\"${scriptPath}\" \"\${lastRelease.version}\" \"\${nextRelease.version}\"'") tabs=" " fi scriptPath=${SEMREL_HOOKS_DIR}/${SEMREL_FAIL_CMD} if [[ -f "${scriptPath}" ]]; then chmod +x "${scriptPath}" scriptsConfig=$(echo -e "${scriptsConfig}\n${tabs}failCmd: '\"${scriptPath}\" \"\${lastRelease.version}\" \"\${nextRelease.version}\"'") tabs=" " fi if [[ -n "${scriptsConfig}" ]]; then echo " - - '@semantic-release/exec'" echo "${scriptsConfig}" else echo "" fi } function maybe_install_yq() { if ! command -v yq > /dev/null then yq_version=$(github_get_latest_version mikefarah/yq) yq_binary=yq_linux_amd64 yq_url="https://github.com/mikefarah/yq/releases/download/${yq_version}/${yq_binary}.tar.gz" yq_cache="$XDG_CACHE_HOME/yq-$(echo "$yq_url" | md5sum | cut -d" " -f1)" if [[ -f "$yq_cache" ]] then log_info "yq found in cache: reuse" else log_info "yq not found in cache: download" log_info "Download latest yq version: \\e[32m$yq_url\\e[0m" download_file "${yq_url}" "${yq_binary}.tar.gz" tar xvf "${yq_binary}.tar.gz" mkdir -p "$XDG_CACHE_HOME" mv "${yq_binary}" "$yq_cache" fi ln -s "$yq_cache" /usr/bin/yq fi } function dotenv_semrel_info() { # removing user conf as we need to override it temporarily (git reset will put things back to normal) # see https://www.npmjs.com/package/cosmiconfig for configuration files resolution order (we will use .releaserc) releaserc_file="${semrelConfigFile}" rm -f "package.json" yq eval -oyaml -P 'with_entries(select((.key | . != "plugins") and (.key | . != "verifyConditions")))' "${releaserc_file}" > "${releaserc_file}.new" # Generating the hook scripts that will generate the dotenv file # The dotenv file is generated in $TMPDIR so it will survive the git reset dotenv_tmp="$(mktemp -t semrel-info-XXXXXXXXXX.dotenv)" commitPresetConfig=$(generate_commit_preset_conf) export_last_version_hook_script="./export-last-version.sh" { echo "#!/bin/bash" echo "{" echo "echo \"SEMREL_INFO_LAST_VERSION=\$1\"" echo "} > \"${dotenv_tmp}\"" } > "${export_last_version_hook_script}" chmod +x ${export_last_version_hook_script} export_next_version_hook_script="./export-next-version.sh" { echo "#!/bin/bash" echo "{" echo "echo \"SEMREL_INFO_NEXT_VERSION=\$1\"" echo "echo \"SEMREL_INFO_NEXT_VERSION_TYPE=\$2\"" echo "} >> \"${dotenv_tmp}\"" } > "${export_next_version_hook_script}" chmod +x ${export_next_version_hook_script} if [[ -n "$TRACE" ]]; then echo "generated analyzeCommits hook script:" cat "${export_last_version_hook_script}" echo "generated verifyRelease hook script:" cat "${export_next_version_hook_script}" fi # Generating temporary semantic-release config { echo "" echo "# injected (replace your plugins) plugins by the template to generate dotenv" echo "" echo "plugins:" echo " - - '@semantic-release/commit-analyzer'" echo "${commitPresetConfig}" echo " - - '@semantic-release/exec'" echo " - analyzeCommitsCmd: '\"${export_last_version_hook_script}\" \"\${lastRelease.version}\"'" echo " verifyReleaseCmd: '\"${export_next_version_hook_script}\" \"\${nextRelease.version}\" \"\${nextRelease.type}\"'" echo "" } >> "${releaserc_file}.new" mv -f "${releaserc_file}.new" ".releaserc" if [[ -n "$TRACE" ]]; then log_info "--- generated .releaserc:" cat ".releaserc" fi npm install --global "semantic-release@${SEMREL_VERSION}" "@semantic-release/exec@${SEMREL_EXEC_VERSION}" semantic-release --dry-run # Rollback temporary semantic-release configuration git reset --hard mv "${dotenv_tmp}" ./semrel.out.env log_info "--- semrel dotenv artifact:" cat ./semrel.out.env } function configure_commit_signing() { if [[ -z "${SEMREL_GPG_SIGNKEY}" ]]; then log_info "No GPG key provided." return fi log_info "Setting commit signing up." if [[ ! -f "${HOME}/.gnupg" ]]; then log_info "creating GPG base configuration" gpg -k fi if [[ ! -f "${SEMREL_GPG_SIGNKEY}" ]]; then fail "SEMREL_GPG_SIGNKEY is not a file." fi if ! gpg --batch --dry-run --yes --import "${SEMREL_GPG_SIGNKEY}"; then fail "Could not import GPG key." fi # import the key and extract its ID from the command output _GPG_KEY_ID=$(gpg --batch --yes --import "${SEMREL_GPG_SIGNKEY}" 2>&1 | grep "key [A-F0-9]" | head -n 1 | sed -e 's/^.*key \([A-F0-9]*\): .*$/\1/g') if [[ -z "${_GPG_KEY_ID}" ]]; then fail "Could not extract key ID from gpg --import command." fi git config --global commit.gpgsign true git config --global user.signingkey "${_GPG_KEY_ID}" log_info "Commit signing setup complete." } unscope_variables eval_all_secrets # ENDSCRIPT .semrel-base: image: $SEMREL_IMAGE services: - name: "$TBC_TRACKING_IMAGE" command: ["--service", "semrel", "3.11.5"] before_script: - !reference [.semrel-scripts] - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" # install git and OpenSSH - maybe_install_packages ca-certificates git openssh-client gpg gpg-agent - maybe_install_yq - cd "${SEMREL_CONFIG_DIR}" - prepare_semantic_release - install_semantic_release_plugins variables: # download cache XDG_CACHE_HOME: "$CI_PROJECT_DIR/.cache" # NPM cache npm_config_cache: "$CI_PROJECT_DIR/.npm" # Cache downloaded dependencies and plugins between builds. # To keep cache across branches add 'key: "$CI_JOB_NAME"' cache: # cache shall be per branch per template key: "$CI_COMMIT_REF_SLUG-SEMREL" when: always paths: - "$CI_PROJECT_DIR/.npm" - "$XDG_CACHE_HOME" semantic-release-info: extends: .semrel-base stage: .pre script: - dotenv_semrel_info artifacts: reports: dotenv: "${SEMREL_CONFIG_DIR}/semrel.out.env" rules: - if: $CI_COMMIT_TAG when: never - if: '$SEMREL_INFO_ON == "prod" && $CI_COMMIT_REF_NAME =~ $PROD_REF' - if: '$SEMREL_INFO_ON == "branches-ref" && $CI_COMMIT_REF_NAME =~ $SEMREL_BRANCHES_REF' - if: '$SEMREL_INFO_ON == "protected" && $CI_COMMIT_REF_PROTECTED == "true"' - if: '$SEMREL_INFO_ON == "all"' semantic-release: extends: .semrel-base stage: publish script: - configure_commit_signing - if [[ "$SEMREL_DRY_RUN" == "true" ]]; then dry_run_opt="--dry-run"; fi - semantic-release ${TRACE:+--debug} --ci $dry_run_opt dependencies: [] rules: - if: '$SEMREL_RELEASE_DISABLED == "true"' when: never - if: $CI_COMMIT_TAG when: never # exclude if branch doesn't match $SEMREL_BRANCHES_REF - if: '$CI_COMMIT_REF_NAME !~ $SEMREL_BRANCHES_REF' when: never # if $SEMREL_AUTO_RELEASE_ENABLED: auto - if: '$SEMREL_AUTO_RELEASE_ENABLED == "true"' # else manual - when: manual allow_failure: true