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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c1b327d..99eb2d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,18 +2,22 @@ name: Release on: push: + branches: + - main 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 @@ -32,7 +36,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 +43,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 }} 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` | +| 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` | -### 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: +### Branch Info Overview -| 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 | +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. -### 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: +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): -| 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 | + +```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