From cce4f7ae29f83e5732bf54551e6971b4048c0cf9 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 5 Aug 2024 14:54:26 -0400 Subject: [PATCH 1/5] Update readme --- README.md | 55 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 401b052..b9ba84e 100644 --- a/README.md +++ b/README.md @@ -9,29 +9,32 @@ The `rancher/shell` image is used: ## Branches and Releases This is the current branch strategy for `rancher/shell`, it may change in the future. -| Branch | Tag | Rancher | -|----------------|----------|-------------------| -| `master` | `v0.1.x` | `v2.7.x`,`v2.8.x` | -| `release/v2.9` | `v0.2.x` | `v2.9.x` | - -### v0.1.x Branch -This branch supports Rancher 2.7/2.8 and spans supporting k8s 1.23 through 1.28. -Here are the current component versions and their k8s support: - -| Component | Version | Supported K8s | -|-------------|---------|-----------------------------| -| `kubectl` |`v1.26.x`| `1.25`,`1.26`,`1.27` | -| `kustomize` |`v5.4.x` | n/a | -| `helm` |`v3.13.3-rancher1`| `1.25`,`1.26`,`1.27`,`1.28` | -| `k9s` |`v0.32.4`| Uses `client-go` v0.29.3 | - -### v0.2.x Branch -This branch supports Rancher 2.9 and spans supporting k8s 1.27 through 1.30. -Here are the current component versions and their k8s support: - -| Component | Version | Supported K8s | -|-------------|--------------------|----------------------------| -| `kubectl` | `v1.28.x` | `1.27`,`1.28`,`1.29` | -| `kustomize` | `v5.4.x` | n/a | -| `helm` | `v3.14.3-rancher1` | `1.26`,`1.27`,`1.28`,`1.29`| -| `k9s` | `v0.32.4` | Uses `client-go` v0.29.3 | +| Branch | Tag | Rancher | +|-----------------------|----------|------------------------| +| `main` | `head` | `main` branch (`head`) | +| `release/v2.9` | `v0.2.x` | `v2.9.x` | +| `release/v2.8` | `v0.1.x` | `v2.7.x`,`v2.8.x` | +| `master` (deprecated) | `v0.1.x` | `v2.7.x`,`v2.8.x` | + +### Branch Info Overview + +Each shell branch must constrain itself to use versions compatible with the respective Rancher releases. +Specifically to ensure maximum possible compatibility with the k8s versions that the Rancher release it targets supports. + +Always refer to the [Support Compatability Matrix](https://www.suse.com/suse-rancher/support-matrix/) (or internal docs for future releases) as an official to ensure compatability. +That said, here a quick visual reference (Aug 2024): + + +```mermaid + +gantt + title k8s versions supported by `rancher/rancher` + todayMarker off + dateFormat X + axisFormat 1.%S + tickInterval 1second + section Rancher + 2.7.X (EOL 18 Nov 2024) :23,27 + 2.8.X :25,28 + 2.9.X :27,30 +``` \ No newline at end of file From 943c0bce507cc25179df9ddba1de73da986c74bd Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 5 Aug 2024 15:00:05 -0400 Subject: [PATCH 2/5] Reuse the BRO modified porting workflows --- .github/workflows/port-issue.yaml | 117 +++++++++++++++++++++++++ .github/workflows/port-pr.yaml | 141 ++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 .github/workflows/port-issue.yaml create mode 100644 .github/workflows/port-pr.yaml diff --git a/.github/workflows/port-issue.yaml b/.github/workflows/port-issue.yaml new file mode 100644 index 0000000..4397d1d --- /dev/null +++ b/.github/workflows/port-issue.yaml @@ -0,0 +1,117 @@ +# create a backport/forwardport of an issue when "/backport " is commented +name: Port issue +run-name: "Port issue ${{ github.event.issue.number }}: ${{ github.event.issue.title }}" + +on: + issue_comment: + types: + - created + +jobs: + port-issue: + runs-on: ubuntu-latest + if: ${{ !github.event.issue.pull_request && (contains(github.event.comment.body, '/backport') || contains(github.event.comment.body, '/forwardport')) }} + steps: + - name: Check org membership or repo ownership + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Check if the repository owner is an organization + is_org=$(gh api users/${GITHUB_REPOSITORY_OWNER} | jq -r '.type == "Organization"') + + if [[ "$is_org" == "true" ]]; then + # Check if the actor is a member of the organization + # User's membership must be set public + if gh api orgs/${GITHUB_REPOSITORY_OWNER}/members --paginate | jq -e --arg GITHUB_ACTOR "$GITHUB_ACTOR" '.[] | select(.login == $GITHUB_ACTOR)' > /dev/null; then + echo "${GITHUB_ACTOR} is a member" + echo "is_member=true" >> $GITHUB_ENV + else + echo "${GITHUB_ACTOR} is not a member of ${GITHUB_REPOSITORY_OWNER}" >> $GITHUB_STEP_SUMMARY + echo "is_member=false" >> $GITHUB_ENV + fi + else + # If the owner is not an organization, treat it as an individual repo + if [[ "$GITHUB_REPOSITORY_OWNER" == "$GITHUB_ACTOR" ]]; then + echo "${GITHUB_ACTOR} is the repository owner" + echo "is_member=true" >> $GITHUB_ENV + else + echo "${GITHUB_ACTOR} is not the repository owner" >> $GITHUB_STEP_SUMMARY + echo "is_member=false" >> $GITHUB_ENV + fi + fi + - name: Check milestone + if: ${{ env.is_member == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ORIGINAL_ISSUE_NUMBER: ${{ github.event.issue.number }} + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + BODY_MILESTONE=$(echo "${COMMENT_BODY}" | awk '{ print $2 }') + echo "BODY_MILESTONE '${BODY_MILESTONE}'" + + # Sanitize input + MILESTONE=${BODY_MILESTONE//[^a-zA-Z0-9\-\.]/} + echo "MILESTONE '${MILESTONE}'" + + if gh api repos/${GITHUB_REPOSITORY}/milestones --paginate | jq -e --arg MILESTONE "$MILESTONE" '.[] | select(.title == $MILESTONE)' > /dev/null; then + echo "Milestone '${MILESTONE}' exists" + echo "milestone_exists=true" >> $GITHUB_ENV + else + echo "Milestone '${MILESTONE}' does not exist" >> $GITHUB_STEP_SUMMARY + gh issue comment -R ${GITHUB_REPOSITORY} ${ORIGINAL_ISSUE_NUMBER} --body "Not creating port issue, milestone ${MILESTONE} does not exist or is not an open milestone" + echo "milestone_exists=false" >> $GITHUB_ENV + fi + - name: Port issue + if: ${{ env.is_member == 'true' && env.milestone_exists == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ORIGINAL_ISSUE_NUMBER: ${{ github.event.issue.number }} + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + declare -a additional_cmd + BODY=$(mktemp) + ORIGINAL_ISSUE=$(gh issue view -R ${GITHUB_REPOSITORY} ${ORIGINAL_ISSUE_NUMBER} --json title,body,assignees) + ORIGINAL_TITLE=$(echo "${ORIGINAL_ISSUE}" | jq -r .title) + TYPE=$(echo "${COMMENT_BODY}" | awk '{ print $1 }' | sed -e 's_/__') + MILESTONE=$(echo "${COMMENT_BODY}" | awk '{ print $2 }') + NEW_TITLE="[${TYPE}] ${ORIGINAL_TITLE}" + if [[ $MILESTONE =~ (v[0-9]\.[0-9]+) ]]; then + NEW_TITLE="[${TYPE} ${MILESTONE}] ${ORIGINAL_TITLE}" + fi + additional_cmd+=("--label") + additional_cmd+=("QA/None") + ORIGINAL_LABELS=$(gh issue view -R ${GITHUB_REPOSITORY} ${ORIGINAL_ISSUE_NUMBER} --json labels --jq '.labels[].name' | grep -v '^\[zube\]:' | paste -sd "," -) + if [ -n "$ORIGINAL_LABELS" ]; then + additional_cmd+=("--label") + additional_cmd+=("${ORIGINAL_LABELS}") + fi + ORIGINAL_PROJECT=$(gh issue view -R ${GITHUB_REPOSITORY} ${ORIGINAL_ISSUE_NUMBER} --json projectItems --jq '.projectItems[].title') + if [ -n "$ORIGINAL_PROJECT" ]; then + additional_cmd+=("--project") + additional_cmd+=("${ORIGINAL_PROJECT}") + fi + ASSIGNEES=$(echo "${ORIGINAL_ISSUE}" | jq -r .assignees[].login) + if [ -n "$ASSIGNEES" ]; then + echo "Checking if assignee is member before assigning" + DELIMITER="" + NEW_ASSIGNEES="" + for ASSIGNEE in $ASSIGNEES; do + if gh api orgs/${GITHUB_REPOSITORY_OWNER}/members --paginate | jq -e --arg GITHUB_ACTOR "$GITHUB_ACTOR" '.[] | select(.login == $GITHUB_ACTOR)' > /dev/null; then + echo "${ASSIGNEE} is a member, adding to assignees" + NEW_ASSIGNEES="${NEW_ASSIGNEES}${DELIMITER}${ASSIGNEE}" + DELIMITER="," + fi + done + if [ -n "$NEW_ASSIGNEES" ]; then + echo "Assignees for new issue: ${NEW_ASSIGNEES}" + additional_cmd+=("--assignee") + additional_cmd+=("${NEW_ASSIGNEES}") + fi + fi + if [ -n "$MILESTONE" ]; then + echo -e "This is a ${TYPE} issue for #${ORIGINAL_ISSUE_NUMBER}, automatically created via [GitHub Actions workflow]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) initiated by @${GITHUB_ACTOR}\n" > $BODY + echo -e "\nOriginal issue body:\n" >> $BODY + echo "${ORIGINAL_ISSUE}" | jq -r '.body[0:65536]' >> $BODY + NEW_ISSUE=$(gh issue create -R "${GITHUB_REPOSITORY}" --title "${NEW_TITLE}" --body-file "${BODY}" -m "${MILESTONE}" "${additional_cmd[@]}") + echo "Port issue created: ${NEW_ISSUE}" >> $GITHUB_STEP_SUMMARY + fi \ No newline at end of file diff --git a/.github/workflows/port-pr.yaml b/.github/workflows/port-pr.yaml new file mode 100644 index 0000000..90ddee9 --- /dev/null +++ b/.github/workflows/port-pr.yaml @@ -0,0 +1,141 @@ +# create a backport/forwardport of a PR when "/backport " is commented +name: Port PR +run-name: "Port PR ${{ github.event.issue.number }}: ${{ github.event.issue.title }}" + +on: + issue_comment: + types: + - created + +env: + ORIGINAL_ISSUE_NUMBER: ${{ github.event.issue.number }} + +jobs: + port-pr: + runs-on: ubuntu-latest + if: ${{ github.event.issue.pull_request && (startsWith(github.event.comment.body, '/backport') || startsWith(github.event.comment.body, '/forwardport')) }} + steps: + - name: Check org membership or repo ownership + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Check if the repository owner is an organization + is_org=$(gh api users/${GITHUB_REPOSITORY_OWNER} | jq -r '.type == "Organization"') + + if [[ "$is_org" == "true" ]]; then + # Check if the actor is a member of the organization + # User's membership must be set public + if gh api orgs/${GITHUB_REPOSITORY_OWNER}/members --paginate | jq -e --arg GITHUB_ACTOR "$GITHUB_ACTOR" '.[] | select(.login == $GITHUB_ACTOR)' > /dev/null; then + echo "${GITHUB_ACTOR} is a member" + echo "is_member=true" >> $GITHUB_ENV + else + echo "${GITHUB_ACTOR} is not a member of ${GITHUB_REPOSITORY_OWNER}" >> $GITHUB_STEP_SUMMARY + echo "is_member=false" >> $GITHUB_ENV + fi + else + # If the owner is not an organization, treat it as an individual repo + if [[ "$GITHUB_REPOSITORY_OWNER" == "$GITHUB_ACTOR" ]]; then + echo "${GITHUB_ACTOR} is the repository owner" + echo "is_member=true" >> $GITHUB_ENV + else + echo "${GITHUB_ACTOR} is not the repository owner" >> $GITHUB_STEP_SUMMARY + echo "is_member=false" >> $GITHUB_ENV + fi + fi + - name: Check milestone + if: ${{ env.is_member == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + BODY_MILESTONE=$(echo "${COMMENT_BODY}" | awk '{ print $2 }') + # Sanitize input + MILESTONE=${BODY_MILESTONE//[^a-zA-Z0-9\-\.]/} + if gh api repos/${GITHUB_REPOSITORY}/milestones --paginate | jq -e --arg MILESTONE "$MILESTONE" '.[] | select(.title == $MILESTONE)' > /dev/null; then + echo "Milestone ${MILESTONE} exists" >> $GITHUB_STEP_SUMMARY + echo "milestone_exists=true" >> $GITHUB_ENV + echo "milestone=${MILESTONE}" >> $GITHUB_ENV + else + echo "Milestone ${MILESTONE} does not exist" >> $GITHUB_STEP_SUMMARY + echo "milestone_exists=false" >> $GITHUB_ENV + fi + - name: Get target branch + if: ${{ env.is_member == 'true' }} + env: + COMMENT_BODY: ${{ github.event.comment.body }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TYPE=$(echo "${COMMENT_BODY}" | awk '{ print $1 }' | sed -e 's_/__') + echo "Type: ${TYPE}" >> $GITHUB_STEP_SUMMARY + echo "type=${TYPE}" >> $GITHUB_ENV + TARGET_BRANCH=$(echo "${COMMENT_BODY}" | awk '{ print $3 }') + echo "Target branch: ${TARGET_BRANCH}" >> $GITHUB_STEP_SUMMARY + echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_ENV + if gh api repos/${GITHUB_REPOSITORY}/branches --paginate | jq -e --arg TARGET_BRANCH "$TARGET_BRANCH" '.[] | select(.name == $TARGET_BRANCH)' > /dev/null; then + echo "target_branch_exists=true" >> $GITHUB_ENV + else + gh issue comment -R ${GITHUB_REPOSITORY} ${ORIGINAL_ISSUE_NUMBER} --body "Not creating port issue, target ${TARGET_BRANCH} does not exist" + echo "target_branch_exists=false" >> $GITHUB_ENV + fi + - name: Checkout + if: ${{ env.is_member == 'true' && env.target_branch_exists == 'true' }} + uses: actions/checkout@v4 + with: + ref: ${{ env.target_branch }} + fetch-depth: '0' + token: ${{ secrets.GITHUB_TOKEN }} + - name: Port PR + if: ${{ env.is_member == 'true' && env.target_branch_exists == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TYPE: ${{ env.type }} + TARGET_BRANCH: ${{ env.target_branch }} + MILESTONE: ${{ env.milestone }} + run: | + PATCH_FILE=$(mktemp) + gh pr diff $ORIGINAL_ISSUE_NUMBER --patch > $PATCH_FILE + BRANCH="gha-portpr-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + echo "branch=${BRANCH}" >> $GITHUB_ENV + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "Rancher PR Port Bot" + git checkout -b $BRANCH + + if ! git am -3 "$PATCH_FILE" > error.log 2>&1; then + ERROR_MESSAGE=$(cat error.log) + FORMATTED_ERROR_MESSAGE=$(printf "\n\`\`\`\n%s\n\`\`\`" "$ERROR_MESSAGE") + gh issue comment ${ORIGINAL_ISSUE_NUMBER} --body "Not creating port PR, there was an error running git am -3: $FORMATTED_ERROR_MESSAGE" + echo "Port PR not created." >> $GITHUB_STEP_SUMMARY + else + git push origin $BRANCH + ORIGINAL_PR=$(gh pr view ${ORIGINAL_ISSUE_NUMBER} --json title,body,assignees) + ORIGINAL_TITLE=$(echo "${ORIGINAL_PR}" | jq -r .title) + ORIGINAL_ASSIGNEE=$(echo "${ORIGINAL_PR}" | jq -r '.assignee.login // empty') + BODY=$(mktemp) + echo -e "This is an automated request to port PR #${ORIGINAL_ISSUE_NUMBER} by @${GITHUB_ACTOR}\n\n" > $BODY + echo -e "Original PR body:\n\n" >> $BODY + echo "${ORIGINAL_PR}" | jq -r .body >> $BODY + ASSIGNEES=$(echo "${ORIGINAL_PR}" | jq -r .assignees[].login) + if [ -n "$ASSIGNEES" ]; then + echo "Checking if assignee is member before assigning" + DELIMITER="" + NEW_ASSIGNEES="" + for ASSIGNEE in $ASSIGNEES; do + if gh api orgs/${GITHUB_REPOSITORY_OWNER}/members --paginate | jq -e --arg GITHUB_ACTOR "$GITHUB_ACTOR" '.[] | select(.login == $GITHUB_ACTOR)' > /dev/null; then + echo "${ASSIGNEE} is a member, adding to assignees" + NEW_ASSIGNEES="${NEW_ASSIGNEES}${DELIMITER}${ASSIGNEE}" + DELIMITER="," + fi + done + if [ -n "$NEW_ASSIGNEES" ]; then + echo "Assignees for new issue: ${NEW_ASSIGNEES}" + additional_cmd+=("--assignee") + additional_cmd+=("${NEW_ASSIGNEES}") + fi + fi + NAMED_TARGET=${MILESTONE} + if [ -z "${MILESTONE}" ]; then + NAMED_TARGET="${TARGET_BRANCH}" + fi + NEW_PR=$(gh pr create --title="[${TYPE} ${NAMED_TARGET}] ${ORIGINAL_TITLE}" --body-file="${BODY}" --head "${BRANCH}" --base "${TARGET_BRANCH}" --milestone "${MILESTONE}" "${additional_cmd[@]}") + echo "Port PR created: ${NEW_PR}" >> $GITHUB_STEP_SUMMARY + fi \ No newline at end of file From c7813b11d3e9abc616708e771600c31891335369 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 5 Aug 2024 15:11:18 -0400 Subject: [PATCH 3/5] set the tag conditionally --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c1b327d..d3b8d4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,6 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Checkout code uses: actions/checkout@v4 - - name: Build and push all image variations run: | make image-push @@ -40,5 +39,5 @@ jobs: TAG="${TAG}-arm64" TARGET_PLATFORMS=linux/arm64 make image-push TAG="${TAG}-s390x" TARGET_PLATFORMS=linux/s390x make image-push env: - TAG: ${{ github.ref_name }} + TAG: ${{ github.ref == 'refs/heads/main' && 'head' || github.ref_name }} REPO: ${{ vars.PUBLIC_REGISTRY }}/${{ vars.PUBLIC_REGISTRY_REPO }} From e582173ac28666a559cdb5fd9e22ace373922004 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 5 Aug 2024 15:13:33 -0400 Subject: [PATCH 4/5] Add main branch to release workflow --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3b8d4a..134bbe3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,8 @@ name: Release on: push: + branches: + - main tags: - 'v*' From fb986fd7048cd5b7258be41c7ebd1456018c48bf Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 5 Aug 2024 15:19:07 -0400 Subject: [PATCH 5/5] Add concurrency limit and cancel to job... ...the logic here being and every job producing the same tag in this scenario. So there's no benefit to run every single build that would be triggered if PRs are merged sequentially. This will ensure only the "final" PR that was merged in a series of merges should build. --- .github/workflows/release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 134bbe3..99eb2d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,15 +7,17 @@ on: tags: - 'v*' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: write # Upload artefacts to release. id-token: write # required by read-vault-secrets. jobs: - publish-public: runs-on: ubuntu-latest - steps: - name: Load Secrets from Vault uses: rancher-eio/read-vault-secrets@main