# ========================================================================================= # 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:latest version: description: The [semantic-release](https://www.npmjs.com/package/semantic-release) version to use default: latest 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: '' info-on: description: Define on which branch(es) the job shall be run options: - '' - prod - protected - all default: '' --- workflow: rules: # prevent branch pipeline when an MR is open (prefer MR pipeline) - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS' 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 ]] # default production ref name (pattern) PROD_REF: /^(master|main)$/ 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 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 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 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 " - '@semantic-release/release-notes-generator'" 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 -g "semantic-release@${SEMREL_VERSION}" ${required_plugins} } # 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 install_yq() { if ! command -v yq > /dev/null then yq_binary=$1 yq_version=$2 yq_url="https://github.com/mikefarah/yq/releases/download/${yq_version}/${yq_binary}.tar.gz" wget -q "${yq_url}" -O - | tar xz && mv "${yq_binary}" /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 -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)" 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 " [" echo " \"@semantic-release/exec\"," echo " {" echo " \"analyzeCommitsCmd\": \"${export_last_version_hook_script} \\\"\${lastRelease.version}\\\"\"", echo " \"verifyReleaseCmd\": \"${export_next_version_hook_script} \\\"\${nextRelease.version}\\\" \\\"\${nextRelease.type}\\\"\"" echo " }" echo " ]," echo "]" } >> "${releaserc_file}.new" mv -f "${releaserc_file}.new" ".releaserc" if [[ -n "$TRACE" ]]; then log_info "--- generated .releaserc:" cat ".releaserc" fi npm install -g "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.8.2"] before_script: - !reference [.semrel-scripts] - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - cd "${SEMREL_CONFIG_DIR}" - install_yq "yq_linux_amd64" "v4.21.1" - prepare_semantic_release - install_semantic_release_plugins cache: # cache shall be per branch per template key: "$CI_COMMIT_REF_SLUG-SEMREL" paths: - .npm/ 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 == "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="-d"; fi - semantic-release ${TRACE:+--debug} --ci $dry_run_opt dependencies: [] rules: - if: '$SEMREL_RELEASE_DISABLED == "true"' when: never - if: $CI_COMMIT_TAG when: never # on production branch(es): auto if SEMREL_AUTO_RELEASE_ENABLED - if: '$SEMREL_AUTO_RELEASE_ENABLED == "true" && $CI_COMMIT_REF_NAME =~ $PROD_REF' # on production branch(es): manual by default - if: '$CI_COMMIT_REF_NAME =~ $PROD_REF' when: manual allow_failure: true