Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["docs/_build", "Thumbs.db", ".DS_Store"]

# Suppress ambiguous cross-reference warnings from autodoc.
# Multiple Schema classes define fields with the same name (e.g. "path"),
# which Sphinx can't disambiguate.
suppress_warnings = ["ref.python"]


# -- Options for HTML output -------------------------------------------------

Expand Down
159 changes: 68 additions & 91 deletions src/taskgraph/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,106 +8,83 @@
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Literal, Optional, Union

from voluptuous import ALLOW_EXTRA, All, Any, Extra, Length, Optional, Required

from .util.caches import CACHES
from .util.python_path import find_object
from .util.schema import LegacySchema, optionally_keyed_by, validate_schema
from .util.schema import Schema, TaskPriority, optionally_keyed_by, validate_schema
from .util.vcs import get_repository
from .util.yaml import load_yaml

logger = logging.getLogger(__name__)

CacheType = Literal["cargo", "checkout", "npm", "pip", "uv"]


class WorkerAlias(Schema):
provisioner: optionally_keyed_by("level", str, use_msgspec=True) # type: ignore
implementation: str
os: str
worker_type: optionally_keyed_by("level", str, use_msgspec=True) # type: ignore


class WorkersConfig(Schema):
aliases: dict[str, WorkerAlias]


class RunConfig(Schema):
# List of caches to enable, or a boolean to enable/disable all of them.
use_caches: Optional[Union[bool, list[CacheType]]] = None


class RepositoryConfig(Schema, forbid_unknown_fields=False):
name: str
project_regex: Optional[str] = None
ssh_secret_name: Optional[str] = None
# FIXME: Extra keys allowed via forbid_unknown_fields=False


class TaskgraphConfig(Schema):
repositories: dict[str, RepositoryConfig]
# Python function to call to register extensions.
register: Optional[str] = None
decision_parameters: Optional[str] = None
# The taskcluster index prefix to use for caching tasks.
# Defaults to `trust-domain`.
cached_task_prefix: Optional[str] = None
# Should tasks from pull requests populate the cache
cache_pull_requests: Optional[bool] = None
# Regular expressions matching index paths to be summarized.
index_path_regexes: Optional[list[str]] = None
# Configuration related to the 'run' transforms.
run: Optional[RunConfig] = None

def __post_init__(self):
# Validate repositories has at least 1 entry (was All(..., Length(min=1)))
if not self.repositories:
raise ValueError("'repositories' must have at least one entry")


#: Schema for the graph config
graph_config_schema = LegacySchema(
{
# The trust-domain for this graph.
# (See https://firefox-source-docs.mozilla.org/taskcluster/taskcluster/taskgraph.html#taskgraph-trust-domain) # noqa
Required("trust-domain"): str,
Optional(
"docker-image-kind",
description="Name of the docker image kind (default: docker-image)",
): str,
Required("task-priority"): optionally_keyed_by(
"project",
"level",
Any(
"highest",
"very-high",
"high",
"medium",
"low",
"very-low",
"lowest",
),
),
Optional(
"task-deadline-after",
description="Default 'deadline' for tasks, in relative date format. "
"Eg: '1 week'",
): optionally_keyed_by("project", str),
Optional(
"task-expires-after",
description="Default 'expires-after' for level 1 tasks, in relative date format. "
"Eg: '90 days'",
): str,
Required("workers"): {
Required("aliases"): {
str: {
Required("provisioner"): optionally_keyed_by("level", str),
Required("implementation"): str,
Required("os"): str,
Required("worker-type"): optionally_keyed_by("level", str),
}
},
},
Required("taskgraph"): {
Optional(
"register",
description="Python function to call to register extensions.",
): str,
Optional("decision-parameters"): str,
Optional(
"cached-task-prefix",
description="The taskcluster index prefix to use for caching tasks. "
"Defaults to `trust-domain`.",
): str,
Optional(
"cache-pull-requests",
description="Should tasks from pull requests populate the cache",
): bool,
Optional(
"index-path-regexes",
description="Regular expressions matching index paths to be summarized.",
): [str],
Optional(
"run",
description="Configuration related to the 'run' transforms.",
): {
Optional(
"use-caches",
description="List of caches to enable, or a boolean to "
"enable/disable all of them.",
): Any(bool, list(CACHES.keys())),
},
Required("repositories"): All(
{
str: {
Required("name"): str,
Optional("project-regex"): str,
Optional("ssh-secret-name"): str,
# FIXME
Extra: str,
}
},
Length(min=1),
),
},
},
extra=ALLOW_EXTRA,
)
class GraphConfigSchema(Schema, forbid_unknown_fields=False):
# The trust-domain for this graph.
trust_domain: str
task_priority: optionally_keyed_by( # type: ignore
"project", "level", TaskPriority, use_msgspec=True
)
workers: WorkersConfig
taskgraph: TaskgraphConfig
# Name of the docker image kind (default: docker-image)
docker_image_kind: Optional[str] = None
# Default 'deadline' for tasks, in relative date format. Eg: '1 week'
task_deadline_after: Optional[
optionally_keyed_by("project", str, use_msgspec=True) # type: ignore
] = None
# Default 'expires-after' for level 1 tasks, in relative date format.
# Eg: '90 days'
task_expires_after: Optional[str] = None


graph_config_schema = GraphConfigSchema


@dataclass(frozen=True, eq=False)
Expand Down
14 changes: 7 additions & 7 deletions src/taskgraph/decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import shutil
import time
from pathlib import Path
from typing import Optional

import yaml
from voluptuous import Optional

from taskgraph.actions import render_actions_json
from taskgraph.create import create_tasks
Expand All @@ -20,7 +20,7 @@
from taskgraph.taskgraph import TaskGraph
from taskgraph.util import json
from taskgraph.util.python_path import find_object
from taskgraph.util.schema import LegacySchema, validate_schema
from taskgraph.util.schema import Schema, validate_schema
from taskgraph.util.vcs import get_repository
from taskgraph.util.yaml import load_yaml

Expand All @@ -40,11 +40,11 @@


#: Schema for try_task_config.json version 2
try_task_config_schema_v2 = LegacySchema(
{
Optional("parameters"): {str: object},
}
)
class TryTaskConfigSchemaV2(Schema, forbid_unknown_fields=True):
parameters: Optional[dict[str, object]] = None


try_task_config_schema_v2 = TryTaskConfigSchemaV2


def full_task_graph_to_runnable_tasks(full_task_json):
Expand Down
4 changes: 2 additions & 2 deletions src/taskgraph/transforms/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from ..config import GraphConfig
from ..parameters import Parameters
from ..util.schema import LegacySchema, validate_schema
from ..util.schema import Schema, validate_schema


@dataclass(frozen=True)
Expand Down Expand Up @@ -138,7 +138,7 @@ def add_validate(self, schema):

@dataclass
class ValidateSchema:
schema: LegacySchema
schema: Schema

def __call__(self, config, tasks):
for task in tasks:
Expand Down
57 changes: 19 additions & 38 deletions src/taskgraph/transforms/chunking.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,30 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import copy
from textwrap import dedent

from voluptuous import ALLOW_EXTRA, Optional, Required
from typing import Optional

from taskgraph.transforms.base import TransformSequence
from taskgraph.util.schema import LegacySchema
from taskgraph.util.schema import Schema
from taskgraph.util.templates import substitute


class ChunkConfig(Schema):
# The total number of chunks to split the task into.
total_chunks: int
# A list of fields that need to have `{this_chunk}` and/or
# `{total_chunks}` replaced in them.
substitution_fields: list[str] = []


#: Schema for chunking transforms
CHUNK_SCHEMA = LegacySchema(
{
# Optional, so it can be used for a subset of tasks in a kind
Optional(
"chunk",
description=dedent(
"""
`chunk` can be used to split one task into `total-chunks`
tasks, substituting `this_chunk` and `total_chunks` into any
fields in `substitution-fields`.
""".lstrip()
),
): {
Required(
"total-chunks",
description=dedent(
"""
The total number of chunks to split the task into.
""".lstrip()
),
): int,
Optional(
"substitution-fields",
description=dedent(
"""
A list of fields that need to have `{this_chunk}` and/or
`{total_chunks}` replaced in them.
""".lstrip()
),
): [str],
}
},
extra=ALLOW_EXTRA,
)
class ChunkSchema(Schema, forbid_unknown_fields=False):
# `chunk` can be used to split one task into `total-chunks`
# tasks, substituting `this_chunk` and `total_chunks` into any
# fields in `substitution-fields`.
chunk: Optional[ChunkConfig] = None


CHUNK_SCHEMA = ChunkSchema

transforms = TransformSequence()
transforms.add_validate(CHUNK_SCHEMA)
Expand Down
Loading
Loading