This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 295eea2b505 Add cherry picker (#44102)
295eea2b505 is described below
commit 295eea2b50567efdf7dae7b5976d5d2e225726e3
Author: GPK <[email protected]>
AuthorDate: Sat Nov 16 23:53:37 2024 +0000
Add cherry picker (#44102)
* adding cherry picker to support backport pr's
* update checkout_no_credentails logic to ignore for backport step
---
.github/workflows/backport-branch-finder.yml | 77 +++++++++++++++
.github/workflows/backport-cli.yml | 116 +++++++++++++++++++++++
dev/backport/.cherry_picker.toml | 23 +++++
dev/backport/update_backport_status.py | 91 ++++++++++++++++++
scripts/ci/pre_commit/checkout_no_credentials.py | 7 ++
5 files changed, 314 insertions(+)
diff --git a/.github/workflows/backport-branch-finder.yml
b/.github/workflows/backport-branch-finder.yml
new file mode 100644
index 00000000000..c52941792c5
--- /dev/null
+++ b/.github/workflows/backport-branch-finder.yml
@@ -0,0 +1,77 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+---
+name: Backport branch finder
+on: # yamllint disable-line rule:truthy
+ push:
+ branches:
+ - main
+
+jobs:
+ get-pr-info:
+ name: "Get PR information"
+ runs-on: ubuntu-latest
+ outputs:
+ branches: ${{ steps.pr-info.outputs.branches }}
+ commit-sha: ${{ github.sha }}
+ steps:
+ - name: Get commit SHA
+ id: get-sha
+ run: echo "COMMIT_SHA=${GITHUB_SHA}" >> $GITHUB_ENV
+
+ - name: Find PR information
+ id: pr-info
+ uses: actions/github-script@v7
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ script: |
+ const { data: pullRequest } = await
github.rest.repos.listPullRequestsAssociatedWithCommit({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ commit_sha: process.env.GITHUB_SHA
+ });
+ if (pullRequest.length > 0) {
+ const pr = pullRequest[0];
+ const backportBranches = pr.labels
+ .filter(label => label.name.startsWith('backport-to-'))
+ .map(label => label.name.replace('backport-to-', ''));
+
+ console.log(`Commit ${process.env.GITHUB_SHA} is associated
with PR ${pr.number}`);
+ console.log(`Backport branches: ${backportBranches}`);
+ core.setOutput('branches', JSON.stringify(backportBranches));
+ } else {
+ console.log('No pull request found for this commit.');
+ core.setOutput('branches', '[]');
+ }
+
+ trigger-backport:
+ name: "Trigger Backport"
+ uses: ./.github/workflows/backport-cli.yml
+ needs: get-pr-info
+ if: ${{ needs.get-pr-info.outputs.branches != '[]' }}
+ strategy:
+ matrix:
+ branch: ${{ fromJSON(needs.get-pr-info.outputs.branches) }}
+ fail-fast: false
+ permissions:
+ contents: write
+ pull-requests: write
+ with:
+ target-branch: ${{ matrix.branch }}
+ commit-sha: ${{ needs.get-pr-info.outputs.commit-sha }}
diff --git a/.github/workflows/backport-cli.yml
b/.github/workflows/backport-cli.yml
new file mode 100644
index 00000000000..f521dcb7d83
--- /dev/null
+++ b/.github/workflows/backport-cli.yml
@@ -0,0 +1,116 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+---
+name: Backport Commit
+on: # yamllint disable-line rule:truthy
+ workflow_dispatch:
+ inputs:
+ commit-sha:
+ description: "Commit sha to backport."
+ required: true
+ type: string
+ target-branch:
+ description: "Target branch to backport."
+ required: true
+ type: string
+
+ workflow_call:
+ inputs:
+ commit-sha:
+ description: "Commit sha to backport."
+ required: true
+ type: string
+ target-branch:
+ description: "Target branch to backport."
+ required: true
+ type: string
+
+permissions:
+ contents: write
+ pull-requests: write
+jobs:
+ backport:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
+ id: checkout-for-backport
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: true
+ fetch-depth: 0
+
+ - name: Install Python dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install cherry-picker==2.4.0 requests==2.32.3
+
+ - name: Run backport script
+ id: execute-backport
+ env:
+ GH_AUTH: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ git config --global user.email "[email protected]"
+ git config --global user.name "Your Name"
+ set +e
+ {
+ echo 'cherry_picker_output<<EOF'
+ cherry_picker --config-path dev/backport/.cherry_picker.toml \
+ ${{ inputs.commit-sha }} ${{ inputs.target-branch }}
+ echo EOF
+ } >> "${GITHUB_OUTPUT}"
+ continue-on-error: true
+
+ - name: Parse backport output
+ id: parse-backport-output
+ run: |
+ set +e
+ echo "${{ steps.execute-backport.outputs.cherry_picker_output }}"
+
+ url=$(echo "${{ steps.execute-backport.outputs.cherry_picker_output
}}" | \
+ grep -o 'Backport PR created at https://[^ ]*' | \
+ awk '{print $5}')
+
+ url=${url:-"EMPTY"}
+ if [ "$url" == "EMPTY" ]; then
+ # If the backport failed, abort the workflow
+ cherry_picker --abort
+ fi
+ echo "backport-url=$url" >> "${GITHUB_OUTPUT}"
+ continue-on-error: true
+
+ - name: Update Status
+ id: backport-status
+ env:
+ GH_TOKEN: ${{ github.token }}
+ REPOSITORY: ${{ github.repository }}
+ RUN_ID: ${{ github.run_id }}
+ run: |
+ COMMIT_INFO_URL="https://api.github.com/repos/${{ github.repository
}}/commits/"
+ COMMIT_INFO_URL="${COMMIT_INFO_URL}${{ inputs.commit-sha }}/pulls"
+
+ PR_NUMBER=$(gh api \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ /repos/${{ github.repository }}/commits/${{ inputs.commit-sha
}}/pulls \
+ --jq '.[0].number')
+
+ python ./dev/backport/update_backport_status.py \
+ ${{ steps.parse-backport-output.outputs.backport-url }} \
+ ${{ inputs.commit-sha }} ${{ inputs.target-branch }} \
+ "$PR_NUMBER"
diff --git a/dev/backport/.cherry_picker.toml b/dev/backport/.cherry_picker.toml
new file mode 100644
index 00000000000..677644159c7
--- /dev/null
+++ b/dev/backport/.cherry_picker.toml
@@ -0,0 +1,23 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+team = "apache"
+repo = "airflow"
+"check_sha"= "a85d94e6cdcd09efe93c3acee0b4ce5c9508bc23"
+fix_commit_msg = false
+default_branch = "main"
+require_version_in_branch_name=false
diff --git a/dev/backport/update_backport_status.py
b/dev/backport/update_backport_status.py
new file mode 100644
index 00000000000..e6e3ce064ce
--- /dev/null
+++ b/dev/backport/update_backport_status.py
@@ -0,0 +1,91 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import os
+import sys
+
+import requests
+
+
+def get_success_comment(branch: str, pr_url: str, pr_number: str):
+ shield_url = f"https://img.shields.io/badge/PR-{pr_number}-blue"
+ comment = f"""### Backport successfully created: {branch}\n\n<table>
+ <tr>
+ <th>Status</th>
+ <th>Branch</th>
+ <th>Result</th>
+ </tr>
+ <tr>
+ <td>✅</td>
+ <td>{branch}</td>
+ <td><a href="{pr_url}"><img src="{shield_url}" alt="PR
Link"></a></td>
+ </tr>
+ </table>"""
+ return comment
+
+
+def get_failure_comment(branch: str, commit_sha_url: str, commit_sha: str):
+ commit_shield_url =
f"https://img.shields.io/badge/Commit-{commit_sha[:7]}-red"
+ comment = f"""### Backport failed to create: {branch}. View the failure
log <a
href='https://github.com/{os.getenv("REPOSITORY")}/actions/runs/{os.getenv("RUN_ID")}'>
Run details </a>\n\n<table>
+ <tr>
+ <th>Status</th>
+ <th>Branch</th>
+ <th>Result</th>
+ </tr>
+ <tr>
+ <td>❌</td>
+ <td>{branch}</td>
+ <td><a href="{commit_sha_url}"><img src='{commit_shield_url}'
alt='Commit Link'></a></td>
+ </tr>
+ </table>"""
+ return comment
+
+
+def add_comments(backport_url: str, target_branch: str, commit_sha: str,
source_pr_number: str):
+ if backport_url.strip() != "EMPTY":
+ pr_number = backport_url.split("/")[-1]
+ comment = get_success_comment(branch=target_branch,
pr_url=backport_url, pr_number=pr_number)
+ else:
+ commit_sha_url =
f"https://github.com/{os.getenv('REPOSITORY')}/commit/{commit_sha}"
+ comment = get_failure_comment(
+ branch=target_branch, commit_sha_url=commit_sha_url,
commit_sha=commit_sha
+ )
+
+ token = os.getenv("GH_TOKEN")
+ comment_url =
f"https://api.github.com/repos/{os.getenv('REPOSITORY')}/issues/{source_pr_number}/comments"
+
+ headers = {"Authorization": f"Bearer {token}", "Accept":
"application/vnd.github+json"}
+
+ data = {"body": comment}
+
+ try:
+ response = requests.post(comment_url, headers=headers, json=data)
+ if not (response.status_code == 201):
+ raise requests.HTTPError(response=response)
+ except requests.HTTPError as e:
+ print(e)
+ print(f"Error: Failed to add comments to pr {source_pr_number}")
+ sys.exit(e.response.status_code)
+
+
+if __name__ == "__main__":
+ bp_url = sys.argv[1]
+ commit = sys.argv[2]
+ tg_branch = sys.argv[3]
+ source_pr = sys.argv[4]
+ add_comments(backport_url=bp_url, target_branch=tg_branch,
commit_sha=commit, source_pr_number=source_pr)
diff --git a/scripts/ci/pre_commit/checkout_no_credentials.py
b/scripts/ci/pre_commit/checkout_no_credentials.py
index 02e8f0a20f7..02a720eda6d 100755
--- a/scripts/ci/pre_commit/checkout_no_credentials.py
+++ b/scripts/ci/pre_commit/checkout_no_credentials.py
@@ -57,6 +57,13 @@ def check_file(the_file: Path) -> int:
# build. This is ok for security, because we are pushing
it only in the `main` branch
# of the repository and only for unprotected constraints
branch
continue
+ if step.get("id") == "checkout-for-backport":
+ # This is a special case - we are ok with persisting
credentials in backport
+ # step, because we need them to push backport branch back
to the repository in
+ # backport checkout-for-backport step and create pr for
cherry-picker. This is ok for
+ # security, because cherry picker pushing it only in the
`main` branch of the repository
+ # and only for unprotected backport branch
+ continue
persist_credentials = with_clause.get("persist-credentials")
if persist_credentials is None:
console.print(