| # 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.join(config_root) |
| |
| # for recipe tests, add mock files. |
| api.path.mock_add_paths(goma_config_dir.join("gke", "rbe-dev", "cluster.yaml")) |
| api.path.mock_add_paths(goma_config_dir.join("gke-res", "rbe-dev", "storage.yaml")) |
| if gke_operation: |
| gke_res_dir = goma_config_dir.join("gke-res", cluster) |
| gke_dir = goma_config_dir.join("gke", cluster) |
| api.gcloud( |
| "deployment-manager", |
| "deployments", |
| gke_operation, |
| f"{cluster}-global-addr", |
| "--config", |
| gke_res_dir.join("global-addr.yaml"), |
| step_name="deploy-global-addr", |
| ) |
| api.gcloud( |
| "deployment-manager", |
| "deployments", |
| gke_operation, |
| f"{cluster}-storage", |
| "--config", |
| gke_res_dir.join("storage.yaml"), |
| step_name="deploy-storage", |
| ) |
| api.gcloud( |
| "deployment-manager", |
| "deployments", |
| gke_operation, |
| cluster, |
| "--config", |
| gke_dir.join("cluster.yaml"), |
| step_name="deploy-cluster", |
| ) |
| |
| if deploy_endpoint: |
| endpoints_dir = goma_config_dir.join("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.join("api_config.yaml") |
| service_pb = api.path.cleanup_dir.join("service_descriptor.pb") |
| generate_yaml_from_template( |
| api, |
| endpoints_dir.join("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.join("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.join("k8s", cluster, "backend-config")), |
| step_name="deploy-backend-config", |
| ) |
| api.kubectl( |
| "apply", |
| "-f", |
| str(goma_config_dir.join("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.join("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.join("goma") |
| ensure_gomatools(api, gomatools_dir) |
| with api.context(cwd=gomatools_dir): |
| api.step( |
| "gen nginx certs", |
| [ |
| gomatools_dir.join("tlscert"), |
| "-ip", |
| f"{cluster}.endpoints.{project}.cloud.goog", |
| ], |
| ) |
| api.file.move( |
| "rename key.pem", gomatools_dir.join("key.pem"), gomatools_dir.join("nginx.key") |
| ) |
| api.file.move( |
| "rename cert.pem", |
| gomatools_dir.join("cert.pem"), |
| gomatools_dir.join("nginx.crt"), |
| ) |
| api.kubectl( |
| "create", |
| "secret", |
| "generic", |
| "nginx-ssl", |
| f"--from-file={str(gomatools_dir.join('nginx.crt'))}", |
| f"--from-file={str(gomatools_dir.join('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) |
| ) |