diff --git a/.github/workflows/release-build-java.yml b/.github/workflows/release-build-java.yml index 90ad6d3..f00abd9 100644 --- a/.github/workflows/release-build-java.yml +++ b/.github/workflows/release-build-java.yml @@ -4,9 +4,21 @@ on: push: tags: - 'java-v[0-9]+.[0-9]+.[0-9]+' + workflow_dispatch: jobs: + validate-ref: + runs-on: ubuntu-22.04 + steps: + - name: Ensure ref is a release tag + run: | + if ! [[ "${GITHUB_REF_NAME}" =~ ^java-v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::This workflow must be dispatched from a release tag (java-vX.Y.Z), not '${GITHUB_REF_NAME}'" + exit 1 + fi + build-release-artifacts: + needs: validate-ref uses: ./.github/workflows/build-artifacts.yml with: BUILD_COMMAND: make build-java diff --git a/.github/workflows/release-build-nodejs.yml b/.github/workflows/release-build-nodejs.yml index 4e76fbd..724c936 100644 --- a/.github/workflows/release-build-nodejs.yml +++ b/.github/workflows/release-build-nodejs.yml @@ -4,9 +4,21 @@ on: push: tags: - 'nodejs-v[0-9]+.[0-9]+.[0-9]+' + workflow_dispatch: jobs: + validate-ref: + runs-on: ubuntu-22.04 + steps: + - name: Ensure ref is a release tag + run: | + if ! [[ "${GITHUB_REF_NAME}" =~ ^nodejs-v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::This workflow must be dispatched from a release tag (nodejs-vX.Y.Z), not '${GITHUB_REF_NAME}'" + exit 1 + fi + build-release-artifacts: + needs: validate-ref uses: ./.github/workflows/build-artifacts.yml with: BUILD_COMMAND: make build-nodejs diff --git a/.github/workflows/release-build-python.yml b/.github/workflows/release-build-python.yml index 2aa9f8e..bcda08d 100644 --- a/.github/workflows/release-build-python.yml +++ b/.github/workflows/release-build-python.yml @@ -4,9 +4,21 @@ on: push: tags: - 'python-v[0-9]+.[0-9]+.[0-9]+' + workflow_dispatch: jobs: + validate-ref: + runs-on: ubuntu-22.04 + steps: + - name: Ensure ref is a release tag + run: | + if ! [[ "${GITHUB_REF_NAME}" =~ ^python-v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::This workflow must be dispatched from a release tag (python-vX.Y.Z), not '${GITHUB_REF_NAME}'" + exit 1 + fi + build-release-artifacts: + needs: validate-ref uses: ./.github/workflows/build-artifacts.yml with: BUILD_COMMAND: make build-python diff --git a/.github/workflows/release-check-upstream.yml b/.github/workflows/release-check-upstream.yml new file mode 100644 index 0000000..be34f15 --- /dev/null +++ b/.github/workflows/release-check-upstream.yml @@ -0,0 +1,161 @@ +name: Check Upstream Releases + +on: + schedule: + - cron: '7 9 * * 1' + workflow_dispatch: + inputs: + dry_run: + description: "If true, detect releases but don't trigger prepare workflow" + required: false + type: boolean + default: true + language: + description: "Only check this language (leave empty to check all)" + required: false + type: choice + options: + - '' + - java + - nodejs + - python + +permissions: + contents: read + actions: write + pull-requests: read + +jobs: + check: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Check for new upstream releases + env: + GH_TOKEN: ${{ github.token }} + DRY_RUN: ${{ inputs.dry_run || 'false' }} + FILTER_LANGUAGE: ${{ inputs.language || '' }} + run: | + set -euo pipefail + + UPSTREAM="https://github.com/open-telemetry/opentelemetry-lambda.git" + + declare -A TAG_PREFIX=( + [java]="layer-javaagent" + [nodejs]="layer-nodejs" + [python]="layer-python" + ) + + semver_gt() { + local lhs="$1" rhs="$2" + if [[ "${lhs}" == "${rhs}" ]]; then + return 1 + fi + local IFS='.' + read -ra L <<< "${lhs}" + read -ra R <<< "${rhs}" + for i in 0 1 2; do + if (( ${L[$i]:-0} > ${R[$i]:-0} )); then + return 0 + elif (( ${L[$i]:-0} < ${R[$i]:-0} )); then + return 1 + fi + done + return 1 + } + + minor_bump() { + local ver="$1" + local IFS='.' + read -ra parts <<< "${ver}" + echo "${parts[0]}.$(( parts[1] + 1 )).0" + } + + latest_upstream_tag() { + local prefix="$1" + git ls-remote --tags "${UPSTREAM}" "refs/tags/${prefix}/*" \ + | sed 's|.*refs/tags/||' \ + | grep -v '\^{}' \ + | sort -t/ -k2 -V \ + | tail -1 + } + + TRIGGERED=() + + LANGUAGES=(java nodejs python) + if [[ -n "${FILTER_LANGUAGE}" ]]; then + LANGUAGES=("${FILTER_LANGUAGE}") + fi + + for LANGUAGE in "${LANGUAGES[@]}"; do + PREFIX="${TAG_PREFIX[${LANGUAGE}]}" + + echo "--- Checking ${LANGUAGE} (prefix: ${PREFIX}) ---" + + LATEST_UPSTREAM=$(latest_upstream_tag "${PREFIX}") + if [[ -z "${LATEST_UPSTREAM}" ]]; then + echo "WARNING: No upstream tags found for ${PREFIX}" + continue + fi + echo "Latest upstream: ${LATEST_UPSTREAM}" + + VERSION_FILE="${LANGUAGE}/version.txt" + CURRENT_VER=$(grep '^current_version=' "${VERSION_FILE}" | cut -d= -f2-) + LAST_PROCESSED=$(grep '^upstream_release_tag=' "${VERSION_FILE}" | cut -d= -f2-) + echo "Current version: ${CURRENT_VER}" + echo "Last processed upstream: ${LAST_PROCESSED}" + + if [[ -z "${CURRENT_VER}" || -z "${LAST_PROCESSED}" ]]; then + echo "ERROR: could not read named values from ${VERSION_FILE}" + continue + fi + + if [[ "${LATEST_UPSTREAM}" == "${LAST_PROCESSED}" ]]; then + echo "No new release for ${LANGUAGE}" + continue + fi + + UPSTREAM_VER="${LATEST_UPSTREAM#"${PREFIX}/"}" + PROCESSED_VER="${LAST_PROCESSED#"${PREFIX}/"}" + if ! semver_gt "${UPSTREAM_VER}" "${PROCESSED_VER}"; then + echo "Upstream ${UPSTREAM_VER} is not newer than ${PROCESSED_VER}" + continue + fi + + echo "New release detected: ${LATEST_UPSTREAM} > ${LAST_PROCESSED}" + + EXISTING_PR=$(gh pr list \ + --state open \ + --base main \ + --json number,headRefName \ + --jq "map(select(.headRefName | startswith(\"prepare-${LANGUAGE}-v\")))[0].number // empty") + if [[ -n "${EXISTING_PR}" ]]; then + echo "Skipping ${LANGUAGE}: open prepare PR #${EXISTING_PR} exists" + continue + fi + + NEXT_VER=$(minor_bump "${CURRENT_VER}") + echo "Version bump: ${CURRENT_VER} -> ${NEXT_VER}" + + if [[ "${DRY_RUN}" == "true" ]]; then + echo "[DRY RUN] Would trigger release-prepare for ${LANGUAGE} v${NEXT_VER} with tag ${LATEST_UPSTREAM}" + continue + fi + + echo "Triggering release-prepare for ${LANGUAGE} v${NEXT_VER}" + gh workflow run release-prepare.yml \ + -f language="${LANGUAGE}" \ + -f version="${NEXT_VER}" \ + -f otel_lambda_tag="${LATEST_UPSTREAM}" + TRIGGERED+=("${LANGUAGE}-v${NEXT_VER}") + done + + if [[ ${#TRIGGERED[@]} -gt 0 ]]; then + echo "" + echo "=== Triggered releases ===" + printf ' %s\n' "${TRIGGERED[@]}" + else + echo "" + echo "=== No release workflows triggered ===" + fi diff --git a/.github/workflows/release-finalize.yml b/.github/workflows/release-finalize.yml new file mode 100644 index 0000000..38c59e7 --- /dev/null +++ b/.github/workflows/release-finalize.yml @@ -0,0 +1,80 @@ +name: Release Finalize + +on: + workflow_dispatch: + inputs: + tag: + description: "Pre-release tag to finalize" + required: true + type: string + +permissions: + contents: write + +concurrency: + group: release-finalize-${{ inputs.tag }} + cancel-in-progress: false + +jobs: + finalize: + runs-on: ubuntu-22.04 + steps: + - name: Extract release details + id: parse + env: + TAG: ${{ inputs.tag }} + run: | + if ! [[ "${TAG}" =~ ^(java|nodejs|python)-v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + echo "::error::Unexpected release tag: ${TAG}" + exit 1 + fi + + LANGUAGE="${BASH_REMATCH[1]}" + VERSION="${BASH_REMATCH[2]}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "language=${LANGUAGE}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "release_branch=release-${TAG}" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email \ + "github-actions[bot]@users.noreply.github.com" + + - name: Create release branch + id: branch + env: + RELEASE_BRANCH: ${{ steps.parse.outputs.release_branch }} + run: | + if git ls-remote --exit-code --heads origin "${RELEASE_BRANCH}"; then + echo "Release branch ${RELEASE_BRANCH} already exists" + echo "exists=true" >> "$GITHUB_OUTPUT" + else + git checkout -b "${RELEASE_BRANCH}" + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Update language README title + if: steps.branch.outputs.exists != 'true' + env: + TAG: ${{ steps.parse.outputs.tag }} + LANGUAGE: ${{ steps.parse.outputs.language }} + VERSION: ${{ steps.parse.outputs.version }} + run: ./ci/release-finalize.sh + + - name: Push release branch + if: steps.branch.outputs.exists != 'true' + env: + TAG: ${{ steps.parse.outputs.tag }} + LANGUAGE: ${{ steps.parse.outputs.language }} + RELEASE_BRANCH: ${{ steps.parse.outputs.release_branch }} + run: | + git add "${LANGUAGE}/README.md" + git commit -m "docs: update ${LANGUAGE} README for ${TAG}" + git push origin "${RELEASE_BRANCH}" diff --git a/.github/workflows/release-prepare.yml b/.github/workflows/release-prepare.yml new file mode 100644 index 0000000..b88d92d --- /dev/null +++ b/.github/workflows/release-prepare.yml @@ -0,0 +1,182 @@ +name: Release Prepare + +on: + workflow_dispatch: + inputs: + language: + description: "Language to release" + required: true + type: choice + options: + - java + - nodejs + - python + version: + description: "Version (e.g. 1.41.0)" + required: true + type: string + otel_lambda_tag: + description: >- + opentelemetry-lambda release tag to pin the submodule to + (e.g. layer-python/0.19.0, layer-nodejs/0.21.0, + layer-javaagent/0.19.0) + required: true + type: string + +permissions: + contents: write + pull-requests: write + +concurrency: + group: release-prepare-${{ inputs.language }} + cancel-in-progress: false + +jobs: + prepare: + runs-on: ubuntu-22.04 + env: + LANGUAGE: ${{ inputs.language }} + VERSION: ${{ inputs.version }} + OTEL_LAMBDA_TAG: ${{ inputs.otel_lambda_tag }} + BRANCH: prepare-${{ inputs.language }}-v${{ inputs.version }} + steps: + - name: Validate inputs + run: | + if ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Version must be semver without v prefix (e.g. 1.41.0)" + exit 1 + fi + + case "${LANGUAGE}" in + java) EXPECTED_PREFIX="layer-javaagent/" ;; + nodejs) EXPECTED_PREFIX="layer-nodejs/" ;; + python) EXPECTED_PREFIX="layer-python/" ;; + *) + echo "::error::Unsupported language: ${LANGUAGE}" + exit 1 + ;; + esac + + if [[ "${OTEL_LAMBDA_TAG}" != "${EXPECTED_PREFIX}"* ]]; then + echo "::error::${LANGUAGE} requires an upstream tag starting with ${EXPECTED_PREFIX}" + exit 1 + fi + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + + - name: Verify upstream release tag exists + run: | + if ! git ls-remote --exit-code --tags \ + https://github.com/open-telemetry/opentelemetry-lambda.git \ + "refs/tags/${OTEL_LAMBDA_TAG}" >/dev/null; then + echo "::error::Upstream tag ${OTEL_LAMBDA_TAG} does not exist" + exit 1 + fi + + - name: Check release tag and branch do not already exist + run: | + TAG="${LANGUAGE}-v${VERSION}" + if git tag --list "${TAG}" | grep -q .; then + echo "::error::Tag ${TAG} already exists" + exit 1 + fi + if git ls-remote --heads origin "${BRANCH}" | grep -q .; then + echo "::error::Branch ${BRANCH} already exists on remote" + exit 1 + fi + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email \ + "github-actions[bot]@users.noreply.github.com" + + - name: Create branch + run: git checkout -b "${BRANCH}" + + - name: Pin opentelemetry-lambda release + id: submodule + run: | + git -C opentelemetry-lambda fetch --tags origin + git -C opentelemetry-lambda checkout --detach "${OTEL_LAMBDA_TAG}" + echo "commit=$(git -C opentelemetry-lambda rev-parse HEAD)" \ + >> "$GITHUB_OUTPUT" + + - name: Run prepare script + run: ./ci/release-prepare.sh + + - name: Commit changes + run: | + git add \ + "changelog/${LANGUAGE}-v${VERSION}.md" \ + "${LANGUAGE}/version.txt" \ + "${LANGUAGE}/layer-data.sh" \ + "${LANGUAGE}/sample-apps/template.yaml" \ + "${LANGUAGE}/README.md" \ + README.md \ + opentelemetry-lambda + COMMIT_MSG="feat: prepare release ${LANGUAGE} v${VERSION}" + git commit -m "${COMMIT_MSG}" + + - name: Push branch + run: git push origin "${BRANCH}" + + - name: Ensure release label exists + env: + GH_TOKEN: ${{ github.token }} + run: | + gh label create release \ + --color "0075ca" \ + --description "Release PR" \ + 2>/dev/null || true + + - name: Create pull request + env: + GH_TOKEN: ${{ github.token }} + run: | + UPSTREAM_REPO="https://github.com/open-telemetry/opentelemetry-lambda" + gh pr create \ + --title "feat: prepare release ${LANGUAGE} v${VERSION}" \ + --body "$(cat <- + github.event.pull_request.merged == true && + (startsWith( + github.event.pull_request.head.ref, + 'prepare-java-v') || + startsWith( + github.event.pull_request.head.ref, + 'prepare-nodejs-v') || + startsWith( + github.event.pull_request.head.ref, + 'prepare-python-v')) + runs-on: ubuntu-22.04 + steps: + - name: Extract release details + id: parse + env: + BRANCH: ${{ github.event.pull_request.head.ref }} + run: | + TAG="${BRANCH#prepare-}" + if ! [[ "${TAG}" =~ ^(java|nodejs|python)-v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Unexpected release branch: ${BRANCH}" + exit 1 + fi + LANGUAGE="${BASH_REMATCH[1]}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "language=${LANGUAGE}" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email \ + "github-actions[bot]@users.noreply.github.com" + + - name: Check release state + id: state + env: + TAG: ${{ steps.parse.outputs.tag }} + run: | + if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then + echo "Tag ${TAG} already exists; skipping changelog assembly" + echo "tag_exists=true" >> "$GITHUB_OUTPUT" + else + echo "tag_exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Assemble changelog fragment + if: steps.state.outputs.tag_exists != 'true' + env: + TAG: ${{ steps.parse.outputs.tag }} + run: | + FRAGMENT="changelog/${TAG}.md" + if [[ ! -f "${FRAGMENT}" ]]; then + echo "::error::Missing changelog fragment ${FRAGMENT}" + exit 1 + fi + if ! grep -q '^All notable changes' CHANGELOG.md; then + echo "::error::Could not find changelog insertion anchor" + exit 1 + fi + + sed -i "/^All notable changes/r ${FRAGMENT}" CHANGELOG.md + rm "${FRAGMENT}" + + - name: Commit changelog and create tag + if: steps.state.outputs.tag_exists != 'true' + env: + TAG: ${{ steps.parse.outputs.tag }} + run: | + git add CHANGELOG.md "changelog/${TAG}.md" + git commit -m "release: ${TAG}" + git tag -m "${TAG}" "${TAG}" + git push --atomic origin main "${TAG}" + + - name: Dispatch release build + env: + GH_TOKEN: ${{ github.token }} + LANGUAGE: ${{ steps.parse.outputs.language }} + TAG: ${{ steps.parse.outputs.tag }} + run: gh workflow run "release-build-${LANGUAGE}.yml" --ref "${TAG}" diff --git a/changelog/.gitkeep b/changelog/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ci/release-finalize.sh b/ci/release-finalize.sh new file mode 100755 index 0000000..74e5df7 --- /dev/null +++ b/ci/release-finalize.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -euo pipefail + +: "${LANGUAGE:?LANGUAGE is required (java, nodejs, or python)}" +: "${VERSION:?VERSION is required (e.g. 1.41.0)}" + +if [[ ! "${LANGUAGE}" =~ ^(java|nodejs|python)$ ]]; then + echo "ERROR: Unsupported language: ${LANGUAGE}" >&2 + exit 1 +fi +if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: Invalid version: ${VERSION}" >&2 + exit 1 +fi + +README="${LANGUAGE}/README.md" + +python3 - "${README}" "${VERSION}" <<'PYEOF' +import re +import sys + +readme_path = sys.argv[1] +version = sys.argv[2] + +with open(readme_path) as f: + content = f.read() + +title_pattern = re.compile( + r"^(# .*?)(?:unreleased version|v\d+\.\d+\.\d+)(.*)$", + re.MULTILINE, +) +if not title_pattern.search(content): + print(f"ERROR: release version not found in title of {readme_path}", + file=sys.stderr) + sys.exit(1) + +content = title_pattern.sub(rf"\g<1>v{version}\g<2>", content, count=1) + +with open(readme_path, "w") as f: + f.write(content) + +print(f"Updated {readme_path} title to v{version}") +PYEOF diff --git a/ci/release-prepare.sh b/ci/release-prepare.sh new file mode 100755 index 0000000..0dca927 --- /dev/null +++ b/ci/release-prepare.sh @@ -0,0 +1,155 @@ +#!/bin/bash + +set -euo pipefail + +: "${LANGUAGE:?LANGUAGE is required (java, nodejs, or python)}" +: "${VERSION:?VERSION is required (e.g. 1.41.0)}" +: "${OTEL_LAMBDA_TAG:?OTEL_LAMBDA_TAG is required (e.g. layer-python/0.19.0)}" + +VERSION_DASHED="v${VERSION//./-}" +TAG="${LANGUAGE}-v${VERSION}" +RELEASE_BRANCH="release-${TAG}" +DATE="$(date +%Y-%m-%d)" + +LAYER_DATA="${LANGUAGE}/layer-data.sh" +TEMPLATE="${LANGUAGE}/sample-apps/template.yaml" +VERSION_FILE="${LANGUAGE}/version.txt" +LANGUAGE_README="${LANGUAGE}/README.md" + +cleanup() { find . -maxdepth 3 -name '*.bak' -delete; } +trap cleanup EXIT + +OLD_VERSION_DASHED=$(grep '^VERSION=' "${LAYER_DATA}" | cut -d= -f2) +if [[ -z "${OLD_VERSION_DASHED}" ]]; then + echo "ERROR: could not extract VERSION from ${LAYER_DATA}" >&2 + exit 1 +fi + +# --- Create changelog fragment (avoids CHANGELOG.md merge conflicts) --- + +mkdir -p changelog +cat > "changelog/${TAG}.md" < "${VERSION_FILE}" + +# --- Update layer-data.sh --- + +sed -i.bak "s|^VERSION=.*|VERSION=${VERSION_DASHED}|" "${LAYER_DATA}" + +sed -i.bak \ + "s|tree/[^/]*/\{0,1\}${LANGUAGE}|tree/${RELEASE_BRANCH}/${LANGUAGE}|" \ + "${LAYER_DATA}" + +# --- Update template.yaml --- + +sed -i.bak "s|${OLD_VERSION_DASHED}|${VERSION_DASHED}|g" "${TEMPLATE}" + +# --- Detect component versions from submodule --- + +COLLECTOR_VERSION=$(grep "go.opentelemetry.io/collector/otelcol v" \ + opentelemetry-lambda/collector/go.mod | awk '{print $2}') +if [[ -z "${COLLECTOR_VERSION}" ]]; then + echo "ERROR: could not extract COLLECTOR_VERSION from collector/go.mod" >&2 + exit 1 +fi + +case "${LANGUAGE}" in + python) + SDK_VERSION=$(grep "^opentelemetry-sdk==" \ + opentelemetry-lambda/python/src/otel/otel_sdk/requirements.txt \ + | cut -d= -f3) + INSTRUMENTATION_VERSION=$(grep "^opentelemetry-distro==" \ + opentelemetry-lambda/python/src/otel/otel_sdk/requirements.txt \ + | cut -d= -f3) + if [[ -z "${SDK_VERSION}" ]]; then + echo "ERROR: could not extract opentelemetry-sdk version from requirements.txt" >&2 + exit 1 + fi + if [[ -z "${INSTRUMENTATION_VERSION}" ]]; then + echo "ERROR: could not extract opentelemetry-distro version from requirements.txt" >&2 + exit 1 + fi + ;; + nodejs) + SDK_VERSION=v$(grep -A5 '"node_modules/@opentelemetry/sdk-trace-node"' \ + opentelemetry-lambda/nodejs/package-lock.json \ + | grep '"version"' | grep -o '[0-9][0-9.]*' | head -1) + if [[ "${SDK_VERSION}" == "v" ]]; then + echo "ERROR: could not extract @opentelemetry/sdk-trace-node version from package-lock.json" >&2 + exit 1 + fi + ;; + java) + SDK_VERSION=$(grep 'opentelemetry-javaagent:' \ + opentelemetry-lambda/java/dependencyManagement/build.gradle.kts \ + | grep -o '[0-9][0-9.]*' | head -1) + if [[ -z "${SDK_VERSION}" ]]; then + echo "ERROR: could not extract opentelemetry-javaagent version from build.gradle.kts" >&2 + exit 1 + fi + ;; +esac + +# --- Update language README.md --- + +python3 - "${LANGUAGE_README}" "${VERSION}" <<'PYEOF' +import re +import sys + +readme_path = sys.argv[1] +version = sys.argv[2] + +with open(readme_path) as f: + content = f.read() + +pattern = re.compile( + r"^(# .*?)(?:unreleased version|v\d+\.\d+\.\d+)(.*)$", + re.MULTILINE, +) +if not pattern.search(content): + print(f"ERROR: release version not found in title of {readme_path}", + file=sys.stderr) + sys.exit(1) + +content = pattern.sub(rf"\g<1>v{version}\g<2>", content, count=1) + +with open(readme_path, "w") as f: + f.write(content) +PYEOF + +# --- Update root README.md --- + +sed -i.bak \ + "s|release-${LANGUAGE}-v[0-9][0-9.]*/${LANGUAGE}|release-${LANGUAGE}-v${VERSION}/${LANGUAGE}|g" \ + README.md + +case "${LANGUAGE}" in + python) + sed -i.bak "/Python layer/s|SDK \`v[^\`]*\`|SDK \`v${SDK_VERSION}\`|" README.md + sed -i.bak "/Python layer/s|instrumentation \`v[^\`]*\`|instrumentation \`v${INSTRUMENTATION_VERSION}\`|" README.md + sed -i.bak "/Python layer/s|Collector \`v[^\`]*\`|Collector \`${COLLECTOR_VERSION}\`|" README.md + ;; + nodejs) + sed -i.bak "/NodeJS layer/s|SDK \`v[^\`]*\`|SDK \`${SDK_VERSION}\`|" README.md + sed -i.bak "/NodeJS layer/s|Collector \`v[^\`]*\`|Collector \`${COLLECTOR_VERSION}\`|" README.md + ;; + java) + sed -i.bak "/Java wrapper/s|Java \`v[^\`]*\`|Java \`v${SDK_VERSION}\`|" README.md + sed -i.bak "/Java wrapper/s|Collector \`v[^\`]*\`|Collector \`${COLLECTOR_VERSION}\`|" README.md + ;; +esac + +echo "Release preparation complete for ${TAG}" diff --git a/docs/release.md b/docs/release.md index 0ba3c8d..6e2cd85 100644 --- a/docs/release.md +++ b/docs/release.md @@ -1,46 +1,105 @@ # Releasing guide -Perform the following steps in order to release new versions: +Release preparation and publication are automated with GitHub Actions. Java, +NodeJS, and Python preparation pull requests may be open at the same time. -1. Prepare and merge PR with following changes: +## Release metadata - - update [changelog](../CHANGELOG.md) - - in [README.md](../README.md) update version of the latest lambda layers - - update `/layer-data.sh` - - update `/sample-apps/template.yaml` +Each language has a `/version.txt` file with named values: -1. Create and push new tag: +```text +current_version=1.41.0 +upstream_release_tag=layer-python/0.20.0 +``` - Pushing new tag will trigger job which will create a pre-release draft. +- `current_version` is the latest Sumo Logic release for that language. +- `upstream_release_tag` is the upstream OpenTelemetry Lambda release detected + for that language. - ```bash - export LANGUAGE= # Possible values: java, nodejs, or python - export TAG=${LANGUAGE}-vx.y.z - git checkout main - git pull - git tag -m "${TAG}" "${TAG}" - git push origin "${TAG}" - ``` +The upstream tag prefixes are: -1. Prepare release branch +- Java: `layer-javaagent/` +- NodeJS: `layer-nodejs/` +- Python: `layer-python/` - - branch out: +Every prepare workflow pins the shared `opentelemetry-lambda` gitlink to the +selected language-specific upstream tag before deriving component versions. The +prepare pull request records both that tag and its resolved immutable commit SHA. - ```bash - git checkout -b "release-${TAG}" - ``` +## Prepare a release - - in specific language README.md (e.g. python/README.md) - - set a version as `x.y.z` (`unreleased version` in title) - - update Layer ARNs tables (look at pre-release draft) - - update Container Layer links with dependencies (look at pre-release draft) - - in [README.md](../README.md) update version of the latest lambda layers - - push branch: +The `Check Upstream Releases` workflow runs weekly and can also be dispatched +manually. It compares the latest language-specific upstream tag with +`upstream_release_tag` and dispatches `Release Prepare` when it finds a newer +release. - ```bash - git push -u origin "release-${TAG}" - ``` +`Release Prepare` can also be dispatched directly with: -1. Create [new release][releases] based on generated pre-release draft. +- `language`: `java`, `nodejs`, or `python` +- `version`: the next release version without a `v` prefix +- `otel_lambda_tag`: the matching language-specific upstream tag -[releases]: https://github.com/SumoLogic/sumologic-otel-lambda/releases +The workflow opens `prepare--v` with these changes: + +- `changelog/-v.md` +- `/version.txt` +- `/layer-data.sh` +- `/sample-apps/template.yaml` +- `/README.md` +- root `README.md` +- `opentelemetry-lambda` gitlink + +The changelog fragment prevents concurrent release preparations from editing +`CHANGELOG.md`. Before merging the pull request: + +1. Replace the fragment's TODO with the release changes. +2. Verify the named values in `/version.txt`. +3. Verify the layer version in `/layer-data.sh`. +4. Verify the layer version in `/sample-apps/template.yaml`. +5. Verify the version in the title of `/README.md`. +6. Verify the root README release link and component versions. +7. Verify that `opentelemetry-lambda` points to the upstream tag and commit SHA + listed in the pull request. +8. Resolve any normal pull-request conflict, including the shared root README + or submodule gitlink conflicts caused by another release merged first. Keep + the upstream pin selected for the pull request being merged, then recheck the + generated component versions. + +Only one open prepare pull request is allowed per language. An open Java +prepare pull request does not block NodeJS or Python preparation. Concurrent +language releases may point the shared submodule at different commits, so the +later pull request may require conflict resolution before merge. + +## Automatic post-merge flow + +Merging a prepare pull request triggers the following sequence: + +1. `Release Tag` serializes changes to `main` across all languages. +2. It inserts `changelog/-v.md` at the top of + `CHANGELOG.md` and deletes the fragment. +3. It commits the assembled changelog and creates + `-v` at that commit. +4. It atomically pushes `main` and the release tag, then explicitly dispatches + the matching `release-build-.yml` workflow at that tag. Explicit + dispatch is required because tag pushes made with `GITHUB_TOKEN` do not + start downstream push-triggered workflows. +5. The release-build workflow creates artifacts, publishes Lambda layers, and + creates a GitHub pre-release containing the ARN tables and release artifacts. +6. Review the pre-release. The GitHub release remains a pre-release. +7. When ready to publish, open the pre-release on the GitHub Releases page, + edit it, clear the pre-release setting, mark it as the latest release, and + publish it. + +Final promotion is a manual GitHub UI step. + +## Recovery + +- If preparation fails, correct the input or workflow issue and dispatch + `Release Prepare` again. +- If changelog assembly fails, restore or correct the expected fragment or the + `All notable changes` anchor before rerunning the merge-triggered workflow. +- If a tag exists but its build did not start or must be retried, dispatch the + matching `release-build-.yml` workflow with the existing release + tag as its ref. + +Do not promote the pre-release before its contents have been reviewed. diff --git a/java/version.txt b/java/version.txt new file mode 100644 index 0000000..6939b30 --- /dev/null +++ b/java/version.txt @@ -0,0 +1,2 @@ +current_version=2.19.0 +upstream_release_tag=layer-javaagent/0.19.0 diff --git a/nodejs/version.txt b/nodejs/version.txt new file mode 100644 index 0000000..15162ab --- /dev/null +++ b/nodejs/version.txt @@ -0,0 +1,2 @@ +current_version=2.0.2 +upstream_release_tag=layer-nodejs/0.21.0 diff --git a/opentelemetry-lambda b/opentelemetry-lambda index c0ac4ce..e285846 160000 --- a/opentelemetry-lambda +++ b/opentelemetry-lambda @@ -1 +1 @@ -Subproject commit c0ac4ce0bc0f33b07d7fa73b5fda734400625704 +Subproject commit e285846f398b9b5d7b88f63402f1cec89c19ce95 diff --git a/python/version.txt b/python/version.txt new file mode 100644 index 0000000..1ec435c --- /dev/null +++ b/python/version.txt @@ -0,0 +1,2 @@ +current_version=1.40.0 +upstream_release_tag=layer-python/0.19.0