Skip to content
Merged
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
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
36 changes: 36 additions & 0 deletions src/mozilla_taskgraph/util/attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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: dict, params: dict):
"""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.

A build is only ever "production" at level 3. ``params`` provides ``level``
and ``project``, plus ``head_ref`` for projects configured with a branch
list.

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

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

if branches is True:
return "production"

if isinstance(branches, list):

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.

There's a possible edge case here of level == 3 and branches is not a list (eg: perhaps it's a string). That will end up falling back to "staging".

I think this is fine; just calling it out explicitly in case you want to address it.

@kryoseu kryoseu Jun 24, 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.

Thanks for that. I believe that's fine too. I think it is better to fallback to staging in the event that release_branches is "malformed" in config.yml, rather than the other way around. By "malformed" I mean not honoring the types defined in the schema MozillaGraphConfigSchema. Do you agree?

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.

Yeah, I think falling back in this way is fine. If it becomes a problem in the future we can always update the code.

match = re.match(r"refs/heads/(\S+)$", params["head_ref"])

if match and match.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",
),
# Empty `release-branches` mapping -> staging.
(
{},
{"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