blob: 09340b43b05bf31508d0a770b16a71108f8b0cb3 [file] [log] [blame]
# Copyright 2019 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.
"""Recipe for deploying goma configurations."""
from recipe_engine.recipe_api import Property
from string import Template
DEPS = [
"fuchsia/buildbucket_util",
"fuchsia/docker",
"fuchsia/gcloud",
"fuchsia/git_checkout",
"fuchsia/jsonutil",
"fuchsia/kubectl",
"fuchsia/yaml",
"recipe_engine/cipd",
"recipe_engine/context",
"recipe_engine/file",
"recipe_engine/path",
"recipe_engine/properties",
"recipe_engine/raw_io",
"recipe_engine/step",
"recipe_engine/url",
]
PROPERTIES = {
"repository": Property(
kind=str,
help="repository that hold the goma configurations",
default="https://fuchsia.googlesource.com/infra/config",
),
"config_root": Property(
kind=str,
help="root directory in repository that stores goma configurations",
default="goma",
),
"domain": Property(kind=str, help="domain", default="gcr.io"),
"project": Property(
kind=str,
help="gcloud project that hosting goma clusters",
default="goma-fuchsia",
),
"cluster": Property(kind=str, help="the name of the cluster", default="rbe-dev"),
"toolchain_project": Property(
kind=str,
help="gcloud project that hosting goma clusters",
default="fuchsia-toolchain-images-gcr",
),
"gke_operation": Property(
kind=str,
help="operation argument for gcloud deployment manager, can be 'create' or 'update'. Leave it blank means skip gke related steps",
default="",
),
"deploy_endpoint": Property(
kind=bool, help="deploy endpoint configuration.", default=False
),
"reload_services": Property(
kind=bool, help="deploy endpoint configuration.", default=True
),
}
YAML_TEMPLATE_TEST_DATA = """
type: google.api.Service
config_version: 3
name: $CLUSTER.endpoints.$PROJECT_ID.cloud.goog
title: Goma gRPC API on $CLUSTER
apis:
- name: devtools_goma.ExecService
- name: devtools_goma.FileService
- name: devtools_goma.LogService
documentation:
summary: >-
Goma gRPC API on $CLUSTER in $PROJECT_ID project.
endpoints:
- name: $CLUSTER.endpoints.$PROJECT_ID.cloud.goog
target: "$ADDR"
"""
GOMATOOLS_VERSION = "git_revision:22684ee9174606073a2604e1d58fbf03ba3ad564"
GOMA_SERVICES = [
"auth-server",
"exec-server",
"execlog-server",
"file-server",
"frontend",
]
def RunSteps(
api,
repository,
config_root,
domain,
project,
cluster,
toolchain_project,
gke_operation,
deploy_endpoint,
reload_services,
):
api.gcloud("config", "set", "project", project, step_name="set gcloud project")
# checkout
infra_config_dir, _ = api.git_checkout(repository, submodules=False, cache=False)
goma_config_dir = infra_config_dir / config_root
# for recipe tests, add mock files.
api.path.mock_add_paths(goma_config_dir.joinpath("gke", "rbe-dev", "cluster.yaml"))
api.path.mock_add_paths(
goma_config_dir.joinpath("gke-res", "rbe-dev", "storage.yaml")
)
if gke_operation:
gke_res_dir = goma_config_dir.joinpath("gke-res", cluster)
gke_dir = goma_config_dir.joinpath("gke", cluster)
api.gcloud(
"deployment-manager",
"deployments",
gke_operation,
f"{cluster}-global-addr",
"--config",
gke_res_dir / "global-addr.yaml",
step_name="deploy-global-addr",
)
api.gcloud(
"deployment-manager",
"deployments",
gke_operation,
f"{cluster}-storage",
"--config",
gke_res_dir / "storage.yaml",
step_name="deploy-storage",
)
api.gcloud(
"deployment-manager",
"deployments",
gke_operation,
cluster,
"--config",
gke_dir / "cluster.yaml",
step_name="deploy-cluster",
)
if deploy_endpoint:
endpoints_dir = goma_config_dir / "endpoints"
ip_name = f"{cluster}-endpoints-address"
addr = api.gcloud(
"compute",
"addresses",
"describe",
ip_name,
"--global",
"--format=get(address)",
step_name="get addr",
stdout=api.raw_io.output_text(),
step_test_data=lambda: api.raw_io.test_api.stream_output_text("test"),
).stdout.strip()
if not addr:
raise api.step.InfraFailure(
f"address not found in {ip_name}"
) # pragma no cover
api_config_yaml = api.path.cleanup_dir / "api_config.yaml"
service_pb = api.path.cleanup_dir / "service_descriptor.pb"
generate_yaml_from_template(
api,
endpoints_dir / "api_config.yaml.in",
api_config_yaml,
project,
cluster,
addr,
test_data=YAML_TEMPLATE_TEST_DATA,
)
with api.docker.create(
api.url.join(domain, toolchain_project, "frontend") + ":" + "latest"
) as temp_copy:
api.docker.copy(
temp_copy, ["/opt/goma/etc/service_descriptor.pb"], api.path.cleanup_dir
)
api.gcloud("endpoints", "services", "deploy", api_config_yaml, service_pb)
region = get_region_for_cluster(api, goma_config_dir, cluster)
api.gcloud(
"container",
"clusters",
"get-credentials",
f"--project={project}",
f"--region={region}",
cluster,
step_name=f"get credential for {project}",
)
with api.context(
env={"CLOUDSDK_COMPUTE_REGION": region, "CLOUDSDK_CONTAINER_CLUSTER": cluster}
):
api.kubectl(
"apply",
"-f",
str(goma_config_dir.joinpath("k8s", cluster, "goma")),
step_name="deploy-goma",
)
api.kubectl(
"annotate",
"serviceaccount",
"--overwrite",
"default",
f"iam.gke.io/gcp-service-account=rbe-cluster@{project}.iam.gserviceaccount.com",
step_name="annotate default ksa",
)
api.kubectl(
"apply",
"-f",
str(goma_config_dir.joinpath("k8s", cluster, "backend-config")),
step_name="deploy-backend-config",
)
api.kubectl(
"apply",
"-f",
str(goma_config_dir.joinpath("k8s", cluster, "ingress")),
step_name="deploy-ingress",
)
deploy_nginx_ssl(api, project, cluster)
if reload_services:
for service in GOMA_SERVICES:
api.kubectl(
"rollout",
"restart",
"deployment",
service,
step_name=f"reload {service}",
)
def get_region_for_cluster(api, goma_config_dir, cluster):
storage_yaml = goma_config_dir.joinpath("gke-res", cluster, "storage.yaml")
if not api.path.exists(storage_yaml):
raise api.step.StepFailure(f"unknown cluster name {cluster}") # pragma no cover
data = api.yaml.read_file(
"read storage.yaml", storage_yaml, test_data={"region": "us-central"}
)
return api.jsonutil.retrieve_nested_field(data, "region")
def generate_yaml_from_template(
api, input_yaml, output_yaml, project, cluster, addr, test_data=""
):
replace_dict = {
"PROJECT_ID": project,
"CLUSTER": cluster,
"ADDR": addr,
}
infile = api.file.read_text(
f"read input template {input_yaml}", input_yaml, test_data=test_data
)
outfile = ""
for curline in infile.splitlines(True):
curline_temp = Template(curline)
curline = curline_temp.substitute(replace_dict)
outfile += curline
api.file.write_text(f"write yaml {output_yaml}", output_yaml, outfile)
def ensure_gomatools(api, gomatools_dir):
with api.step.nest("ensure gomatools"):
pkgs = api.cipd.EnsureFile()
pkgs.add_package(
"fuchsia_internal/third_party/goma/server/gomatools/linux-amd64",
GOMATOOLS_VERSION,
)
api.cipd.ensure(gomatools_dir, pkgs)
def deploy_nginx_ssl(api, project, cluster):
# Check if SSL certs already exist.
kube_result = api.kubectl(
"get",
"secret",
"nginx-ssl",
step_name="kubectl get ngnix secret",
ok_ret="any",
)
if kube_result.retcode == 0:
return
# Generate new SSL certs.
gomatools_dir = api.path.cleanup_dir / "goma"
ensure_gomatools(api, gomatools_dir)
with api.context(cwd=gomatools_dir):
api.step(
"gen nginx certs",
[
gomatools_dir / "tlscert",
"-ip",
f"{cluster}.endpoints.{project}.cloud.goog",
],
)
api.file.move(
"rename key.pem",
gomatools_dir.joinpath("key.pem"),
gomatools_dir.joinpath("nginx.key"),
)
api.file.move(
"rename cert.pem",
gomatools_dir / "cert.pem",
gomatools_dir / "nginx.crt",
)
api.kubectl(
"create",
"secret",
"generic",
"nginx-ssl",
f"--from-file={str(gomatools_dir / 'nginx.crt')}",
f"--from-file={str(gomatools_dir / 'nginx.key')}",
)
def GenTests(api):
default_properties = api.properties(
repository="https://fuchsia.googlesource.com/infra/config",
config_root="goma",
domain="gcr.io",
project="goma-fuchsia",
cluster="rbe-dev",
gke_operation="create",
deploy_endpoint=True,
)
yield api.buildbucket_util.test("default") + default_properties
yield (
api.buildbucket_util.test("deploy_ssl")
+ default_properties
+ api.override_step_data("kubectl get ngnix secret", retcode=1)
)