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