diff --git a/doc/source/cli/nova.rst b/doc/source/cli/nova.rst index e53f4c1a6..35b950b0c 100644 --- a/doc/source/cli/nova.rst +++ b/doc/source/cli/nova.rst @@ -1011,6 +1011,7 @@ nova boot [--config-drive ] [--poll] [--admin-pass ] [--access-ip-v4 ] [--access-ip-v6 ] [--description ] + [--trusted-image-certificate-id] Boot a new server. @@ -1164,6 +1165,13 @@ Boot a new server. Description for the server. (Supported by API versions '2.19' - '2.latest') +``--trusted-image-certificate-id `` + 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. (Supported by API versions '2.63' - '2.latest') + .. _nova_cell-capacities: nova cell-capacities @@ -2660,6 +2668,8 @@ nova rebuild [--minimal] [--preserve-ephemeral] [--name ] [--description ] [--meta ] [--file ] + [--trusted-image-certificate-id ] + [--trusted-image-certificates-unset] Shutdown, re-image, and re-boot a server. @@ -2707,6 +2717,18 @@ Shutdown, re-image, and re-boot a server. to on the new server. You may store up to 5 files. +``--trusted-image-certificate-id `` + 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. (Supported by API versions '2.63' - '2.latest') + +``--trusted-image-certificates-unset`` + Unset trusted_image_certificates in the server. Cannot be + specified with the ``--trusted-image-certificate-id`` option. + (Supported by API versions '2.63' - '2.latest') + .. _nova_refresh-network: nova refresh-network diff --git a/doc/source/user/shell.rst b/doc/source/user/shell.rst index bd1fb7e93..882bb7560 100644 --- a/doc/source/user/shell.rst +++ b/doc/source/user/shell.rst @@ -60,6 +60,16 @@ some environment variables: The Keystone region name. Defaults to the first region if multiple regions are available. +.. envvar:: OS_TRUSTED_IMAGE_CERTIFICATE_IDS + + A comma-delimited list of trusted image certificate IDs. Only used + with the ``nova boot`` and ``nova rebuild`` commands starting with the + 2.63 microversion. + + For example:: + + export OS_TRUSTED_IMAGE_CERTIFICATE_IDS=trusted-cert-id1,trusted-cert-id2 + For example, in Bash you'd use:: export OS_USERNAME=yourname diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 1530b37a7..3bcad2062 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.62") +API_MAX_VERSION = api_versions.APIVersion("2.63") diff --git a/novaclient/tests/unit/v2/test_servers.py b/novaclient/tests/unit/v2/test_servers.py index 07ad1497e..540968cf5 100644 --- a/novaclient/tests/unit/v2/test_servers.py +++ b/novaclient/tests/unit/v2/test_servers.py @@ -1542,3 +1542,70 @@ class ServersV257Test(ServersV256Test): exceptions.UnsupportedAttribute, s.rebuild, image=1, name='new', meta={'foo': 'bar'}, files=files) self.assertIn('files', six.text_type(ex)) + + +class ServersV263Test(ServersV257Test): + + api_version = "2.63" + + def test_create_server_with_trusted_image_certificates(self): + self.cs.servers.create( + name="My server", + image=1, + flavor=1, + meta={'foo': 'bar'}, + userdata="hello moto", + key_name="fakekey", + nics=self._get_server_create_default_nics(), + trusted_image_certificates=['id1', 'id2'], + ) + self.assert_called('POST', '/servers', + {'server': { + 'flavorRef': '1', + 'imageRef': '1', + 'key_name': 'fakekey', + 'max_count': 1, + 'metadata': {'foo': 'bar'}, + 'min_count': 1, + 'name': 'My server', + 'networks': 'auto', + 'trusted_image_certificates': ['id1', 'id2'], + 'user_data': 'aGVsbG8gbW90bw==' + }} + ) + + def test_create_server_with_trusted_image_certificates_pre_263_fails(self): + self.cs.api_version = api_versions.APIVersion('2.62') + ex = self.assertRaises( + exceptions.UnsupportedAttribute, self.cs.servers.create, + name="My server", image=1, flavor=1, meta={'foo': 'bar'}, + userdata="hello moto", key_name="fakekey", + nics=self._get_server_create_default_nics(), + trusted_image_certificates=['id1', 'id2']) + self.assertIn('trusted_image_certificates', six.text_type(ex)) + + def test_rebuild_server_with_trusted_image_certificates(self): + s = self.cs.servers.get(1234) + ret = s.rebuild(image="1", trusted_image_certificates=['id1', 'id2']) + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': { + 'imageRef': '1', + 'trusted_image_certificates': ['id1', 'id2']}}) + + def test_rebuild_server_with_trusted_image_certificates_none(self): + s = self.cs.servers.get(1234) + ret = s.rebuild(image="1", trusted_image_certificates=None) + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': { + 'imageRef': '1', + 'trusted_image_certificates': None}}) + + def test_rebuild_with_trusted_image_certificates_pre_263_fails(self): + self.cs.api_version = api_versions.APIVersion('2.62') + ex = self.assertRaises(exceptions.UnsupportedAttribute, + self.cs.servers.rebuild, + '1234', fakes.FAKE_IMAGE_UUID_1, + trusted_image_certificates=['id1', 'id2']) + self.assertIn('trusted_image_certificates', six.text_type(ex)) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index b4ee79acd..a32d623b8 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -1155,6 +1155,113 @@ class ShellTest(utils.TestCase): self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.51') + def test_boot_with_single_trusted_image_certificates(self): + self.run_command('boot --flavor 1 --image %s --nic auto some-server ' + '--trusted-image-certificate-id id1' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'trusted_image_certificates': ['id1'] + }}, + ) + + def test_boot_with_multiple_trusted_image_certificates(self): + self.run_command('boot --flavor 1 --image %s --nic auto some-server ' + '--trusted-image-certificate-id id1 ' + '--trusted-image-certificate-id id2' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'trusted_image_certificates': ['id1', 'id2'] + }}, + ) + + def test_boot_with_trusted_image_certificates_envar(self): + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) + self.run_command('boot --flavor 1 --image %s --nic auto some-server' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'trusted_image_certificates': ['var_id1', 'var_id2'] + }}, + ) + + def test_boot_without_trusted_image_certificates_v263(self): + self.run_command('boot --flavor 1 --image %s --nic auto some-server' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + }}, + ) + + def test_boot_with_trusted_image_certificates_pre_v263(self): + cmd = ('boot --flavor 1 --image %s some-server ' + '--trusted-image-certificate-id id1 ' + '--trusted-image-certificate-id id2' % FAKE_UUID_1) + self.assertRaises(SystemExit, self.run_command, + cmd, api_version='2.62') + + # OS_TRUSTED_IMAGE_CERTIFICATE_IDS environment variable is not supported in + # microversions < 2.63 (should result in an UnsupportedAttribute exception) + def test_boot_with_trusted_image_certificates_envar_pre_v263(self): + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) + cmd = ('boot --flavor 1 --image %s --nic auto some-server ' + % FAKE_UUID_1) + self.assertRaises(exceptions.UnsupportedAttribute, self.run_command, + cmd, api_version='2.62') + + def test_boot_with_trusted_image_certificates_arg_and_envvar(self): + """Tests that if both the environment variable and argument are + specified, the argument takes precedence. + """ + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'cert1')) + self.run_command('boot --flavor 1 --image %s --nic auto ' + '--trusted-image-certificate-id cert2 some-server' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'trusted_image_certificates': ['cert2'] + }}, + ) + def test_flavor_list(self): out, _ = self.run_command('flavor-list') self.assert_called_anytime('GET', '/flavors/detail') @@ -1664,6 +1771,148 @@ class ShellTest(utils.TestCase): self.assertIn("Cannot specify '--user-data-unset' with " "'--user-data'.", six.text_type(ex)) + def test_rebuild_with_single_trusted_image_certificates(self): + self.run_command('rebuild sample-server %s ' + '--trusted-image-certificate-id id1' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': ['id1'] + } + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_with_multiple_trusted_image_certificate_ids(self): + self.run_command('rebuild sample-server %s ' + '--trusted-image-certificate-id id1 ' + '--trusted-image-certificate-id id2' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': ['id1', + 'id2'] + } + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_with_trusted_image_certificates_envar(self): + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) + self.run_command('rebuild sample-server %s' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': + ['var_id1', 'var_id2']} + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_without_trusted_image_certificates_v263(self): + self.run_command('rebuild sample-server %s' % FAKE_UUID_1, + api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + } + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_with_trusted_image_certificates_pre_v263(self): + cmd = ('rebuild sample-server %s' + '--trusted-image-certificate-id id1 ' + '--trusted-image-certificate-id id2' % FAKE_UUID_1) + self.assertRaises(SystemExit, self.run_command, + cmd, api_version='2.62') + + # OS_TRUSTED_IMAGE_CERTIFICATE_IDS environment variable is not supported in + # microversions < 2.63 (should result in an UnsupportedAttribute exception) + def test_rebuild_with_trusted_image_certificates_envar_pre_v263(self): + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) + cmd = ('rebuild sample-server %s' % FAKE_UUID_1) + self.assertRaises(exceptions.UnsupportedAttribute, self.run_command, + cmd, api_version='2.62') + + def test_rebuild_with_trusted_image_certificates_unset(self): + """Tests explicitly unsetting the existing server trusted image + certificate IDs. + """ + self.run_command('rebuild sample-server %s ' + '--trusted-image-certificates-unset' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': None + } + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_with_trusted_image_certificates_unset_arg_conflict(self): + """Tests the error condition that trusted image certs are both unset + and set via argument during rebuild. + """ + ex = self.assertRaises( + exceptions.CommandError, self.run_command, + 'rebuild sample-server %s --trusted-image-certificate-id id1 ' + '--trusted-image-certificates-unset' % FAKE_UUID_1, + api_version='2.63') + self.assertIn("Cannot specify '--trusted-image-certificates-unset' " + "with '--trusted-image-certificate-id'", + six.text_type(ex)) + + def test_rebuild_with_trusted_image_certificates_unset_env_conflict(self): + """Tests the error condition that trusted image certs are both unset + and set via environment variable during rebuild. + """ + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1')) + ex = self.assertRaises( + exceptions.CommandError, self.run_command, + 'rebuild sample-server %s --trusted-image-certificates-unset' % + FAKE_UUID_1, api_version='2.63') + self.assertIn("Cannot specify '--trusted-image-certificates-unset' " + "with '--trusted-image-certificate-id'", + six.text_type(ex)) + + def test_rebuild_with_trusted_image_certificates_arg_and_envar(self): + """Tests that if both the environment variable and argument are + specified, the argument takes precedence. + """ + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'cert1')) + self.run_command('rebuild sample-server ' + '--trusted-image-certificate-id cert2 %s' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': + ['cert2']} + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + def test_start(self): self.run_command('start sample-server') self.assert_called('POST', '/servers/1234/action', {'os-start': None}) @@ -3547,6 +3796,7 @@ class ShellTest(utils.TestCase): 60, # There are no client-side changes for volume multiattach. 61, # There are no version-wrapped shell method changes for this. 62, # There are no version-wrapped shell method changes for this. + 63, # There are no version-wrapped shell method changes for this. ]) versions_supported = set(range(0, novaclient.API_MAX_VERSION.ver_minor + 1)) diff --git a/novaclient/v2/servers.py b/novaclient/v2/servers.py index a7b3eda16..d872fa016 100644 --- a/novaclient/v2/servers.py +++ b/novaclient/v2/servers.py @@ -650,7 +650,7 @@ class ServerManager(base.BootingManagerWithFind): block_device_mapping_v2=None, nics=None, scheduler_hints=None, config_drive=None, admin_pass=None, disk_config=None, access_ip_v4=None, access_ip_v6=None, description=None, - tags=None, **kwargs): + tags=None, trusted_image_certificates=None, **kwargs): """ Create (boot) a new server. """ @@ -768,6 +768,10 @@ class ServerManager(base.BootingManagerWithFind): if tags: body['server']['tags'] = tags + if trusted_image_certificates: + body['server']['trusted_image_certificates'] = ( + trusted_image_certificates) + return self._create('/servers', body, response_key, return_raw=return_raw, **kwargs) @@ -1191,7 +1195,8 @@ class ServerManager(base.BootingManagerWithFind): block_device_mapping=None, block_device_mapping_v2=None, nics=None, scheduler_hints=None, config_drive=None, disk_config=None, admin_pass=None, - access_ip_v4=None, access_ip_v6=None, **kwargs): + access_ip_v4=None, access_ip_v6=None, + trusted_image_certificates=None, **kwargs): # TODO(anthony): indicate in doc string if param is an extension # and/or optional """ @@ -1252,6 +1257,8 @@ class ServerManager(base.BootingManagerWithFind): microversion 2.19) :param tags: A list of arbitrary strings to be added to the server as tags (allowed since microversion 2.52) + :param trusted_image_certificates: A list of trusted certificate IDs + (allowed since microversion 2.63) """ if not min_count: min_count = 1 @@ -1292,6 +1299,12 @@ class ServerManager(base.BootingManagerWithFind): if files and self.api_version >= personality_files_deprecation: raise exceptions.UnsupportedAttribute('files', '2.0', '2.56') + trusted_certs_microversion = api_versions.APIVersion("2.63") + if (trusted_image_certificates and + self.api_version < trusted_certs_microversion): + raise exceptions.UnsupportedAttribute("trusted_image_certificates", + "2.63") + boot_kwargs = dict( meta=meta, files=files, userdata=userdata, reservation_id=reservation_id, min_count=min_count, @@ -1299,7 +1312,8 @@ class ServerManager(base.BootingManagerWithFind): key_name=key_name, availability_zone=availability_zone, scheduler_hints=scheduler_hints, config_drive=config_drive, disk_config=disk_config, admin_pass=admin_pass, - access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6, **kwargs) + access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6, + trusted_image_certificates=trusted_image_certificates, **kwargs) if block_device_mapping: boot_kwargs['block_device_mapping'] = block_device_mapping @@ -1416,6 +1430,9 @@ class ServerManager(base.BootingManagerWithFind): well or a string. If None is specified, the existing user_data is unset. (starting from microversion 2.57) + :param trusted_image_certificates: A list of trusted certificate IDs + or None to unset/reset the servers trusted image + certificates (allowed since microversion 2.63) :returns: :class:`Server` """ descr_microversion = api_versions.APIVersion("2.19") @@ -1436,6 +1453,15 @@ class ServerManager(base.BootingManagerWithFind): if 'userdata' in kwargs and self.api_version < files_and_userdata: raise exceptions.UnsupportedAttribute('userdata', '2.57') + trusted_certs_microversion = api_versions.APIVersion("2.63") + # trusted_image_certificates is intentionally *not* a named kwarg + # so that trusted_image_certificates=None is not confused with an + # intentional unset/reset request. + if ("trusted_image_certificates" in kwargs and + self.api_version < trusted_certs_microversion): + raise exceptions.UnsupportedAttribute("trusted_image_certificates", + "2.63") + body = {'imageRef': base.getid(image)} if password is not None: body['adminPass'] = password @@ -1449,6 +1475,9 @@ class ServerManager(base.BootingManagerWithFind): body["description"] = kwargs["description"] if 'key_name' in kwargs: body['key_name'] = kwargs['key_name'] + if "trusted_image_certificates" in kwargs: + body["trusted_image_certificates"] = kwargs[ + "trusted_image_certificates"] if meta: body['metadata'] = meta if files: diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 160418cc5..c1c044356 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -510,6 +510,14 @@ def _boot(cs, args): if include_files: boot_kwargs['files'] = files + if ('trusted_image_certificates' in args and + args.trusted_image_certificates): + boot_kwargs['trusted_image_certificates'] = ( + args.trusted_image_certificates) + elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): + boot_kwargs["trusted_image_certificates"] = utils.env( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS').split(',') + return boot_args, boot_kwargs @@ -874,6 +882,18 @@ def _boot(cs, args): action="store_true", default=False, help=_("Return a reservation id bound to created servers.")) +@utils.arg( + '--trusted-image-certificate-id', + metavar='', + action='append', + dest='trusted_image_certificates', + default=[], + 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.'), + start_version="2.63") def do_boot(cs, args): """Boot a new server.""" boot_args, boot_kwargs = _boot(cs, args) @@ -1807,6 +1827,25 @@ def do_reboot(cs, args): help=_("Unset user_data in the server. Cannot be specified with the " "'--user-data' option."), start_version='2.57') +@utils.arg( + '--trusted-image-certificate-id', + metavar='', + action='append', + dest='trusted_image_certificates', + default=[], + 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.'), + start_version="2.63") +@utils.arg( + '--trusted-image-certificates-unset', + action='store_true', + default=False, + help=_("Unset trusted_image_certificates in the server. Cannot be " + "specified with the '--trusted-image-certificate-id' option."), + start_version="2.63") def do_rebuild(cs, args): """Shutdown, re-image, and re-boot a server.""" server = _find_server(cs, args.server) @@ -1861,6 +1900,33 @@ def do_rebuild(cs, args): elif args.key_name: kwargs['key_name'] = args.key_name + if cs.api_version >= api_versions.APIVersion('2.63'): + # First determine if the user specified anything via the command line + # or the environment variable. + trusted_image_certificates = None + if ('trusted_image_certificates' in args and + args.trusted_image_certificates): + trusted_image_certificates = args.trusted_image_certificates + elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): + trusted_image_certificates = utils.env( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS').split(',') + + if args.trusted_image_certificates_unset: + kwargs['trusted_image_certificates'] = None + # Check for conflicts in option usage. + if trusted_image_certificates: + raise exceptions.CommandError( + _("Cannot specify '--trusted-image-certificates-unset' " + "with '--trusted-image-certificate-id' or with " + "OS_TRUSTED_IMAGE_CERTIFICATE_IDS env variable set.")) + elif trusted_image_certificates: + # Only specify the kwarg if there is a value specified to avoid + # confusion with unsetting the value. + kwargs['trusted_image_certificates'] = trusted_image_certificates + elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): + raise exceptions.UnsupportedAttribute("trusted_image_certificates", + "2.63") + server = server.rebuild(image, _password, **kwargs) _print_server(cs, args, server) diff --git a/releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml b/releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml new file mode 100644 index 000000000..f8299656f --- /dev/null +++ b/releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + Added support for `microversion 2.63`_, which includes the following + changes: + + - New environment variable called ``OS_TRUSTED_IMAGE_CERTIFICATE_IDS`` + - New ``nova boot`` option called ``--trusted-image-certificate-id`` + - New ``nova rebuild`` options called ``--trusted-image-certificate-id`` + and ``--trusted-image-certificates-unset`` + - New kwarg called ``trusted_image_certificates`` added to python API + bindings: + + - ``novaclient.v2.servers.ServerManager.create()`` + - ``novaclient.v2.servers.ServerManager.rebuild()`` + + .. _microversion 2.63: https://docs.openstack.org/nova/latest/api_microversion_history.html#id57