|
2 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | 4 |
|
5 | | - |
6 | 5 | import logging |
7 | 6 | import os |
8 | 7 | import sys |
9 | 8 | from dataclasses import dataclass |
10 | 9 | from pathlib import Path |
11 | | -from typing import Dict |
12 | | - |
13 | | -from voluptuous import ALLOW_EXTRA, All, Any, Extra, Length, Optional, Required |
| 10 | +from typing import Dict, List, Literal, Optional, Union |
14 | 11 |
|
15 | 12 | from .util.caches import CACHES |
16 | 13 | from .util.python_path import find_object |
17 | | -from .util.schema import Schema, optionally_keyed_by, validate_schema |
| 14 | +from .util.schema import Schema, TaskPriority, optionally_keyed_by, validate_schema |
18 | 15 | from .util.vcs import get_repository |
19 | 16 | from .util.yaml import load_yaml |
20 | 17 |
|
21 | 18 | logger = logging.getLogger(__name__) |
22 | 19 |
|
| 20 | +# CacheName type for valid cache names |
| 21 | +CacheName = Literal[tuple(CACHES.keys())] |
| 22 | + |
| 23 | + |
| 24 | +class WorkerAliasSchema(Schema): |
| 25 | + """Worker alias configuration.""" |
| 26 | + |
| 27 | + provisioner: optionally_keyed_by("level", str) # type: ignore |
| 28 | + implementation: str |
| 29 | + os: str |
| 30 | + worker_type: optionally_keyed_by("level", str) # type: ignore |
| 31 | + |
| 32 | + |
| 33 | +class WorkersSchema(Schema, rename=None): |
| 34 | + """Workers configuration.""" |
| 35 | + |
| 36 | + aliases: Dict[str, WorkerAliasSchema] |
| 37 | + |
| 38 | + |
| 39 | +class Repository(Schema, forbid_unknown_fields=False): |
| 40 | + """Repository configuration. |
| 41 | +
|
| 42 | + This schema allows extra fields for repository-specific configuration. |
| 43 | + """ |
| 44 | + |
| 45 | + # Required fields first |
| 46 | + name: str |
| 47 | + |
| 48 | + # Optional fields |
| 49 | + project_regex: Optional[str] = None # Maps from "project-regex" |
| 50 | + ssh_secret_name: Optional[str] = None # Maps from "ssh-secret-name" |
| 51 | + |
| 52 | + |
| 53 | +class RunConfig(Schema): |
| 54 | + """Run transforms configuration.""" |
| 55 | + |
| 56 | + # List of caches to enable, or a boolean to enable/disable all of them. |
| 57 | + use_caches: Optional[Union[bool, List[str]]] = None # Maps from "use-caches" |
| 58 | + |
| 59 | + def __post_init__(self): |
| 60 | + """Validate that cache names are valid.""" |
| 61 | + if isinstance(self.use_caches, list): |
| 62 | + invalid = set(self.use_caches) - set(CACHES.keys()) |
| 63 | + if invalid: |
| 64 | + raise ValueError( |
| 65 | + f"Invalid cache names: {invalid}. " |
| 66 | + f"Valid names are: {list(CACHES.keys())}" |
| 67 | + ) |
| 68 | + |
| 69 | + |
| 70 | +class TaskGraphSchema(Schema): |
| 71 | + """Taskgraph specific configuration.""" |
| 72 | + |
| 73 | + # Required fields first |
| 74 | + repositories: Dict[str, Repository] |
| 75 | + |
| 76 | + # Optional fields |
| 77 | + # Python function to call to register extensions. |
| 78 | + register: Optional[str] = None |
| 79 | + decision_parameters: Optional[str] = None # Maps from "decision-parameters" |
| 80 | + # The taskcluster index prefix to use for caching tasks. Defaults to `trust-domain`. |
| 81 | + cached_task_prefix: Optional[str] = None # Maps from "cached-task-prefix" |
| 82 | + # Should tasks from pull requests populate the cache |
| 83 | + cache_pull_requests: Optional[bool] = None # Maps from "cache-pull-requests" |
| 84 | + # Regular expressions matching index paths to be summarized. |
| 85 | + index_path_regexes: Optional[List[str]] = None # Maps from "index-path-regexes" |
| 86 | + # Configuration related to the 'run' transforms. |
| 87 | + run: Optional[RunConfig] = None |
| 88 | + |
| 89 | + |
| 90 | +class GraphConfigSchema(Schema, forbid_unknown_fields=False): |
| 91 | + """Main graph configuration schema. |
| 92 | +
|
| 93 | + This schema allows extra fields for flexibility in graph configuration. |
| 94 | + """ |
| 95 | + |
| 96 | + # Required fields first |
| 97 | + # The trust-domain for this graph. |
| 98 | + # (See https://firefox-source-docs.mozilla.org/taskcluster/taskcluster/taskgraph.html#taskgraph-trust-domain) |
| 99 | + trust_domain: str # Maps from "trust-domain" |
| 100 | + task_priority: optionally_keyed_by("project", "level", TaskPriority) # type: ignore |
| 101 | + workers: WorkersSchema |
| 102 | + taskgraph: TaskGraphSchema |
23 | 103 |
|
24 | | -#: Schema for the graph config |
25 | | -graph_config_schema = Schema( |
26 | | - { |
27 | | - # The trust-domain for this graph. |
28 | | - # (See https://firefox-source-docs.mozilla.org/taskcluster/taskcluster/taskgraph.html#taskgraph-trust-domain) # noqa |
29 | | - Required("trust-domain"): str, |
30 | | - Optional( |
31 | | - "docker-image-kind", |
32 | | - description="Name of the docker image kind (default: docker-image)", |
33 | | - ): str, |
34 | | - Required("task-priority"): optionally_keyed_by( |
35 | | - "project", |
36 | | - "level", |
37 | | - Any( |
38 | | - "highest", |
39 | | - "very-high", |
40 | | - "high", |
41 | | - "medium", |
42 | | - "low", |
43 | | - "very-low", |
44 | | - "lowest", |
45 | | - ), |
46 | | - ), |
47 | | - Optional( |
48 | | - "task-deadline-after", |
49 | | - description="Default 'deadline' for tasks, in relative date format. " |
50 | | - "Eg: '1 week'", |
51 | | - ): optionally_keyed_by("project", str), |
52 | | - Optional( |
53 | | - "task-expires-after", |
54 | | - description="Default 'expires-after' for level 1 tasks, in relative date format. " |
55 | | - "Eg: '90 days'", |
56 | | - ): str, |
57 | | - Required("workers"): { |
58 | | - Required("aliases"): { |
59 | | - str: { |
60 | | - Required("provisioner"): optionally_keyed_by("level", str), |
61 | | - Required("implementation"): str, |
62 | | - Required("os"): str, |
63 | | - Required("worker-type"): optionally_keyed_by("level", str), |
64 | | - } |
65 | | - }, |
66 | | - }, |
67 | | - Required("taskgraph"): { |
68 | | - Optional( |
69 | | - "register", |
70 | | - description="Python function to call to register extensions.", |
71 | | - ): str, |
72 | | - Optional("decision-parameters"): str, |
73 | | - Optional( |
74 | | - "cached-task-prefix", |
75 | | - description="The taskcluster index prefix to use for caching tasks. " |
76 | | - "Defaults to `trust-domain`.", |
77 | | - ): str, |
78 | | - Optional( |
79 | | - "cache-pull-requests", |
80 | | - description="Should tasks from pull requests populate the cache", |
81 | | - ): bool, |
82 | | - Optional( |
83 | | - "index-path-regexes", |
84 | | - description="Regular expressions matching index paths to be summarized.", |
85 | | - ): [str], |
86 | | - Optional( |
87 | | - "run", |
88 | | - description="Configuration related to the 'run' transforms.", |
89 | | - ): { |
90 | | - Optional( |
91 | | - "use-caches", |
92 | | - description="List of caches to enable, or a boolean to " |
93 | | - "enable/disable all of them.", |
94 | | - ): Any(bool, list(CACHES.keys())), |
95 | | - }, |
96 | | - Required("repositories"): All( |
97 | | - { |
98 | | - str: { |
99 | | - Required("name"): str, |
100 | | - Optional("project-regex"): str, |
101 | | - Optional("ssh-secret-name"): str, |
102 | | - # FIXME |
103 | | - Extra: str, |
104 | | - } |
105 | | - }, |
106 | | - Length(min=1), |
107 | | - ), |
108 | | - }, |
109 | | - }, |
110 | | - extra=ALLOW_EXTRA, |
111 | | -) |
| 104 | + # Optional fields |
| 105 | + # Name of the docker image kind (default: docker-image) |
| 106 | + docker_image_kind: Optional[str] = None # Maps from "docker-image-kind" |
| 107 | + # Default 'deadline' for tasks, in relative date format. Eg: '1 week' |
| 108 | + task_deadline_after: Optional[optionally_keyed_by("project", str)] = None # type: ignore |
| 109 | + # Default 'expires-after' for level 1 tasks, in relative date format. Eg: '90 days' |
| 110 | + task_expires_after: Optional[str] = None # Maps from "task-expires-after" |
112 | 111 |
|
113 | 112 |
|
114 | 113 | @dataclass(frozen=True, eq=False) |
@@ -179,7 +178,8 @@ def kinds_dir(self): |
179 | 178 |
|
180 | 179 |
|
181 | 180 | def validate_graph_config(config): |
182 | | - validate_schema(graph_config_schema, config, "Invalid graph configuration:") |
| 181 | + """Validate graph configuration using msgspec.""" |
| 182 | + validate_schema(GraphConfigSchema, config, "Invalid graph configuration:") |
183 | 183 |
|
184 | 184 |
|
185 | 185 | def load_graph_config(root_dir): |
|
0 commit comments