|
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