blob: 9fbe52a8ddbb7ac1c9af658c76914e14b9ce798c [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.
load('@stdlib//internal/graph.star', 'graph')
load('@stdlib//internal/luci/common.star', 'builder_ref', 'keys', 'triggerer')
load('@stdlib//internal/luci/lib/validate.star', 'validate')
load('@stdlib//internal/luci/lib/swarming.star', 'swarming')
def builder(
name=None,
bucket=None,
recipe=None,
# Execution environment parameters.
properties=None,
service_account=None,
caches=None,
execution_timeout=None,
# Scheduling parameters.
dimensions=None,
priority=None,
swarming_tags=None,
expiration_timeout=None,
# Tweaks.
build_numbers=None,
experimental=None,
task_template_canary_percentage=None,
# Deprecated stuff, candidate for deletion.
luci_migration_host=None,
# Relations.
triggers=None,
triggered_by=None,
):
"""Defines a generic builder.
It runs some recipe in some requested environment, passing it a struct with
given properties. It is launched whenever something triggers it (a poller or
some other builder, or maybe some external actor via Buildbucket or LUCI
Scheduler APIs).
The full unique builder name (as expected by Buildbucket RPC interface) is
a pair `(<project>, <bucket>/<name>)`, but within a single project config
this builder can be referred to either via its bucket-scoped name (i.e.
`<bucket>/<name>`) or just via it's name alone (i.e. `<name>`), if this
doesn't introduce ambiguities.
The definition of what can *potentially* trigger what is defined through
`triggers` and `triggered_by` fields. They specify how to prepare ACLs and
other configuration of services that execute builds. If builder **A** is
defined as "triggers builder **B**", it means all services should expect **A**
builds to trigger **B** builds via LUCI Scheduler's EmitTriggers RPC or via
Buildbucket's ScheduleBuild RPC, but the actual triggering is still the
responsibility of **A**'s recipe.
There's a caveat though: only Scheduler ACLs are auto-generated by the config
generator when one builder triggers another, because each Scheduler job has
its own ACL and we can precisely configure who's allowed to trigger this job.
Buildbucket ACLs are left unchanged, since they apply to an entire bucket, and
making a large scale change like that (without really knowing whether
Buildbucket API will be used) is dangerous. If the recipe triggers other
builds directly through Buildbucket, it is the responsibility of the config
author (you) to correctly specify Buildbucket ACLs, for example by adding the
corresponding service account to the bucket ACLs:
```python
core.bucket(
...
acls = [
...
acl.entry(acl.BUILDBUCKET_TRIGGERER, <builder service account>),
...
],
)
```
This is not necessary if the recipe uses Scheduler API instead of Buildbucket.
Args:
name: name of the builder, will show up in UIs and logs. Required.
bucket: name of the bucket the builder belongs to. Required.
recipe: name of a recipe to run, see core.recipe(...) rule. Required.
properties: a dict with string keys and JSON-serializable values, defining
properties to pass to the recipe.
service_account: an email of a service account to run the recipe under:
the recipe (and various tools it calls, e.g. gsutil) will be able to
make outbound HTTP calls that have an OAuth access token belonging to
this service account (provided it is registered with LUCI).
caches: a list of swarming.cache(...) objects describing Swarming named
caches that should be present on the bot. See swarming.cache(...) doc
for more details.
execution_timeout: how long to wait for a running build to finish before
forcefully aborting it and marking the build as timed out. If None,
defer the decision to Buildbucket service.
dimensions: a dict with swarming dimensions, indicating requirements for
a bot to execute the build. Keys are strings (e.g. `os`), and values are
either strings (e.g. `Linux`), swarming.dimension(...) objects (for
defining expiring dimensions) or lists of thereof.
priority: int [1-255] or None, indicating swarming task priority, lower is
more important. If None, defer the decision to Buildbucket service.
swarming_tags: a list of tags (`k:v` strings) to assign to the Swarming task
that runs the builder. Each tag will also end up in `swarming_tag`
Buildbucket tag, for example `swarming_tag:builder:release`.
expiration_timeout: how long to wait for a build to be picked up by a
matching bot (based on `dimensions`) before canceling the build and
marking it as expired. If None, defer the decision to Buildbucket
service.
build_numbers: if True, generate monotonically increasing contiguous numbers
for each build, unique within the builder. If None, defer the decision
to Buildbucket service.
experimental: if True, by default a new build in this builder will be marked
as experimental. This is seen from recipes and they may behave
differently (e.g. avoiding any side-effects). If None, defer the
decision to Buildbucket service.
task_template_canary_percentage: int [0-100] or None, indicating percentage
of builds that should use a canary swarming task template. If None,
defer the decision to Buildbucket service.
luci_migration_host: deprecated setting that was important during the
migration from Buildbot to LUCI. Refer to Buildbucket docs for the
meaning.
triggers: names of builders this builder triggers.
triggered_by: names of builders or pollers this builder is triggered by.
"""
name = validate.string('name', name)
bucket = validate.string('bucket', bucket)
recipe = validate.string('recipe', recipe)
# Node that carries the full definition of the builder.
builder_key = keys.builder(bucket, name)
graph.add_node(builder_key, props = {
'name': name,
'bucket': bucket,
'properties': validate.str_dict('properties', properties),
'service_account': validate.string('service_account', service_account, required=False),
'caches': swarming.validate_caches('caches', caches),
'execution_timeout': validate.duration('execution_timeout', execution_timeout, required=False),
'dimensions': swarming.validate_dimensions('dimensions', dimensions),
'priority': validate.int('priority', priority, min=1, max=255, required=False),
'swarming_tags': swarming.validate_tags('swarming_tags', swarming_tags),
'expiration_timeout': validate.duration('expiration_timeout', expiration_timeout, required=False),
'build_numbers': validate.bool('build_numbers', build_numbers, required=False),
'experimental': validate.bool('experimental', experimental, required=False),
'task_template_canary_percentage': validate.int('task_template_canary_percentage', task_template_canary_percentage, min=0, max=100, required=False),
'luci_migration_host': validate.string('luci_migration_host', luci_migration_host, required=False)
})
graph.add_edge(keys.bucket(bucket), builder_key)
graph.add_edge(builder_key, keys.recipe(recipe))
# Allow this builder to be referenced from other nodes via its bucket-scoped
# name and via a global (perhaps ambiguous) name. See builder_ref.add(...).
# Ambiguity is checked during the graph traversal via builder_ref.follow(...).
builder_ref_key = builder_ref.add(builder_key)
# Setup nodes that indicate this builder can be referenced in 'triggered_by'
# relations (either via its bucket-scoped name or via its global name).
triggerer_key = triggerer.add(builder_key)
# Link to builders triggered by this builder.
for t in validate.list('triggers', triggers):
graph.add_edge(
parent = triggerer_key,
child = keys.builder_ref(validate.string('triggers', t)),
title = 'triggers',
)
# And link to nodes this builder is triggered by.
for t in validate.list('triggered_by', triggered_by):
graph.add_edge(
parent = keys.triggerer(validate.string('triggered_by', t)),
child = builder_ref_key,
title = 'triggered_by',
)