build api push_image endpoint fix
BUG=chromium:1157040
TEST=run_tests and call_scripts
Change-Id: Ibb500c07c610d8750d0298ef212a60eb29ba72b6
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2582144
Reviewed-by: LaMont Jones <lamontjones@chromium.org>
Reviewed-by: George Engelbrecht <engeg@google.com>
Commit-Queue: Jack Neus <jackneus@google.com>
Tested-by: Jack Neus <jackneus@google.com>
diff --git a/api/contrib/call_templates/image__push_image_example_input.json b/api/contrib/call_templates/image__push_image_example_input.json
new file mode 100644
index 0000000..2d0d4f7
--- /dev/null
+++ b/api/contrib/call_templates/image__push_image_example_input.json
@@ -0,0 +1,14 @@
+{
+ "dryrun": true,
+ "gs_image_dir": "gs://chromeos-image-archive/atlas-release/R89-13604.0.0",
+ "sysroot": {
+ "build_target": {
+ "name": "atlas"
+ }
+ },
+ "profile": {
+ "name": "baz"
+ },
+ "sign_types_description": ["IMAGE_TYPE_BASE", "IMAGE_TYPE_RECOVERY"],
+ "sign_types": [1, 6]
+}
diff --git a/api/controller/image.py b/api/controller/image.py
index bc25c15..b7968e9 100644
--- a/api/controller/image.py
+++ b/api/controller/image.py
@@ -21,10 +21,11 @@
from chromite.lib import cros_build_lib
from chromite.lib import constants
from chromite.lib import image_lib
+from chromite.lib import cros_logging as logging
+from chromite.scripts import pushimage
from chromite.service import image
from chromite.utils import metrics
-
# The image.proto ImageType enum ids.
_BASE_ID = common_pb2.BASE
_DEV_ID = common_pb2.DEV
@@ -60,6 +61,17 @@
_TEST_GUEST_VM_ID: _IMAGE_MAPPING[_TEST_ID],
}
+# Supported image types for PushImage.
+SUPPORTED_IMAGE_TYPES = {
+ common_pb2.IMAGE_TYPE_RECOVERY: constants.IMAGE_TYPE_RECOVERY,
+ common_pb2.IMAGE_TYPE_FACTORY: constants.IMAGE_TYPE_FACTORY,
+ common_pb2.IMAGE_TYPE_FIRMWARE: constants.IMAGE_TYPE_FIRMWARE,
+ common_pb2.IMAGE_TYPE_ACCESSORY_USBPD: constants.IMAGE_TYPE_ACCESSORY_USBPD,
+ common_pb2.IMAGE_TYPE_ACCESSORY_RWSIG: constants.IMAGE_TYPE_ACCESSORY_RWSIG,
+ common_pb2.IMAGE_TYPE_BASE: constants.IMAGE_TYPE_BASE,
+ common_pb2.IMAGE_TYPE_GSC_FIRMWARE: constants.IMAGE_TYPE_GSC_FIRMWARE
+}
+
def _CreateResponse(_input_proto, output_proto, _config):
"""Set output_proto success field on a successful Create response."""
@@ -88,8 +100,8 @@
build_config = _ParseCreateBuildConfig(input_proto)
# Sorted isn't really necessary here, but it's much easier to test.
- result = image.Build(board=board, images=sorted(list(image_types)),
- config=build_config)
+ result = image.Build(
+ board=board, images=sorted(list(image_types)), config=build_config)
output_proto.success = result.success
@@ -171,8 +183,11 @@
disk_layout = input_proto.disk_layout or None
builder_path = input_proto.builder_path or None
return image.BuildConfig(
- enable_rootfs_verification=enable_rootfs_verification, replace=True,
- version=version, disk_layout=disk_layout, builder_path=builder_path,
+ enable_rootfs_verification=enable_rootfs_verification,
+ replace=True,
+ version=version,
+ disk_layout=disk_layout,
+ builder_path=builder_path,
)
@@ -258,3 +273,43 @@
return controller.RETURN_CODE_SUCCESS
else:
return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
+
+
+@faux.empty_success
+@faux.empty_completed_unsuccessfully_error
+@validate.require('gs_image_dir', 'sysroot.build_target.name')
+def PushImage(input_proto, _output_proto, config):
+ """Push artifacts from the archive bucket to the release bucket.
+
+ Wraps chromite/scripts/pushimage.py.
+
+ Args:
+ input_proto (PushImageRequest): Input proto.
+ _output_proto (PushImageResponse): Output proto.
+ config (api.config.ApiConfig): The API call config.
+
+ Returns:
+ A controller return code (e.g. controller.RETURN_CODE_SUCCESS).
+ """
+ sign_types = []
+ if input_proto.sign_types:
+ for sign_type in input_proto.sign_types:
+ if sign_type not in SUPPORTED_IMAGE_TYPES:
+ logging.error('unsupported sign type %g', sign_type)
+ return controller.RETURN_CODE_INVALID_INPUT
+ sign_types.append(SUPPORTED_IMAGE_TYPES[sign_type])
+
+ # If configured for validation only we're done here.
+ if config.validate_only:
+ return controller.RETURN_CODE_VALID_INPUT
+
+ try:
+ pushimage.PushImage(
+ input_proto.gs_image_dir,
+ input_proto.sysroot.build_target.name,
+ dry_run=input_proto.dryrun,
+ profile=input_proto.profile.name,
+ sign_types=sign_types)
+ return controller.RETURN_CODE_SUCCESS
+ except Exception:
+ return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
diff --git a/api/controller/image_unittest.py b/api/controller/image_unittest.py
index 56de3c3..c647e11 100644
--- a/api/controller/image_unittest.py
+++ b/api/controller/image_unittest.py
@@ -16,11 +16,13 @@
from chromite.api.controller import image as image_controller
from chromite.api.gen.chromite.api import image_pb2
from chromite.api.gen.chromiumos import common_pb2
+from chromite.api.gen.chromite.api import sysroot_pb2
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import image_lib
from chromite.lib import osutils
+from chromite.scripts import pushimage
from chromite.service import image as image_service
@@ -30,7 +32,11 @@
def setUp(self):
self.response = image_pb2.CreateImageResult()
- def _GetRequest(self, board=None, types=None, version=None, builder_path=None,
+ def _GetRequest(self,
+ board=None,
+ types=None,
+ version=None,
+ builder_path=None,
disable_rootfs_verification=False):
"""Helper to build a request instance."""
return image_pb2.CreateImageRequest(
@@ -84,8 +90,8 @@
build_patch = self.PatchObject(image_service, 'Build', return_value=result)
image_controller.Create(request, self.response, self.api_config)
- build_patch.assert_called_with(images=[constants.IMAGE_TYPE_BASE],
- board='board', config=mock.ANY)
+ build_patch.assert_called_with(
+ images=[constants.IMAGE_TYPE_BASE], board='board', config=mock.ANY)
def testSingleTypeSpecified(self):
"""Test it's properly using a specified type."""
@@ -96,8 +102,8 @@
build_patch = self.PatchObject(image_service, 'Build', return_value=result)
image_controller.Create(request, self.response, self.api_config)
- build_patch.assert_called_with(images=[constants.IMAGE_TYPE_DEV],
- board='board', config=mock.ANY)
+ build_patch.assert_called_with(
+ images=[constants.IMAGE_TYPE_DEV], board='board', config=mock.ANY)
def testMultipleAndImpliedTypes(self):
"""Test multiple types and implied type handling."""
@@ -112,8 +118,8 @@
build_patch = self.PatchObject(image_service, 'Build', return_value=result)
image_controller.Create(request, self.response, self.api_config)
- build_patch.assert_called_with(images=expected_images, board='board',
- config=mock.ANY)
+ build_patch.assert_called_with(
+ images=expected_images, board='board', config=mock.ANY)
def testFailedPackageHandling(self):
"""Test failed packages are populated correctly."""
@@ -320,3 +326,111 @@
self.PatchObject(image_service, 'Test', return_value=False)
image_controller.Test(input_proto, output_proto, self.api_config)
self.assertFalse(output_proto.success)
+
+
+class PushImageTest(cros_test_lib.MockTestCase, api_config.ApiConfigMixin):
+ """Push image test."""
+
+ def setUp(self):
+ self.response = image_pb2.PushImageResponse()
+
+ def _GetRequest(
+ self,
+ gs_image_dir='gs://chromeos-image-archive/atlas-release/R89-13604.0.0',
+ build_target_name='atlas',
+ profile='foo',
+ sign_types=None,
+ dryrun=True):
+ return image_pb2.PushImageRequest(
+ gs_image_dir=gs_image_dir,
+ sysroot=sysroot_pb2.Sysroot(
+ build_target=common_pb2.BuildTarget(name=build_target_name)),
+ profile=common_pb2.Profile(name=profile),
+ sign_types=sign_types,
+ dryrun=dryrun)
+
+ def testValidateOnly(self):
+ """Check that a validate only call does not execute any logic."""
+ patch = self.PatchObject(pushimage, 'PushImage')
+
+ req = self._GetRequest(sign_types=[
+ common_pb2.IMAGE_TYPE_RECOVERY, common_pb2.IMAGE_TYPE_FACTORY,
+ common_pb2.IMAGE_TYPE_FIRMWARE, common_pb2.IMAGE_TYPE_ACCESSORY_USBPD,
+ common_pb2.IMAGE_TYPE_ACCESSORY_RWSIG, common_pb2.IMAGE_TYPE_BASE,
+ common_pb2.IMAGE_TYPE_GSC_FIRMWARE
+ ])
+ res = image_controller.PushImage(req, self.response,
+ self.validate_only_config)
+ patch.assert_not_called()
+ self.assertEqual(res, controller.RETURN_CODE_VALID_INPUT)
+
+ def testValidateOnlyInvalid(self):
+ """Check that validate call rejects invalid sign types."""
+ patch = self.PatchObject(pushimage, 'PushImage')
+
+ # Pass unsupported image type.
+ req = self._GetRequest(sign_types=[common_pb2.IMAGE_TYPE_DLC])
+ res = image_controller.PushImage(req, self.response,
+ self.validate_only_config)
+ patch.assert_not_called()
+ self.assertEqual(res, controller.RETURN_CODE_INVALID_INPUT)
+
+ def testMockCall(self):
+ """Test that mock call does not execute any logic, returns mocked value."""
+ patch = self.PatchObject(pushimage, 'PushImage')
+
+ rc = image_controller.PushImage(self._GetRequest(), self.response,
+ self.mock_call_config)
+ patch.assert_not_called()
+ self.assertEqual(controller.RETURN_CODE_SUCCESS, rc)
+
+ def testMockError(self):
+ """Test that mock call does not execute any logic, returns error."""
+ patch = self.PatchObject(pushimage, 'PushImage')
+
+ rc = image_controller.PushImage(self._GetRequest(), self.response,
+ self.mock_error_config)
+ patch.assert_not_called()
+ self.assertEqual(controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY, rc)
+
+ def testNoBuildTarget(self):
+ """Test no build target given fails."""
+ request = self._GetRequest(build_target_name='')
+
+ # No build target should cause it to fail.
+ with self.assertRaises(cros_build_lib.DieSystemExit):
+ image_controller.PushImage(request, self.response, self.api_config)
+
+ def testNoGsImageDir(self):
+ """Test no image dir given fails."""
+ request = self._GetRequest(gs_image_dir='')
+
+ # No image dir should cause it to fail.
+ with self.assertRaises(cros_build_lib.DieSystemExit):
+ image_controller.PushImage(request, self.response, self.api_config)
+
+ def testCallCorrect(self):
+ """Check that a call is called with the correct parameters."""
+ patch = self.PatchObject(pushimage, 'PushImage')
+
+ request = self._GetRequest(
+ dryrun=False, profile='', sign_types=[common_pb2.IMAGE_TYPE_RECOVERY])
+ image_controller.PushImage(request, self.response, self.api_config)
+ patch.assert_called_with(
+ request.gs_image_dir,
+ request.sysroot.build_target.name,
+ dry_run=request.dryrun,
+ profile=request.profile.name,
+ sign_types=['recovery'])
+
+ def testCallSucceeds(self):
+ """Check that a (dry run) call is made successfully."""
+ request = self._GetRequest(sign_types=[common_pb2.IMAGE_TYPE_RECOVERY])
+ res = image_controller.PushImage(request, self.response, self.api_config)
+ self.assertEqual(res, controller.RETURN_CODE_SUCCESS)
+
+ def testCallFailsWithBadImageDir(self):
+ """Check that a (dry run) call fails when given a bad gs_image_dir."""
+ request = self._GetRequest(gs_image_dir='foo')
+ res = image_controller.PushImage(request, self.response, self.api_config)
+ self.assertEqual(res, controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY)