Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/notify-main-failure.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ on:
description: "Silence this caller entirely while the staging freeze window is closed. For staging pipelines, where neither a failure nor its recovery is release-relevant mid-week."
type: boolean
default: false
ruleset-name:
description: "The freeze ruleset to read. Must match what the repo's release-freeze caller sets, or this reads a name that does not exist and escalates every staging failure forever."
freeze-property:
description: "The freeze custom property to read. Must match what the repo's release-freeze caller sets, or this reads a name that does not exist and escalates every staging failure forever."
type: string
default: staging-freeze
default: staging_frozen
force-post:
description: "Post a 'recovered' message with no prior failure to recover from. Only the smoke-test dispatch sets this; a real pipeline leaves it off, or every green run reports a recovery."
type: boolean
Expand Down Expand Up @@ -173,7 +173,7 @@ jobs:
env-name: ${{ inputs.env-name }}
status: ${{ inputs.status }}
freeze-scoped: ${{ inputs.freeze-scoped }}
ruleset-name: ${{ inputs.ruleset-name }}
freeze-property: ${{ inputs.freeze-property }}
freeze-token: ${{ steps.freeze-token.outputs.token }}
github-token: ${{ github.token }}
force-post: ${{ inputs.force-post }}
Expand Down
24 changes: 12 additions & 12 deletions .github/workflows/notify-startup-failure.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ on:
description: "Which of `branches` is the release candidate, and so is only reported while the freeze window is open"
type: string
default: staging
ruleset-name:
description: "The freeze ruleset to read. Must match what the repo's release-freeze caller sets."
freeze-property:
description: "The freeze custom property to read. Must match what the repo's release-freeze caller sets."
type: string
default: staging-freeze
default: staging_frozen
runs-on:
description: "Runner label for the sweep jobs"
type: string
Expand Down Expand Up @@ -188,10 +188,10 @@ on:
description: "Which branch is the release candidate"
type: string
default: staging
ruleset-name:
description: "The freeze ruleset to read"
freeze-property:
description: "The freeze custom property to read"
type: string
default: staging-freeze
default: staging_frozen
lookback-minutes:
description: "Lookback window in minutes (widen it to replay a past incident)"
type: number
Expand Down Expand Up @@ -390,10 +390,10 @@ jobs:
path: .ci-shared
persist-credentials: false

# Reading rulesets needs admin, which the default workflow token does not
# carry, so this reuses the App that owns the freeze toggle. A repo that is
# not on the release train has no App and no ruleset; the read escalates to
# frozen, which for this sweep means staging is swept like any other branch.
# This reuses the App that owns the freeze toggle, so the reader and the
# writer share one credential. A repo that is not on the release train has
# no App installed; the read escalates to frozen, which for this sweep means
# staging is swept like any other branch.
- name: Mint release-train App token
id: freeze-token
continue-on-error: true
Expand All @@ -407,7 +407,7 @@ jobs:
env:
GH_TOKEN: ${{ steps.freeze-token.outputs.token }}
REPO: ${{ github.repository }}
RULESET_NAME: ${{ inputs.ruleset-name }}
FREEZE_PROPERTY: ${{ inputs.freeze-property }}
run: |
set -uo pipefail
if [ -z "${GH_TOKEN:-}" ]; then
Expand All @@ -416,7 +416,7 @@ jobs:
exit 0
fi
python3 .ci-shared/scripts/freeze_state.py read \
--repo "${REPO}" --ruleset-name "${RULESET_NAME}" --on-error escalate
--repo "${REPO}" --property-name "${FREEZE_PROPERTY}" --on-error escalate

- name: Find deploy branches that are still red
id: find
Expand Down
42 changes: 21 additions & 21 deletions .github/workflows/release-freeze.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Reusable workflow: freeze the staging branch to open the weekly QA window.
#
# Freezing flips a *pre-provisioned* repository ruleset (default name
# `staging-freeze`) from `disabled` to `active`. That ruleset carries a single
# `update` rule on the staging branch with the `mindsdb-release-train` GitHub
# App as a bypass actor, so ordinary pushes to staging are blocked while the
# App (and the release sync-back push) can still write. Because rulesets layer
# on top of classic branch protection, toggling enforcement can never clobber
# the underlying protection config.
# Freezing sets this repo's `staging_frozen` custom property to `true`. One
# org-level ruleset (terraform: newprod/global/github) targets the staging
# branch of every repo where that property is `true`, with a single `update`
# rule and the `mindsdb-release-train` GitHub App as a bypass actor, so ordinary
# pushes to staging are blocked while the App (and the release sync-back push)
# can still write. The ruleset itself is never edited here; only the property
# moves, so terraform's plan stays empty across the weekly cycle.
#
# If staging has no unreleased changes (staging == main), the freeze is skipped.
#
Expand All @@ -33,10 +33,10 @@ on:
description: "Production branch staging is compared against"
type: string
default: main
ruleset-name:
description: "Name of the pre-provisioned freeze ruleset to toggle"
freeze-property:
description: "Custom property that marks this repo frozen. Must match the org ruleset's condition in terraform."
type: string
default: staging-freeze
default: staging_frozen

# No workflow-level `permissions:` block, deliberately, so this job inherits the
# calling job's grant. That is what makes the notify step's recovery lookup work:
Expand Down Expand Up @@ -75,10 +75,10 @@ jobs:
fetch-depth: 0
persist-credentials: false

# The ruleset read-modify-write lives in `scripts/freeze_state.py`, which is
# also what the alerting workflows use to READ the freeze state. One file
# owns the contract, so a repo that renames its ruleset cannot end up frozen
# by one name and alerted on another.
# The property write lives in `scripts/freeze_state.py`, which is also what
# the alerting workflows use to READ the freeze state. One file owns the
# contract, so a repo cannot end up frozen under one property name and
# alerted on another.
- name: Check out the shared release-freeze contract
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
Expand Down Expand Up @@ -112,29 +112,29 @@ jobs:
echo "${STAGING} is ${AHEAD} commit(s) ahead of ${BASE} — freezing."
fi

- name: Freeze staging (activate ruleset)
- name: Freeze staging (set the frozen property)
if: steps.check.outputs.should_freeze == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REPO: ${{ github.repository }}
RULESET_NAME: ${{ inputs.ruleset-name }}
FREEZE_PROPERTY: ${{ inputs.freeze-property }}
STAGING: ${{ inputs.staging-branch }}
ENFORCEMENT: active
FROZEN: "true"
run: |
set -euo pipefail
# Fails loudly on purpose, unlike the alerting path's read: a freeze
# that could not be applied has to stop the release train rather than
# let the window appear to open.
python3 .ci-shared/scripts/freeze_state.py set \
--repo "${REPO}" \
--ruleset-name "${RULESET_NAME}" \
--enforcement "${ENFORCEMENT}" \
--body-path "${RUNNER_TEMP}/ruleset.json"
--property-name "${FREEZE_PROPERTY}" \
--frozen "${FROZEN}" \
--body-path "${RUNNER_TEMP}/freeze-property.json"
echo "${STAGING} is FROZEN."
{
echo "## Staging branch FROZEN"
echo ""
echo "The \`${STAGING}\` branch is **frozen** via ruleset \`${RULESET_NAME}\`."
echo "The \`${STAGING}\` branch is **frozen**: \`${FREEZE_PROPERTY}=true\`."
} >> "$GITHUB_STEP_SUMMARY"

# The alert lives here rather than in each repo's wrapper, which is what
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ on:
description: "Target branch for the release PR"
type: string
default: main
ruleset-name:
description: "The freeze ruleset to read, to decide draft vs ready. Must match what the repo's release-freeze caller sets."
freeze-property:
description: "The freeze custom property to read, to decide draft vs ready. Must match what the repo's release-freeze caller sets."
type: string
default: staging-freeze
default: staging_frozen

# No workflow-level `permissions:` block, deliberately, so this job inherits the
# calling job's grant. That is what makes the notify step's recovery lookup work:
Expand Down Expand Up @@ -103,11 +103,11 @@ jobs:
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REPO: ${{ github.repository }}
RULESET_NAME: ${{ inputs.ruleset-name }}
FREEZE_PROPERTY: ${{ inputs.freeze-property }}
run: |
set -uo pipefail
if ! python3 .ci-shared/scripts/freeze_state.py read \
--repo "${REPO}" --ruleset-name "${RULESET_NAME}" --on-error fail; then
--repo "${REPO}" --property-name "${FREEZE_PROPERTY}" --on-error fail; then
echo "::warning::Could not read the freeze state. Leaving the release PR as a draft."
echo "frozen=false" >> "$GITHUB_OUTPUT"
fi
Expand Down
41 changes: 21 additions & 20 deletions .github/workflows/release-unfreeze.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# Reusable workflow: unfreeze staging when the weekly release PR merges, then
# sync main back into staging.
#
# Unfreezing flips the pre-provisioned `staging-freeze` ruleset back to
# `disabled`, reopening staging for normal development the moment the release
# ships — no fixed Monday-morning wait. It is also what turns staging's alerting
# back off: the alert path reads the same ruleset, so an unfrozen staging stops
# paging the channel without anything else being told.
# Unfreezing sets this repo's `staging_frozen` custom property back to `false`,
# which takes the branch out of the org-level freeze ruleset and reopens staging
# for normal development the moment the release ships — no fixed Monday-morning
# wait. It is also what turns staging's alerting back off: the alert path reads
# the same property, so an unfrozen staging stops paging the channel without
# anything else being told.
#
# Order is load-bearing: unlock first, then push. If the sync-back push fails,
# staging is left unlocked, which is the acceptable failure mode.
#
# The sync-back merge (main -> staging) is load-bearing on squash-merge repos:
# without it, every previously shipped commit reappears in the next release
# PR's commit list. The push is performed with the `mindsdb-release-train` App
# token; the App is the ruleset bypass actor on staging, so the push lands even
# token; the App is the freeze ruleset's bypass actor, so the push lands even
# though staging otherwise requires review.
#
# Called by a per-repo wrapper, e.g.
Expand All @@ -39,10 +40,10 @@ on:
description: "Production branch to sync back from"
type: string
default: main
ruleset-name:
description: "Name of the pre-provisioned freeze ruleset to toggle"
freeze-property:
description: "Custom property that marks this repo frozen. Must match the org ruleset's condition in terraform."
type: string
default: staging-freeze
default: staging_frozen

# No workflow-level `permissions:` block, deliberately, so this job inherits the
# calling job's grant. That is what makes the notify step's recovery lookup work:
Expand Down Expand Up @@ -70,10 +71,10 @@ jobs:
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

# The ruleset read-modify-write lives in `scripts/freeze_state.py`, which is
# also what the alerting workflows use to READ the freeze state. One file
# owns the contract, so unfreezing and "staging alerts are off again" can
# never disagree about which ruleset they mean.
# The property write lives in `scripts/freeze_state.py`, which is also what
# the alerting workflows use to READ the freeze state. One file owns the
# contract, so unfreezing and "staging alerts are off again" can never
# disagree about which property they mean.
#
# This has to precede the unlock, because the unlock runs that script. It is
# therefore the one step whose failure leaves staging FROZEN, which is the
Expand All @@ -87,26 +88,26 @@ jobs:
path: .ci-shared
persist-credentials: false

- name: Unfreeze staging (disable ruleset)
- name: Unfreeze staging (clear the frozen property)
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REPO: ${{ github.repository }}
RULESET_NAME: ${{ inputs.ruleset-name }}
FREEZE_PROPERTY: ${{ inputs.freeze-property }}
STAGING: ${{ inputs.staging-branch }}
ENFORCEMENT: disabled
FROZEN: "false"
run: |
set -euo pipefail
python3 .ci-shared/scripts/freeze_state.py set \
--repo "${REPO}" \
--ruleset-name "${RULESET_NAME}" \
--enforcement "${ENFORCEMENT}" \
--body-path "${RUNNER_TEMP}/ruleset.json"
--property-name "${FREEZE_PROPERTY}" \
--frozen "${FROZEN}" \
--body-path "${RUNNER_TEMP}/freeze-property.json"
PR_NUMBER='${{ github.event.pull_request.number }}'
echo "${STAGING} is UNFROZEN."
{
echo "## Staging branch UNFROZEN"
echo ""
echo "The \`${STAGING}\` branch is **unfrozen** via ruleset \`${RULESET_NAME}\` (triggered by PR #${PR_NUMBER:-manual})."
echo "The \`${STAGING}\` branch is **unfrozen**: \`${FREEZE_PROPERTY}=false\` (triggered by PR #${PR_NUMBER:-manual})."
} >> "$GITHUB_STEP_SUMMARY"

# Order is load-bearing and this is the whole reason for it: unlock first,
Expand Down
Loading
Loading