1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
infra / config / validators / builder-group-triggers.star [blame]
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A validator to enforce that triggers do not cross builder groups."""
load("@stdlib//internal/graph.star", "graph")
load("@stdlib//internal/luci/common.star", "keys", "triggerer")
# These triggers cross builder groups but predate us restricting triggers to
# within a builder group, we should attempt to remove these where feasible
_LEGACY_CROSS_BUILDER_GROUP_TRIGGERS = {
("ci", "Android x64 Builder (dbg)"): [
("ci", "android-12-x64-dbg-tests"),
("ci", "android-webview-12-x64-dbg-tests"),
("ci", "android-webview-13-x64-dbg-tests"),
],
("ci", "Mac Builder (dbg)"): [
("ci", "mac-osxbeta-rel"),
],
("ci", "Win x64 Builder"): [
("ci", "win-network-sandbox-tester"),
],
("ci", "android-12-x64-rel"): [
("ci", "android-12-x64-fyi-rel"),
],
("ci", "mac-arm64-rel"): [
("ci", "mac-fieldtrial-tester"),
],
}
def _check_trigger_builder_groups(ctx):
cfg = ctx.output["luci/cr-buildbucket.cfg"]
builder_group_by_builder = {}
for bucket in cfg.buckets:
if not proto.has(bucket, "swarming"):
continue
for builder in bucket.swarming.builders:
builder_group = json.decode(builder.properties).get("builder_group")
if builder_group != None:
builder_group_by_builder[(bucket.name, builder.name)] = builder_group
# Traverse the graph for triggering. If builder X triggers Y, the nodes will
# be: BUILDER(X) -> TRIGGERER(X) -> BUILDER_REF(Y) -> BUILDER(Y)
# The TRIGGERER nodes abstract things that can trigger (builders or pollers)
# The BUILDER_REF nodes abstract out referring to a builder by simple name
# (e.g. mac-rel) or bucket-qualified name (e.g. try/mac-rel)
bad_triggers = []
for (bucket_name, builder_name), builder_group in builder_group_by_builder.items():
builder_node = graph.node(keys.builder(bucket_name, builder_name))
legacy_triggers = _LEGACY_CROSS_BUILDER_GROUP_TRIGGERS.get((bucket_name, builder_name), [])
for n in triggerer.targets(builder_node):
child_bucket_name = n.key.container.id
child_builder_name = n.key.id
child_group = builder_group_by_builder.get((child_bucket_name, child_builder_name))
if child_group == None:
fail("A builder without a group is being triggered: {}/{} ({}) -> {}/{}"
.format(bucket_name, builder_name, builder_group, child_bucket_name, child_builder_name))
if child_group == builder_group:
continue
if (child_bucket_name, child_builder_name) in legacy_triggers:
continue
bad_triggers.append("* {}/{} ({}) -> {}/{} ({})"
.format(bucket_name, builder_name, builder_group, child_bucket_name, child_builder_name, child_group))
if bad_triggers:
fail("The following triggers cross builder groups:\n{}".format("\n".join(sorted(bad_triggers))))
lucicfg.generator(_check_trigger_builder_groups)