Skip to content

Upstream Release Watch #50

Upstream Release Watch

Upstream Release Watch #50

name: Upstream Release Watch
# Keeps the fork level with DataDog's released version.
#
# Upstream publishes a v<N> GitHub release. Two things have to be true for us to
# match it: main must contain upstream v<N>, and Tero-Datadog-Extension-<N> must
# be published. This job checks both daily and whenever an upstream-version
# update lands on main, then moves whichever one is behind:
#
# main is behind -> open a PR merging upstream v<N>
# main is level, unreleased -> tag v<N>, which publishes and verifies the matrix
# source and full matrix level -> nothing to do
#
# The version main contains is recorded in .upstream-version. The merge PR bumps
# it, so releasing only ever happens after a human merged and CI was green.
on:
push:
branches:
- main
paths:
- .upstream-version
schedule:
# Daily at 13:00 UTC. Upstream cuts a release roughly monthly, so this is
# about noticing within a day rather than racing.
- cron: "0 13 * * *"
workflow_dispatch:
inputs:
release:
description: "Publish a release when main is level with upstream but unreleased"
required: false
default: true
type: boolean
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
jobs:
check:
name: Check upstream release
runs-on: ubuntu-22.04
outputs:
upstream_version: ${{ steps.compare.outputs.upstream_version }}
merged_version: ${{ steps.compare.outputs.merged_version }}
needs_merge: ${{ steps.compare.outputs.needs_merge }}
needs_release: ${{ steps.compare.outputs.needs_release }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Compare upstream release with our state
id: compare
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
UPSTREAM=$(gh release view \
--repo DataDog/datadog-lambda-extension \
--json tagName --jq '.tagName' | sed 's/^v//')
if [[ ! "$UPSTREAM" =~ ^[0-9]+$ ]]; then
echo "::error::Upstream latest release '$UPSTREAM' is not a plain version"
exit 1
fi
MERGED=$(tr -d '[:space:]' < .upstream-version)
echo "Upstream released: v$UPSTREAM | main contains: v$MERGED"
NEEDS_MERGE=false
NEEDS_RELEASE=false
if [ "$MERGED" -lt "$UPSTREAM" ]; then
NEEDS_MERGE=true
else
# A release is complete only when both architectures exist with a
# public policy in every supported region.
if ! ./scripts/verify_release_matrix.sh "$UPSTREAM" 1; then
NEEDS_RELEASE=true
fi
fi
{
echo "upstream_version=$UPSTREAM"
echo "merged_version=$MERGED"
echo "needs_merge=$NEEDS_MERGE"
echo "needs_release=$NEEDS_RELEASE"
} >> "$GITHUB_OUTPUT"
{
echo "## Upstream release watch"
echo ""
echo "| | Version |"
echo "|---|---|"
echo "| DataDog released | v$UPSTREAM |"
echo "| main contains | v$MERGED |"
echo ""
if [ "$NEEDS_MERGE" = true ]; then
echo "main is behind — opening a merge PR."
elif [ "$NEEDS_RELEASE" = true ]; then
echo "main is level with upstream but v$UPSTREAM is not published."
else
echo "Source and the complete production matrix are in parity. Nothing to do."
fi
} >> "$GITHUB_STEP_SUMMARY"
merge:
name: Open upstream merge PR
runs-on: ubuntu-22.04
needs: check
if: needs.check.outputs.needs_merge == 'true'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Merge upstream release
id: merge
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPSTREAM_VERSION: ${{ needs.check.outputs.upstream_version }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
BRANCH="chore/upstream-v${UPSTREAM_VERSION}"
if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
echo "Branch $BRANCH already exists; a merge is already in flight."
exit 0
fi
git remote add upstream https://github.com/DataDog/datadog-lambda-extension.git
git fetch --tags upstream "v${UPSTREAM_VERSION}"
git checkout -b "$BRANCH"
if ! git merge --no-edit "v${UPSTREAM_VERSION}"; then
CONFLICTS=$(git diff --name-only --diff-filter=U)
git merge --abort
echo "conflicts<<EOF" >> "$GITHUB_OUTPUT"
echo "$CONFLICTS" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "conflicted=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "$UPSTREAM_VERSION" > .upstream-version
git commit -am "chore: record upstream v${UPSTREAM_VERSION}" --allow-empty
git push origin "$BRANCH"
echo "conflicted=false" >> "$GITHUB_OUTPUT"
- name: Open pull request
if: steps.merge.outputs.conflicted == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPSTREAM_VERSION: ${{ needs.check.outputs.upstream_version }}
run: |
set -euo pipefail
gh pr create \
--base main \
--head "chore/upstream-v${UPSTREAM_VERSION}" \
--title "chore: merge upstream v${UPSTREAM_VERSION}" \
--body "$(printf '%s\n' \
"## Overview" \
"" \
"Merges DataDog upstream \`v${UPSTREAM_VERSION}\` and records it in \`.upstream-version\`." \
"Opened automatically by Upstream Release Watch." \
"" \
"## Testing" \
"" \
"CI must be green before merge. Once this lands, the watch publishes" \
"and verifies \`Tero-Datadog-Extension-${UPSTREAM_VERSION}\` in every supported" \
"region, then opens a separate consumer-template promotion PR." )"
# A conflicted merge needs judgement — stale lock entries and API breakage
# that only clippy catches. Report it and let a human drive.
- name: Report conflicts
if: steps.merge.outputs.conflicted == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPSTREAM_VERSION: ${{ needs.check.outputs.upstream_version }}
CONFLICTS: ${{ steps.merge.outputs.conflicts }}
run: |
set -euo pipefail
TITLE="Upstream v${UPSTREAM_VERSION} merge has conflicts"
if gh issue list --state open --search "$TITLE in:title" --json title \
--jq '.[].title' | grep -qxF "$TITLE"; then
echo "Issue already open."
exit 0
fi
gh issue create --title "$TITLE" --body "$(printf '%s\n' \
"Merging upstream \`v${UPSTREAM_VERSION}\` into main conflicts, so no PR was opened." \
"" \
"Conflicted files:" \
"" \
'```' \
"$CONFLICTS" \
'```' \
"" \
"Resolve locally, then set \`.upstream-version\` to ${UPSTREAM_VERSION}:" \
"" \
'```bash' \
"git remote add upstream https://github.com/DataDog/datadog-lambda-extension.git" \
"git fetch --tags upstream v${UPSTREAM_VERSION}" \
"git checkout -b chore/upstream-v${UPSTREAM_VERSION} && git merge v${UPSTREAM_VERSION}" \
'```' )"
# Tagging IS the release: pushing v<N> triggers release-extension.yml, which
# owns publishing. There is deliberately no second path from here.
#
# The push needs RELEASE_TAG_TOKEN (a PAT or GitHub App token with
# contents:write). A tag pushed with the default GITHUB_TOKEN does not trigger
# another workflow, so with GITHUB_TOKEN the tag would land and nothing would
# publish.
tag:
name: Tag v${{ needs.check.outputs.upstream_version }}
runs-on: ubuntu-22.04
needs: check
if: |
needs.check.outputs.needs_release == 'true'
&& (github.event_name == 'schedule' || github.event_name == 'push' || inputs.release)
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
# Checked out with the PAT so the tag push below is attributed to it
# and triggers the release workflow.
token: ${{ secrets.RELEASE_TAG_TOKEN }}
- name: Tag the release
env:
GH_TOKEN: ${{ secrets.RELEASE_TAG_TOKEN }}
UPSTREAM_VERSION: ${{ needs.check.outputs.upstream_version }}
run: |
set -euo pipefail
TAG="v${UPSTREAM_VERSION}"
# The tag already existing means a previous release was tagged but did
# not publish, so re-tagging cannot retry it. Fail loudly rather than
# leave the fork silently behind.
if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
echo "::error::$TAG is already tagged but Tero-Datadog-Extension-${UPSTREAM_VERSION} is not published."
echo "::error::Re-run Release Lambda Extension for $UPSTREAM_VERSION with dev unticked, then check why the tagged run failed."
exit 1
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag "$TAG"
git push origin "$TAG"
gh release create "$TAG" \
--title "$TAG" \
--notes "Tracks [DataDog ${TAG}](https://github.com/DataDog/datadog-lambda-extension/releases/tag/${TAG}). Publishes \`Tero-Datadog-Extension-${UPSTREAM_VERSION}\` and \`Tero-Datadog-Extension-${UPSTREAM_VERSION}-ARM\`."
echo "::notice::Pushed $TAG. Release Lambda Extension takes over from here."