python-openstackclient 8.0.0__py3-none-any.whl → 8.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. openstackclient/api/compute_v2.py +2 -2
  2. openstackclient/api/volume_v2.py +60 -0
  3. openstackclient/api/volume_v3.py +60 -0
  4. openstackclient/compute/client.py +5 -0
  5. openstackclient/compute/v2/console.py +7 -0
  6. openstackclient/compute/v2/console_connection.py +48 -0
  7. openstackclient/compute/v2/flavor.py +14 -1
  8. openstackclient/compute/v2/keypair.py +10 -3
  9. openstackclient/compute/v2/server.py +76 -13
  10. openstackclient/compute/v2/server_event.py +1 -1
  11. openstackclient/identity/common.py +85 -11
  12. openstackclient/identity/v3/application_credential.py +88 -87
  13. openstackclient/identity/v3/domain.py +67 -49
  14. openstackclient/identity/v3/group.py +113 -68
  15. openstackclient/identity/v3/project.py +42 -20
  16. openstackclient/identity/v3/role.py +7 -2
  17. openstackclient/identity/v3/user.py +38 -5
  18. openstackclient/image/client.py +5 -0
  19. openstackclient/image/v1/image.py +16 -1
  20. openstackclient/image/v2/cache.py +10 -6
  21. openstackclient/image/v2/image.py +59 -12
  22. openstackclient/image/v2/metadef_objects.py +8 -2
  23. openstackclient/image/v2/metadef_properties.py +9 -2
  24. openstackclient/network/client.py +0 -6
  25. openstackclient/network/v2/floating_ip.py +58 -29
  26. openstackclient/network/v2/network_qos_rule.py +3 -11
  27. openstackclient/network/v2/port.py +16 -0
  28. openstackclient/network/v2/router.py +1 -1
  29. openstackclient/network/v2/security_group.py +49 -7
  30. openstackclient/network/v2/security_group_rule.py +18 -1
  31. openstackclient/shell.py +1 -1
  32. openstackclient/tests/functional/base.py +5 -1
  33. openstackclient/tests/functional/compute/v2/test_keypair.py +41 -5
  34. openstackclient/tests/functional/identity/v3/test_access_rule.py +1 -1
  35. openstackclient/tests/functional/identity/v3/test_application_credential.py +7 -7
  36. openstackclient/tests/functional/image/v2/test_image.py +36 -14
  37. openstackclient/tests/functional/volume/v2/test_volume.py +1 -1
  38. openstackclient/tests/functional/volume/v3/test_volume.py +2 -2
  39. openstackclient/tests/unit/api/test_volume_v2.py +124 -0
  40. openstackclient/tests/unit/api/test_volume_v3.py +124 -0
  41. openstackclient/tests/unit/compute/v2/fakes.py +81 -305
  42. openstackclient/tests/unit/compute/v2/test_console.py +18 -1
  43. openstackclient/tests/unit/compute/v2/test_console_connection.py +72 -0
  44. openstackclient/tests/unit/compute/v2/test_flavor.py +160 -175
  45. openstackclient/tests/unit/compute/v2/test_keypair.py +12 -5
  46. openstackclient/tests/unit/compute/v2/test_server.py +211 -97
  47. openstackclient/tests/unit/compute/v2/test_server_backup.py +32 -71
  48. openstackclient/tests/unit/compute/v2/test_server_event.py +2 -2
  49. openstackclient/tests/unit/compute/v2/test_server_image.py +33 -72
  50. openstackclient/tests/unit/compute/v2/test_server_migration.py +4 -4
  51. openstackclient/tests/unit/identity/v3/test_application_credential.py +93 -65
  52. openstackclient/tests/unit/identity/v3/test_domain.py +117 -107
  53. openstackclient/tests/unit/identity/v3/test_group.py +353 -202
  54. openstackclient/tests/unit/identity/v3/test_project.py +46 -53
  55. openstackclient/tests/unit/identity/v3/test_role.py +2 -8
  56. openstackclient/tests/unit/identity/v3/test_user.py +86 -6
  57. openstackclient/tests/unit/image/v1/test_image.py +55 -9
  58. openstackclient/tests/unit/image/v2/test_image.py +128 -58
  59. openstackclient/tests/unit/image/v2/test_metadef_objects.py +22 -0
  60. openstackclient/tests/unit/image/v2/test_metadef_properties.py +24 -10
  61. openstackclient/tests/unit/network/v2/fakes.py +406 -485
  62. openstackclient/tests/unit/network/v2/test_floating_ip_network.py +13 -19
  63. openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py +2 -2
  64. openstackclient/tests/unit/network/v2/test_ndp_proxy.py +3 -5
  65. openstackclient/tests/unit/network/v2/test_network.py +4 -4
  66. openstackclient/tests/unit/network/v2/test_network_agent.py +15 -29
  67. openstackclient/tests/unit/network/v2/test_network_qos_policy.py +16 -19
  68. openstackclient/tests/unit/network/v2/test_network_qos_rule.py +79 -152
  69. openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +4 -6
  70. openstackclient/tests/unit/network/v2/test_network_rbac.py +2 -2
  71. openstackclient/tests/unit/network/v2/test_port.py +57 -17
  72. openstackclient/tests/unit/network/v2/test_router.py +73 -57
  73. openstackclient/tests/unit/network/v2/test_security_group_network.py +31 -27
  74. openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +1 -3
  75. openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +82 -39
  76. openstackclient/tests/unit/volume/v2/fakes.py +1 -2
  77. openstackclient/tests/unit/volume/v2/test_service.py +57 -91
  78. openstackclient/tests/unit/volume/v2/test_volume.py +466 -410
  79. openstackclient/tests/unit/volume/v2/test_volume_backup.py +141 -148
  80. openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +293 -283
  81. openstackclient/tests/unit/volume/v3/test_block_storage_log_level.py +61 -71
  82. openstackclient/tests/unit/volume/v3/test_service.py +221 -141
  83. openstackclient/tests/unit/volume/v3/test_volume.py +569 -534
  84. openstackclient/tests/unit/volume/v3/test_volume_attachment.py +1 -1
  85. openstackclient/tests/unit/volume/v3/test_volume_backup.py +198 -203
  86. openstackclient/tests/unit/volume/v3/test_volume_snapshot.py +682 -47
  87. openstackclient/volume/v2/service.py +41 -38
  88. openstackclient/volume/v2/volume.py +140 -88
  89. openstackclient/volume/v2/volume_backup.py +9 -3
  90. openstackclient/volume/v2/volume_snapshot.py +121 -84
  91. openstackclient/volume/v3/block_storage_log_level.py +22 -28
  92. openstackclient/volume/v3/service.py +105 -14
  93. openstackclient/volume/v3/volume.py +287 -99
  94. openstackclient/volume/v3/volume_backup.py +24 -19
  95. openstackclient/volume/v3/volume_group.py +1 -1
  96. openstackclient/volume/v3/volume_snapshot.py +485 -10
  97. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/AUTHORS +13 -0
  98. python_openstackclient-8.2.0.dist-info/METADATA +264 -0
  99. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/RECORD +104 -98
  100. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/entry_points.txt +7 -6
  101. python_openstackclient-8.2.0.dist-info/pbr.json +1 -0
  102. python_openstackclient-8.0.0.dist-info/METADATA +0 -166
  103. python_openstackclient-8.0.0.dist-info/pbr.json +0 -1
  104. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/LICENSE +0 -0
  105. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/WHEEL +0 -0
  106. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/top_level.txt +0 -0
@@ -17,7 +17,7 @@
17
17
 
18
18
  import logging
19
19
 
20
- from keystoneauth1 import exceptions as ks_exc
20
+ from openstack import exceptions as sdk_exc
21
21
  from osc_lib.command import command
22
22
  from osc_lib import exceptions
23
23
  from osc_lib import utils
@@ -29,6 +29,25 @@ from openstackclient.identity import common
29
29
  LOG = logging.getLogger(__name__)
30
30
 
31
31
 
32
+ def _format_group(group):
33
+ columns = (
34
+ 'description',
35
+ 'domain_id',
36
+ 'id',
37
+ 'name',
38
+ )
39
+ column_headers = (
40
+ 'description',
41
+ 'domain_id',
42
+ 'id',
43
+ 'name',
44
+ )
45
+ return (
46
+ column_headers,
47
+ utils.get_item_properties(group, columns),
48
+ )
49
+
50
+
32
51
  class AddUserToGroup(command.Command):
33
52
  _description = _("Add user to group")
34
53
 
@@ -53,19 +72,19 @@ class AddUserToGroup(command.Command):
53
72
  return parser
54
73
 
55
74
  def take_action(self, parsed_args):
56
- identity_client = self.app.client_manager.identity
75
+ identity_client = self.app.client_manager.sdk_connection.identity
57
76
 
58
- group_id = common.find_group(
77
+ group_id = common.find_group_id_sdk(
59
78
  identity_client, parsed_args.group, parsed_args.group_domain
60
- ).id
79
+ )
61
80
 
62
81
  result = 0
63
82
  for i in parsed_args.user:
64
83
  try:
65
- user_id = common.find_user(
84
+ user_id = common.find_user_id_sdk(
66
85
  identity_client, i, parsed_args.user_domain
67
- ).id
68
- identity_client.users.add_to_group(user_id, group_id)
86
+ )
87
+ identity_client.add_user_to_group(user_id, group_id)
69
88
  except Exception as e:
70
89
  result += 1
71
90
  msg = _("%(user)s not added to group %(group)s: %(e)s") % {
@@ -109,32 +128,41 @@ class CheckUserInGroup(command.Command):
109
128
  return parser
110
129
 
111
130
  def take_action(self, parsed_args):
112
- identity_client = self.app.client_manager.identity
131
+ identity_client = self.app.client_manager.sdk_connection.identity
113
132
 
114
- user_id = common.find_user(
115
- identity_client, parsed_args.user, parsed_args.user_domain
116
- ).id
117
- group_id = common.find_group(
118
- identity_client, parsed_args.group, parsed_args.group_domain
119
- ).id
133
+ user_id = common.find_user_id_sdk(
134
+ identity_client,
135
+ parsed_args.user,
136
+ parsed_args.user_domain,
137
+ validate_actor_existence=False,
138
+ )
139
+ group_id = common.find_group_id_sdk(
140
+ identity_client,
141
+ parsed_args.group,
142
+ parsed_args.group_domain,
143
+ validate_actor_existence=False,
144
+ )
120
145
 
146
+ user_in_group = False
121
147
  try:
122
- identity_client.users.check_in_group(user_id, group_id)
123
- except ks_exc.http.HTTPClientError as e:
124
- if e.http_status == 403 or e.http_status == 404:
125
- msg = _("%(user)s not in group %(group)s\n") % {
126
- 'user': parsed_args.user,
127
- 'group': parsed_args.group,
128
- }
129
- self.app.stderr.write(msg)
130
- else:
131
- raise e
132
- else:
148
+ user_in_group = identity_client.check_user_in_group(
149
+ user_id, group_id
150
+ )
151
+ except sdk_exc.ForbiddenException:
152
+ # Assume False if forbidden
153
+ pass
154
+ if user_in_group:
133
155
  msg = _("%(user)s in group %(group)s\n") % {
134
156
  'user': parsed_args.user,
135
157
  'group': parsed_args.group,
136
158
  }
137
159
  self.app.stdout.write(msg)
160
+ else:
161
+ msg = _("%(user)s not in group %(group)s\n") % {
162
+ 'user': parsed_args.user,
163
+ 'group': parsed_args.group,
164
+ }
165
+ self.app.stderr.write(msg)
138
166
 
139
167
 
140
168
  class CreateGroup(command.ShowOne):
@@ -165,29 +193,33 @@ class CreateGroup(command.ShowOne):
165
193
  return parser
166
194
 
167
195
  def take_action(self, parsed_args):
168
- identity_client = self.app.client_manager.identity
196
+ identity_client = self.app.client_manager.sdk_connection.identity
169
197
 
170
- domain = None
198
+ kwargs = {}
199
+ if parsed_args.name:
200
+ kwargs['name'] = parsed_args.name
201
+ if parsed_args.description:
202
+ kwargs['description'] = parsed_args.description
171
203
  if parsed_args.domain:
172
- domain = common.find_domain(identity_client, parsed_args.domain).id
204
+ kwargs['domain_id'] = common.find_domain_id_sdk(
205
+ identity_client, parsed_args.domain
206
+ )
173
207
 
174
208
  try:
175
- group = identity_client.groups.create(
176
- name=parsed_args.name,
177
- domain=domain,
178
- description=parsed_args.description,
179
- )
180
- except ks_exc.Conflict:
209
+ group = identity_client.create_group(**kwargs)
210
+ except sdk_exc.ConflictException:
181
211
  if parsed_args.or_show:
182
- group = utils.find_resource(
183
- identity_client.groups, parsed_args.name, domain_id=domain
184
- )
212
+ if parsed_args.domain:
213
+ group = identity_client.find_group(
214
+ parsed_args.name, domain_id=parsed_args.domain
215
+ )
216
+ else:
217
+ group = identity_client.find_group(parsed_args.name)
185
218
  LOG.info(_('Returning existing group %s'), group.name)
186
219
  else:
187
220
  raise
188
221
 
189
- group._info.pop('links')
190
- return zip(*sorted(group._info.items()))
222
+ return _format_group(group)
191
223
 
192
224
 
193
225
  class DeleteGroup(command.Command):
@@ -209,15 +241,15 @@ class DeleteGroup(command.Command):
209
241
  return parser
210
242
 
211
243
  def take_action(self, parsed_args):
212
- identity_client = self.app.client_manager.identity
244
+ identity_client = self.app.client_manager.sdk_connection.identity
213
245
 
214
246
  errors = 0
215
247
  for group in parsed_args.groups:
216
248
  try:
217
- group_obj = common.find_group(
249
+ group_id = common.find_group_id_sdk(
218
250
  identity_client, group, parsed_args.domain
219
251
  )
220
- identity_client.groups.delete(group_obj.id)
252
+ identity_client.delete_group(group_id)
221
253
  except Exception as e:
222
254
  errors += 1
223
255
  LOG.error(
@@ -262,29 +294,37 @@ class ListGroup(command.Lister):
262
294
  return parser
263
295
 
264
296
  def take_action(self, parsed_args):
265
- identity_client = self.app.client_manager.identity
297
+ identity_client = self.app.client_manager.sdk_connection.identity
266
298
 
267
299
  domain = None
268
300
  if parsed_args.domain:
269
- domain = common.find_domain(identity_client, parsed_args.domain).id
301
+ domain = common.find_domain_id_sdk(
302
+ identity_client, parsed_args.domain
303
+ )
270
304
 
305
+ data = []
271
306
  if parsed_args.user:
272
- user = common.find_user(
307
+ user = common.find_user_id_sdk(
273
308
  identity_client,
274
309
  parsed_args.user,
275
310
  parsed_args.user_domain,
276
- ).id
311
+ )
312
+ if domain:
313
+ # NOTE(0weng): The API doesn't actually support filtering additionally by domain_id,
314
+ # so this doesn't really do anything.
315
+ data = identity_client.user_groups(user, domain_id=domain)
316
+ else:
317
+ data = identity_client.user_groups(user)
277
318
  else:
278
- user = None
319
+ if domain:
320
+ data = identity_client.groups(domain_id=domain)
321
+ else:
322
+ data = identity_client.groups()
279
323
 
280
324
  # List groups
281
325
  columns: tuple[str, ...] = ('ID', 'Name')
282
326
  if parsed_args.long:
283
327
  columns += ('Domain ID', 'Description')
284
- data = identity_client.groups.list(
285
- domain=domain,
286
- user=user,
287
- )
288
328
 
289
329
  return (
290
330
  columns,
@@ -323,19 +363,19 @@ class RemoveUserFromGroup(command.Command):
323
363
  return parser
324
364
 
325
365
  def take_action(self, parsed_args):
326
- identity_client = self.app.client_manager.identity
366
+ identity_client = self.app.client_manager.sdk_connection.identity
327
367
 
328
- group_id = common.find_group(
368
+ group_id = common.find_group_id_sdk(
329
369
  identity_client, parsed_args.group, parsed_args.group_domain
330
- ).id
370
+ )
331
371
 
332
372
  result = 0
333
373
  for i in parsed_args.user:
334
374
  try:
335
- user_id = common.find_user(
375
+ user_id = common.find_user_id_sdk(
336
376
  identity_client, i, parsed_args.user_domain
337
- ).id
338
- identity_client.users.remove_from_group(user_id, group_id)
377
+ )
378
+ identity_client.remove_user_from_group(user_id, group_id)
339
379
  except Exception as e:
340
380
  result += 1
341
381
  msg = _("%(user)s not removed from group %(group)s: %(e)s") % {
@@ -387,8 +427,8 @@ class SetGroup(command.Command):
387
427
  return parser
388
428
 
389
429
  def take_action(self, parsed_args):
390
- identity_client = self.app.client_manager.identity
391
- group = common.find_group(
430
+ identity_client = self.app.client_manager.sdk_connection.identity
431
+ group = common.find_group_id_sdk(
392
432
  identity_client, parsed_args.group, parsed_args.domain
393
433
  )
394
434
  kwargs = {}
@@ -397,7 +437,7 @@ class SetGroup(command.Command):
397
437
  if parsed_args.description:
398
438
  kwargs['description'] = parsed_args.description
399
439
 
400
- identity_client.groups.update(group.id, **kwargs)
440
+ identity_client.update_group(group, **kwargs)
401
441
 
402
442
 
403
443
  class ShowGroup(command.ShowOne):
@@ -418,13 +458,18 @@ class ShowGroup(command.ShowOne):
418
458
  return parser
419
459
 
420
460
  def take_action(self, parsed_args):
421
- identity_client = self.app.client_manager.identity
461
+ identity_client = self.app.client_manager.sdk_connection.identity
422
462
 
423
- group = common.find_group(
424
- identity_client,
425
- parsed_args.group,
426
- domain_name_or_id=parsed_args.domain,
427
- )
463
+ if parsed_args.domain:
464
+ domain = common.find_domain_id_sdk(
465
+ identity_client, parsed_args.domain
466
+ )
467
+ group = identity_client.find_group(
468
+ parsed_args.group, domain_id=domain, ignore_missing=False
469
+ )
470
+ else:
471
+ group = identity_client.find_group(
472
+ parsed_args.group, ignore_missing=False
473
+ )
428
474
 
429
- group._info.pop('links')
430
- return zip(*sorted(group._info.items()))
475
+ return _format_group(group)
@@ -59,17 +59,22 @@ class CreateProject(command.ShowOne):
59
59
  enable_group.add_argument(
60
60
  '--enable',
61
61
  action='store_true',
62
+ dest='enabled',
63
+ default=True,
62
64
  help=_('Enable project'),
63
65
  )
64
66
  enable_group.add_argument(
65
67
  '--disable',
66
- action='store_true',
68
+ action='store_false',
69
+ dest='enabled',
70
+ default=True,
67
71
  help=_('Disable project'),
68
72
  )
69
73
  parser.add_argument(
70
74
  '--property',
71
75
  metavar='<key=value>',
72
76
  action=parseractions.KeyValueAction,
77
+ dest='properties',
73
78
  help=_(
74
79
  'Add a property to <name> '
75
80
  '(repeat option to set multiple properties)'
@@ -98,15 +103,9 @@ class CreateProject(command.ShowOne):
98
103
  parsed_args.parent,
99
104
  ).id
100
105
 
101
- enabled = True
102
- if parsed_args.disable:
103
- enabled = False
104
-
105
- options = common.get_immutable_options(parsed_args)
106
-
107
106
  kwargs = {}
108
- if parsed_args.property:
109
- kwargs = parsed_args.property.copy()
107
+ if parsed_args.properties:
108
+ kwargs = parsed_args.properties.copy()
110
109
  if 'is_domain' in kwargs.keys():
111
110
  if kwargs['is_domain'].lower() == "true":
112
111
  kwargs['is_domain'] = True
@@ -117,13 +116,17 @@ class CreateProject(command.ShowOne):
117
116
 
118
117
  kwargs['tags'] = list(set(parsed_args.tags))
119
118
 
119
+ options = {}
120
+ if parsed_args.immutable is not None:
121
+ options['immutable'] = parsed_args.immutable
122
+
120
123
  try:
121
124
  project = identity_client.projects.create(
122
125
  name=parsed_args.name,
123
126
  domain=domain,
124
127
  parent=parent,
125
128
  description=parsed_args.description,
126
- enabled=enabled,
129
+ enabled=parsed_args.enabled,
127
130
  options=options,
128
131
  **kwargs,
129
132
  )
@@ -240,6 +243,20 @@ class ListProject(command.Lister):
240
243
  'keys and directions.'
241
244
  ),
242
245
  )
246
+ parser.add_argument(
247
+ '--enabled',
248
+ action='store_true',
249
+ dest='is_enabled',
250
+ default=None,
251
+ help=_('List only enabled projects'),
252
+ )
253
+ parser.add_argument(
254
+ '--disabled',
255
+ action='store_false',
256
+ dest='is_enabled',
257
+ default=None,
258
+ help=_('List only disabled projects'),
259
+ )
243
260
  tag.add_tag_filtering_option_to_parser(parser, _('projects'))
244
261
  return parser
245
262
 
@@ -277,6 +294,9 @@ class ListProject(command.Lister):
277
294
 
278
295
  kwargs['user'] = user_id
279
296
 
297
+ if parsed_args.is_enabled is not None:
298
+ kwargs['is_enabled'] = parsed_args.is_enabled
299
+
280
300
  tag.get_tag_filtering_args(parsed_args, kwargs)
281
301
 
282
302
  if parsed_args.my_projects:
@@ -339,16 +359,21 @@ class SetProject(command.Command):
339
359
  enable_group.add_argument(
340
360
  '--enable',
341
361
  action='store_true',
362
+ dest='enabled',
363
+ default=None,
342
364
  help=_('Enable project'),
343
365
  )
344
366
  enable_group.add_argument(
345
367
  '--disable',
346
- action='store_true',
368
+ action='store_false',
369
+ dest='enabled',
370
+ default=None,
347
371
  help=_('Disable project'),
348
372
  )
349
373
  parser.add_argument(
350
374
  '--property',
351
375
  metavar='<key=value>',
376
+ dest='properties',
352
377
  action=parseractions.KeyValueAction,
353
378
  help=_(
354
379
  'Set a property on <project> '
@@ -371,15 +396,12 @@ class SetProject(command.Command):
371
396
  kwargs['name'] = parsed_args.name
372
397
  if parsed_args.description:
373
398
  kwargs['description'] = parsed_args.description
374
- if parsed_args.enable:
375
- kwargs['enabled'] = True
376
- if parsed_args.disable:
377
- kwargs['enabled'] = False
378
- options = common.get_immutable_options(parsed_args)
379
- if options:
380
- kwargs['options'] = options
381
- if parsed_args.property:
382
- kwargs.update(parsed_args.property)
399
+ if parsed_args.enabled is not None:
400
+ kwargs['enabled'] = parsed_args.enabled
401
+ if parsed_args.immutable is not None:
402
+ kwargs['options'] = {'immutable': parsed_args.immutable}
403
+ if parsed_args.properties:
404
+ kwargs.update(parsed_args.properties)
383
405
  tag.update_tags_in_args(parsed_args, project, kwargs)
384
406
 
385
407
  identity_client.projects.update(project.id, **kwargs)
@@ -334,9 +334,12 @@ class CreateRole(command.ShowOne):
334
334
 
335
335
  if parsed_args.name:
336
336
  create_kwargs['name'] = parsed_args.name
337
+
337
338
  if parsed_args.description:
338
339
  create_kwargs['description'] = parsed_args.description
339
- create_kwargs['options'] = common.get_immutable_options(parsed_args)
340
+
341
+ if parsed_args.immutable is not None:
342
+ create_kwargs['options'] = {"immutable": parsed_args.immutable}
340
343
 
341
344
  try:
342
345
  role = identity_client.create_role(**create_kwargs)
@@ -585,7 +588,9 @@ class SetRole(command.Command):
585
588
  )
586
589
  update_kwargs["domain_id"] = domain_id
587
590
 
588
- update_kwargs["options"] = common.get_immutable_options(parsed_args)
591
+ if parsed_args.immutable is not None:
592
+ update_kwargs["options"] = {"immutable": parsed_args.immutable}
593
+
589
594
  role = _find_sdk_id(
590
595
  identity_client.find_role,
591
596
  name_or_id=parsed_args.role,
@@ -41,6 +41,7 @@ def _format_user(user):
41
41
  'name',
42
42
  'description',
43
43
  'password_expires_at',
44
+ 'options',
44
45
  )
45
46
  column_headers = (
46
47
  'default_project_id',
@@ -51,6 +52,7 @@ def _format_user(user):
51
52
  'name',
52
53
  'description',
53
54
  'password_expires_at',
55
+ 'options',
54
56
  )
55
57
  return (
56
58
  column_headers,
@@ -289,7 +291,7 @@ class CreateUser(command.ShowOne):
289
291
  elif parsed_args.password_prompt:
290
292
  password = utils.get_password(self.app.stdin)
291
293
 
292
- if not parsed_args.password:
294
+ if not password:
293
295
  LOG.warning(
294
296
  _(
295
297
  "No password was supplied, authentication will fail "
@@ -412,6 +414,24 @@ class ListUser(command.Lister):
412
414
  default=False,
413
415
  help=_('List additional fields in output'),
414
416
  )
417
+ parser.add_argument(
418
+ '--enabled',
419
+ action='store_true',
420
+ dest='is_enabled',
421
+ default=None,
422
+ help=_(
423
+ 'List only enabled users, does nothing with --project and --group'
424
+ ),
425
+ )
426
+ parser.add_argument(
427
+ '--disabled',
428
+ action='store_false',
429
+ dest='is_enabled',
430
+ default=None,
431
+ help=_(
432
+ 'List only disabled users, does nothing with --project and --group'
433
+ ),
434
+ )
415
435
  return parser
416
436
 
417
437
  def take_action(self, parsed_args):
@@ -431,6 +451,9 @@ class ListUser(command.Lister):
431
451
  ignore_missing=False,
432
452
  ).id
433
453
 
454
+ if parsed_args.is_enabled is not None:
455
+ enabled = parsed_args.is_enabled
456
+
434
457
  if parsed_args.project:
435
458
  if domain is not None:
436
459
  project = identity_client.find_project(
@@ -469,9 +492,15 @@ class ListUser(command.Lister):
469
492
  group=group,
470
493
  )
471
494
  else:
472
- data = identity_client.users(
473
- domain_id=domain,
474
- )
495
+ if parsed_args.is_enabled is not None:
496
+ data = identity_client.users(
497
+ domain_id=domain,
498
+ is_enabled=enabled,
499
+ )
500
+ else:
501
+ data = identity_client.users(
502
+ domain_id=domain,
503
+ )
475
504
 
476
505
  # Column handling
477
506
  if parsed_args.long:
@@ -659,6 +688,8 @@ class SetPasswordUser(command.Command):
659
688
 
660
689
  def take_action(self, parsed_args):
661
690
  identity_client = self.app.client_manager.sdk_connection.identity
691
+ conn = self.app.client_manager.sdk_connection
692
+ user_id = conn.config.get_auth().get_user_id(conn.identity)
662
693
 
663
694
  # FIXME(gyee): there are two scenarios:
664
695
  #
@@ -701,7 +732,9 @@ class SetPasswordUser(command.Command):
701
732
  )
702
733
 
703
734
  identity_client.update_user(
704
- current_password=current_password, password=password
735
+ user=user_id,
736
+ current_password=current_password,
737
+ password=password,
705
738
  )
706
739
 
707
740
 
@@ -46,3 +46,8 @@ def build_option_parser(parser):
46
46
  % DEFAULT_API_VERSION,
47
47
  )
48
48
  return parser
49
+
50
+
51
+ def check_api_version(check_version):
52
+ # SDK supports auto-negotiation for us: always return True
53
+ return True
@@ -528,6 +528,16 @@ class SaveImage(command.Command):
528
528
 
529
529
  def get_parser(self, prog_name):
530
530
  parser = super().get_parser(prog_name)
531
+ parser.add_argument(
532
+ "--chunk-size",
533
+ type=int,
534
+ default=1024,
535
+ metavar="<chunk-size>",
536
+ help=_(
537
+ "Size in bytes to read from the wire and buffer at one "
538
+ "time (default: 1024)"
539
+ ),
540
+ )
531
541
  parser.add_argument(
532
542
  "--file",
533
543
  metavar="<filename>",
@@ -550,7 +560,12 @@ class SaveImage(command.Command):
550
560
  if output_file is None:
551
561
  output_file = getattr(sys.stdout, "buffer", sys.stdout)
552
562
 
553
- image_client.download_image(image.id, stream=True, output=output_file)
563
+ image_client.download_image(
564
+ image.id,
565
+ stream=True,
566
+ output=output_file,
567
+ chunk_size=parsed_args.chunk_size,
568
+ )
554
569
 
555
570
 
556
571
  class SetImage(command.Command):
@@ -37,14 +37,18 @@ def _format_image_cache(cached_images):
37
37
  image_obj = copy.deepcopy(image)
38
38
  image_obj['state'] = 'cached'
39
39
  image_obj['last_accessed'] = (
40
- datetime.datetime.utcfromtimestamp(
41
- image['last_accessed']
42
- ).isoformat()
40
+ datetime.datetime.fromtimestamp(
41
+ image['last_accessed'], tz=datetime.timezone.utc
42
+ )
43
+ .replace(tzinfo=None)
44
+ .isoformat()
43
45
  )
44
46
  image_obj['last_modified'] = (
45
- datetime.datetime.utcfromtimestamp(
46
- image['last_modified']
47
- ).isoformat()
47
+ datetime.datetime.fromtimestamp(
48
+ image['last_modified'], tz=datetime.timezone.utc
49
+ )
50
+ .replace(tzinfo=None)
51
+ .isoformat()
48
52
  )
49
53
  image_list.append(image_obj)
50
54
  elif item == "queued_images":