diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..6dc985e6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,71 @@ +name: Release + +on: + workflow_dispatch: + inputs: + dryRun: + description: Perform a dry-run only + required: false + type: boolean + releaseVersion: + description: Next release version + required: true + type: string + changeLog: + description: Pending changelog + required: true + type: string + +jobs: + release: + permissions: + id-token: 'write' + runs-on: ubuntu-latest + env: + LD_RELEASE_VERSION: ${{ inputs.releaseVersion }} + DRY_RUN: ${{ inputs.dryRun || 'false' }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ARTIFACT_DIRECTORY: "/tmp/release-artifacts" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 + name: Get secrets + with: + aws_assume_role: ${{ vars.AWS_ROLE_ARN }} + ssm_parameter_pairs: '/global/services/docker/public/username = DOCKER_USERNAME, /global/services/docker/public/token = DOCKER_TOKEN, /production/common/releasing/circleci/orb-token= CIRCLECI_CLI_TOKEN, /production/common/releasing/bitbucket/username = BITBUCKET_USERNAME, /production/common/releasing/bitbucket/token = BITBUCKET_TOKEN' + - name: setup access for find-code-references + uses: launchdarkly/gh-actions/actions/ssh-key-by-repo@main + with: + repo_keys_map: | + { + "launchdarkly/find-code-references": ${{ toJSON(secrets.LAUNCHDARKLY_FIND_CODE_REFERENCES_DEPLOY_KEY) }} + } + - name: build + run: | + if [[ $LD_RELEASE_VERSION == v* ]]; then + echo "Remove v prefix from version: $LD_RELEASE_VERSION" + exit 1 + fi + + make build + - name: prepare release + run: ./scripts/release/prepare-release.sh + - name: publish + run: | + if [[ "$DRY_RUN" = true ]]; then + ./scripts/release/publish-dry-run.sh + else + ./scripts/release/publish.sh + fi + - name: Commit changes and tag + run: | + ./scripts/release/commit-and-tag.sh + - name: Create Github release + uses: ncipollo/release-action@v1.14.0 + if: ${{ inputs.dryRun != 'true' }} + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: v${{ inputs.releaseVersion }} + body: ${{ inputs.changeLog }} diff --git a/Makefile b/Makefile index 49aa5b85..9f832327 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ clean: rm -f build/package/github-actions/ld-find-code-refs-github-action rm -f build/package/bitbucket-pipelines/ld-find-code-refs-bitbucket-pipeline -RELEASE_CMD=curl -sL https://git.io/goreleaser | GOPATH=$(mktemp -d) VERSION=$(GORELEASER_VERSION) GITHUB_TOKEN=$(GITHUB_TOKEN) bash -s -- --clean --release-notes $(RELEASE_NOTES) +RELEASE_CMD=curl -sL https://git.io/goreleaser | GOPATH=$(mktemp -d) VERSION=$(GORELEASER_VERSION) GITHUB_TOKEN=$(GITHUB_TOKEN) bash -s -- --clean --debug --release-notes $(RELEASE_NOTES) publish: $(RELEASE_CMD) diff --git a/scripts/release/commit-and-tag.sh b/scripts/release/commit-and-tag.sh new file mode 100755 index 00000000..72e9fdb3 --- /dev/null +++ b/scripts/release/commit-and-tag.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -euo pipefail + +RELEASE_TAG="v${LD_RELEASE_VERSION}" + +echo "Changes staged for release $RELEASE_TAG:" +git diff + +if [[ "$DRY_RUN" == "true" ]]; then + echo "Dry run mode: skipping commit, tag, and push" +else + if tag_exists; then + echo "Tag $RELEASE_TAG already exists. Aborting." + exit 1 + fi + + git config user.name "LaunchDarklyReleaseBot" + git config user.email "releasebot@launchdarkly.com" + git add . + git commit -m "Prepare release ${RELEASE_TAG}" + git tag "${RELEASE_TAG}" + git push origin HEAD + git push origin "${RELEASE_TAG}" +fi + diff --git a/scripts/release/prepare-release.sh b/scripts/release/prepare-release.sh new file mode 100755 index 00000000..4970a681 --- /dev/null +++ b/scripts/release/prepare-release.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -euo pipefail + +RELEASE_TAG="v${LD_RELEASE_VERSION}" + +update_go() ( + sed -i "s/const Version =.*/const Version = \"${LD_RELEASE_VERSION}\"/g" internal/version/version.go +) + +update_orb() ( + sed -i "s#launchdarkly/ld-find-code-refs@.*#launchdarkly/ld-find-code-refs@${LD_RELEASE_VERSION}#g" build/package/circleci/orb.yml + sed -i "s#- image: launchdarkly/ld-find-code-refs:.*#- image: launchdarkly/ld-find-code-refs:${LD_RELEASE_VERSION}#g" build/package/circleci/orb.yml +) + +update_gha() ( + sed -i "s#launchdarkly/find-code-references@v.*#launchdarkly/find-code-references@${RELEASE_TAG}#g" build/metadata/github-actions/README.md + sed -i "s#launchdarkly/ld-find-code-refs-github-action:.*#launchdarkly/ld-find-code-refs-github-action:${LD_RELEASE_VERSION}#g" build/metadata/github-actions/Dockerfile +) + +update_bitbucket() ( + sed -i "s#- pipe: launchdarkly/ld-find-code-refs-pipe.*#- pipe: launchdarkly/ld-find-code-refs-pipe:${LD_RELEASE_VERSION}#g" build/metadata/bitbucket/README.md + sed -i "s#image: launchdarkly/ld-find-code-refs-bitbucket-pipeline:.*#image: launchdarkly/ld-find-code-refs-bitbucket-pipeline:${LD_RELEASE_VERSION}#g" build/metadata/bitbucket/pipe.yml +) + +tag_exists() ( + git fetch --tags + git rev-parse "${RELEASE_TAG}" >/dev/null 2>&1 +) + +update_go +update_orb +update_gha +update_bitbucket diff --git a/scripts/release/publish-dry-run.sh b/scripts/release/publish-dry-run.sh new file mode 100755 index 00000000..8704dce5 --- /dev/null +++ b/scripts/release/publish-dry-run.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -euo pipefail + +echo ${DOCKER_TOKEN} | sudo docker login --username ${DOCKER_USERNAME} --password-stdin + +sudo PATH=${PATH} GITHUB_TOKEN=${GITHUB_TOKEN} make products-for-release + +mkdir -p ${ARTIFACT_DIRECTORY} + +# Copy the Docker image that goreleaser just built into the artifacts - we only do +# this in a dry run, because in a real release the image will be available from +# DockerHub anyway so there's no point in attaching it to the release. +BASE_CODEREFS=ld-find-code-refs +GH_CODEREFS=ld-find-code-refs-github-action +BB_CODEREFS=ld-find-code-refs-bitbucket-pipeline +sudo docker save launchdarkly/${BASE_CODEREFS}:latest | gzip >${ARTIFACT_DIRECTORY}/${BASE_CODEREFS}.tar.gz +sudo docker save launchdarkly/${GH_CODEREFS}:latest | gzip >${ARTIFACT_DIRECTORY}/${GH_CODEREFS}.tar.gz +sudo docker save launchdarkly/${BB_CODEREFS}:latest | gzip >${ARTIFACT_DIRECTORY}/${BB_CODEREFS}.tar.gz + +for script in $(dirname $0)/targets/*.sh; do + source $script +done + +dry_run_bitbucket +dry_run_gha +dry_run_circleci diff --git a/scripts/release/publish.sh b/scripts/release/publish.sh new file mode 100755 index 00000000..59d4aca4 --- /dev/null +++ b/scripts/release/publish.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euo pipefail + +sudo docker login --username ${DOCKER_USERNAME} --password-stdin ${DOCKER_TOKEN} + +sudo PATH=${PATH} GITHUB_TOKEN=${GITHUB_TOKEN} make publish + +# make bitbucket and github known hosts to push successfully +mkdir –m700 ~/.ssh +touch ~/.ssh/known_hosts +chmod 644 ~/.ssh/known_hosts +ssh-keyscan -t rsa bitbucket.org >> ~/.ssh/known_hosts +ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + +for script in $(dirname $0)/targets/*.sh; do + source $script +done + +publish_gha +publish_circleci +publish_bitbucket diff --git a/scripts/release/targets/bitbucket.sh b/scripts/release/targets/bitbucket.sh new file mode 100755 index 00000000..efdc16ad --- /dev/null +++ b/scripts/release/targets/bitbucket.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Run this in publish step after all version information have been updated. +set -euo pipefail + +setup_bitbucket() ( + rm -rf bitbucketMetadataUpdates + mkdir -p bitbucketMetadataUpdates + git clone "https://${BITBUCKET_USERNAME}:${BITBUCKET_TOKEN}@bitbucket.org/launchdarkly/ld-find-code-refs-pipe.git" bitbucketMetadataUpdates + cp build/metadata/bitbucket/* bitbucketMetadataUpdates/ + cp CHANGELOG.md bitbucketMetadataUpdates/ + cd bitbucketMetadataUpdates + git config user.email "launchdarklyreleasebot@launchdarkly.com" + git config user.name "LaunchDarklyReleaseBot" + git add -u + git commit -m "Release auto update version $LD_RELEASE_VERSION" + git remote add bb-origin "https://${BITBUCKET_USERNAME}:${BITBUCKET_TOKEN}@bitbucket.org/launchdarkly/ld-find-code-refs-pipe.git" +) + +clean_up_bitbucket() ( + cd .. && rm -rf bitbucketMetadataUpdates +) + +publish_bitbucket() ( + setup_bitbucket + cd bitbucketMetadataUpdates + + if git ls-remote --tags origin "refs/tags/v$VERSION" | grep -q "v$VERSION"; then + echo "Version exists; skipping publishing BitBucket Pipe" + else + echo "Live run: will publish pipe to bitbucket." + git tag $LD_RELEASE_VERSION + git push bb-origin master --tags + fi + + clean_up_bitbucket +) + +dry_run_bitbucket() ( + setup_bitbucket + + echo "Dry run: will not publish pipe to bitbucket." + cd bitbucketMetadataUpdates + git push bb-origin master --tags --dry-run + + clean_up_bitbucket +) diff --git a/scripts/release/targets/circleci.sh b/scripts/release/targets/circleci.sh new file mode 100755 index 00000000..32ae63a6 --- /dev/null +++ b/scripts/release/targets/circleci.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Run this in publish step after all version information have been updated. +set -euo pipefail + +CIRCLECI_CLI_HOST="https://circleci.com" + +install_circleci() ( + # Install the CircleCI CLI tool. + # https://github.com/CircleCI-Public/circleci-cli + # Dependencies: curl, cut + # The version to install and the binary location can be passed in via VERSION and DESTDIR respectively. + + # GitHub's URL for the latest release, will redirect. + local GITHUB_BASE_URL="https://github.com/CircleCI-Public/circleci-cli" + local LATEST_URL="${GITHUB_BASE_URL}/releases/latest/" + local DESTDIR="${DESTDIR:-/usr/local/bin}" + local VERSION=$(curl -sLI -o /dev/null -w '%{url_effective}' "$LATEST_URL" | cut -d "v" -f 2) + + # Run the script in a temporary directory that we know is empty. + local SCRATCH=$(mktemp -d || mktemp -d -t 'tmp') + cd "$SCRATCH" + + error() ( + echo "An error occured installing the tool." + echo "The contents of the directory $SCRATCH have been left in place to help to debug the issue." + ) + + trap error ERR + + case "$(uname)" in + Linux) + OS='linux' + ;; + Darwin) + OS='darwin' + ;; + *) + echo "This operating system is not supported." + exit 1 + ;; + esac + + local RELEASE_URL="${GITHUB_BASE_URL}/releases/download/v${VERSION}/circleci-cli_${VERSION}_${OS}_amd64.tar.gz" + + # Download & unpack the release tarball. + curl -sL --retry 3 "${RELEASE_URL}" | tar zx --strip 1 + install circleci "$DESTDIR" + command -v circleci + + # Delete the working directory when the install was successful. + rm -r "$SCRATCH" +) + +validate_circleci_orb_config() ( + circleci orb validate build/package/circleci/orb.yml || (echo "Unable to validate orb"; exit 1) +) + +publish_circleci() ( + install_circleci + validate_circleci_orb_config + + if circleci orb list | grep launchdarkly/ld-find-code-refs@$LD_RELEASE_VERSION; then + echo "Version exists; skipping publishing CircleCI Orb" + else + echo "Live run: will publish orb to production circleci repo." + circleci orb publish build/package/circleci/orb.yml launchdarkly/ld-find-code-refs@$LD_RELEASE_VERSION --token $CIRCLECI_CLI_TOKEN --host $CIRCLECI_CLI_HOST + fi +) + +dry_run_circleci() ( + install_circleci + validate_circleci_orb_config + + echo "Dry run: will not publish orb to production. Will publish to circleci dev repo instead." + circleci orb publish build/package/circleci/orb.yml launchdarkly/ld-find-code-refs@dev:$LD_RELEASE_VERSION --token $CIRCLECI_CLI_TOKEN --host $CIRCLECI_CLI_HOST +) diff --git a/scripts/release/targets/gha.sh b/scripts/release/targets/gha.sh new file mode 100755 index 00000000..4f229bcc --- /dev/null +++ b/scripts/release/targets/gha.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Run this in publish step after all version information have been updated. +set -euo pipefail + +RELEASE_TAG="v${LD_RELEASE_VERSION}" +RELEASE_NOTES="$(make echo-release-notes)" + +# All users of github action to reference major version tag +VERSION_MAJOR="${LD_RELEASE_VERSION%%\.*}" +RELEASE_TAG_MAJOR="v${VERSION_MAJOR}" + +setup_gha() ( + # install gh cli so we can create a release later https://github.com/cli/cli/blob/trunk/docs/install_linux.md + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt update + sudo apt install gh + + gh auth setup-git + + # clone checkout commit and push all metadata changes to gha repo + mkdir -p githubActionsMetadataUpdates + gh repo clone launchdarkly/find-code-references githubActionsMetadataUpdates + cp build/metadata/github-actions/* githubActionsMetadataUpdates + cd githubActionsMetadataUpdates + git config user.email "launchdarklyreleasebot@launchdarkly.com" + git config user.name "LaunchDarklyReleaseBot" + git branch -vv + git add -u + git commit -m "Release auto update version $LD_RELEASE_VERSION" + pwd +) + +clean_up_gha() ( + cd .. && rm -rf githubActionsMetadataUpdates +) + +publish_gha() ( + setup_gha + + if git ls-remote --tags origin "refs/tags/v$VERSION" | grep -q "v$VERSION"; then + echo "Version exists; skipping publishing GHA" + else + echo "Live run: will publish action to github action marketplace." + # tag the commit with the release version and create release + git tag $RELEASE_TAG + git push origin main --tags + git tag -f $RELEASE_TAG_MAJOR + git push -f origin $RELEASE_TAG_MAJOR + gh release create $RELEASE_TAG --notes "$RELEASE_NOTES" + fi + + clean_up +) + +dry_run_gha() ( + setup_gha + + echo "Dry run: will not publish action to github action marketplace." + cd githubActionsMetadataUpdates + git push origin main --tags --dry-run + + clean_up_gha +)