Skip to content

Commit bd58977

Browse files
committed
Move update code from image create command
Openstack image create command updates existing image (with same name) by default. That might be confusing since glance allows create multiple images with same names and may lead to unwanted image update by image create command. Image update code was moved from image create action to image set action. BackwardsIncompatibleImpact Change-Id: I1686c6544c366262efab9e33c066d5f8a667f707 Closes-Bug: #1461817
1 parent 2eb0f72 commit bd58977

4 files changed

Lines changed: 223 additions & 105 deletions

File tree

‎doc/source/backwards-incompatible.rst‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@ List of Backwards Incompatible Changes
4747
* Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1450872
4848
* Commit: https://review.openstack.org/#/c/179446/
4949

50+
4. Command `openstack image create` does not update already existing image
51+
52+
Previously, the image create command updated already existing image if it had
53+
same name. It disabled possibility to create multiple images with same name
54+
and lead to potentially unwanted update of existing images by image create
55+
command.
56+
Now, update code was moved from create action to set action.
57+
58+
* In favor of: Create multiple images with same name (as glance does).
59+
* As of: 1.5.0
60+
* Removed in: NA
61+
* Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1461817
62+
* Commit: https://review.openstack.org/#/c/194654/
63+
5064
For Developers
5165
==============
5266

‎doc/source/command-objects/image.rst‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ Set image properties
210210
[--size <size>]
211211
[--protected | --unprotected]
212212
[--public | --private]
213+
[--store <store>]
214+
[--location <image-url>]
215+
[--copy-from <image-url>]
216+
[--file <file>]
217+
[--volume <volume>]
218+
[--force]
219+
[--checksum <checksum>]
220+
[--stdin]
213221
[--property <key=value> [...] ]
214222
<image>
215223
@@ -260,6 +268,38 @@ Set image properties
260268
261269
Image is inaccessible to the public (default)
262270
271+
.. option:: --store <store>
272+
273+
Upload image to this store
274+
275+
.. option:: --location <image-url>
276+
277+
Download image from an existing URL
278+
279+
.. option:: --copy-from <image-url>
280+
281+
Copy image from the data store (similar to --location)
282+
283+
.. option:: --file <file>
284+
285+
Upload image from local file
286+
287+
.. option:: --volume <volume>
288+
289+
Update image with a volume
290+
291+
.. option:: --force
292+
293+
Force image update if volume is in use (only meaningful with --volume)
294+
295+
.. option:: --checksum <checksum>
296+
297+
Image hash used for verification
298+
299+
.. option:: --stdin
300+
301+
Allow to read image data from standard input
302+
263303
.. option:: --property <key=value>
264304
265305
Set a property on this image (repeat for multiple values)

‎openstackclient/image/v1/image.py‎

Lines changed: 104 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333

3434
from glanceclient.common import utils as gc_utils
3535
from openstackclient.api import utils as api_utils
36-
from openstackclient.common import exceptions
3736
from openstackclient.common import parseractions
3837
from openstackclient.common import utils
3938

@@ -244,29 +243,7 @@ def take_action(self, parsed_args):
244243

245244
# Wrap the call to catch exceptions in order to close files
246245
try:
247-
try:
248-
image = utils.find_resource(
249-
image_client.images,
250-
parsed_args.name,
251-
)
252-
253-
# Preserve previous properties if any are being set now
254-
if image.properties:
255-
if parsed_args.properties:
256-
image.properties.update(kwargs['properties'])
257-
kwargs['properties'] = image.properties
258-
259-
except exceptions.CommandError:
260-
if not parsed_args.volume:
261-
# This is normal for a create or reserve (create w/o
262-
# an image), but skip for create from volume
263-
image = image_client.images.create(**kwargs)
264-
else:
265-
# Update an existing reservation
266-
267-
# If an image is specified via --file, --location or
268-
# --copy-from let the API handle it
269-
image = image_client.images.update(image.id, **kwargs)
246+
image = image_client.images.create(**kwargs)
270247
finally:
271248
# Clean up open files - make sure data isn't a string
272249
if ('data' in kwargs and hasattr(kwargs['data'], 'close') and
@@ -560,6 +537,51 @@ def get_parser(self, prog_name):
560537
help="Set a property on this image "
561538
"(repeat option to set multiple properties)",
562539
)
540+
parser.add_argument(
541+
"--store",
542+
metavar="<store>",
543+
help="Upload image to this store",
544+
)
545+
parser.add_argument(
546+
"--location",
547+
metavar="<image-url>",
548+
help="Download image from an existing URL",
549+
)
550+
parser.add_argument(
551+
"--copy-from",
552+
metavar="<image-url>",
553+
help="Copy image from the data store (similar to --location)",
554+
)
555+
parser.add_argument(
556+
"--file",
557+
metavar="<file>",
558+
help="Upload image from local file",
559+
)
560+
parser.add_argument(
561+
"--volume",
562+
metavar="<volume>",
563+
help="Create image from a volume",
564+
)
565+
parser.add_argument(
566+
"--force",
567+
dest='force',
568+
action='store_true',
569+
default=False,
570+
help="Force image change if volume is in use "
571+
"(only meaningful with --volume)",
572+
)
573+
parser.add_argument(
574+
"--stdin",
575+
dest='stdin',
576+
action='store_true',
577+
default=False,
578+
help="Read image data from standard input",
579+
)
580+
parser.add_argument(
581+
"--checksum",
582+
metavar="<checksum>",
583+
help="Image hash used for verification",
584+
)
563585
return parser
564586

565587
def take_action(self, parsed_args):
@@ -568,7 +590,8 @@ def take_action(self, parsed_args):
568590

569591
kwargs = {}
570592
copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties',
571-
'container_format', 'disk_format', 'size')
593+
'container_format', 'disk_format', 'size', 'store',
594+
'location', 'copy_from', 'volume', 'force', 'checksum')
572595
for attr in copy_attrs:
573596
if attr in parsed_args:
574597
val = getattr(parsed_args, attr, None)
@@ -591,20 +614,63 @@ def take_action(self, parsed_args):
591614
if parsed_args.private:
592615
kwargs['is_public'] = False
593616

594-
if not kwargs:
595-
self.log.warning('no arguments specified')
596-
return {}, {}
597-
598-
image = utils.find_resource(
599-
image_client.images,
600-
parsed_args.image,
601-
)
602-
603-
if image.properties and parsed_args.properties:
604-
image.properties.update(kwargs['properties'])
605-
kwargs['properties'] = image.properties
617+
# Wrap the call to catch exceptions in order to close files
618+
try:
619+
image = utils.find_resource(
620+
image_client.images,
621+
parsed_args.image,
622+
)
606623

607-
image = image_client.images.update(image.id, **kwargs)
624+
if not parsed_args.location and not parsed_args.copy_from:
625+
if parsed_args.volume:
626+
volume_client = self.app.client_manager.volume
627+
source_volume = utils.find_resource(
628+
volume_client.volumes,
629+
parsed_args.volume,
630+
)
631+
response, body = volume_client.volumes.upload_to_image(
632+
source_volume.id,
633+
parsed_args.force,
634+
parsed_args.image,
635+
(parsed_args.container_format
636+
if parsed_args.container_format
637+
else image.container_format),
638+
(parsed_args.disk_format
639+
if parsed_args.disk_format
640+
else image.disk_format),
641+
)
642+
info = body['os-volume_upload_image']
643+
elif parsed_args.file:
644+
# Send an open file handle to glanceclient so it will
645+
# do a chunked transfer
646+
kwargs["data"] = io.open(parsed_args.file, "rb")
647+
else:
648+
# Read file from stdin
649+
if sys.stdin.isatty() is not True:
650+
if parsed_args.stdin:
651+
if msvcrt:
652+
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
653+
# Send an open file handle to glanceclient so it
654+
# will do a chunked transfer
655+
kwargs["data"] = sys.stdin
656+
else:
657+
self.log.warning('Use --stdin to enable read image'
658+
' data from standard input')
659+
660+
if image.properties and parsed_args.properties:
661+
image.properties.update(kwargs['properties'])
662+
kwargs['properties'] = image.properties
663+
664+
if not kwargs:
665+
self.log.warning('no arguments specified')
666+
return {}, {}
667+
668+
image = image_client.images.update(image.id, **kwargs)
669+
finally:
670+
# Clean up open files - make sure data isn't a string
671+
if ('data' in kwargs and hasattr(kwargs['data'], 'close') and
672+
kwargs['data'] != sys.stdin):
673+
kwargs['data'].close()
608674

609675
info = {}
610676
info.update(image._info)

0 commit comments

Comments
 (0)