diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4750583812..291397769f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1171,6 +1171,19 @@ class CreateServer(command.ShowOne): action='store_true', help=_('Wait for build to complete'), ) + parser.add_argument( + '--trusted-image-cert', + metavar='', + action='append', + dest='trusted_image_certs', + help=_( + 'Trusted image certificate IDs used to validate certificates ' + 'during the image signature verification process. ' + 'May be specified multiple times to pass multiple trusted ' + 'image certificate IDs. ' + '(supported by --os-compute-api-version 2.63 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -1640,6 +1653,24 @@ class CreateServer(command.ShowOne): boot_kwargs['hostname'] = parsed_args.hostname + # TODO(stephenfin): Handle OS_TRUSTED_IMAGE_CERTIFICATE_IDS + if parsed_args.trusted_image_certs: + if not (image and not parsed_args.boot_from_volume): + msg = _( + '--trusted-image-cert option is only supported for ' + 'servers booted directly from images' + ) + raise exceptions.CommandError(msg) + if compute_client.api_version < api_versions.APIVersion('2.63'): + msg = _( + '--os-compute-api-version 2.63 or greater is required to ' + 'support the --trusted-image-cert option' + ) + raise exceptions.CommandError(msg) + + certs = parsed_args.trusted_image_certs + boot_kwargs['trusted_image_certificates'] = certs + LOG.debug('boot_args: %s', boot_args) LOG.debug('boot_kwargs: %s', boot_kwargs) @@ -3277,7 +3308,6 @@ class RebuildServer(command.ShowOne): help=_( 'Trusted image certificate IDs used to validate certificates ' 'during the image signature verification process. ' - 'Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. ' 'May be specified multiple times to pass multiple trusted ' 'image certificate IDs. ' 'Cannot be specified with the --no-trusted-certs option. ' diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index cab9efd0c0..13431e005d 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -3624,6 +3624,156 @@ class TestServerCreate(TestServer): exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_create_with_trusted_image_cert(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + admin_pass=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + trusted_image_certificates=['foo', 'bar'], + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_trusted_image_cert_prev263(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.62') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_server_create_with_trusted_image_cert_from_volume(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + arglist = [ + '--volume', 'volume1', + '--flavor', 'flavor1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('volume', 'volume1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_server_create_with_trusted_image_cert_from_snapshot(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + arglist = [ + '--snapshot', 'snapshot1', + '--flavor', 'flavor1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('snapshot', 'snapshot1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_server_create_with_trusted_image_cert_boot_from_volume(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--boot-from-volume', '1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('boot_from_volume', 1), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestServerDelete(TestServer): diff --git a/releasenotes/notes/add-trusted-certs-option-server-create-a660488407300f22.yaml b/releasenotes/notes/add-trusted-certs-option-server-create-a660488407300f22.yaml new file mode 100644 index 0000000000..8814a63ae6 --- /dev/null +++ b/releasenotes/notes/add-trusted-certs-option-server-create-a660488407300f22.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added ``--trusted-image-cert`` option for server create. It is available + only when directly booting server from image (not from volume, not from + snapshot and not via image converted to volume first). + This option is supported for Compute API version >=2.63