Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions src/mozilla_taskgraph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def register(graph_config):
"worker_types",
]
)

validate_graph_config(graph_config._config)


Expand Down
17 changes: 16 additions & 1 deletion src/mozilla_taskgraph/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from textwrap import dedent
from typing import Optional
from typing import Optional, Union

from taskgraph import config as tg
from taskgraph.util.schema import Schema
Expand All @@ -26,6 +26,12 @@ class MozillaGraphConfigSchema(tg.graph_config_schema):
# string to use for release tasks.
# Defaults to ``mozilla_taskgraph.version:default_parser``.
version_parser: Optional[str] = None
# Mapping of project to the branches that should be considered
# "production" releases. A value of ``True`` means all branches of the
# project are release branches, while a list restricts releases to the
# named branches. Consumed by
# ``mozilla_taskgraph.util.attributes:release_level``.
release_branches: Optional[dict[str, Union[bool, list[str]]]] = None

else:
# Legacy voluptuous-based graph_config_schema (e.g. gecko_taskgraph override).
Expand All @@ -48,6 +54,15 @@ class MozillaGraphConfigSchema(tg.graph_config_schema):
Defaults to ``mozilla_taskgraph.version:default_parser``.
""".lstrip()),
): str,
Vol_Optional(
"release-branches",
description=dedent("""
Mapping of project to the branches that should be considered
"production" releases. A value of ``True`` means all branches
of the project are release branches, while a list restricts
releases to the named branches.
""".lstrip()),
): {str: object},
}
)

Expand Down
29 changes: 29 additions & 0 deletions src/mozilla_taskgraph/util/attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# 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 re


def release_level(release_branches, params):
"""Whether this is a production release or not.

``release_branches`` is the graph config's ``release-branches`` mapping of
project to the branches considered "production" for it. A value of ``True``
for a project means every branch of that project is a release branch (the
model used by Mercurial based projects), while a list restricts releases to
the named branches.

:return str: One of "production" or "staging".
"""

if params["level"] == "3":
branches = (release_branches or {}).get(params["project"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than allow release_branches to be something other than a dict, let's just assume it is, and require callers to make that happen. (And maybe add a type annotation to help any callers that run linters?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that sounds good!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for the feedback. The latest commit should address these points.

if branches is True or (
branches

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally optional, but you could break out this part into an intermediate variable to use in the if expression to avoid multiple lines for this.

@kryoseu kryoseu Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've restructured the control flow a bit. Now I have 3 return statements, however I think that makes it easier to read and makes the intent and checks clear. Let me know what you think

and (m := re.match(r"refs/heads/(\S+)$", params["head_ref"]))
and m.group(1) in branches
):
return "production"

return "staging"
56 changes: 56 additions & 0 deletions test/util/test_attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# 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 pytest

from mozilla_taskgraph.util.attributes import release_level

FIREFOX_BRANCHES = ["main", "beta", "release", "esr140"]
RELEASE_BRANCHES = {
"firefox": FIREFOX_BRANCHES,
"mozilla-central": True,
}


@pytest.mark.parametrize(
"release_branches,params,expected",
(
# Not level 3 -> always staging, regardless of branch.
(RELEASE_BRANCHES, {"level": "1", "project": "mozilla-central"}, "staging"),
(
RELEASE_BRANCHES,
{"level": "1", "project": "firefox", "head_ref": "refs/heads/beta"},
"staging",
),
# No `release-branches` mapping at all -> staging.
(
None,
{"level": "3", "project": "firefox", "head_ref": "refs/heads/beta"},
"staging",
),
# Project not listed in the mapping -> staging (e.g. autoland).
(RELEASE_BRANCHES, {"level": "3", "project": "autoland"}, "staging"),
# Whole project is a release project (Mercurial model).
(RELEASE_BRANCHES, {"level": "3", "project": "mozilla-central"}, "production"),
# Git monorepo model: only listed branches are production.
(
RELEASE_BRANCHES,
{"level": "3", "project": "firefox", "head_ref": "refs/heads/beta"},
"production",
),
(
RELEASE_BRANCHES,
{"level": "3", "project": "firefox", "head_ref": "refs/heads/test"},
"staging",
),
# Only refs/heads/* match, not tags.
(
RELEASE_BRANCHES,
{"level": "3", "project": "firefox", "head_ref": "refs/tags/beta"},
"staging",
),
),
)
def test_release_level(release_branches, params, expected):
assert release_level(release_branches, params) == expected