blob: 6c5ae9b5271ed4f1e70472c8321f561af19d4c29 [file] [log] [blame]
# 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