diff --git a/.github/workflows/release-labeling.yml b/.github/workflows/release-labeling.yml new file mode 100644 index 00000000000..752094561c4 --- /dev/null +++ b/.github/workflows/release-labeling.yml @@ -0,0 +1,162 @@ +name: Release Labeler + +# Security note: This workflow uses pull_request_target to work with forked repositories. +# When editing file, please read: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ + +# This is safe because: +# 1. We do NOT checkout the repository (no untrusted code execution) +# 2. We only use GitHub CLI commands to manage labels, so we DO need write permissions +# 3. We only read from GitHub context variables, not from PR content +# 4. All date calculations are done with trusted shell commands + +permissions: + pull-requests: write + +on: + pull_request_target: + types: [opened, ready_for_review] # ready_for_review: run when draft PRs are marked ready + branches: + - develop + +jobs: + add-release-label: + name: Add Release Label + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + steps: + - name: Calculate next release date + id: calculate_date + run: | + # Function to adjust date to next Tuesday if needed + adjust_to_tuesday() { + local input_date="$1" + local day_of_week=$(date -d "$input_date" +%u) + if [ $day_of_week -ne 2 ]; then + local days_to_add=$(( (2 - $day_of_week + 7) % 7 )) + date -d "$input_date + $days_to_add days" +%Y-%m-%d + else + echo "$input_date" + fi + } + + # Function to calculate release candidate cutoff date (Wednesday before release) + get_release_candidate_date() { + local release_date="$1" + # Go back 6 days from Tuesday to get the previous Wednesday + date -d "$release_date - 6 days" +%Y-%m-%d + } + + RELEASE_START="${{ vars.RELEASE_START_DATE || '2025-08-12' }}" + + FREEZE_START_1="${{ vars.FREEZE_START_1 || '2025-12-02' }}" + FREEZE_END_1="${{ vars.FREEZE_END_1 || '2025-12-15' }}" + FREEZE_START_2="${{ vars.FREEZE_START_2 || '2025-12-30' }}" + FREEZE_END_2="${{ vars.FREEZE_END_2 || '2026-01-06' }}" + + TODAY=$(date +%Y-%m-%d) + + # Calculate which release cycle we're in (releases every 14 days) + DAYS_SINCE_START=$(( ($(date -d "$TODAY" +%s) - $(date -d "$RELEASE_START" +%s)) / 86400 )) + + if [ $DAYS_SINCE_START -lt 0 ]; then + NEXT_RELEASE_DATE="$RELEASE_START" + else + CYCLES_PASSED=$(( $DAYS_SINCE_START / 14 )) + NEXT_CYCLE=$(( $CYCLES_PASSED + 1 )) + NEXT_RELEASE_DATE=$(date -d "$RELEASE_START + $(( $NEXT_CYCLE * 14 )) days" +%Y-%m-%d) + fi + + # Skip freeze periods - if calculated date falls during freeze, + # jump to first Tuesday after freeze ends + MAX_ITERATIONS=3 # Safety guard - worst case is 2 freeze periods + ITERATION=0 + while [ $ITERATION -lt $MAX_ITERATIONS ]; do + RELEASE_TIMESTAMP=$(date -d "$NEXT_RELEASE_DATE" +%s) + FREEZE1_START_TS=$(date -d "$FREEZE_START_1" +%s) + FREEZE1_END_TS=$(date -d "$FREEZE_END_1" +%s) + FREEZE2_START_TS=$(date -d "$FREEZE_START_2" +%s) + FREEZE2_END_TS=$(date -d "$FREEZE_END_2" +%s) + + # Check if calculated release date falls during December freeze (Dec 2-15) + if [ $RELEASE_TIMESTAMP -ge $FREEZE1_START_TS ] && [ $RELEASE_TIMESTAMP -le $FREEZE1_END_TS ]; then + # Skip freeze period: move to day after freeze ends, then find next Tuesday + NEXT_RELEASE_DATE=$(date -d "$FREEZE_END_1 + 1 day" +%Y-%m-%d) + NEXT_RELEASE_DATE=$(adjust_to_tuesday "$NEXT_RELEASE_DATE") + ITERATION=$((ITERATION + 1)) + continue + fi + + # Check if calculated release date falls during year-end freeze (Dec 30-Jan 6) + if [ $RELEASE_TIMESTAMP -ge $FREEZE2_START_TS ] && [ $RELEASE_TIMESTAMP -le $FREEZE2_END_TS ]; then + # Skip freeze period: move to day after freeze ends, then find next Tuesday + NEXT_RELEASE_DATE=$(date -d "$FREEZE_END_2 + 1 day" +%Y-%m-%d) + NEXT_RELEASE_DATE=$(adjust_to_tuesday "$NEXT_RELEASE_DATE") + ITERATION=$((ITERATION + 1)) + continue + fi + + break + done + + if [ $ITERATION -eq $MAX_ITERATIONS ]; then + echo "Error: Too many freeze period adjustments" >&2 + exit 1 + fi + + # Check if today is the release candidate cutoff date (Wednesday before release) + RELEASE_CANDIDATE_DATE=$(get_release_candidate_date "$NEXT_RELEASE_DATE") + + if [ "$TODAY" = "$RELEASE_CANDIDATE_DATE" ]; then + # If today is the release candidate cutoff date, use the next release cycle + NEXT_RELEASE_DATE=$(date -d "$NEXT_RELEASE_DATE + 14 days" +%Y-%m-%d) + + # Re-check freeze periods for the next release date + ITERATION=0 + while [ $ITERATION -lt $MAX_ITERATIONS ]; do + RELEASE_TIMESTAMP=$(date -d "$NEXT_RELEASE_DATE" +%s) + FREEZE1_START_TS=$(date -d "$FREEZE_START_1" +%s) + FREEZE1_END_TS=$(date -d "$FREEZE_END_1" +%s) + FREEZE2_START_TS=$(date -d "$FREEZE_START_2" +%s) + FREEZE2_END_TS=$(date -d "$FREEZE_END_2" +%s) + + # Check if calculated release date falls during December freeze (Dec 2-15) + if [ $RELEASE_TIMESTAMP -ge $FREEZE1_START_TS ] && [ $RELEASE_TIMESTAMP -le $FREEZE1_END_TS ]; then + # Skip freeze period: move to day after freeze ends, then find next Tuesday + NEXT_RELEASE_DATE=$(date -d "$FREEZE_END_1 + 1 day" +%Y-%m-%d) + NEXT_RELEASE_DATE=$(adjust_to_tuesday "$NEXT_RELEASE_DATE") + ITERATION=$((ITERATION + 1)) + continue + fi + + # Check if calculated release date falls during year-end freeze (Dec 30-Jan 6) + if [ $RELEASE_TIMESTAMP -ge $FREEZE2_START_TS ] && [ $RELEASE_TIMESTAMP -le $FREEZE2_END_TS ]; then + # Skip freeze period: move to day after freeze ends, then find next Tuesday + NEXT_RELEASE_DATE=$(date -d "$FREEZE_END_2 + 1 day" +%Y-%m-%d) + NEXT_RELEASE_DATE=$(adjust_to_tuesday "$NEXT_RELEASE_DATE") + ITERATION=$((ITERATION + 1)) + continue + fi + + break + done + fi + + if ! date -d "$NEXT_RELEASE_DATE" >/dev/null 2>&1; then + echo "Error: Invalid date calculated" >&2 + exit 1 + fi + + echo "date=$NEXT_RELEASE_DATE" >> $GITHUB_OUTPUT + + - name: Create and add release label to PR + run: | + LABEL_NAME="release:${{ steps.calculate_date.outputs.date }}" + + # Try to create label if it doesn't exist (will fail silently if it already exists or no permissions) + gh label create "$LABEL_NAME" --description "Release scheduled for ${{ steps.calculate_date.outputs.date }}" --color "0e8a16" || echo "Could not create label (may already exist or insufficient permissions)" + + # Try to add label to PR (will fail if label doesn't exist or no permissions) + gh pr edit ${{ github.event.pull_request.number }} --add-label "$LABEL_NAME" || echo "Could not add label to PR (label may not exist or insufficient permissions)" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} +