blob: 221369228b1564c288d69945009c1cc815270999 [file] [log] [blame]
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import base64
from google.protobuf import text_format as textpb
from recipe_engine import recipe_api
from PB.go.chromium.org.luci.buildbucket.proto import project_config as bb_pb2
from PB.go.chromium.org.luci.cv.api.config.v2 import config as cv_config_pb2
from PB.go.chromium.org.luci.milo.api.config import project as milo_pb2
from PB.go.chromium.org.luci.scheduler.appengine.messages import config as scheduler_pb2
from PB.go.chromium.org.luci.config_service.proto import (
config_service as config_service_pb2,
)
class LuciConfigApi(recipe_api.RecipeApi):
"""Module for polling and parsing luci config files via the luci-config API.
Depends on `prpc` binary being available in $PATH:
https://godoc.org/go.chromium.org/luci/grpc/cmd/prpc
"""
def fetch_config(self, config_name, message_type, project=None, local_dir=None):
"""Fetch a config file from the luci-config API and return it as a proto object.
Args:
config_name (str): The name of the config file to fetch, e.g.
"commit-queue.cfg".
message_type (type): The Python type corresponding to the
config's protobuf message type.
project (str): The name of the LUCI project to fetch the config
from; e.g. "fuchsia". Defaults to the project that the
current Buildbucket build is running in.
local_dir (Path): If specified, assumed to point to a local
directory of files generated by lucicfg. The specified config
file will be read from the corresponding local file rather
than fetching it from the LUCI Config service.
"""
if not project:
project = self.m.buildbucket.build.builder.project
assert project, "buildbucket input has no project set"
if local_dir:
text = self.m.file.read_text(
f"read {project} {config_name}", local_dir / config_name
)
else:
with self.m.step.nest(f"fetch {project} {config_name}"):
text = self._fetch_config_textproto(project, config_name)
cfg = message_type()
textpb.Parse(text, cfg)
return cfg
def _fetch_config_textproto(self, project, config_name):
req = config_service_pb2.GetConfigRequest(
config_set=f"projects/{project}", path=config_name
)
resp = self.m.step(
name="get",
cmd=[
"prpc",
"call",
"-format=json",
"config.luci.app",
"config.service.v2.Configs.GetConfig",
],
stdin=self.m.proto.input(req, "JSONPB"),
stdout=self.m.proto.output(config_service_pb2.Config, "JSONPB"),
infra_step=True,
step_test_data=lambda: self.m.proto.test_api.output_stream(
config_service_pb2.Config(raw_content=b"")
),
).stdout
# Responses for extremely large config files will have the `signed_url`
# populated instead of `raw_content` and the file must be fetched using
# the signed URL. At time of writing this code is not used to fetch
# config files of that size, so support for signed URLs is not required.
assert (
resp.WhichOneof("content") == "raw_content"
), f"{config_name} can only be fetched by signed URL. TODO(you): Implemented signed URL fetching :)"
return resp.raw_content
def buildbucket(self, **kwargs):
return self.fetch_config("cr-buildbucket.cfg", bb_pb2.BuildbucketCfg, **kwargs)
def commit_queue(self, config_name=None, **kwargs):
# Support loading a CQ config file with a non-default name to support
# projects that don't want a dedicated CQ instance but for which we
# still want to know which tryjobs exist.
config_name = config_name or "commit-queue.cfg"
return self.fetch_config(config_name, cv_config_pb2.Config, **kwargs)
def milo(self, **kwargs):
return self.fetch_config("luci-milo.cfg", milo_pb2.Project, **kwargs)
def scheduler(self, **kwargs):
return self.fetch_config(
"luci-scheduler.cfg", scheduler_pb2.ProjectConfig, **kwargs
)