Add handling for build_target in BundleChromeOSConfig.

- Needed so this endpoint can handle current Recipes
calls that set build_target.

- Add test to api/controller/artifacts_unittest.py.

- Also add test in service/artifacts_unittest.py for
case when no config payload is found.

- Add call template for BundleChromeOSConfig.

TEST=./run_tests
BUG=chromium:987401

Change-Id: I9b7da6e5f2de71a3ada8ca36f66e594c54dc2175
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1756263
Tested-by: Andrew Lamb <andrewlamb@chromium.org>
Reviewed-by: David Burger <dburger@chromium.org>
Commit-Queue: Andrew Lamb <andrewlamb@chromium.org>
diff --git a/api/contrib/call_templates/artifacts__bundle_chrome_os_config_input.json b/api/contrib/call_templates/artifacts__bundle_chrome_os_config_input.json
new file mode 100644
index 0000000..f15ee73
--- /dev/null
+++ b/api/contrib/call_templates/artifacts__bundle_chrome_os_config_input.json
@@ -0,0 +1 @@
+{"output_dir": "/tmp", "build_target": {"name": "reef"}}
diff --git a/api/controller/artifacts.py b/api/controller/artifacts.py
index 09799d5..07f0ed4 100644
--- a/api/controller/artifacts.py
+++ b/api/controller/artifacts.py
@@ -322,9 +322,18 @@
     _config (api_config.ApiConfig): The API call config.
   """
   output_dir = input_proto.output_dir
-  sysroot = sysroot_lib.Sysroot(input_proto.sysroot.path)
+  sysroot_path = input_proto.sysroot.path
   chroot = controller_util.ParseChroot(input_proto.chroot)
 
+  # TODO(mmortensen) Cleanup legacy handling after it has been switched over.
+  target = input_proto.build_target.name
+  if target:
+    # Legacy handling.
+    build_root = constants.SOURCE_ROOT
+    chroot = chroot_lib.Chroot(path=os.path.join(build_root, 'chroot'))
+    sysroot_path = os.path.join('/build', target)
+
+  sysroot = sysroot_lib.Sysroot(sysroot_path)
   chromeos_config = artifacts.BundleChromeOSConfig(chroot, sysroot, output_dir)
   if chromeos_config is None:
     cros_build_lib.Die(
diff --git a/api/controller/artifacts_unittest.py b/api/controller/artifacts_unittest.py
index 10995e2..43b28c2 100644
--- a/api/controller/artifacts_unittest.py
+++ b/api/controller/artifacts_unittest.py
@@ -407,6 +407,54 @@
       artifacts.BundleEbuildLogs(self.request, self.response, self.api_config)
 
 
+class BundleChromeOSConfigTest(BundleTestCase):
+  """Unittests for BundleChromeOSConfig"""
+
+  def testValidateOnly(self):
+    """Sanity check that a validate only call does not execute any logic."""
+    patch = self.PatchObject(artifacts_svc, 'BundleChromeOSConfig')
+    artifacts.BundleChromeOSConfig(self.input_proto, self.output_proto,
+                                   self.validate_only_config)
+    patch.assert_not_called()
+
+  def testBundleChromeOSConfigCallWithSysroot(self):
+    """Call with a request that sets sysroot."""
+    bundle_chromeos_config = self.PatchObject(
+        artifacts_svc, 'BundleChromeOSConfig', return_value='config.yaml')
+    artifacts.BundleChromeOSConfig(self.request, self.output_proto,
+                                   self.api_config)
+    self.assertEqual(
+        [artifact.path for artifact in self.output_proto.artifacts],
+        [os.path.join(self.output_dir, 'config.yaml')])
+
+    sysroot = sysroot_lib.Sysroot(self.sysroot_path)
+    self.assertEqual(bundle_chromeos_config.call_args_list,
+                     [mock.call(mock.ANY, sysroot, self.output_dir)])
+
+  def testBundleChromeOSConfigCallWithBuildTarget(self):
+    """Call with a request that sets build_target."""
+    bundle_chromeos_config = self.PatchObject(
+        artifacts_svc, 'BundleChromeOSConfig', return_value='config.yaml')
+    artifacts.BundleChromeOSConfig(self.input_proto, self.output_proto,
+                                   self.api_config)
+
+    self.assertEqual(
+        [artifact.path for artifact in self.output_proto.artifacts],
+        [os.path.join(self.output_dir, 'config.yaml')])
+
+    sysroot = sysroot_lib.Sysroot(self.sysroot_path)
+    self.assertEqual(bundle_chromeos_config.call_args_list,
+                     [mock.call(mock.ANY, sysroot, self.output_dir)])
+
+  def testBundleChromeOSConfigNoConfigFound(self):
+    """An error is raised if the config payload isn't found."""
+    self.PatchObject(artifacts_svc, 'BundleChromeOSConfig', return_value=None)
+
+    with self.assertRaises(cros_build_lib.DieSystemExit):
+      artifacts.BundleChromeOSConfig(self.request, self.output_proto,
+                                     self.api_config)
+
+
 class BundleTestUpdatePayloadsTest(cros_test_lib.MockTempDirTestCase,
                                    api_config.ApiConfigMixin):
   """Unittests for BundleTestUpdatePayloads."""
diff --git a/service/artifacts_unittest.py b/service/artifacts_unittest.py
index 31856f0..1636418 100644
--- a/service/artifacts_unittest.py
+++ b/service/artifacts_unittest.py
@@ -252,17 +252,21 @@
 class BundleChromeOSConfigTest(cros_test_lib.TempDirTestCase):
   """BundleChromeOSConfig tests."""
 
-  def testBundleChromeOSConfig(self):
-    """Verifies that the correct ChromeOS config file is bundled."""
-    board = 'samus'
+  def setUp(self):
+    self.board = 'samus'
+
     # Create chroot object and sysroot object
     chroot_path = os.path.join(self.tempdir, 'chroot')
-    chroot = chroot_lib.Chroot(path=chroot_path)
-    sysroot_path = os.path.join('build', board)
-    sysroot = sysroot_lib.Sysroot(sysroot_path)
+    self.chroot = chroot_lib.Chroot(path=chroot_path)
+    sysroot_path = os.path.join('build', self.board)
+    self.sysroot = sysroot_lib.Sysroot(sysroot_path)
 
+    self.archive_dir = self.tempdir
+
+  def testBundleChromeOSConfig(self):
+    """Verifies that the correct ChromeOS config file is bundled."""
     # Create parent dir for ChromeOS Config output.
-    config_parent_dir = os.path.join(chroot.path, 'build')
+    config_parent_dir = os.path.join(self.chroot.path, 'build')
 
     # Names of ChromeOS Config files typically found in a build directory.
     config_files = ('config.json',
@@ -270,8 +274,8 @@
                         'config.c', 'config.yaml', 'ec_config.c', 'ec_config.h',
                         'model.yaml', 'private-model.yaml'
                     ]))
-    config_files_root = os.path.join(config_parent_dir,
-                                     '%s/usr/share/chromeos-config' % board)
+    config_files_root = os.path.join(
+        config_parent_dir, '%s/usr/share/chromeos-config' % self.board)
     # Generate a representative set of config files produced by a typical build.
     cros_test_lib.CreateOnDiskHierarchy(config_files_root, config_files)
 
@@ -288,14 +292,19 @@
     with open(os.path.join(config_files_root, 'yaml', 'config.yaml'), 'w') as f:
       json.dump(test_config_payload, f)
 
-    archive_dir = self.tempdir
-    config_filename = artifacts.BundleChromeOSConfig(chroot, sysroot,
-                                                     archive_dir)
+    config_filename = artifacts.BundleChromeOSConfig(self.chroot, self.sysroot,
+                                                     self.archive_dir)
     self.assertEqual('config.yaml', config_filename)
 
-    with open(os.path.join(archive_dir, config_filename), 'r') as f:
+    with open(os.path.join(self.archive_dir, config_filename), 'r') as f:
       self.assertEqual(test_config_payload, json.load(f))
 
+  def testNoChromeOSConfigFound(self):
+    """Verifies that None is returned when no ChromeOS config file is found."""
+    self.assertIsNone(
+        artifacts.BundleChromeOSConfig(self.chroot, self.sysroot,
+                                       self.archive_dir))
+
 
 class BundleVmFilesTest(cros_test_lib.TempDirTestCase):
   """BundleVmFiles tests."""