From a6f1ae558ac6602476b48e49b3fad7aabee3dd15 Mon Sep 17 00:00:00 2001 From: Derek Su Date: Sun, 25 Aug 2024 13:00:43 +0800 Subject: [PATCH] chore(workflow): periodically build sprint release Longhorn 9237 Signed-off-by: Derek Su --- .github/workflows/release-sprint.yml | 273 ++++++++++++++++++++------- scripts/check-sprint-last-day.py | 129 +++++++++++++ 2 files changed, 333 insertions(+), 69 deletions(-) create mode 100644 scripts/check-sprint-last-day.py diff --git a/.github/workflows/release-sprint.yml b/.github/workflows/release-sprint.yml index ce133b4..f543675 100644 --- a/.github/workflows/release-sprint.yml +++ b/.github/workflows/release-sprint.yml @@ -3,6 +3,8 @@ name: Release - Sprint on: workflow_dispatch: inputs: + schedule: + - cron: '0 1 * * 0' # every Sunday at 01:00 defaults: run: @@ -24,27 +26,21 @@ jobs: strategy: matrix: - branch_tag: ['master:v1.7.0'] + branch_tag: ['master:v1.8.0', 'v1.7.x:v1.7.1', 'v1.6.x:v1.6.3'] steps: - - name: Setup Git + - name: Install dependencies run: | - gh auth setup-git - - - uses: anchore/sbom-action/download-syft@v0.17.2 - - - run: | - git config --global user.email "dko@suse.com" - git config --global user.name "David Ko" + python -m pip install requests - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - - uses: actions/checkout@v4 - with: - repository: innobead/renote - ref: main - path: renote - - run: cargo install --path ./renote + - name: Check sprint build is required + id: check_sprint_build_required + continue-on-error: true + run: | + python ./scripts/check-sprint-last-day.py "longhorn" "longhorn" "Longhorn Sprint" - id: var name: Get branch, tag, milestone @@ -53,56 +49,188 @@ jobs: branch="${tokens[0]}" tag="${tokens[1]}-dev-$(date '+%Y%m%d')" milestone="${tokens[2]}" + + current_date=$(date '+%Y%m%d') + prev_sprint_end_date=$(date -d "${current_date} - 14 days" '+%Y%m%d') + + prev_tag="${tokens[1]}-dev-${prev_sprint_end_date}" if [ -z "${tokens[2]}" ]; then milestone="${tokens[1]}" ; fi echo "branch=${branch}" >> $GITHUB_OUTPUT echo "tag=${tag}" >> $GITHUB_OUTPUT + echo "prev_tag=${prev_tag}" >> $GITHUB_OUTPUT echo "milestone=${milestone}" >> $GITHUB_OUTPUT cat < /dev/null 2>&1; echo $?) + echo "Tag ${{ steps.var.outputs.prev_tag }} already exists: ${exists}" + if [ $exists -ne 0 ]; then + echo "Creating tag ${{ steps.var.outputs.prev_tag }} for repository ${repo}..." + current_date=$(date '+%Y%m%d') + prev_sprint_end_date=$(date -d "${current_date} - 14 days" '+%Y%m%d') + + formatted_date=$(date -u -Is -d "${prev_sprint_end_date} 23:59:59") + + commit_sha=$(gh api graphql -f query=' + query($owner: String!, $repo: String!, $branch: String!, $until: GitTimestamp!) { + repository(owner: $owner, name: $repo) { + ref(qualifiedName: $branch) { + target { + ... on Commit { + history(until: $until, first: 1) { + nodes { + oid + committedDate + } + } + } + } + } + } + } + ' -F owner=longhorn -F repo=${repo} -F branch="${{ steps.var.outputs.branch }}" -F until="${formatted_date}" --jq '.data.repository.ref.target.history.nodes[0].oid') + + echo "Commit ${commit_sha} found before ${prev_sprint_end_date} for repository ${repo}." + if [ -n "$commit_sha" ]; then + gh api "repos/longhorn/${repo}/git/refs" \ + -F ref="refs/tags/${{ steps.var.outputs.prev_tag }}" \ + -F sha="${commit_sha}" + else + echo "No commit found before ${prev_sprint_end_date} for repository ${repo}." + fi + else + echo "Tag ${{ steps.var.outputs.prev_tag }} already exists for repository ${repo}." + fi + done + + - id: pr + name: Create a release PR (dryrun) + if: steps.check_sprint_build_required.outcome == 'success' + run: | + common_args=( + --owner longhorn + --repo longhorn + --branch "${{ steps.var.outputs.branch }}" + --tag "${{ steps.var.outputs.tag }}" + --longhorn-chart-repo charts + --longhorn-repos longhorn-manager + --longhorn-repos longhorn-engine + --longhorn-repos longhorn-instance-manager + --longhorn-repos longhorn-share-manager + --longhorn-repos backing-image-manager + --longhorn-repos longhorn-ui + --dryrun + ) + + if [ "${{ steps.var.outputs.branch }}" != "v1.6.x" ]; then + common_args+=(--longhorn-repos cli:longhorn-cli) + fi + + renote pr "${common_args[@]}" - id: artifact name: Collect artifacts + if: steps.check_sprint_build_required.outcome == 'success' run: | ./scripts/collect-artifacts.sh longhorn charts - id: tag name: Create a tag for each release repo + if: steps.check_sprint_build_required.outcome == 'success' run: | - renote tag \ - --owner longhorn \ - --branch ${{ steps.var.outputs.branch }} \ - --tag ${{ steps.var.outputs.tag }} \ - --repos longhorn-manager \ - --repos longhorn-engine \ - --repos longhorn-instance-manager \ - --repos longhorn-share-manager \ - --repos backing-image-manager \ - --repos longhorn-ui \ - --repos cli \ + common_args=( + --owner longhorn + --branch "${{ steps.var.outputs.branch }}" + --tag "${{ steps.var.outputs.tag }}" + --repos longhorn-manager + --repos longhorn-engine + --repos longhorn-instance-manager + --repos longhorn-share-manager + --repos backing-image-manager + --repos longhorn-ui --repos longhorn + ) + + if [ "${{ steps.var.outputs.branch }}" != "v1.6.x" ]; then + common_args+=(--repos cli) + fi + + renote tag "${common_args[@]}" - id: changelog name: Create a changelog from the last release + if: steps.check_sprint_build_required.outcome == 'success' run: | + repos=( + longhorn-manager + longhorn-engine + longhorn-instance-manager + longhorn-share-manager + backing-image-manager + longhorn-ui + longhorn + ) + + if [ "${{ steps.var.outputs.branch }}" != "v1.6.x" ]; then + repos+=(cli) + fi + output=$(renote changelog \ --log-level error \ --owner longhorn \ - --branch ${{ steps.var.outputs.branch }} \ - --tag ${{ steps.var.outputs.tag }} \ - --repos longhorn-manager \ - --repos longhorn-engine \ - --repos longhorn-instance-manager \ - --repos longhorn-share-manager \ - --repos backing-image-manager \ - --repos longhorn-ui \ - --repos cli \ - --repos longhorn \ + --branch "${{ steps.var.outputs.branch }}" \ + --tag "${{ steps.var.outputs.tag }}" \ + --prev-tag "${{ steps.var.outputs.prev_tag }}" \ + $(printf -- '--repos %s ' "${repos[@]}") \ --markdown-folding) echo "$output" @@ -110,37 +238,44 @@ jobs: - id: release name: Create a release + if: steps.check_sprint_build_required.outcome == 'success' run: | - renote release \ - --owner longhorn \ - --repo longhorn \ - --branch ${{ steps.var.outputs.branch }} \ - --tag ${{ steps.var.outputs.tag }} \ - --milestone ${{ steps.var.outputs.milestone }} \ - --pre-note ./assets/release-sprint/pre-note.md \ - --post-note ./assets/release-sprint/post-note.md \ - --note-contributors innobead \ - --note-section-disable \ - --since-days 14 \ - --exclude-labels "kind/test" \ - --exclude-labels "area/infra" \ - --exclude-labels "area/ci" \ - --exclude-labels "area/qa" \ - --exclude-labels "wontfix" \ - --exclude-labels "duplicated" \ - --exclude-labels "invalid" \ - --exclude-labels "release/task" \ - --pre-hook ./scripts/check-images-ready.sh \ - --pre-hook-args "longhornio/longhorn-manager:${{ steps.var.outputs.tag }}" \ - --pre-hook-args "longhornio/longhorn-engine:${{ steps.var.outputs.tag }}" \ - --pre-hook-args "longhornio/longhorn-instance-manager:${{ steps.var.outputs.tag }}" \ - --pre-hook-args "longhornio/longhorn-share-manager:${{ steps.var.outputs.tag }}" \ - --pre-hook-args "longhornio/backing-image-manager:${{ steps.var.outputs.tag }}" \ - --pre-hook-args "longhornio/longhorn-ui:${{ steps.var.outputs.tag }}" \ - --pre-hook-args "longhornio/longhorn-cli:${{ steps.var.outputs.tag }}" \ - --artifacts ./longhorn.yaml \ - --artifacts ./longhorn-images.txt \ - --artifacts ./charts.tar.gz \ - --artifacts ./longhorn-images-sbom.tar.gz \ - --pre-release \ + common_args=( + --owner longhorn + --repo longhorn + --branch "${{ steps.var.outputs.branch }}" + --tag "${{ steps.var.outputs.tag }}" + --milestone "${{ steps.var.outputs.milestone }}" + --pre-note ./assets/release-sprint/pre-note.md + --post-note ./assets/release-sprint/post-note.md + --note-contributors innobead + --note-section-disable + --since-days 14 + --exclude-labels "kind/test" + --exclude-labels "area/infra" + --exclude-labels "area/ci" + --exclude-labels "area/qa" + --exclude-labels "wontfix" + --exclude-labels "duplicated" + --exclude-labels "invalid" + --exclude-labels "release/task" + --pre-hook ./scripts/check-images-ready.sh + --pre-hook-args "longhornio/longhorn-manager:${{ steps.var.outputs.tag }}" + --pre-hook-args "longhornio/longhorn-engine:${{ steps.var.outputs.tag }}" + --pre-hook-args "longhornio/longhorn-instance-manager:${{ steps.var.outputs.tag }}" + --pre-hook-args "longhornio/longhorn-share-manager:${{ steps.var.outputs.tag }}" + --pre-hook-args "longhornio/backing-image-manager:${{ steps.var.outputs.tag }}" + --pre-hook-args "longhornio/longhorn-ui:${{ steps.var.outputs.tag }}" + --artifacts ./longhorn.yaml + --artifacts ./longhorn-images.txt + --artifacts ./charts.tar.gz + --artifacts ./longhorn-images-sbom.tar.gz + --pre-release --draft + ) + + if [ "${{ steps.var.outputs.branch }}" != "v1.6.x" ]; then + common_args+=(--pre-hook-args "longhornio/longhorn-cli:${{ steps.var.outputs.tag }}") + fi + + renote release "${common_args[@]}" diff --git a/scripts/check-sprint-last-day.py b/scripts/check-sprint-last-day.py new file mode 100644 index 0000000..847928e --- /dev/null +++ b/scripts/check-sprint-last-day.py @@ -0,0 +1,129 @@ +import requests +import os +import sys +from datetime import datetime, timedelta + + +GITHUB_GRAPHQL_URL = "https://api.github.com/graphql" + + +def get_github_project_info(github_token, github_org, github_project): + headers = { + "Authorization": f"Bearer {github_token}", + "Content-Type": "application/json" + } + query = ''' + { + organization(login: "%s") { + projectsV2(first: 20) { + nodes { + id + title + number + } + } + } + } + ''' % (github_org) + payload = { + "query": query + } + + response = requests.post(GITHUB_GRAPHQL_URL, headers=headers, json=payload) + if response.status_code == 200: + # fine project by title + nodes = response.json().get("data").get("organization").get("projectsV2").get("nodes") + for node in nodes: + if node.get("title") == github_project: + return node + else: + response.raise_for_status() + + +def get_current_sprint(github_token, project_id): + headers = { + "Authorization": f"Bearer {github_token}", + "Content-Type": "application/json" + } + query = ''' + query { + node(id: "%s") { + ... on ProjectV2 { + fields(first: 20) { + nodes { + ... on ProjectV2IterationField { + configuration { + iterations { + startDate + id + } + } + } + } + } + } + } + } + ''' % (project_id) + + payload = { + "query": query + } + + response = requests.post(GITHUB_GRAPHQL_URL, headers=headers, json=payload) + if response.status_code == 200: + # fine project by title + result = response.json().get("data").get("node").get("fields").get("nodes") + filtered_result = [node for node in result if 'configuration' in node] + iterations = filtered_result[0].get("configuration").get("iterations") + + # Find current iteration + current_date = datetime.now().date() + current_iteration = None + for iteration in iterations: + start_date = datetime.strptime(iteration['startDate'], "%Y-%m-%d").date() + end_date = start_date + timedelta(days=13) + if start_date <= current_date <= end_date: + current_iteration = iteration + break + + return current_iteration + else: + response.raise_for_status() + + +def is_today_is_in_last_day_of_current_sprint(github_token, project_id): + current_iteration = get_current_sprint(github_token, project_id) + if current_iteration is None: + print("Current sprint not found") + return False + + current_date = datetime.now().date() + + end_date = datetime.strptime(current_iteration['startDate'], "%Y-%m-%d").date() + timedelta(days=13) + + print("Current date: %s, end date: %s" % (current_date, end_date)) + + return current_date == end_date + + +if __name__ == "__main__": + if len(sys.argv) < 4: + print('Usage: python scan_and_notify_testing_items.py github_org github_repo github_project') + sys.exit() + + github_token = os.getenv("GITHUB_TOKEN") + + github_org = sys.argv[1] + github_repo = sys.argv[2] + github_project = sys.argv[3] + + project = get_github_project_info(github_token, github_org, github_project) + print(f"GitHub Project Details: {project}") + + last_day = is_today_is_in_last_day_of_current_sprint(github_token, project.get("id")) + if not last_day: + print("Today %s is not in last day of current sprint" % datetime.now().date()) + sys.exit(1) + else: + sys.exit(0)