Microversion 2.63 - Add trusted_image_certificates

This change adds a `--trusted-image-certificate-id` option to the
`nova boot` and `nova rebuild` commands. This option takes in a
a single trusted certificate ID. The option may be used multiple times
to specify multiple trusted certificate IDs, which will be
used to validate certificates in the image signature verification
process. If ID values are not specified using this option, the value of
the newly added OS_TRUSTED_IMAGE_CERTIFICATE_IDS environment variable
will be used instead. This value will be converted into a list before
being passed on.

The ``nova rebuild`` command also gets a new
``--trusted-image-certificates-unset`` option to unset/reset the
trusted image certificates in a server during rebuild. This is
similar to unsetting key_name and user_data during rebuild.

Corresponding `trusted_image_certificates` kwarg has been added to the
server create and rebuild Python API bindings.

Co-Authored-By: Brianna Poulos <Brianna.Poulos@jhuapl.edu>
Co-Authored-By: Matt Riedemann <mriedem.os@gmail.com>
Change-Id: I235541a689732826950c7b2a510d5835211120c3
Implements: blueprint nova-validate-certificates
This commit is contained in:
Jackie Truong 2017-09-03 17:24:40 -04:00 committed by Matt Riedemann
parent 7907528256
commit 7f10707e5d
8 changed files with 465 additions and 4 deletions

View File

@ -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
@ -2660,6 +2668,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.
@ -2707,6 +2717,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

View File

@ -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

View File

@ -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")

View File

@ -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))

View File

@ -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))

View File

@ -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:

View File

@ -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)

View File

@ -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