summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-06-18 00:52:39 +0000
committerGerrit Code Review <review@openstack.org>2018-06-18 00:52:39 +0000
commit910328c30dbdd2495730bef719a29c72d313eb7a (patch)
tree57af39cfc97bed7d20d6eeafea24f8992e82b488
parent0ac7734e67f615aa2fd15d785b699b8832204e08 (diff)
parent7f10707e5d060874f1a562b5efdce5ddc2701389 (diff)
downloadpython-novaclient-910328c30dbdd2495730bef719a29c72d313eb7a.tar.gz
Merge "Microversion 2.63 - Add trusted_image_certificates"
-rw-r--r--doc/source/cli/nova.rst22
-rw-r--r--doc/source/user/shell.rst10
-rw-r--r--novaclient/__init__.py2
-rw-r--r--novaclient/tests/unit/v2/test_servers.py67
-rw-r--r--novaclient/tests/unit/v2/test_shell.py250
-rw-r--r--novaclient/v2/servers.py35
-rw-r--r--novaclient/v2/shell.py66
-rw-r--r--releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml17
8 files changed, 465 insertions, 4 deletions
diff --git a/doc/source/cli/nova.rst b/doc/source/cli/nova.rst
index e066955f..ea11e8b6 100644
--- a/doc/source/cli/nova.rst
+++ b/doc/source/cli/nova.rst
@@ -1011,6 +1011,7 @@ nova boot
[--config-drive <value>] [--poll] [--admin-pass <value>]
[--access-ip-v4 <value>] [--access-ip-v6 <value>]
[--description <description>]
+ [--trusted-image-certificate-id]
<name>
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-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
@@ -2683,6 +2691,8 @@ nova rebuild
[--minimal] [--preserve-ephemeral] [--name <name>]
[--description <description>] [--meta <key=value>]
[--file <dst-path=src-path>]
+ [--trusted-image-certificate-id <trusted-image-certificate-id>]
+ [--trusted-image-certificates-unset]
<server> <image>
Shutdown, re-image, and re-boot a server.
@@ -2730,6 +2740,18 @@ Shutdown, re-image, and re-boot a server.
to <dst-path> on the new server. You may store
up to 5 files.
+``--trusted-image-certificate-id <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 bd1fb7e9..882bb756 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 1530b37a..3bcad206 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 07ad1497..540968cf 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 b4ee79ac..a32d623b 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 a7b3eda1..d872fa01 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 160418cc..c1c04435 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='<trusted-image-certificate-id>',
+ 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='<trusted-image-certificate-id>',
+ 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 00000000..f8299656
--- /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