Skip to content
Open
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
61 changes: 61 additions & 0 deletions .github/workflows/pending-deploy-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Pending Deploy Check

# This workflow validates that pending deploy PRs are safe to merge.
#
# Trigger: Pull requests to main from pending-deploy-* branches
# Purpose: Verify all commits in the PR have been deployed in managed-service
#
# Why: SDK changes are generated from managed-service OpenAPI specs. We must ensure
# the corresponding managed-service changes have been deployed before merging the SDK changes,
# otherwise the SDK could reference unreleased API features.
#
# Flow:
# 1. For each commit in the PR, extract the Managed-service-commit-SHA trailer
# 2. Check if that SHA is in the latest managed-service release tag
# 3. If any commits are not yet deployed, post a detailed comment and fail the PR

on:
pull_request:
branches: [main]

permissions:
contents: read
pull-requests: write

jobs:
pending-deploy-check:
runs-on: ubuntu-latest
if: startsWith(github.head_ref, 'pending-deploy-')
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Run deployment check
id: check-deployment
env:
GITHUB_HEAD_REF: ${{ github.head_ref }}
MANAGED_SERVICE_TOKEN: ${{ secrets.MANAGED_SERVICE_TOKEN }}
run: scripts/pending-deploy-check.sh

- name: Post failure comment and fail
if: steps.check-deployment.outputs.has_issues == 'true'
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_TOKEN: ${{ github.token }}
run: |
scripts/post-failure-comment.sh
source scripts/lib/logging.sh
log_error "Deployment check failed - see PR comment for details"
exit 1

- name: Summary
if: always()
run: |
source scripts/lib/logging.sh
if [ "${{ steps.check-deployment.outputs.has_issues }}" != "true" ]; then
log_info "All commits in pending deploy branch are deployed in managed-service"
else
log_info "Some commits are not deployed or potentially not deployed"
fi
58 changes: 58 additions & 0 deletions .github/workflows/pending-deploy-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Pending Deploy PR

# This workflow creates PRs to apply pending deploy changes to main.
#
# Trigger: workflow_dispatch - called by TeamCity when managed-service is deployed
# Purpose: Automatically create a PR to merge the latest pending-deploy-* branch into main
#
# Flow:
# 1. Find the latest pending-deploy-YYYYMMDD-HHMMSS branch
# 2. Check if it has commits not in main
# 3. Create a PR if one doesn't already exist

on:
workflow_dispatch:
inputs:
timestamp:
description: 'Deployment timestamp'
required: true
type: string
commit_sha:
description: 'Deployed commit SHA'
required: true
type: string

# Required permissions:
# - contents:read for checking out code and reading branches/tags
# - pull-requests:write for creating/updating PRs
permissions:
contents: read
pull-requests: write

jobs:
pending-deploy-pr:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0 # Need full history to compare branches and commits

- name: Run pending deploy PR workflow
id: create-pr
env:
GITHUB_TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: scripts/pending-deploy-pr.sh

- name: Summary
if: always()
run: |
source scripts/lib/logging.sh
if [ -z "${{ steps.create-pr.outputs.branch }}" ]; then
log_info "No pending deploy branches found"
elif [ "${{ steps.create-pr.outputs.has_commits }}" != "true" ]; then
log_info "Pending deploy branch has no new commits"
elif [ -n "${{ steps.create-pr.outputs.pr_url }}" ]; then
log_info "PR created or updated for pending deploy branch"
fi
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Automated pending deploy branch management with two GitHub Actions workflows:
- `pending-deploy-pr.yml`: Creates PRs from pending deploy branches to main (triggered via
workflow_dispatch)
- `pending-deploy-check.yml`: Validates that SDK commits reference deployed managed-service changes
before allowing merge

## [7.1.0] - 2026-04-14

### Added
Expand Down
27 changes: 27 additions & 0 deletions scripts/lib/logging.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Logging functions for GitHub Actions workflows

# Output an informational message to stdout
log_info() {
local message="$1"
echo "$message"
}

# Output an error message using GitHub Actions workflow command format
# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
log_error() {
local message="$1"
echo "::error::$message" >&2
}

# Output a warning message using GitHub Actions workflow command format
log_warning() {
local message="$1"
echo "::warning::$message" >&2
}

# Output a notice message using GitHub Actions workflow command format
log_notice() {
local message="$1"
echo "::notice::$message"
}
92 changes: 92 additions & 0 deletions scripts/lib/release-helpers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# Shared utility functions for pending deploy workflows

# Verify logging functions are available
if ! command -v log_error &> /dev/null; then
echo "Error: logging.sh must be sourced before release-helpers.sh" >&2
exit 1
fi

# Get the latest managed-service release tag
# Exports: LATEST_RELEASE_TAG
# Returns: 0 on success, 1 on failure
get_latest_release_tag() {
if [[ -z "${MANAGED_SERVICE_TOKEN:-}" ]]; then
log_error "MANAGED_SERVICE_TOKEN environment variable is not set"
return 1
fi

export GH_TOKEN="$MANAGED_SERVICE_TOKEN"

log_info "Fetching release tags from managed-service repository"

# Fetch all release tags matching release-YYYY-MM-DD-N pattern
# The GitHub API returns results in pages (30 items per page). Without --paginate, we'd only get
# the first page. With --paginate, gh automatically fetches all pages for us. We need all tags
# because the API doesn't support sorting by date, so we must fetch everything and sort ourselves.
local all_tags
all_tags=$(gh api repos/cockroachlabs/managed-service/tags --paginate --jq '.[].name' | grep -E '^release-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]+$')

if [[ -z "${all_tags:-}" ]]; then
log_error "No release tags found in managed-service repository"
return 1
fi

# Sort and get the latest (tags sort lexicographically in chronological order)
LATEST_RELEASE_TAG=$(echo "$all_tags" | sort -r | head -1)

log_info "Latest release tag: $LATEST_RELEASE_TAG"
export LATEST_RELEASE_TAG
return 0
}

# Check if a managed-service commit SHA is deployed as of the latest release tag
#
# Args: $1 - managed-service commit SHA
# $2 - latest release tag
# Returns: 0 if deployed, 1 if not deployed, 2 if uncertain/unexpected
# Outputs to stdout: Status string when deployment status is uncertain
check_deployment_status() {
local ms_sha="$1"
local latest_tag="$2"

if [[ -z "${ms_sha:-}" || -z "${latest_tag:-}" ]]; then
log_error "Both ms_sha and latest_tag are required"
echo "missing_parameters"
return 2
fi

if [[ -z "${MANAGED_SERVICE_TOKEN:-}" ]]; then
log_error "MANAGED_SERVICE_TOKEN environment variable is not set"
echo "missing_token"
return 2
fi

export GH_TOKEN="$MANAGED_SERVICE_TOKEN"

# Compare the latest release tag with the commit SHA
# Status can be: identical, ahead, behind, or diverged
local compare_status
if ! compare_status=$(gh api "repos/cockroachlabs/managed-service/compare/${latest_tag}...${ms_sha}" --jq '.status' 2>&1); then
log_error "Failed to compare commit with release tag: $compare_status"
echo "unknown"
return 2
fi

case "$compare_status" in
identical|behind)
# SHA has been deployed
return 0
;;
ahead)
# SHA is ahead of the latest release - not deployed yet
return 1
;;
*)
# Unexpected status (e.g., diverged or other)
echo "$compare_status"
log_error "Unexpected comparison status: $compare_status"
return 2
;;
esac
}
21 changes: 21 additions & 0 deletions scripts/lib/validation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Validation helper functions

# Check if required commands are available in PATH
# Usage: check_required_commands "cmd1" "cmd2" "cmd3"
check_required_commands() {
local missing_commands=()

for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing_commands+=("$cmd")
fi
done

if [[ ${#missing_commands[@]} -gt 0 ]]; then
log_error "Required command(s) not found in PATH: ${missing_commands[*]}"
return 1
fi

return 0
}
Loading
Loading