Skip to content

Commit 2eabcd6

Browse files
dkirov-ddclaude
andauthored
refactor(release): use peter-evans/repository-dispatch and remove dev target (DataDog#23055)
* refactor(release): use peter-evans/repository-dispatch and remove dev target - Replace custom urllib HTTP dispatch with peter-evans/repository-dispatch action - Split dispatch job into prepare + matrix dispatch jobs (one action call per batch) - Remove dev target option; tags are now always pushed unless dry-run - Drop target field from client_payload and summary table Rationale: simplify the release pipeline by delegating HTTP dispatch to a maintained action, and remove the unused dev target path which added complexity without benefit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(release): default dry-run to true Keeps the pipeline safe while the new dispatch approach is validated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(release): remove 'all' packages keyword, rename _package_args - Drop support for 'all' as a user-facing packages input; ddev owns auto-detection via `ddev release tag all` when no packages are specified - Rename _package_args → _tag_package_args to clarify its purpose - Update input descriptions to reflect auto-detect from tags at ref Rationale: the 'all' keyword was redundant with the empty/auto-detect path and leaked an internal ddev detail into the public workflow interface. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(release): add TODO to flip dry-run default back to false - Mark dry-run defaults with TODOs so they're not forgotten - Unblock once agent-integration-wheels-release handles the new payload Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4c78ace commit 2eabcd6

11 files changed

Lines changed: 129 additions & 311 deletions

.github/workflows/release-dispatch.yml

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,21 @@ on:
1313
packages:
1414
description: >-
1515
Packages to release. Accepts a JSON array (e.g. '["postgres","datadog_checks_base"]'),
16-
'all' to release every Python package in the repo, or omit to auto-detect from new tags.
16+
or omit to auto-detect from tags pointing at the specified ref.
1717
required: false
1818
type: string
1919
source-repo-ref:
2020
description: "Commit SHA or ref to build from"
2121
required: false
2222
type: string
23-
target:
24-
description: "Target environment (dev = no tag push, dev S3; prod = push tags, prod S3)"
25-
required: false
26-
type: string
27-
default: prod # callers (e.g. release-trigger.yml) typically override this
2823
dry-run:
2924
description: >-
3025
When true, print what would be released and where without pushing tags
3126
or triggering downstream wheel builds.
3227
required: false
3328
type: boolean
34-
default: false
29+
# TODO: flip back to false once agent-integration-wheels-release is updated to handle the new payload
30+
default: true
3531
ddev-version:
3632
description: "ddev version, pinned by default."
3733
required: false
@@ -46,10 +42,13 @@ permissions:
4642
contents: write # ddev needs to push tags
4743

4844
jobs:
49-
dispatch:
50-
name: Tag releases and dispatch wheel builds
45+
prepare:
46+
name: Tag releases and build dispatch batches
5147
runs-on: ubuntu-latest
52-
environment: release
48+
49+
outputs:
50+
has_packages: ${{ steps.prepare.outputs.has_packages }}
51+
batches: ${{ steps.release-dispatch.outputs.batches }}
5352

5453
steps:
5554
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -71,29 +70,46 @@ jobs:
7170
- name: Prepare dispatch
7271
id: prepare
7372
env:
74-
TARGET: ${{ inputs.target }}
7573
DRY_RUN: ${{ inputs.dry-run }}
7674
SELECTED_PACKAGES: ${{ inputs.packages }}
7775
SOURCE_REPO: ${{ inputs.source-repo || 'integrations-core' }}
7876
REF: ${{ inputs.source-repo-ref || github.sha }}
7977
IS_STABLE_RELEASE: ${{ inputs.is-stable-release }}
8078
run: python .github/workflows/scripts/release_prepare.py
8179

80+
- name: Build dispatch batches
81+
id: release-dispatch
82+
if: steps.prepare.outputs.has_packages == 'true'
83+
env:
84+
PACKAGES: ${{ steps.prepare.outputs.packages }}
85+
SOURCE_REPO: ${{ inputs.source-repo || 'integrations-core' }}
86+
REF: ${{ inputs.source-repo-ref || github.sha }}
87+
DRY_RUN: ${{ inputs.dry-run }}
88+
run: python .github/workflows/scripts/release_dispatch.py
89+
90+
dispatch:
91+
name: Dispatch wheel builds (batch ${{ strategy.job-index + 1 }})
92+
needs: prepare
93+
if: needs.prepare.outputs.has_packages == 'true' && !inputs.dry-run
94+
runs-on: ubuntu-latest
95+
environment: release
96+
97+
strategy:
98+
matrix:
99+
payload: ${{ fromJson(needs.prepare.outputs.batches) }}
100+
101+
steps:
82102
- name: Get GitHub token via dd-octo-sts
83-
if: steps.prepare.outputs.has_packages == 'true' && !inputs.dry-run
84103
id: octo-sts
85104
uses: DataDog/dd-octo-sts-action@08f2144903ced3254a3dafec2592563409ba2aa0 # v1.0.1
86105
with:
87106
scope: DataDog/agent-integration-wheels-release
88107
policy: integrations-core.dispatch-wheel-builds
89108

90109
- name: Dispatch release
91-
if: steps.prepare.outputs.has_packages == 'true'
92-
env:
93-
GH_TOKEN: ${{ steps.octo-sts.outputs.token }}
94-
PACKAGES: ${{ steps.prepare.outputs.packages }}
95-
SOURCE_REPO: ${{ inputs.source-repo || 'integrations-core' }}
96-
REF: ${{ inputs.source-repo-ref || github.sha }}
97-
TARGET: ${{ inputs.target }}
98-
DRY_RUN: ${{ inputs.dry-run }}
99-
run: python .github/workflows/scripts/release_dispatch.py
110+
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
111+
with:
112+
token: ${{ steps.octo-sts.outputs.token }}
113+
repository: DataDog/agent-integration-wheels-release
114+
event-type: build-wheels
115+
client-payload: ${{ toJson(matrix.payload) }}

.github/workflows/release-trigger.yml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,19 @@ on:
2020
packages:
2121
description: >-
2222
Packages to release. JSON array (e.g. '["postgres","datadog_checks_base"]'),
23-
'all' to release every Python package in the repo, or omit to auto-detect from new tags.
23+
or omit to auto-detect from tags pointing at the specified ref.
2424
required: false
2525
type: string
2626
source-repo-ref:
2727
description: "Commit SHA or ref to build from"
2828
required: true
2929
type: string
30-
target:
31-
description: "Target environment (dev or prod)"
32-
required: false
33-
type: choice
34-
options:
35-
- dev
36-
- prod
37-
default: dev
3830
dry-run:
3931
description: "Print what would be released without pushing tags or starting builds"
4032
required: false
4133
type: boolean
42-
default: false
34+
# TODO: flip back to false once agent-integration-wheels-release is updated to handle the new payload
35+
default: true
4336
ddev-version:
4437
description: "ddev version, pinned by default."
4538
required: false
@@ -70,8 +63,7 @@ jobs:
7063
source-repo: integrations-core
7164
packages: ${{ inputs.packages || '' }}
7265
source-repo-ref: ${{ github.event_name == 'workflow_dispatch' && inputs.source-repo-ref || github.sha }}
73-
target: ${{ inputs.target || 'dev' }}
74-
dry-run: ${{ inputs.dry-run || false }}
66+
dry-run: ${{ inputs.dry-run || true }} # TODO: flip back to false
7567
ddev-version: ${{ inputs.ddev-version || '' }}
7668
is-stable-release: ${{ needs.context.outputs.is-stable-release }}
7769
permissions:
Lines changed: 15 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,29 @@
1-
"""HTTP dispatch logic for repository_dispatch events."""
2-
import http.client
3-
import json
4-
import ssl
5-
import sys
6-
import time
7-
import urllib.error
8-
import urllib.request
9-
10-
from . import TARGET_REPO
1+
"""Client payload builder for repository_dispatch events."""
112

123
BATCH_SIZE = 200
13-
DISPATCH_URL = f"https://api.github.com/repos/{TARGET_REPO}/dispatches"
14-
MAX_ATTEMPTS = 5
15-
164

17-
class DispatchError(Exception):
18-
"""Raised when a dispatch request fails after all retry attempts."""
195

20-
21-
def build_payload(batch: list[str], source_repo: str, ref: str, target: str) -> dict:
22-
"""Return the ``repository_dispatch`` payload for one batch."""
6+
def build_client_payload(packages: list[str], source_repo: str, ref: str) -> dict:
7+
"""Return the ``client_payload`` dict for one batch."""
238
return {
24-
"event_type": "build-wheels",
25-
"client_payload": {
26-
"packages": batch,
27-
"source_repo": source_repo,
28-
"source_repo_ref": ref,
29-
"target": target,
30-
},
9+
"packages": packages,
10+
"source_repo": source_repo,
11+
"source_repo_ref": ref,
3112
}
3213

3314

34-
def _urlopen(req: urllib.request.Request) -> http.client.HTTPResponse:
35-
"""Thin urllib wrapper — exists so tests can patch it without touching stdlib."""
36-
return urllib.request.urlopen(req)
37-
38-
39-
def send_dispatch(
40-
payload: dict,
41-
token: str,
42-
*,
43-
dispatch_url: str = DISPATCH_URL,
44-
max_attempts: int = MAX_ATTEMPTS,
45-
) -> None:
46-
"""POST a single ``repository_dispatch`` event to the wheels-release repo.
47-
48-
Retries up to ``max_attempts`` times on 5xx errors with exponential backoff.
49-
Raises ``DispatchError`` on 4xx errors or after exhausting retries.
50-
"""
51-
req = urllib.request.Request(
52-
dispatch_url,
53-
data=json.dumps(payload).encode(),
54-
headers={
55-
"Authorization": f"Bearer {token}",
56-
"Accept": "application/vnd.github+json",
57-
"X-GitHub-Api-Version": "2022-11-28",
58-
},
59-
method="POST",
60-
)
61-
for attempt in range(1, max_attempts + 1):
62-
try:
63-
with _urlopen(req) as resp:
64-
print(f" Dispatched: HTTP {resp.status}")
65-
return
66-
except (urllib.error.HTTPError, urllib.error.URLError) as e:
67-
if isinstance(e, urllib.error.URLError):
68-
if isinstance(e.reason, ssl.SSLError):
69-
raise DispatchError(f"SSL error (non-retriable): {e}") from e
70-
body = str(e)
71-
code = 503
72-
else:
73-
body = e.read().decode()
74-
code = e.code
75-
if code < 500 or attempt == max_attempts:
76-
print(f"HTTP {code}: {body}", file=sys.stderr)
77-
raise DispatchError(f"HTTP {code}: {body}")
78-
print(f" HTTP {code} on attempt {attempt}/{max_attempts}, retrying...", file=sys.stderr)
79-
time.sleep(2**attempt)
80-
81-
82-
def dispatch_in_batches(
15+
def build_batches(
8316
packages: list[str],
8417
source_repo: str,
8518
ref: str,
86-
target: str,
87-
token: str,
8819
batch_size: int = BATCH_SIZE,
89-
) -> None:
90-
"""Dispatch all packages to the wheels-release repo, batching if needed."""
20+
) -> list[dict]:
21+
"""Split packages into batches and return a list of client_payload dicts."""
9122
if batch_size <= 0:
9223
raise ValueError("batch_size must be > 0")
93-
num_packages = len(packages)
94-
if num_packages == 0:
95-
return
96-
total_batches = (num_packages + batch_size - 1) // batch_size
97-
for batch_num, start in enumerate(range(0, num_packages, batch_size), 1):
98-
end = min(start + batch_size, num_packages)
99-
current_batch = packages[start:end]
100-
print(f"\nBatch {batch_num}/{total_batches}:")
101-
print("\n".join(f" - {name}" for name in current_batch))
102-
send_dispatch(build_payload(current_batch, source_repo, ref, target), token)
24+
if not packages:
25+
return []
26+
return [
27+
build_client_payload(packages[i : i + batch_size], source_repo, ref)
28+
for i in range(0, len(packages), batch_size)
29+
]

.github/workflows/scripts/_release/packages.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,14 @@ def resolve_packages(
3939
"""Resolve the list of packages to release.
4040
4141
Resolution order:
42-
- ``'all'`` / ``'ALL'`` → every package in the repo
43-
- JSON array → use the provided list verbatim
44-
- empty string → auto-detect from git tags at HEAD
42+
- JSON array → use the provided list verbatim
43+
- empty string → auto-detect from git tags at HEAD
4544
4645
Returns ``(packages, mode_description)``.
4746
Raises ``ValueError`` on invalid input or unknown package names.
4847
"""
4948
selected = selected.strip()
5049

51-
if selected.lower() == "all":
52-
return all_packages, f"all ({len(all_packages)} packages in repo)"
53-
5450
if selected:
5551
try:
5652
packages = json.loads(selected)

.github/workflows/scripts/_release/summary.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ def build_summary(
3333
mode: str,
3434
source_repo: str,
3535
ref: str,
36-
target: str,
3736
dry_run: bool,
3837
was_dispatched: bool,
3938
footer: str = "",
@@ -46,7 +45,6 @@ def build_summary(
4645
mode: Human-readable detection mode string.
4746
source_repo: Repository name (e.g. ``integrations-core``).
4847
ref: Commit SHA or ref used as the build source.
49-
target: Deployment target (``dev`` or ``prod``).
5048
dry_run: Whether this was a dry run.
5149
was_dispatched: Whether wheel-build events were actually dispatched.
5250
footer: Optional extra paragraph appended after the package table.
@@ -76,7 +74,6 @@ def build_summary(
7674
"| | |\n|---|---|\n"
7775
f"| **Mode** | {mode} |\n"
7876
f"| **Source** | {_source_link(source_repo, ref)} |\n"
79-
f"| **Target** | {target} S3 |\n"
8077
f"| **Dry run** | {'Yes' if dry_run else 'No'} |\n\n"
8178
"| Package | Version | Status |\n"
8279
"|---------|---------|--------|\n"

.github/workflows/scripts/release_dispatch.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
"""Dispatch build-wheels events to agent-integration-wheels-release in batches.
1+
"""Build repository_dispatch batches and write the release summary.
22
3-
Environment variables: GH_TOKEN, PACKAGES, SOURCE_REPO, REF, TARGET, DRY_RUN.
3+
Environment variables: PACKAGES, SOURCE_REPO, REF, DRY_RUN.
4+
Outputs: batches (JSON array of client_payload dicts, one per batch).
45
"""
56
import json
67
import os
@@ -9,8 +10,8 @@
910

1011
sys.path.insert(0, str(Path(__file__).parent))
1112

12-
from _release.dispatch import DispatchError, dispatch_in_batches
13-
from _release.github import parse_bool_env, write_summary
13+
from _release.dispatch import build_batches
14+
from _release.github import parse_bool_env, set_outputs, write_summary
1415
from _release.summary import build_summary
1516

1617

@@ -37,30 +38,25 @@ def main() -> None:
3738
packages = json.loads(os.environ["PACKAGES"])
3839
source_repo = os.environ["SOURCE_REPO"]
3940
ref = os.environ["REF"]
40-
target = os.environ["TARGET"]
4141

4242
dry_run = parse_bool_env("DRY_RUN", default=False)
4343

4444
validation = _load_validation(os.environ.get("RUNNER_TEMP", "/tmp"))
4545
results = validation.get("results", [])
4646
mode = validation.get("mode", "")
4747

48-
print(f"Releasing {len(packages)} package(s) from {source_repo}@{ref}{target} S3:")
48+
print(f"Releasing {len(packages)} package(s) from {source_repo}@{ref}:")
4949

5050
if dry_run:
5151
for name in packages:
5252
print(f" - {name}")
5353
print("\nDRY RUN: no tags pushed, no builds triggered")
54-
write_summary(build_summary(packages, results, mode, source_repo, ref, target, dry_run, was_dispatched=False))
54+
write_summary(build_summary(packages, results, mode, source_repo, ref, dry_run=True, was_dispatched=False))
5555
return
5656

57-
token = os.environ["GH_TOKEN"]
58-
try:
59-
dispatch_in_batches(packages, source_repo, ref, target, token)
60-
except DispatchError:
61-
sys.exit(1)
62-
63-
write_summary(build_summary(packages, results, mode, source_repo, ref, target, dry_run, was_dispatched=True))
57+
batches = build_batches(packages, source_repo, ref)
58+
set_outputs(batches=json.dumps(batches))
59+
write_summary(build_summary(packages, results, mode, source_repo, ref, dry_run=False, was_dispatched=False))
6460

6561

6662
if __name__ == "__main__":

0 commit comments

Comments
 (0)