blob: 2c7c2f0cf9ab418ed4366a8758829a956e66f8d8 [file] [log] [blame]
# Copyright 2018 The LUCI Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Definitions imported by all other luci/**/*.star modules.
Should not import other LUCI modules to avoid dependency cycles.
"""
load('@stdlib//internal/error.star', 'error')
load('@stdlib//internal/graph.star', 'graph')
# Node edges (parent -> child):
# core.project: root
# core.project -> core.logdog
# core.project -> [core.bucket]
# core.bucket -> [core.builder]
# core.builder_ref -> core.builder
def _bucket_scoped_key(kind, name):
"""Returns either a bucket-scoped or a global key of the given kind.
Bucket-scoped keys have (BUCKET, <name>) as the first component of the key.
Args:
kind: kind of the key.
name: either "<bucket>/<name>" or just "<name>".
"""
chunks = name.split('/', 1)
if len(chunks) == 1:
return graph.key(kind, chunks[0])
return graph.key(kinds.BUCKET, chunks[0], kind, chunks[1])
# Kinds is a enum-like struct with node kinds of various LUCI config nodes.
kinds = struct(
# Publicly declarable nodes.
PROJECT = 'core.project',
LOGDOG = 'core.logdog',
BUCKET = 'core.bucket',
BUILDER = 'core.builder',
# Internal nodes (declared internally as dependency of other nodes).
BUILDER_REF = 'core.builder_ref',
)
# Keys is a collection of key constructors for various LUCI config nodes.
keys = struct(
# Publicly declarable nodes.
project = lambda: graph.key(kinds.PROJECT, '...'), # singleton
logdog = lambda: graph.key(kinds.LOGDOG, '...'), # singleton
bucket = lambda name: graph.key(kinds.BUCKET, name),
builder = lambda bucket, name: graph.key(kinds.BUCKET, bucket, kinds.BUILDER, name),
# Internal nodes.
builder_ref = lambda name: _bucket_scoped_key(kinds.BUILDER_REF, name),
)
################################################################################
## builder_ref implementation.
def _builder_ref_add(target):
"""Adds two builder_ref nodes ("<bucket>/<name>" and "<name>") that have
'target' as a child.
Builder refs are named pointers to builders. Each builder has two such refs:
a bucket-scoped one (for when the builder is referenced using its full name
"<bucket>/<name>"), and a global one (when the builder is referenced just as
"<name>").
Global refs can have more than one child (which means there are multiple
builders with the same name in different buckets). Such refs are reported as
ambiguous by builder_ref.follow(...).
Args:
target: a graph.key (of BUILDER kind) to setup refs for.
Returns:
graph.key of added bucket-scoped BUILDER_REF node ("<bucket>/<name>" one).
"""
if target.kind != kinds.BUILDER:
fail('got %s, expecting a builder key' % (target,))
bucket = target.container.id # name of the bucket, as string
builder = target.id # name of the builder, as string
short = keys.builder_ref(builder)
graph.add_node(short, idempotent=True) # there may be such node already
graph.add_edge(short, target)
full = keys.builder_ref("%s/%s" % (bucket, builder))
graph.add_node(full)
graph.add_edge(full, target)
return full
def _builder_ref_follow(ref_node, context_node):
"""Given a BUILDER_REF node, returns a BUILDER graph.node the ref points to.
Emits an error and returns None if the reference is ambiguous (i.e. 'ref_node'
has more than one child). Such reference can't be used to refer to a single
builder.
Note that the emitted error doesn't stop the generation phase, but marks it
as failed. This allows to collect more errors before giving up.
Args:
ref_node: graph.node with the ref.
context_node: graph.node where this ref is used, for error messages.
Returns:
graph.node of BUILDER kind.
"""
if ref_node.key.kind != kinds.BUILDER_REF:
fail('%s is not builder_ref' % ref_node)
# builder_ref nodes are always linked to something, see _builder_ref_add.
variants = graph.children(ref_node.key)
if not variants:
fail('%s is unexpectedly unconnected' % ref_node)
# No ambiguity.
if len(variants) == 1:
return variants[0]
error(
'ambiguous reference %r in %s, possible variants:\n %s',
ref_node.key.id, context_node, '\n '.join([str(v) for v in variants]),
trace=context_node.trace,
)
return None
# Additional API for dealing with builder_refs.
builder_ref = struct(
add = _builder_ref_add,
follow = _builder_ref_follow,
)