| # Copyright 2023 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. |
| from recipe_engine import recipe_api |
| |
| |
| class BuildProxyWrapInstance: |
| def __init__(self, api, bpw_cfg, bpw, socat, auth_wrapper): |
| """Provides necessary state to run sandboxed builds with buildproxywrap. |
| |
| Args: |
| api: A RecipeScriptApi object. |
| bpw_cfg: buildproxywrap configuration with no placeholders. |
| bpw: Path to buildproxywrap tool. |
| socat: Path to socat tool. |
| auth_wrapper: Path to the script proxying Bazel authentication. |
| """ |
| self._api = api |
| # Path to hold the sockets created by buidproxywrap tool. |
| self._socket_dir = self._api.path.mkdtemp("buildproxy_socket_dir") |
| self._bpw_cfg = bpw_cfg |
| self._bpw = bpw |
| self._socat = socat |
| self._auth_wrapper = auth_wrapper |
| |
| def env(self): |
| """Constructs the set of environment variables used by buildproxywrap. |
| |
| Environment variable values that denote socket file paths are converted |
| to absolute paths by prefixing them with buildproxywrap's socket path. |
| |
| Returns: |
| A mapping of environment variables and their values. |
| """ |
| return { |
| entry["socket_path_env_var"]: self._api.path.join( |
| self._socket_dir, entry["socket_file_name"] |
| ) |
| for entry in self._bpw_cfg |
| } |
| |
| def proxied_step(self, step_name, inner_cmd, **kwargs): |
| """ |
| Constructs a step that runs command inner_cmd with buildproxywrap. |
| |
| Args: |
| step_name(str): The name of the step. |
| inner_cmd (list[str]): The command to run inside of buildproxywrap. |
| **kwargs (dict): Passed through as kwargs to step. |
| |
| Returns: |
| A step that runs the inner_cmd inside of buildproxywrap. |
| """ |
| cmd = [ |
| self._bpw, |
| "-socat", |
| self._socat, |
| "-socket_dir", |
| self._socket_dir, |
| "-cfg", |
| self._api.json.input(self._bpw_cfg), |
| "--", |
| ] |
| cmd.extend(inner_cmd) |
| return self._api.step( |
| step_name, |
| cmd, |
| **kwargs, |
| ) |
| |
| def auth_proxied_cmd(self, cmd): |
| """Wrap command cmd by a proxy server to forward ADC auth requests. |
| |
| This is meant to be run during sandboxed builds because without it, ADC |
| auth requests won't pass through the sandbox. It wraps command cmd to |
| complete the setup needed inside the sandbox to communicate with the |
| setup completed outside the sandbox by buildproxywrap tool. |
| |
| Args: |
| cmd: The command to be wrapped. |
| |
| Returns: |
| The command after being wrapped. |
| """ |
| return [ |
| self._auth_wrapper, |
| self._socat, |
| ] + cmd |
| |
| def ro_mounts(self): |
| """Returns the read-only mounts needed for sandboxed builds. |
| |
| Returns: |
| An iterable of paths corresponding to the read-only mounts. |
| """ |
| return map( |
| str, |
| [ |
| # Creates a proxy metadata server inside the sandbox. |
| self._socat, |
| # Forwards auth to the IPC socket mounted inside the sandbox. |
| self._auth_wrapper, |
| ], |
| ) |
| |
| def rw_mounts(self): |
| """Returns the read-write mounts needed for sandboxed builds. |
| |
| Returns: |
| An iterable of paths corresponding to the read-write mounts.""" |
| return map( |
| str, |
| [ |
| # Used by buildproxywrap to share IPC sockets with the sandbox. |
| self._socket_dir, |
| *self.env().values(), |
| ], |
| ) |
| |
| |
| class BuildProxyWrapApi(recipe_api.RecipeApi): |
| """BuildProxyWrapApi allows callers to run steps with buildproxywrap.""" |
| |
| def new_instance(self) -> BuildProxyWrapInstance: |
| """Sets up state necessary to run buildproxywrap with sandboxed builds. |
| |
| Returns: |
| A BuildProxyWrapInstance that can be queried for the set state. |
| """ |
| bpw_cfg = self._render_config( |
| self.resource("config.json"), |
| ) |
| # Fetch and provision buildproxywrap from CIPD. |
| bpw = self.m.cipd_ensure( |
| self.resource("buildproxywrap/cipd.ensure"), |
| "fuchsia/infra/buildproxywrap/${platform}", |
| ) |
| # Fetch and provision socat from CIPD. |
| socat = self.m.cipd_ensure( |
| self.resource("socat/cipd.ensure"), |
| "fuchsia/third_party/socat/${platform}", |
| executable_path="bin/socat", |
| ) |
| auth_wrapper = self.resource("auth_wrapper.sh") |
| return BuildProxyWrapInstance(self.m, bpw_cfg, bpw, socat, auth_wrapper) |
| |
| def _render_config(self, cfg): |
| """Substitutes placeholders in buildproxywrap configuration. |
| |
| Args: |
| cfg: Path to buildproxywrap configuration with placeholders. |
| |
| Returns: |
| Buildproxywrap configuration with placeholders substituted. |
| """ |
| result = self.m.python3( |
| "render buildproxywrap config", |
| args=[ |
| self.resource("render_config.py"), |
| "--input", |
| cfg, |
| "--output", |
| self.m.json.output(), |
| ], |
| infra_step=True, |
| step_test_data=lambda: self.m.json.test_api.output( |
| [ |
| { |
| "name": "foo", |
| "socket_file_name": "foo.sock", |
| "socket_path_env_var": "FOO", |
| "server_address": "foobar.com", |
| }, |
| ] |
| ), |
| ) |
| return result.json.output |