python-openstackclient 8.3.0__py3-none-any.whl → 9.0.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 (46) hide show
  1. openstackclient/common/module.py +1 -1
  2. openstackclient/common/quota.py +31 -17
  3. openstackclient/compute/v2/server.py +2 -2
  4. openstackclient/identity/common.py +31 -0
  5. openstackclient/identity/v2_0/service.py +3 -1
  6. openstackclient/identity/v3/federation_protocol.py +39 -40
  7. openstackclient/identity/v3/limit.py +85 -84
  8. openstackclient/identity/v3/project.py +181 -112
  9. openstackclient/identity/v3/registered_limit.py +82 -99
  10. openstackclient/identity/v3/tag.py +0 -11
  11. openstackclient/image/v2/image.py +2 -1
  12. openstackclient/tests/functional/identity/v3/test_limit.py +47 -0
  13. openstackclient/tests/functional/image/v2/test_metadef_objects.py +69 -0
  14. openstackclient/tests/functional/volume/v3/test_volume_snapshot.py +46 -132
  15. openstackclient/tests/unit/common/test_quota.py +59 -0
  16. openstackclient/tests/unit/compute/v2/test_server.py +6 -8
  17. openstackclient/tests/unit/identity/v3/test_limit.py +197 -145
  18. openstackclient/tests/unit/identity/v3/test_project.py +831 -512
  19. openstackclient/tests/unit/identity/v3/test_protocol.py +97 -88
  20. openstackclient/tests/unit/identity/v3/test_registered_limit.py +355 -220
  21. openstackclient/tests/unit/image/v2/test_image.py +5 -5
  22. openstackclient/tests/unit/volume/v2/test_consistency_group.py +8 -2
  23. openstackclient/tests/unit/volume/v2/test_volume.py +7 -6
  24. openstackclient/tests/unit/volume/v3/test_volume.py +34 -12
  25. openstackclient/volume/v2/consistency_group.py +8 -8
  26. openstackclient/volume/v2/consistency_group_snapshot.py +2 -2
  27. openstackclient/volume/v2/qos_specs.py +2 -2
  28. openstackclient/volume/v2/volume.py +12 -5
  29. openstackclient/volume/v2/volume_backup.py +2 -2
  30. openstackclient/volume/v2/volume_snapshot.py +2 -2
  31. openstackclient/volume/v2/volume_transfer_request.py +2 -2
  32. openstackclient/volume/v2/volume_type.py +5 -5
  33. openstackclient/volume/v3/volume.py +14 -7
  34. openstackclient/volume/v3/volume_backup.py +2 -2
  35. openstackclient/volume/v3/volume_snapshot.py +2 -2
  36. openstackclient/volume/v3/volume_transfer_request.py +2 -2
  37. openstackclient/volume/v3/volume_type.py +5 -5
  38. {python_openstackclient-8.3.0.dist-info → python_openstackclient-9.0.0.dist-info}/METADATA +1 -1
  39. {python_openstackclient-8.3.0.dist-info → python_openstackclient-9.0.0.dist-info}/RECORD +45 -44
  40. {python_openstackclient-8.3.0.dist-info → python_openstackclient-9.0.0.dist-info}/WHEEL +1 -1
  41. {python_openstackclient-8.3.0.dist-info → python_openstackclient-9.0.0.dist-info}/licenses/AUTHORS +5 -0
  42. python_openstackclient-9.0.0.dist-info/pbr.json +1 -0
  43. python_openstackclient-8.3.0.dist-info/pbr.json +0 -1
  44. {python_openstackclient-8.3.0.dist-info → python_openstackclient-9.0.0.dist-info}/entry_points.txt +0 -0
  45. {python_openstackclient-8.3.0.dist-info → python_openstackclient-9.0.0.dist-info}/licenses/LICENSE +0 -0
  46. {python_openstackclient-8.3.0.dist-info → python_openstackclient-9.0.0.dist-info}/top_level.txt +0 -0
@@ -61,7 +61,7 @@ class ListCommand(command.Lister):
61
61
  # TODO(bapalm): Fix this when cliff properly supports
62
62
  # handling the detection rather than using the hard-code below.
63
63
  if parsed_args.formatter == 'table':
64
- command_names = utils.format_list(command_names, "\n")
64
+ command_names = utils.format_list(command_names, "\n") # type: ignore
65
65
 
66
66
  commands.append((group, command_names))
67
67
 
@@ -272,7 +272,10 @@ class ListQuota(command.Lister):
272
272
  sdk_exceptions.NotFoundException,
273
273
  ) as exc:
274
274
  # Project not found, move on to next one
275
- LOG.warning(f"Project {project_id} not found: {exc}")
275
+ LOG.warning(
276
+ 'Project %(project_id)s not found: %(exc)s',
277
+ {'project_id': project_id, 'exc': exc},
278
+ )
276
279
  continue
277
280
 
278
281
  project_result = _xform_get_quota(
@@ -334,7 +337,10 @@ class ListQuota(command.Lister):
334
337
  sdk_exceptions.NotFoundException,
335
338
  ) as exc:
336
339
  # Project not found, move on to next one
337
- LOG.warning(f"Project {project_id} not found: {exc}")
340
+ LOG.warning(
341
+ 'Project %(project_id)s not found: %(exc)s',
342
+ {'project_id': project_id, 'exc': exc},
343
+ )
338
344
  continue
339
345
 
340
346
  project_result = _xform_get_quota(
@@ -389,7 +395,10 @@ class ListQuota(command.Lister):
389
395
  sdk_exceptions.ForbiddenException,
390
396
  ) as exc:
391
397
  # Project not found, move on to next one
392
- LOG.warning(f"Project {project_id} not found: {exc}")
398
+ LOG.warning(
399
+ 'Project %(project_id)s not found: %(exc)s',
400
+ {'project_id': project_id, 'exc': exc},
401
+ )
393
402
  continue
394
403
 
395
404
  project_result = _xform_get_quota(
@@ -797,20 +806,25 @@ and ``server-group-members`` output for a given quota class."""
797
806
  info.update(volume_quota_info)
798
807
  info.update(network_quota_info)
799
808
 
800
- # Map the internal quota names to the external ones
801
- # COMPUTE_QUOTAS and NETWORK_QUOTAS share floating-ips,
802
- # secgroup-rules and secgroups as dict value, so when
803
- # neutron is enabled, quotas of these three resources
804
- # in nova will be replaced by neutron's.
805
- for k, v in itertools.chain(
806
- COMPUTE_QUOTAS.items(),
807
- NOVA_NETWORK_QUOTAS.items(),
808
- VOLUME_QUOTAS.items(),
809
- NETWORK_QUOTAS.items(),
810
- ):
811
- if not k == v and info.get(k) is not None:
812
- info[v] = info[k]
813
- info.pop(k)
809
+ def _normalize_names(section: dict) -> None:
810
+ # Map the internal quota names to the external ones
811
+ # COMPUTE_QUOTAS and NETWORK_QUOTAS share floating-ips,
812
+ # secgroup-rules and secgroups as dict value, so when
813
+ # neutron is enabled, quotas of these three resources
814
+ # in nova will be replaced by neutron's.
815
+ for k, v in itertools.chain(
816
+ COMPUTE_QUOTAS.items(),
817
+ NOVA_NETWORK_QUOTAS.items(),
818
+ VOLUME_QUOTAS.items(),
819
+ NETWORK_QUOTAS.items(),
820
+ ):
821
+ if not k == v and section.get(k) is not None:
822
+ section[v] = section.pop(k)
823
+
824
+ _normalize_names(info)
825
+ if parsed_args.usage:
826
+ _normalize_names(info["reservation"])
827
+ _normalize_names(info["usage"])
814
828
 
815
829
  # Remove the 'id' field since it's not very useful
816
830
  if 'id' in info:
@@ -4267,7 +4267,7 @@ release the new server and restart the old one."""
4267
4267
  metavar='<server>',
4268
4268
  help=_('Server (name or ID)'),
4269
4269
  )
4270
- phase_group = parser.add_mutually_exclusive_group()
4270
+ phase_group = parser.add_mutually_exclusive_group(required=True)
4271
4271
  phase_group.add_argument(
4272
4272
  '--flavor',
4273
4273
  metavar='<flavor>',
@@ -5018,7 +5018,7 @@ class SshServer(command.Command):
5018
5018
  )
5019
5019
 
5020
5020
  cmd = ' '.join(['ssh', ip_address] + args)
5021
- LOG.debug(f"ssh command: {cmd}")
5021
+ LOG.debug('ssh command: %s', cmd)
5022
5022
  # we intentionally pass through user-provided arguments and run this in
5023
5023
  # the user's shell
5024
5024
  os.system(cmd) # noqa: S605
@@ -256,6 +256,37 @@ def find_project(identity_client, name_or_id, domain_name_or_id=None):
256
256
  )
257
257
 
258
258
 
259
+ def find_project_id_sdk(
260
+ identity_client,
261
+ name_or_id,
262
+ domain_name_or_id=None,
263
+ *,
264
+ validate_actor_existence=True,
265
+ validate_domain_actor_existence=None,
266
+ ):
267
+ if domain_name_or_id is None:
268
+ return _find_sdk_id(
269
+ identity_client.find_project,
270
+ name_or_id=name_or_id,
271
+ validate_actor_existence=validate_actor_existence,
272
+ )
273
+
274
+ if validate_domain_actor_existence is None:
275
+ validate_domain_actor_existence = validate_actor_existence
276
+
277
+ domain_id = find_domain_id_sdk(
278
+ identity_client,
279
+ name_or_id=domain_name_or_id,
280
+ validate_actor_existence=validate_domain_actor_existence,
281
+ )
282
+ return _find_sdk_id(
283
+ identity_client.find_project,
284
+ name_or_id=name_or_id,
285
+ validate_actor_existence=validate_actor_existence,
286
+ domain_id=domain_id,
287
+ )
288
+
289
+
259
290
  def find_user(identity_client, name_or_id, domain_name_or_id=None):
260
291
  if domain_name_or_id is None:
261
292
  return _find_identity_resource(
@@ -160,7 +160,9 @@ class ShowService(command.ShowOne):
160
160
  for service, service_endpoints in endpoints.items():
161
161
  if service_endpoints:
162
162
  info = {"type": service}
163
- info.update(service_endpoints[0])
163
+ # FIXME(stephenfin): The return type for this in ksa is
164
+ # wrong
165
+ info.update(service_endpoints[0]) # type: ignore
164
166
  return zip(*sorted(info.items()))
165
167
 
166
168
  msg = _(
@@ -26,6 +26,15 @@ from openstackclient.i18n import _
26
26
  LOG = logging.getLogger(__name__)
27
27
 
28
28
 
29
+ def _format_protocol(protocol):
30
+ columns = ('name', 'idp_id', 'mapping_id')
31
+ column_headers = ('id', 'identity_provider', 'mapping')
32
+ return (
33
+ column_headers,
34
+ utils.get_item_properties(protocol, columns),
35
+ )
36
+
37
+
29
38
  class CreateProtocol(command.ShowOne):
30
39
  _description = _("Create new federation protocol")
31
40
 
@@ -58,21 +67,15 @@ class CreateProtocol(command.ShowOne):
58
67
  return parser
59
68
 
60
69
  def take_action(self, parsed_args):
61
- identity_client = self.app.client_manager.identity
62
- protocol = identity_client.federation.protocols.create(
63
- protocol_id=parsed_args.federation_protocol,
64
- identity_provider=parsed_args.identity_provider,
65
- mapping=parsed_args.mapping,
70
+ identity_client = self.app.client_manager.sdk_connection.identity
71
+
72
+ protocol = identity_client.create_federation_protocol(
73
+ name=parsed_args.federation_protocol,
74
+ idp_id=parsed_args.identity_provider,
75
+ mapping_id=parsed_args.mapping,
66
76
  )
67
- info = dict(protocol._info)
68
- # NOTE(marek-denis): Identity provider is not included in a response
69
- # from Keystone, however it should be listed to the user. Add it
70
- # manually to the output list, simply reusing value provided by the
71
- # user.
72
- info['identity_provider'] = parsed_args.identity_provider
73
- info['mapping'] = info.pop('mapping_id')
74
- info.pop('links', None)
75
- return zip(*sorted(info.items()))
77
+
78
+ return _format_protocol(protocol)
76
79
 
77
80
 
78
81
  class DeleteProtocol(command.Command):
@@ -99,12 +102,15 @@ class DeleteProtocol(command.Command):
99
102
  return parser
100
103
 
101
104
  def take_action(self, parsed_args):
102
- identity_client = self.app.client_manager.identity
105
+ identity_client = self.app.client_manager.sdk_connection.identity
106
+
103
107
  result = 0
104
108
  for i in parsed_args.federation_protocol:
105
109
  try:
106
- identity_client.federation.protocols.delete(
107
- parsed_args.identity_provider, i
110
+ identity_client.delete_federation_protocol(
111
+ idp_id=parsed_args.identity_provider,
112
+ protocol=i,
113
+ ignore_missing=False,
108
114
  )
109
115
  except Exception as e:
110
116
  result += 1
@@ -140,9 +146,9 @@ class ListProtocols(command.Lister):
140
146
  return parser
141
147
 
142
148
  def take_action(self, parsed_args):
143
- identity_client = self.app.client_manager.identity
149
+ identity_client = self.app.client_manager.sdk_connection.identity
144
150
 
145
- protocols = identity_client.federation.protocols.list(
151
+ protocols = identity_client.federation_protocols(
146
152
  parsed_args.identity_provider
147
153
  )
148
154
  columns = ('id', 'mapping')
@@ -181,21 +187,16 @@ class SetProtocol(command.Command):
181
187
  return parser
182
188
 
183
189
  def take_action(self, parsed_args):
184
- identity_client = self.app.client_manager.identity
190
+ identity_client = self.app.client_manager.sdk_connection.identity
185
191
 
186
- protocol = identity_client.federation.protocols.update(
187
- parsed_args.identity_provider,
188
- parsed_args.federation_protocol,
189
- parsed_args.mapping,
190
- )
191
- info = dict(protocol._info)
192
- # NOTE(marek-denis): Identity provider is not included in a response
193
- # from Keystone, however it should be listed to the user. Add it
194
- # manually to the output list, simply reusing value provided by the
195
- # user.
196
- info['identity_provider'] = parsed_args.identity_provider
197
- info['mapping'] = info.pop('mapping_id')
198
- return zip(*sorted(info.items()))
192
+ kwargs = {'idp_id': parsed_args.identity_provider}
193
+ if parsed_args.federation_protocol:
194
+ kwargs['name'] = parsed_args.federation_protocol
195
+ if parsed_args.mapping:
196
+ kwargs['mapping_id'] = parsed_args.mapping
197
+
198
+ protocol = identity_client.update_federation_protocol(**kwargs)
199
+ return _format_protocol(protocol)
199
200
 
200
201
 
201
202
  class ShowProtocol(command.ShowOne):
@@ -220,12 +221,10 @@ class ShowProtocol(command.ShowOne):
220
221
  return parser
221
222
 
222
223
  def take_action(self, parsed_args):
223
- identity_client = self.app.client_manager.identity
224
+ identity_client = self.app.client_manager.sdk_connection.identity
224
225
 
225
- protocol = identity_client.federation.protocols.get(
226
- parsed_args.identity_provider, parsed_args.federation_protocol
226
+ protocol = identity_client.get_federation_protocol(
227
+ idp_id=parsed_args.identity_provider,
228
+ protocol=parsed_args.federation_protocol,
227
229
  )
228
- info = dict(protocol._info)
229
- info['mapping'] = info.pop('mapping_id')
230
- info.pop('links', None)
231
- return zip(*sorted(info.items()))
230
+ return _format_protocol(protocol)
@@ -25,6 +25,28 @@ from openstackclient.identity import common as common_utils
25
25
  LOG = logging.getLogger(__name__)
26
26
 
27
27
 
28
+ def _format_limit(limit):
29
+ columns = (
30
+ "description",
31
+ "id",
32
+ "project_id",
33
+ "region_id",
34
+ "resource_limit",
35
+ "resource_name",
36
+ "service_id",
37
+ )
38
+ column_headers = (
39
+ "description",
40
+ "id",
41
+ "project_id",
42
+ "region_id",
43
+ "resource_limit",
44
+ "resource_name",
45
+ "service_id",
46
+ )
47
+ return (column_headers, utils.get_item_properties(limit, columns))
48
+
49
+
28
50
  class CreateLimit(command.ShowOne):
29
51
  _description = _("Create a limit")
30
52
 
@@ -46,6 +68,7 @@ class CreateLimit(command.ShowOne):
46
68
  required=True,
47
69
  help=_('Project to associate the resource limit to'),
48
70
  )
71
+ common_utils.add_project_domain_option_to_parser(parser)
49
72
  parser.add_argument(
50
73
  '--service',
51
74
  metavar='<service>',
@@ -67,47 +90,33 @@ class CreateLimit(command.ShowOne):
67
90
  return parser
68
91
 
69
92
  def take_action(self, parsed_args):
70
- identity_client = self.app.client_manager.identity
71
-
72
- project = common_utils.find_project(
73
- identity_client, parsed_args.project
93
+ identity_client = self.app.client_manager.sdk_connection.identity
94
+
95
+ kwargs = {
96
+ "resource_name": parsed_args.resource_name,
97
+ "resource_limit": parsed_args.resource_limit,
98
+ }
99
+ if parsed_args.description:
100
+ kwargs["description"] = parsed_args.description
101
+
102
+ kwargs["project_id"] = common_utils.find_project_id_sdk(
103
+ identity_client,
104
+ parsed_args.project,
105
+ domain_name_or_id=parsed_args.project_domain,
74
106
  )
75
- service = common_utils.find_service(
107
+
108
+ kwargs["service_id"] = common_utils.find_service_sdk(
76
109
  identity_client, parsed_args.service
77
- )
78
- region = None
110
+ ).id
111
+
79
112
  if parsed_args.region:
80
- if 'None' not in parsed_args.region:
81
- # NOTE (vishakha): Due to bug #1799153 and for any another
82
- # related case where GET resource API does not support the
83
- # filter by name, osc_lib.utils.find_resource() method cannot
84
- # be used because that method try to fall back to list all the
85
- # resource if requested resource cannot be get via name. Which
86
- # ends up with NoUniqueMatch error.
87
- # So osc_lib.utils.find_resource() function cannot be used for
88
- # 'regions', using common_utils.get_resource() instead.
89
- region = common_utils.get_resource(
90
- identity_client.regions, parsed_args.region
91
- )
92
- else:
93
- self.log.warning(
94
- _(
95
- "Passing 'None' to indicate no region is deprecated. "
96
- "Instead, don't pass --region."
97
- )
98
- )
113
+ kwargs["region_id"] = identity_client.get_region(
114
+ parsed_args.region
115
+ ).id
99
116
 
100
- limit = identity_client.limits.create(
101
- project,
102
- service,
103
- parsed_args.resource_name,
104
- parsed_args.resource_limit,
105
- description=parsed_args.description,
106
- region=region,
107
- )
117
+ limit = identity_client.create_limit(**kwargs)
108
118
 
109
- limit._info.pop('links', None)
110
- return zip(*sorted(limit._info.items()))
119
+ return _format_limit(limit)
111
120
 
112
121
 
113
122
  class ListLimit(command.Lister):
@@ -136,50 +145,41 @@ class ListLimit(command.Lister):
136
145
  metavar='<project>',
137
146
  help=_('List resource limits associated with project'),
138
147
  )
148
+ common_utils.add_project_domain_option_to_parser(parser)
149
+
139
150
  return parser
140
151
 
141
152
  def take_action(self, parsed_args):
142
- identity_client = self.app.client_manager.identity
153
+ identity_client = self.app.client_manager.sdk_connection.identity
143
154
 
144
- service = None
155
+ kwargs = {}
145
156
  if parsed_args.service:
146
- service = common_utils.find_service(
157
+ kwargs["service_id"] = common_utils.find_service_sdk(
147
158
  identity_client, parsed_args.service
148
159
  )
149
- region = None
160
+
150
161
  if parsed_args.region:
151
- if 'None' not in parsed_args.region:
152
- # NOTE (vishakha): Due to bug #1799153 and for any another
153
- # related case where GET resource API does not support the
154
- # filter by name, osc_lib.utils.find_resource() method cannot
155
- # be used because that method try to fall back to list all the
156
- # resource if requested resource cannot be get via name. Which
157
- # ends up with NoUniqueMatch error.
158
- # So osc_lib.utils.find_resource() function cannot be used for
159
- # 'regions', using common_utils.get_resource() instead.
160
- region = common_utils.get_resource(
161
- identity_client.regions, parsed_args.region
162
- )
163
- else:
164
- self.log.warning(
165
- _(
166
- "Passing 'None' to indicate no region is deprecated. "
167
- "Instead, don't pass --region."
168
- )
169
- )
162
+ kwargs["region_id"] = identity_client.get_region(
163
+ parsed_args.region
164
+ ).id
170
165
 
171
- project = None
172
166
  if parsed_args.project:
173
- project = utils.find_resource(
174
- identity_client.projects, parsed_args.project
167
+ project_domain_id = None
168
+ if parsed_args.project_domain:
169
+ project_domain_id = common_utils.find_domain_id_sdk(
170
+ identity_client, parsed_args.project_domain
171
+ )
172
+
173
+ kwargs["project_id"] = common_utils._find_sdk_id(
174
+ identity_client.find_project,
175
+ name_or_id=parsed_args.project,
176
+ domain_id=project_domain_id,
175
177
  )
176
178
 
177
- limits = identity_client.limits.list(
178
- service=service,
179
- resource_name=parsed_args.resource_name,
180
- region=region,
181
- project=project,
182
- )
179
+ if parsed_args.resource_name:
180
+ kwargs["resource_name"] = parsed_args.resource_name
181
+
182
+ limits = identity_client.limits(**kwargs)
183
183
 
184
184
  columns = (
185
185
  'ID',
@@ -209,10 +209,9 @@ class ShowLimit(command.ShowOne):
209
209
  return parser
210
210
 
211
211
  def take_action(self, parsed_args):
212
- identity_client = self.app.client_manager.identity
213
- limit = identity_client.limits.get(parsed_args.limit_id)
214
- limit._info.pop('links', None)
215
- return zip(*sorted(limit._info.items()))
212
+ identity_client = self.app.client_manager.sdk_connection.identity
213
+ limit = identity_client.get_limit(parsed_args.limit_id)
214
+ return _format_limit(limit)
216
215
 
217
216
 
218
217
  class SetLimit(command.ShowOne):
@@ -240,17 +239,16 @@ class SetLimit(command.ShowOne):
240
239
  return parser
241
240
 
242
241
  def take_action(self, parsed_args):
243
- identity_client = self.app.client_manager.identity
244
-
245
- limit = identity_client.limits.update(
246
- parsed_args.limit_id,
247
- description=parsed_args.description,
248
- resource_limit=parsed_args.resource_limit,
249
- )
242
+ identity_client = self.app.client_manager.sdk_connection.identity
250
243
 
251
- limit._info.pop('links', None)
244
+ kwargs = {}
245
+ if parsed_args.description:
246
+ kwargs["description"] = parsed_args.description
247
+ if parsed_args.resource_limit:
248
+ kwargs["resource_limit"] = parsed_args.resource_limit
249
+ limit = identity_client.update_limit(parsed_args.limit_id, **kwargs)
252
250
 
253
- return zip(*sorted(limit._info.items()))
251
+ return _format_limit(limit)
254
252
 
255
253
 
256
254
  class DeleteLimit(command.Command):
@@ -262,17 +260,20 @@ class DeleteLimit(command.Command):
262
260
  'limit_id',
263
261
  metavar='<limit-id>',
264
262
  nargs="+",
265
- help=_('Limit to delete (ID)'),
263
+ help=_(
264
+ 'Limit to delete (ID) '
265
+ '(repeat option to remove multiple limits)'
266
+ ),
266
267
  )
267
268
  return parser
268
269
 
269
270
  def take_action(self, parsed_args):
270
- identity_client = self.app.client_manager.identity
271
+ identity_client = self.app.client_manager.sdk_connection.identity
271
272
 
272
273
  errors = 0
273
274
  for limit_id in parsed_args.limit_id:
274
275
  try:
275
- identity_client.limits.delete(limit_id)
276
+ identity_client.delete_limit(limit_id)
276
277
  except Exception as e:
277
278
  errors += 1
278
279
  LOG.error(