Skip to content

Commit 49bed38

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Add owner validation for "openstack image create/set""
2 parents a080227 + 0a444fc commit 49bed38

3 files changed

Lines changed: 116 additions & 4 deletions

File tree

doc/source/command-objects/image.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Create/upload an image
3333
[--public | --private]
3434
[--property <key=value> [...] ]
3535
[--tag <tag> [...] ]
36+
[--project-domain <project-domain>]
3637
<image-name>
3738
3839
.. option:: --id <id>
@@ -127,6 +128,11 @@ Create/upload an image
127128

128129
.. versionadded:: 2
129130

131+
.. option:: --project-domain <project-domain>
132+
133+
Domain the project belongs to (name or ID).
134+
This can be used in case collisions between project names exist.
135+
130136
.. describe:: <image-name>
131137

132138
New image name
@@ -244,6 +250,7 @@ Set image properties
244250
[--os-version <os-version>]
245251
[--ramdisk-id <ramdisk-id>]
246252
[--activate|--deactivate]
253+
[--project-domain <project-domain>]
247254
<image>
248255
249256
.. option:: --name <name>
@@ -400,6 +407,11 @@ Set image properties
400407
401408
.. versionadded:: 2
402409
410+
.. option:: --project-domain <project-domain>
411+
412+
Domain the project belongs to (name or ID).
413+
This can be used in case collisions between project names exist.
414+
403415
.. describe:: <image>
404416
405417
Image to modify (name or ID)

openstackclient/image/v2/image.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def get_parser(self, prog_name):
220220
help="Set a tag on this image "
221221
"(repeat option to set multiple tags)",
222222
)
223+
common.add_project_domain_option_to_parser(parser)
223224
for deadopt in self.deadopts:
224225
parser.add_argument(
225226
"--%s" % deadopt,
@@ -231,6 +232,7 @@ def get_parser(self, prog_name):
231232

232233
def take_action(self, parsed_args):
233234
self.log.debug("take_action(%s)", parsed_args)
235+
identity_client = self.app.client_manager.identity
234236
image_client = self.app.client_manager.image
235237

236238
for deadopt in self.deadopts:
@@ -285,6 +287,13 @@ def take_action(self, parsed_args):
285287
self.log.warning("Failed to get an image file.")
286288
return {}, {}
287289

290+
if parsed_args.owner:
291+
kwargs['owner'] = common.find_project(
292+
identity_client,
293+
parsed_args.owner,
294+
parsed_args.project_domain,
295+
).id
296+
288297
# If a volume is specified.
289298
if parsed_args.volume:
290299
volume_client = self.app.client_manager.volume
@@ -704,6 +713,7 @@ def get_parser(self, prog_name):
704713
action="store_true",
705714
help="Activate the image",
706715
)
716+
common.add_project_domain_option_to_parser(parser)
707717
for deadopt in self.deadopts:
708718
parser.add_argument(
709719
"--%s" % deadopt,
@@ -715,6 +725,7 @@ def get_parser(self, prog_name):
715725

716726
def take_action(self, parsed_args):
717727
self.log.debug("take_action(%s)", parsed_args)
728+
identity_client = self.app.client_manager.identity
718729
image_client = self.app.client_manager.image
719730

720731
for deadopt in self.deadopts:
@@ -779,6 +790,13 @@ def take_action(self, parsed_args):
779790
# Tags should be extended, but duplicates removed
780791
kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags)))
781792

793+
if parsed_args.owner:
794+
kwargs['owner'] = common.find_project(
795+
identity_client,
796+
parsed_args.owner,
797+
parsed_args.project_domain,
798+
).id
799+
782800
try:
783801
image = image_client.images.update(image.id, **kwargs)
784802
except Exception as e:

openstackclient/tests/image/v2/test_image.py

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ def setUp(self):
5757

5858
self.new_image = image_fakes.FakeImage.create_one_image()
5959
self.images_mock.create.return_value = self.new_image
60+
61+
self.project_mock.get.return_value = fakes.FakeResource(
62+
None,
63+
copy.deepcopy(identity_fakes.PROJECT),
64+
loaded=True,
65+
)
66+
67+
self.domain_mock.get.return_value = fakes.FakeResource(
68+
None,
69+
copy.deepcopy(identity_fakes.DOMAIN),
70+
loaded=True,
71+
)
72+
6073
# This is the return value for utils.find_resource()
6174
self.images_mock.get.return_value = copy.deepcopy(
6275
image_fakes.FakeImage.get_image_info(self.new_image))
@@ -123,6 +136,7 @@ def test_image_reserve_options(self, mock_open):
123136
if self.new_image.protected else '--unprotected'),
124137
('--private'
125138
if self.new_image.visibility == 'private' else '--public'),
139+
'--project-domain', identity_fakes.domain_id,
126140
self.new_image.name,
127141
]
128142
verifylist = [
@@ -135,6 +149,7 @@ def test_image_reserve_options(self, mock_open):
135149
('unprotected', not self.new_image.protected),
136150
('public', self.new_image.visibility == 'public'),
137151
('private', self.new_image.visibility == 'private'),
152+
('project_domain', identity_fakes.domain_id),
138153
('name', self.new_image.name),
139154
]
140155
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -149,7 +164,7 @@ def test_image_reserve_options(self, mock_open):
149164
disk_format='fs',
150165
min_disk=10,
151166
min_ram=4,
152-
owner=self.new_image.owner,
167+
owner=identity_fakes.project_id,
153168
protected=self.new_image.protected,
154169
visibility=self.new_image.visibility,
155170
)
@@ -168,6 +183,40 @@ def test_image_reserve_options(self, mock_open):
168183
image_fakes.FakeImage.get_image_data(self.new_image),
169184
data)
170185

186+
def test_image_create_with_unexist_owner(self):
187+
self.project_mock.get.side_effect = exceptions.NotFound(None)
188+
self.project_mock.find.side_effect = exceptions.NotFound(None)
189+
190+
arglist = [
191+
'--container-format', 'ovf',
192+
'--disk-format', 'fs',
193+
'--min-disk', '10',
194+
'--min-ram', '4',
195+
'--owner', 'unexist_owner',
196+
'--protected',
197+
'--private',
198+
image_fakes.image_name,
199+
]
200+
verifylist = [
201+
('container_format', 'ovf'),
202+
('disk_format', 'fs'),
203+
('min_disk', 10),
204+
('min_ram', 4),
205+
('owner', 'unexist_owner'),
206+
('protected', True),
207+
('unprotected', False),
208+
('public', False),
209+
('private', True),
210+
('name', image_fakes.image_name),
211+
]
212+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
213+
214+
self.assertRaises(
215+
exceptions.CommandError,
216+
self.cmd.take_action,
217+
parsed_args,
218+
)
219+
171220
@mock.patch('glanceclient.common.utils.get_data_file', name='Open')
172221
def test_image_create_file(self, mock_open):
173222
mock_file = mock.MagicMock(name='File')
@@ -686,6 +735,18 @@ def setUp(self):
686735
schemas.SchemaBasedModel,
687736
)
688737

738+
self.project_mock.get.return_value = fakes.FakeResource(
739+
None,
740+
copy.deepcopy(identity_fakes.PROJECT),
741+
loaded=True,
742+
)
743+
744+
self.domain_mock.get.return_value = fakes.FakeResource(
745+
None,
746+
copy.deepcopy(identity_fakes.DOMAIN),
747+
loaded=True,
748+
)
749+
689750
self.images_mock.get.return_value = self.model(**image_fakes.IMAGE)
690751
self.images_mock.update.return_value = self.model(**image_fakes.IMAGE)
691752
# Get the command object to test
@@ -694,20 +755,22 @@ def setUp(self):
694755
def test_image_set_options(self):
695756
arglist = [
696757
'--name', 'new-name',
697-
'--owner', 'new-owner',
758+
'--owner', identity_fakes.project_name,
698759
'--min-disk', '2',
699760
'--min-ram', '4',
700761
'--container-format', 'ovf',
701762
'--disk-format', 'vmdk',
763+
'--project-domain', identity_fakes.domain_id,
702764
image_fakes.image_id,
703765
]
704766
verifylist = [
705767
('name', 'new-name'),
706-
('owner', 'new-owner'),
768+
('owner', identity_fakes.project_name),
707769
('min_disk', 2),
708770
('min_ram', 4),
709771
('container_format', 'ovf'),
710772
('disk_format', 'vmdk'),
773+
('project_domain', identity_fakes.domain_id),
711774
('image', image_fakes.image_id),
712775
]
713776
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -717,7 +780,7 @@ def test_image_set_options(self):
717780

718781
kwargs = {
719782
'name': 'new-name',
720-
'owner': 'new-owner',
783+
'owner': identity_fakes.project_id,
721784
'min_disk': 2,
722785
'min_ram': 4,
723786
'container_format': 'ovf',
@@ -727,6 +790,25 @@ def test_image_set_options(self):
727790
self.images_mock.update.assert_called_with(
728791
image_fakes.image_id, **kwargs)
729792

793+
def test_image_set_with_unexist_owner(self):
794+
self.project_mock.get.side_effect = exceptions.NotFound(None)
795+
self.project_mock.find.side_effect = exceptions.NotFound(None)
796+
797+
arglist = [
798+
'--owner', 'unexist_owner',
799+
image_fakes.image_id,
800+
]
801+
verifylist = [
802+
('owner', 'unexist_owner'),
803+
('image', image_fakes.image_id),
804+
]
805+
806+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
807+
808+
self.assertRaises(
809+
exceptions.CommandError,
810+
self.cmd.take_action, parsed_args)
811+
730812
def test_image_set_bools1(self):
731813
arglist = [
732814
'--protected',

0 commit comments

Comments
 (0)