diff --git a/.github/workflows/envoy-sync-receive.yaml b/.github/workflows/envoy-sync-receive.yaml new file mode 100644 index 0000000000..4178ad8860 --- /dev/null +++ b/.github/workflows/envoy-sync-receive.yaml @@ -0,0 +1,38 @@ +name: Sync from Envoy + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + branch: + type: string + required: true + description: 'Which branch to sync' + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + sync: + runs-on: ubuntu-22.04 + permissions: + contents: write + if: | + ${{ + !contains(github.actor, '[bot]') + || github.actor == 'sync-envoy[bot]' + }} + steps: + - name: "Checkout ${{ github.repository }}[${{ github.event.inputs.branch }}]" + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + ref: ${{ github.event.inputs.branch }} + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - run: ci/envoy-sync-receive.sh ${{ github.event.inputs.branch }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/ci/envoy-sync-receive.sh b/ci/envoy-sync-receive.sh new file mode 100755 index 0000000000..0dfc96a24d --- /dev/null +++ b/ci/envoy-sync-receive.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# +# Copyright Red Hat +# +# This script is invoked from .github/workflow/envoy-sync-receive.yaml workflow. +# +# It merges the specified branch from the upstream envoyproxy/envoy +# repository into the current branch in the current working directory. +# It is assumed that the envoy-sync-receive.yaml workflow has already +# checked out the destination repository and switched to the destination +# branch, in the current working directory before invoking us. +# +# - If the merge is successful, it: +# - pushes the feature branch to the fork +# - creates the associated pull request if it doesn't already exist +# - closes the associated issue if it already exists +# -If the merge is unsuccessful, it: +# - leaves the associated pull request untouched if it already exists +# - creates the associated issue issue if it doesn't already exist +# - adds a comment on the associated issue to describe the merge fail +# + +set -xeuo pipefail + +[[ -t 1 ]] && ANSI_GREEN="\033[0;32m" +[[ -t 1 ]] && ANSI_RED="\033[0;31m" +[[ -t 1 ]] && ANSI_RESET="\033[0m" + +info() { printf "${ANSI_GREEN:-}INFO: %s${ANSI_RESET:-}\n" "$1"; } +error() { printf "${ANSI_RED:-}ERROR: %s${ANSI_RESET:-}\n" "$1"; } + +SRC_REPO_URL="git@github.com:envoyproxy/envoy.git" +SRC_REPO_FULLNAME="$(echo "${SRC_REPO_URL}" | sed 's/^[^:]*://g;s/\.git$//g')" +SRC_REPO_ORG="${SRC_REPO_FULLNAME/%\/*/}" +SRC_REPO_NAME="${SRC_REPO_FULLNAME/#*\//}" +SRC_BRANCH_NAME="$1" +SRC_HEAD_SHA="$(git ls-remote "${SRC_REPO_URL}" | awk "/\srefs\/heads\/${SRC_BRANCH_NAME/\//\\\/}$/{print \$1}")" + +DST_REPO_URL=$(git remote get-url origin) +DST_REPO_FULLNAME="$(echo "${DST_REPO_URL}" | sed 's/^[^:]*://g;s/\.git$//g')" +DST_REPO_ORG="${DST_REPO_FULLNAME/%\/*/}" +DST_REPO_NAME="${DST_REPO_FULLNAME/#*\//}" +DST_BRANCH_NAME=$(git branch --show-current) +DST_HEAD_SHA=$(git rev-parse HEAD) + + +echo +echo "Source URL : ${SRC_REPO_URL}" +echo "Source org : ${SRC_REPO_ORG}" +echo "Source repo : ${SRC_REPO_NAME}" +echo "Source branch : ${SRC_BRANCH_NAME}" +echo "Source head : ${SRC_HEAD_SHA}" +echo +echo "Destination URL : ${DST_REPO_URL}" +echo "Destination org : ${DST_REPO_ORG}" +echo "Destination repo : ${DST_REPO_NAME}" +echo "Destination branch : ${DST_BRANCH_NAME}" +echo "Destination head : ${DST_HEAD_SHA}" +echo + +# Add the remote upstream repo and fetch the specified branch +git remote remove upstream &> /dev/null || true +git remote add -f -t "${SRC_BRANCH_NAME}" upstream "${SRC_REPO_URL}" + +# Compose text for pull request or issue title +TITLE="auto-merge ${SRC_REPO_ORG}/${SRC_REPO_NAME}[${SRC_BRANCH_NAME}] " +TITLE+="into ${DST_REPO_ORG}/${DST_REPO_NAME}[${DST_BRANCH_NAME}]" + +# Create a new branch name for the merge. Deliberately don't include +# any commit hash or timestamp in the name to ensure it is repeatable. +# This ensures that each time we get invoked, due to an upstream change, +# we accumulate the changes in the same branch and pull request, rather +# than creating new ones that superceed the old one(s) each time. +DST_NEW_BRANCH_NAME="auto-merge-$(echo "${SRC_BRANCH_NAME}" | tr /. -)" + +# Set the default remote for the gh command +gh repo set-default "${DST_REPO_ORG}/${DST_REPO_NAME}" + +# Perform the merge using --no-ff option to force creating a merge commit +info "Performing ${TITLE}" +if git merge --no-ff -m "${TITLE}" --log "upstream/${SRC_BRANCH_NAME}"; then + DST_NEW_HEAD_SHA="$(git rev-parse HEAD)" + if [[ "${DST_NEW_HEAD_SHA}" != "${DST_HEAD_SHA}" ]]; then + git push --force origin "HEAD:${DST_NEW_BRANCH_NAME}" + PR_COUNT=$(gh pr list --head "${DST_NEW_BRANCH_NAME}" \ + --base "${DST_BRANCH_NAME}" \ + --state open | wc -l) + if [[ ${PR_COUNT} == 0 ]]; then + PR_URL=$(gh pr create --head "${DST_NEW_BRANCH_NAME}" \ + --base "${DST_BRANCH_NAME}" \ + --title "${TITLE}" \ + --body "Generated by $(basename "$0")") + MERGE_OUTCOME="Created ${PR_URL}" + else + PR_ID=$(gh pr list --head "${DST_NEW_BRANCH_NAME}" \ + --base "${DST_BRANCH_NAME}" \ + --state open | head -1 | cut -f1) + PR_URL="https://github.com/${DST_REPO_ORG}/${DST_REPO_NAME}/pull/${PR_ID}" + MERGE_OUTCOME="Updated ${PR_URL}" + fi + else + MERGE_OUTCOME="No changes" + fi + info "${TITLE} successful (${MERGE_OUTCOME})" + # Close any related issues with a comment describing why + for ISSUE_ID in $(gh issue list -S "${TITLE} failed" | cut -f1); do + ISSUE_URL="https://github.com/${DST_REPO_ORG}/${DST_REPO_NAME}/issues/${ISSUE_ID}" + gh issue close "${ISSUE_URL}" --comment "Successful ${TITLE} (${MERGE_OUTCOME})" + info "Closed ${ISSUE_URL}" + done +else # merge fail + error "${TITLE} failed" + ISSUE_COUNT=$(gh issue list -S "${TITLE} failed" | wc -l) + if [[ ${ISSUE_COUNT} == 0 ]]; then + ISSUE_URL=$(gh issue create --title "${TITLE} failed" --body "${TITLE} failed") + ISSUE_OUTCOME="Created ${ISSUE_URL}" + else + ISSUE_ID="$(gh issue list -S "${TITLE} failed sort:created-asc" | tail -1 | cut -f1)" + ISSUE_URL="https://github.com/${DST_REPO_ORG}/${DST_REPO_NAME}/issues/${ISSUE_ID}" + ISSUE_OUTCOME="Updated ${ISSUE_URL}" + fi + gh issue comment "${ISSUE_URL}" --body-file - <<-EOF + Failed to ${TITLE} + From [${SRC_HEAD_SHA}](https://github.com/${SRC_REPO_ORG}/${SRC_REPO_NAME}/commit/${SRC_HEAD_SHA}) + @envoyproxy/envoy-openssl-sync + EOF + info "${ISSUE_OUTCOME}" +fi