Skip to content

Commit 45bc99b

Browse files
authored
Replace sequential CI validations with parallel ddev validate all command (DataDog#23249)
* Replace sequential CI validations with parallel ddev validate all command Made-with: Cursor * Refactor validate all into a package with tests - Split all.py into all/ package: __init__.py (click command), github.py (CI helpers, PR comment formatting), orchestrator.py (processor, orchestrator, constants) - Make orchestrator timeouts configurable via --grace-period, --max-timeout, --subprocess-timeout CLI options - Default validations to ALL_CORE_VALIDATIONS when not supplied - Improve test coverage: parametrize processor tests, extract CapturingOrchestrator, add integration tests through ddev CLI * Add changelog entry for ddev validate all command * Add --fix flag, console failure details, and unified VALIDATIONS config - Consolidate ALL_CORE_VALIDATIONS, REPO_WIDE_VALIDATIONS, and FIX_FLAGS into a single VALIDATIONS dict with ValidationConfig dataclass - Add --fix flag that passes --sync/--fix to each validation that supports it - Print failure output and fix commands in console summary - Show auto-fix hint in both console output and PR comments on failure * Delete old validation comments before posting, split tests into package - Add get_pull_request_comments and delete_comment to GitHubManager - Delete previous Validation Report comments before posting a new one - Split test_all.py into tests/cli/validate/all/ package: conftest.py, test_github.py, test_orchestrator.py, test_command.py * Fix mypy error: pass pr_number explicitly to _delete_previous_comments * Add permissions for OIDC token exchange to both caller and callee workflows * Add logging to PR comment posting, remove policy file already on master * Always post validation report as markdown table - Post PR comment on every run (not just on failures) - Replace verbose collapsible output with a concise markdown table (validation name + ✅/❌) - Remove individual validation output from comment/summary since the run link is available - Simplify console output to match the table format * Remove verbose console output and silence httpx request logs * Print failure output to console with rich Rules for each failed validation * Add per-validation fix hint to console failure output * Fix UnicodeEncodeError on Windows by writing step summary as UTF-8 * Show both stdout and stderr in console failure output * Make validation list configurable via .ddev/config.toml Read the list of validations to run from the top-level `validations` key in `.ddev/config.toml`. This allows each repo to enable/disable specific validations. When the key is absent, all known validations run (preserving current behavior). Unknown names emit a warning and are skipped. Made-with: Cursor * Move load_validations to command module and abort on empty config Move _load_validations from orchestrator.py to __init__.py where it belongs as a config-loading concern. Abort with a clear error message when the configured validation list is empty, preventing the ThreadPoolExecutor(max_workers=0) crash. Made-with: Cursor
1 parent d660c37 commit 45bc99b

15 files changed

Lines changed: 1258 additions & 227 deletions

File tree

.ddev/config.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
# Validations to run with `ddev validate all`.
2+
# Each entry must match a known validation name.
3+
# Remove an entry to skip that validation for this repo.
4+
validations = [
5+
"agent-reqs",
6+
"ci",
7+
"codeowners",
8+
"config",
9+
"dep",
10+
"http",
11+
"imports",
12+
"integration-style",
13+
"jmx-metrics",
14+
"labeler",
15+
"legacy-signature",
16+
"license-headers",
17+
"licenses",
18+
"metadata",
19+
"models",
20+
"openmetrics",
21+
"package",
22+
"readmes",
23+
"saved-views",
24+
"version",
25+
]
26+
127
[overrides.display-name]
228
datadog_checks_base = "Datadog Checks Base"
329
datadog_checks_dev = "Datadog Checks Dev"

.github/workflows/run-validations.yml

Lines changed: 14 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -6,106 +6,9 @@ on:
66
repo:
77
required: true
88
type: string
9-
109
ddev-version:
1110
required: false
1211
type: string
13-
agent-reqs:
14-
required: false
15-
default: false
16-
type: boolean
17-
ci:
18-
required: false
19-
default: false
20-
type: boolean
21-
codeowners:
22-
required: false
23-
default: false
24-
type: boolean
25-
config:
26-
required: false
27-
default: false
28-
type: boolean
29-
dashboards:
30-
required: false
31-
default: false
32-
type: boolean
33-
dep:
34-
required: false
35-
default: false
36-
type: boolean
37-
eula:
38-
required: false
39-
default: false
40-
type: boolean
41-
http:
42-
required: false
43-
default: false
44-
type: boolean
45-
imports:
46-
required: false
47-
default: false
48-
type: boolean
49-
integration-style:
50-
required: false
51-
default: false
52-
type: boolean
53-
jmx-metrics:
54-
required: false
55-
default: false
56-
type: boolean
57-
labeler:
58-
required: false
59-
default: false
60-
type: boolean
61-
legacy-signature:
62-
required: false
63-
default: false
64-
type: boolean
65-
license-headers:
66-
required: false
67-
default: false
68-
type: boolean
69-
licenses:
70-
required: false
71-
default: false
72-
type: boolean
73-
metadata:
74-
required: false
75-
default: false
76-
type: boolean
77-
models:
78-
required: false
79-
default: false
80-
type: boolean
81-
openmetrics:
82-
required: false
83-
default: false
84-
type: boolean
85-
package:
86-
required: false
87-
default: false
88-
type: boolean
89-
readmes:
90-
required: false
91-
default: false
92-
type: boolean
93-
saved-views:
94-
required: false
95-
default: false
96-
type: boolean
97-
service-checks:
98-
required: false
99-
default: false
100-
type: boolean
101-
typos:
102-
required: false
103-
default: false
104-
type: boolean
105-
version:
106-
required: false
107-
default: false
108-
type: boolean
10912

11013
defaults:
11114
run:
@@ -117,6 +20,9 @@ jobs:
11720
# Avoid blocking merge queues by validations that can break with false positives
11821
if: github.event_name != 'merge_group'
11922
runs-on: ubuntu-22.04
23+
permissions:
24+
id-token: write
25+
contents: read
12026

12127
env:
12228
PYTHON_VERSION: "3.13"
@@ -140,7 +46,6 @@ jobs:
14046
14147
- name: Install ddev from PyPI
14248
if: inputs.repo != 'core'
143-
# When ddev version is empty we install the latest released ddev.
14449
run: pip install ddev${{ inputs.ddev-version }}
14550

14651
- name: Configure ddev
@@ -151,112 +56,17 @@ jobs:
15156
ddev config set orgs.ci.dd_url "https://app.datadoghq.com"
15257
ddev config set org ci
15358
154-
- name: Validate Agent integration version requirements
155-
if: ${{ !cancelled() && inputs.agent-reqs }}
156-
run: ddev validate agent-reqs $TARGET
157-
158-
- name: Validate CI configuration
159-
if: ${{ !cancelled() && inputs.ci }}
160-
run: ddev validate ci
161-
162-
- name: Validate default configuration files
163-
if: ${{ !cancelled() && inputs.config }}
164-
run: ddev validate config $TARGET
165-
166-
- name: Validate dashboards
167-
if: ${{ !cancelled() && inputs.dashboards }}
168-
run: ddev validate dashboards $TARGET
169-
170-
- name: Validate dependencies
171-
if: ${{ !cancelled() && inputs.dep }}
172-
run: ddev validate dep
173-
174-
- name: Validate EULA files
175-
if: ${{ !cancelled() && inputs.eula }}
176-
run: ddev validate eula $TARGET
177-
178-
- name: Validate usage of HTTP(S) utilities
179-
if: ${{ !cancelled() && inputs.http }}
180-
run: ddev validate http $TARGET
181-
182-
- name: Validate proper modern importing of core packages
183-
if: ${{ !cancelled() && inputs.imports }}
184-
run: ddev validate imports $TARGET
185-
186-
- name: Validate coding conventions
187-
if: ${{ !cancelled() && inputs.integration-style }}
188-
run: ddev validate integration-style $TARGET
189-
190-
- name: Validate default JMX metric files
191-
if: ${{ !cancelled() && inputs.jmx-metrics }}
192-
run: ddev validate jmx-metrics $TARGET
193-
194-
- name: Validate signatures of check classes
195-
if: ${{ !cancelled() && inputs.legacy-signature }}
196-
run: ddev validate legacy-signature $TARGET
197-
198-
- name: Validate all shipped files have license headers
199-
if: ${{ !cancelled() && inputs.license-headers }}
200-
run: ddev validate license-headers $TARGET
201-
202-
- name: Validate metric metadata
203-
if: ${{ !cancelled() && inputs.metadata }}
204-
run: ddev validate metadata $TARGET
205-
206-
- name: Validate configuration models
207-
if: ${{ !cancelled() && inputs.models }}
208-
run: ddev validate models $TARGET
209-
210-
- name: Validate openmetrics metric limit
211-
if: ${{ !cancelled() && inputs.openmetrics }}
212-
run: ddev validate openmetrics $TARGET
213-
214-
- name: Validate Python project metadata
215-
if: ${{ !cancelled() && inputs.package }}
216-
run: ddev validate package $TARGET
217-
218-
- name: Validate README files
219-
if: ${{ !cancelled() && inputs.readmes }}
220-
run: ddev validate readmes $TARGET
221-
222-
- name: Validate saved views
223-
if: ${{ !cancelled() && inputs.saved-views }}
224-
run: ddev validate saved-views $TARGET
225-
226-
- name: Validate service check metadata
227-
if: ${{ !cancelled() && inputs.service-checks }}
228-
run: ddev validate service-checks $TARGET
229-
230-
- name: Validate spelling
231-
if: ${{ !cancelled() && inputs.typos }}
232-
run: ddev validate typos $TARGET
233-
234-
- name: Validate labeler configuration
235-
if: ${{ !cancelled() && inputs.labeler }}
236-
run: ddev validate labeler
237-
238-
- name: Validate target version
239-
if: ${{ !cancelled() && inputs.version }}
240-
run: ddev validate version $TARGET
59+
- name: Get GitHub token for PR comments
60+
if: github.event_name == 'pull_request'
61+
uses: DataDog/dd-octo-sts-action@96a25462dbcb10ebf0bfd6e2ccc917d2ab235b9a # v1.0.4
62+
id: octo-sts
63+
continue-on-error: true
64+
with:
65+
scope: DataDog/integrations-core
66+
policy: self.validate.pull-request
24167

242-
# Every validation below here is sorted by increasing runtime rather than alphabetically
243-
- name: Validate third-party license metadata
244-
if: ${{ !cancelled() && inputs.licenses }}
68+
- name: Run all validations
24569
env:
24670
DD_GITHUB_USER: "${{ github.actor }}"
247-
DD_GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
248-
run: ddev validate licenses
249-
250-
- name: Validate the CODEOWNERS file
251-
if: ${{ !cancelled() && inputs.codeowners }}
252-
run: ddev validate codeowners
253-
254-
- name: Comment PR on failure
255-
if: ${{ failure() && github.event_name == 'pull_request' && github.event.pull_request.merged != true }}
256-
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
257-
continue-on-error: true
258-
with:
259-
github-token: ${{ secrets.GITHUB_TOKEN }}
260-
script: |
261-
const { issue: { number: issue_number }, repo: { owner, repo } } = context;
262-
github.rest.issues.createComment({ issue_number, owner, repo, body: "The `validations` job has failed; please review the `Files changed` tab for possible suggestions to resolve." });
71+
DD_GITHUB_TOKEN: "${{ steps.octo-sts.outputs.token || secrets.GITHUB_TOKEN }}"
72+
run: ddev validate all "$TARGET"

.github/workflows/validate.yml

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,14 @@ concurrency:
1212
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
1313
cancel-in-progress: true
1414

15+
permissions:
16+
id-token: write
17+
contents: read
18+
1519
jobs:
1620
run:
1721
name: Run Validations
1822
uses: ./.github/workflows/run-validations.yml
1923
with:
2024
repo: core
21-
22-
# Validations
23-
agent-reqs: true
24-
ci: true
25-
codeowners: true
26-
config: true
27-
dep: true
28-
http: true
29-
imports: true
30-
integration-style: true
31-
jmx-metrics: true
32-
labeler: true
33-
legacy-signature: true
34-
license-headers: true
35-
licenses: true
36-
metadata: true
37-
models: true
38-
openmetrics: true
39-
package: true
40-
readmes: true
41-
saved-views: true
42-
version: true
4325
secrets: inherit

ddev/changelog.d/23249.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add parallel validation orchestrator with ddev validate all command

ddev/src/ddev/cli/application.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Licensed under a 3-clause BSD style license (see LICENSE)
44
from __future__ import annotations
55

6+
import logging
67
import os
78
from functools import cached_property
89
from typing import cast
@@ -16,6 +17,23 @@
1617
from ddev.utils.platform import Platform
1718

1819

20+
class AppLoggingHandler(logging.Handler):
21+
"""Routes Python logging through the Application display methods."""
22+
23+
def __init__(self, app: Application):
24+
super().__init__()
25+
self._app = app
26+
27+
def emit(self, record: logging.LogRecord) -> None:
28+
msg = self.format(record)
29+
if record.levelno >= logging.ERROR:
30+
self._app.display_error(msg)
31+
elif record.levelno >= logging.WARNING:
32+
self._app.display_warning(msg)
33+
else:
34+
self._app.display_info(msg)
35+
36+
1937
class Application(Terminal):
2038
def __init__(self, exit_func, *args, **kwargs):
2139
super().__init__(*args, **kwargs)
@@ -47,6 +65,14 @@ def data_dir(self) -> Path:
4765

4866
return Path(os.getenv(ConfigEnvVars.DATA) or user_data_dir('ddev', appauthor=False)).expand()
4967

68+
@cached_property
69+
def logger(self) -> logging.Logger:
70+
logger = logging.getLogger("ddev.app")
71+
if not any(isinstance(h, AppLoggingHandler) for h in logger.handlers):
72+
logger.addHandler(AppLoggingHandler(self))
73+
logger.setLevel(logging.WARNING)
74+
return logger
75+
5076
@property
5177
def github(self) -> GitHubManager:
5278
return self.__github

ddev/src/ddev/cli/validate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import click
55
from datadog_checks.dev.tooling.commands.validate.agent_reqs import agent_reqs
66
from datadog_checks.dev.tooling.commands.validate.agent_signature import legacy_signature
7-
from datadog_checks.dev.tooling.commands.validate.all_validations import all
87
from datadog_checks.dev.tooling.commands.validate.codeowners import codeowners
98
from datadog_checks.dev.tooling.commands.validate.config import config
109
from datadog_checks.dev.tooling.commands.validate.dashboards import dashboards
@@ -21,6 +20,7 @@
2120
from datadog_checks.dev.tooling.commands.validate.service_checks import service_checks
2221
from datadog_checks.dev.tooling.commands.validate.typos import typos
2322

23+
from ddev.cli.validate.all import all
2424
from ddev.cli.validate.ci import ci
2525
from ddev.cli.validate.http import http
2626
from ddev.cli.validate.labeler import labeler

0 commit comments

Comments
 (0)