summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/compute/v2/server.py8
-rw-r--r--openstackclient/image/v2/image.py46
-rw-r--r--openstackclient/tests/image/v2/fakes.py133
3 files changed, 178 insertions, 9 deletions
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index d58ebacd..adab29c9 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -276,25 +276,25 @@ class CreateServer(show.ShowOne):
disk_group.add_argument(
'--image',
metavar='<image>',
- help=_('Create server from this image'),
+ help=_('Create server from this image (name or ID)'),
)
disk_group.add_argument(
'--volume',
metavar='<volume>',
- help=_('Create server from this volume'),
+ help=_('Create server from this volume (name or ID)'),
)
parser.add_argument(
'--flavor',
metavar='<flavor>',
required=True,
- help=_('Create server with this flavor'),
+ help=_('Create server with this flavor (name or ID)'),
)
parser.add_argument(
'--security-group',
metavar='<security-group-name>',
action='append',
default=[],
- help=_('Security group to assign to this server '
+ help=_('Security group to assign to this server (name or ID) '
'(repeat for multiple groups)'),
)
parser.add_argument(
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index c0fb5b58..a3c1a99d 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -116,7 +116,6 @@ class CreateImage(show.ShowOne):
def get_parser(self, prog_name):
parser = super(CreateImage, self).get_parser(prog_name)
- # TODO(mordred): add --volume and --force parameters and support
# TODO(bunting): There are additional arguments that v1 supported
# that v2 either doesn't support or supports weirdly.
# --checksum - could be faked clientside perhaps?
@@ -170,6 +169,19 @@ class CreateImage(show.ShowOne):
metavar="<file>",
help="Upload image from local file",
)
+ parser.add_argument(
+ "--volume",
+ metavar="<volume>",
+ help="Create image from a volume",
+ )
+ parser.add_argument(
+ "--force",
+ dest='force',
+ action='store_true',
+ default=False,
+ help="Force image creation if volume is in use "
+ "(only meaningful with --volume)",
+ )
protected_group = parser.add_mutually_exclusive_group()
protected_group.add_argument(
"--protected",
@@ -245,6 +257,7 @@ class CreateImage(show.ShowOne):
if getattr(parsed_args, 'properties', None):
for k, v in six.iteritems(parsed_args.properties):
kwargs[k] = str(v)
+
# Handle exclusive booleans with care
# Avoid including attributes in kwargs if an option is not
# present on the command line. These exclusive booleans are not
@@ -263,12 +276,33 @@ class CreateImage(show.ShowOne):
# open the file first to ensure any failures are handled before the
# image is created
fp = gc_utils.get_data_file(parsed_args)
+ info = {}
+ if fp is not None and parsed_args.volume:
+ raise exceptions.CommandError("Uploading data and using container "
+ "are not allowed at the same time")
if fp is None and parsed_args.file:
self.log.warning("Failed to get an image file.")
return {}, {}
- image = image_client.images.create(**kwargs)
+ # If a volume is specified.
+ if parsed_args.volume:
+ volume_client = self.app.client_manager.volume
+ source_volume = utils.find_resource(
+ volume_client.volumes,
+ parsed_args.volume,
+ )
+ response, body = volume_client.volumes.upload_to_image(
+ source_volume.id,
+ parsed_args.force,
+ parsed_args.name,
+ parsed_args.container_format,
+ parsed_args.disk_format,
+ )
+ info = body['os-volume_upload_image']
+ info['volume_type'] = info['volume_type']['name']
+ else:
+ image = image_client.images.create(**kwargs)
if fp is not None:
with fp:
@@ -289,7 +323,9 @@ class CreateImage(show.ShowOne):
# update the image after the data has been uploaded
image = image_client.images.get(image.id)
- info = _format_image(image)
+ if not info:
+ info = _format_image(image)
+
return zip(*sorted(six.iteritems(info)))
@@ -539,8 +575,8 @@ class SetImage(command.Command):
# --location - maybe location add?
# --copy-from - does not exist in v2
# --file - should be able to upload file
- # --volume - needs adding
- # --force - needs adding
+ # --volume - not possible with v2 as can't change id
+ # --force - see `--volume`
# --checksum - maybe could be done client side
# --stdin - could be implemented
parser.add_argument(
diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py
index 11ad455d..692ef104 100644
--- a/openstackclient/tests/image/v2/fakes.py
+++ b/openstackclient/tests/image/v2/fakes.py
@@ -15,7 +15,10 @@
import copy
import mock
+import random
+import uuid
+from openstackclient.common import utils as common_utils
from openstackclient.tests import fakes
from openstackclient.tests import utils
@@ -167,3 +170,133 @@ class TestImagev2(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
+
+
+class FakeImage(object):
+ """Fake one or more images.
+
+ TODO(xiexs): Currently, only image API v2 is supported by this class.
+ """
+
+ @staticmethod
+ def create_one_image(attrs={}):
+ """Create a fake image.
+
+ :param Dictionary attrs:
+ A dictionary with all attrbutes of image
+ :retrun:
+ A FakeResource object with id, name, owner, protected,
+ visibility and tags attrs
+ """
+ # Set default attribute
+ image_info = {
+ 'id': 'image-id' + uuid.uuid4().hex,
+ 'name': 'image-name' + uuid.uuid4().hex,
+ 'owner': 'image-owner' + uuid.uuid4().hex,
+ 'protected': bool(random.choice([0, 1])),
+ 'visibility': random.choice(['public', 'private']),
+ 'tags': [uuid.uuid4().hex for r in range(random.randint(1, 5))],
+ }
+
+ # Overwrite default attributes if there are some attributes set
+ image_info.update(attrs)
+
+ image = fakes.FakeResource(
+ None,
+ image_info,
+ loaded=True)
+ return image
+
+ @staticmethod
+ def create_images(attrs={}, count=2):
+ """Create multiple fake images.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes of image
+ :param Integer count:
+ The number of images to be faked
+ :return:
+ A list of FakeResource objects
+ """
+ images = []
+ for n in range(0, count):
+ images.append(FakeImage.create_one_image(attrs))
+
+ return images
+
+ @staticmethod
+ def get_images(images=None, count=2):
+ """Get an iterable MagicMock object with a list of faked images.
+
+ If images list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List images:
+ A list of FakeResource objects faking images
+ :param Integer count:
+ The number of images to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ images
+ """
+ if images is None:
+ images = FakeImage.create_images(count)
+
+ return mock.MagicMock(side_effect=images)
+
+ @staticmethod
+ def get_image_info(image=None):
+ """Get the image info from a faked image object.
+
+ :param image:
+ A FakeResource objects faking image
+ :return
+ A dictionary which includes the faked image info as follows:
+ {
+ 'id': image_id,
+ 'name': image_name,
+ 'owner': image_owner,
+ 'protected': image_protected,
+ 'visibility': image_visibility,
+ 'tags': image_tags
+ }
+ """
+ if image is not None:
+ return image._info
+ return {}
+
+ @staticmethod
+ def get_image_columns(image=None):
+ """Get the image columns from a faked image object.
+
+ :param image:
+ A FakeResource objects faking image
+ :return
+ A tuple which may include the following keys:
+ ('id', 'name', 'owner', 'protected', 'visibility', 'tags')
+ """
+ if image is not None:
+ return tuple(k for k in sorted(
+ FakeImage.get_image_info(image).keys()))
+ return tuple([])
+
+ @staticmethod
+ def get_image_data(image=None):
+ """Get the image data from a faked image object.
+
+ :param image:
+ A FakeResource objects faking image
+ :return
+ A tuple which may include the following values:
+ ('image-123', 'image-foo', 'admin', False, 'public', 'bar, baz')
+ """
+ data_list = []
+ if image is not None:
+ for x in sorted(FakeImage.get_image_info(image).keys()):
+ if x == 'tags':
+ # The 'tags' should be format_list
+ data_list.append(
+ common_utils.format_list(getattr(image, x)))
+ else:
+ data_list.append(getattr(image, x))
+ return tuple(data_list)