Build API: Enter chroot on INSIDE assertion.
BUG=chromium:953049
TEST=run_tests
TEST=manual - called INSIDE endpoint and verified entry
Cq-Depend: chromium:1569042
Change-Id: I78ad1a249d90b5de112ed6da4db319bcf19ff836
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1570352
Tested-by: Alex Klein <saklein@chromium.org>
Reviewed-by: Evan Hernandez <evanhernandez@chromium.org>
Reviewed-by: Sean Abraham <seanabraham@chromium.org>
diff --git a/scripts/build_api.py b/scripts/build_api.py
index 785a25c..99cf69b 100644
--- a/scripts/build_api.py
+++ b/scripts/build_api.py
@@ -9,6 +9,7 @@
import importlib
import os
+import shutil
from google.protobuf import json_format
from google.protobuf import symbol_database
@@ -22,6 +23,8 @@
from chromite.api.gen.chromite.api import sdk_pb2
from chromite.api.gen.chromite.api import sysroot_pb2
from chromite.api.gen.chromite.api import test_pb2
+from chromite.api.gen.chromiumos import common_pb2
+from chromite.lib import constants
from chromite.lib import commandline
from chromite.lib import cros_build_lib
from chromite.lib import osutils
@@ -91,6 +94,8 @@
methods = router.ListMethods()
if opts.service_method not in methods:
+ # Unknown method, try to match against known methods and make a suggestion.
+ # This is just for developer sanity, e.g. misspellings when testing.
matched = matching.GetMostLikelyMatchedObject(methods, opts.service_method,
matched_score_threshold=0.6)
error = 'Unrecognized service name.'
@@ -208,9 +213,12 @@
if method_options.HasField('implementation_name'):
method_name = method_options.implementation_name
- # Check the chroot assertion settings before running.
+ # Check the chroot settings before running.
service_options = svc.GetOptions().Extensions[self._service_options]
- self._HandleChrootAssert(service_options, method_options)
+ if self._ChrootCheck(service_options, method_options):
+ # Run inside the chroot instead.
+ return self._ReexecuteInside(input_msg, output_path, service_name,
+ method_name)
# Import the module and get the method.
method_impl = self._GetMethod(module_name, method_name)
@@ -227,12 +235,18 @@
return return_code
- def _HandleChrootAssert(self, service_options, method_options):
- """Check the chroot assert options and execute assertion as needed.
+ def _ChrootCheck(self, service_options, method_options):
+ """Check the chroot options, and execute assertion or note reexec as needed.
Args:
service_options (google.protobuf.Message): The service options.
method_options (google.protobuf.Message): The method options.
+
+ Returns:
+ bool - True iff it needs to be reexeced inside the chroot.
+
+ Raises:
+ cros_build_lib.DieSystemExit when the chroot setting cannot be satisfied.
"""
chroot_assert = build_api_pb2.NO_ASSERTION
if method_options.HasField('method_chroot_assert'):
@@ -242,12 +256,76 @@
# Fall back to the service option.
chroot_assert = service_options.service_chroot_assert
- # Execute appropriate assertion if set.
if chroot_assert == build_api_pb2.INSIDE:
- cros_build_lib.AssertInsideChroot()
+ return not cros_build_lib.IsInsideChroot()
elif chroot_assert == build_api_pb2.OUTSIDE:
+ # If it must be run outside we have to already be outside.
cros_build_lib.AssertOutsideChroot()
+ return False
+
+ def _ReexecuteInside(self, input_msg, output_path, service_name,
+ method_name):
+ """Re-execute the service inside the chroot.
+
+ Args:
+ input_msg (Message): The parsed input message.
+ output_path (str): The path for the serialized output.
+ service_name (str): The name of the service to run.
+ method_name (str): The name of the method to run.
+ """
+ chroot_args = []
+ chroot_path = constants.DEFAULT_CHROOT_PATH
+ chroot_field_name = None
+ # Find the Chroot field. Search for the field by type to prevent it being
+ # tied to a naming convention.
+ for descriptor in input_msg.DESCRIPTOR.fields:
+ field = getattr(input_msg, descriptor.name)
+ if isinstance(field, common_pb2.Chroot):
+ chroot_field_name = descriptor.name
+ chroot = field
+ chroot_path = chroot.path
+ chroot_args.extend(self._GetChrootArgs(chroot))
+ break
+
+ base_dir = os.path.join(chroot_path, 'tmp')
+ with osutils.TempDir(base_dir=base_dir) as tempdir:
+ new_input = os.path.join(tempdir, 'input.json')
+ chroot_input = '/%s' % os.path.relpath(new_input, chroot_path)
+ new_output = os.path.join(tempdir, 'output.json')
+ chroot_output = '/%s' % os.path.relpath(new_output, chroot_path)
+
+ if chroot_field_name:
+ input_msg.ClearField(chroot_field_name)
+ osutils.WriteFile(new_input, json_format.MessageToJson(input_msg))
+ osutils.Touch(new_output)
+
+ cmd = ['build_api', '%s/%s' % (service_name, method_name),
+ '--input-json', chroot_input, '--output-json', chroot_output]
+ result = cros_build_lib.RunCommand(cmd, enter_chroot=True,
+ chroot_args=chroot_args,
+ error_code_ok=True)
+ shutil.move(new_output, output_path)
+
+ return result.returncode
+
+ def _GetChrootArgs(self, chroot):
+ """Translate a Chroot message to chroot enter args.
+
+ Args:
+ chroot (chromiumos.Chroot): A chroot message.
+
+ Returns:
+ list[str]: The cros_sdk args for the chroot.
+ """
+ args = []
+ if chroot.path:
+ args.extend(['--chroot', chroot.path])
+ if chroot.cache_dir:
+ args.extend(['--cache-dir', chroot.cache_dir])
+
+ return args
+
def _GetMethod(self, module_name, method_name):
"""Get the implementation of the method for the service module.