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
1,029 changes: 23 additions & 1,006 deletions .github/workflows/release-automation-reusable.yml

Large diffs are not rendered by default.

11 changes: 3 additions & 8 deletions release_automation/docs/technical-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ tooling/
│ ├── post-bot-comment/ # Create/update bot comments with templates
│ │ └── action.yml
│ │
│ ├── sync-release-issue/ # Issue lifecycle sync
│ │ └── action.yml
│ │
│ ├── update-issue-section/ # Update reserved sections in issue body
│ │ └── action.yml
│ │
Expand Down Expand Up @@ -689,13 +686,11 @@ Updates a reserved section in the issue description between `<!-- BEGIN:{section

**Inputs:** `issue_number`, `section` (STATE, CONFIG, ACTIONS), `content`

### 4.4 sync-release-issue

Synchronizes Release Issue with current derived state. Creates issues when none exists and state is PLANNED. Updates sections, labels, and title on state changes.
### 4.4 Release Issue sync

**Inputs:** `owner`, `repo`, `release_tag`, `state`, `snapshot_branch`, `release_pr_number`, `trigger_type`, `command_result`, `github_token`
No longer a composite action: the `finalize` job calls `issue_sync.sync_for_workflow()` ([finalize.py](../scripts/finalize.py)). It synchronizes the Release Issue with the derived state (or the state the run's command produced), creates the issue when none exists and the state is PLANNED, and updates sections, labels and title on state changes.

**Outputs:** `issue_number`, `issue_action`, `issue_url`
**Returns:** `issue_number`, `issue_action` (`created`, `updated`, `unchanged`, `skipped`, `error`), `issue_url`

### 4.5 create-snapshot

Expand Down
44 changes: 44 additions & 0 deletions release_automation/scripts/bot_responder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
posted during the release automation workflow.
"""

import json
import re
from pathlib import Path
from typing import Any, Dict, List, Optional

import pystache

from .context_builder import build_context


class BotResponderError(Exception):
"""Base exception for bot responder errors."""
Expand Down Expand Up @@ -112,6 +115,29 @@ def render_with_marker(
marker = self.MARKER_FORMAT.format(release_tag=release_tag)
return f"{marker}\n{content}"

def render_comment(
self,
template_name: str,
base_context: Any,
delta: Dict[str, Any],
release_tag: str,
) -> str:
"""
Render a bot comment from the run's base context plus a per-comment delta.

Args:
template_name: Name of the template file (without .md extension)
base_context: Context assembled by the plan job (ignored unless a dict)
delta: Variables of this comment; they override the base context
release_tag: Release tag for the marker

Returns:
Rendered message with marker prepended
"""
return self.render_with_marker(
template_name, merge_comment_context(base_context, delta), release_tag
)

def list_templates(self) -> List[str]:
"""
List all available template names.
Expand Down Expand Up @@ -142,3 +168,21 @@ def extract_marker_tag(content: str) -> Optional[str]:
pattern = r"<!-- release-bot:([^\s]+) -->"
match = re.search(pattern, content)
return match.group(1) if match else None


def merge_comment_context(base_context: Any, delta: Dict[str, Any]) -> Dict[str, Any]:
"""
Base context updated by delta, with apis_json (JSON string or list) turned
into apis, completed to the full schema by build_context().
"""
raw = dict(base_context) if isinstance(base_context, dict) else {}
raw.update(delta)
if "apis_json" in raw:
apis = raw.pop("apis_json")
if isinstance(apis, str):
try:
apis = json.loads(apis)
except (json.JSONDecodeError, TypeError):
apis = []
raw["apis"] = apis if isinstance(apis, list) else []
return build_context(**raw)
6 changes: 6 additions & 0 deletions release_automation/scripts/command_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ def reject(message: str) -> Rejection:
return replace(plan, response=RESPONSE_INTERIM)


def finalize_due(result: Union[Plan, Rejection]) -> bool:
"""Whether the finalize job runs: a Plan whose command runs, not a rejection or confirmation request."""
return result.allowed and result.response != RESPONSE_PUBLISH_CONFIRMATION


def release_info_from_env(env) -> ReleaseInfoResult:
"""Rebuild a ReleaseInfoResult from the derive-release-state outputs."""
if env.get("CONFIG_ERROR"):
Expand Down Expand Up @@ -353,6 +358,7 @@ def main(client=None) -> int:
"allowed": ("true" if result.allowed else "false") if validated else "",
"error_message": result.error_message if validated and not result.allowed else "",
"plan": result.to_json(),
"finalize": "true" if finalize_due(result) else "false",
"response": result.response,
"response_release_tag": (
"unknown" if result.response == RESPONSE_CONFIG_ERROR else (release_info.release_tag or "")
Expand Down
Loading
Loading