python-openstackclient 7.1.3__py3-none-any.whl → 7.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 (128) hide show
  1. openstackclient/api/api.py +2 -1
  2. openstackclient/api/image_v2.py +1 -1
  3. openstackclient/api/object_store_v1.py +12 -20
  4. openstackclient/common/clientmanager.py +1 -1
  5. openstackclient/common/module.py +2 -2
  6. openstackclient/common/quota.py +4 -4
  7. openstackclient/compute/v2/flavor.py +1 -1
  8. openstackclient/compute/v2/server.py +122 -59
  9. openstackclient/compute/v2/server_backup.py +1 -1
  10. openstackclient/compute/v2/server_image.py +1 -1
  11. openstackclient/compute/v2/server_migration.py +11 -2
  12. openstackclient/compute/v2/usage.py +3 -3
  13. openstackclient/identity/common.py +1 -1
  14. openstackclient/identity/v2_0/project.py +1 -1
  15. openstackclient/identity/v2_0/role_assignment.py +1 -1
  16. openstackclient/identity/v2_0/user.py +2 -2
  17. openstackclient/identity/v3/access_rule.py +26 -14
  18. openstackclient/identity/v3/identity_provider.py +1 -1
  19. openstackclient/identity/v3/project.py +1 -1
  20. openstackclient/image/v2/image.py +13 -13
  21. openstackclient/image/v2/metadef_objects.py +6 -4
  22. openstackclient/network/common.py +8 -7
  23. openstackclient/network/v2/floating_ip.py +6 -2
  24. openstackclient/network/v2/floating_ip_port_forwarding.py +2 -2
  25. openstackclient/network/v2/l3_conntrack_helper.py +1 -1
  26. openstackclient/network/v2/ndp_proxy.py +1 -0
  27. openstackclient/network/v2/network_agent.py +2 -6
  28. openstackclient/network/v2/network_qos_rule.py +2 -5
  29. openstackclient/network/v2/network_trunk.py +5 -4
  30. openstackclient/network/v2/port.py +18 -3
  31. openstackclient/network/v2/router.py +7 -4
  32. openstackclient/network/v2/subnet_pool.py +2 -2
  33. openstackclient/shell.py +3 -2
  34. openstackclient/tests/functional/common/test_help.py +3 -9
  35. openstackclient/tests/functional/common/test_module.py +1 -1
  36. openstackclient/tests/functional/common/test_quota.py +2 -4
  37. openstackclient/tests/functional/compute/v2/common.py +1 -3
  38. openstackclient/tests/functional/compute/v2/test_hypervisor.py +3 -3
  39. openstackclient/tests/functional/compute/v2/test_keypair.py +2 -2
  40. openstackclient/tests/functional/compute/v2/test_server.py +1 -1
  41. openstackclient/tests/functional/identity/v2/common.py +31 -48
  42. openstackclient/tests/functional/identity/v2/test_catalog.py +1 -1
  43. openstackclient/tests/functional/identity/v2/test_ec2_credentials.py +2 -2
  44. openstackclient/tests/functional/identity/v2/test_endpoint.py +2 -2
  45. openstackclient/tests/functional/identity/v2/test_project.py +8 -8
  46. openstackclient/tests/functional/identity/v2/test_role.py +14 -34
  47. openstackclient/tests/functional/identity/v2/test_service.py +2 -2
  48. openstackclient/tests/functional/identity/v2/test_token.py +1 -1
  49. openstackclient/tests/functional/identity/v2/test_user.py +7 -9
  50. openstackclient/tests/functional/identity/v3/common.py +69 -110
  51. openstackclient/tests/functional/identity/v3/test_access_rule.py +86 -0
  52. openstackclient/tests/functional/identity/v3/test_application_credential.py +18 -44
  53. openstackclient/tests/functional/identity/v3/test_catalog.py +1 -1
  54. openstackclient/tests/functional/identity/v3/test_domain.py +9 -11
  55. openstackclient/tests/functional/identity/v3/test_endpoint.py +15 -27
  56. openstackclient/tests/functional/identity/v3/test_group.py +32 -93
  57. openstackclient/tests/functional/identity/v3/test_idp.py +3 -3
  58. openstackclient/tests/functional/identity/v3/test_limit.py +32 -32
  59. openstackclient/tests/functional/identity/v3/test_project.py +17 -26
  60. openstackclient/tests/functional/identity/v3/test_region.py +6 -7
  61. openstackclient/tests/functional/identity/v3/test_registered_limit.py +27 -36
  62. openstackclient/tests/functional/identity/v3/test_role.py +30 -60
  63. openstackclient/tests/functional/identity/v3/test_role_assignment.py +33 -80
  64. openstackclient/tests/functional/identity/v3/test_service.py +7 -13
  65. openstackclient/tests/functional/identity/v3/test_service_provider.py +3 -3
  66. openstackclient/tests/functional/identity/v3/test_user.py +17 -34
  67. openstackclient/tests/functional/image/v2/test_image.py +1 -3
  68. openstackclient/tests/functional/network/v2/common.py +1 -3
  69. openstackclient/tests/functional/network/v2/test_default_security_group_rule.py +3 -8
  70. openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py +27 -31
  71. openstackclient/tests/functional/network/v2/test_network.py +9 -12
  72. openstackclient/tests/functional/network/v2/test_network_agent.py +15 -20
  73. openstackclient/tests/functional/network/v2/test_network_flavor.py +2 -2
  74. openstackclient/tests/functional/network/v2/test_network_ndp_proxy.py +17 -39
  75. openstackclient/tests/functional/network/v2/test_network_qos_rule.py +48 -63
  76. openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py +1 -1
  77. openstackclient/tests/functional/network/v2/test_network_segment_range.py +2 -2
  78. openstackclient/tests/functional/network/v2/test_network_trunk.py +15 -25
  79. openstackclient/tests/functional/network/v2/test_port.py +28 -34
  80. openstackclient/tests/functional/network/v2/test_router.py +13 -19
  81. openstackclient/tests/functional/object/v1/test_object.py +4 -7
  82. openstackclient/tests/functional/volume/base.py +5 -17
  83. openstackclient/tests/functional/volume/v1/test_volume_type.py +11 -11
  84. openstackclient/tests/functional/volume/v2/test_volume_backup.py +1 -1
  85. openstackclient/tests/functional/volume/v2/test_volume_type.py +13 -15
  86. openstackclient/tests/functional/volume/v3/test_volume_type.py +13 -15
  87. openstackclient/tests/unit/api/test_compute_v2.py +0 -5
  88. openstackclient/tests/unit/api/test_object_store_v1.py +6 -4
  89. openstackclient/tests/unit/common/test_extension.py +24 -31
  90. openstackclient/tests/unit/compute/v2/test_host.py +0 -1
  91. openstackclient/tests/unit/compute/v2/test_server.py +123 -115
  92. openstackclient/tests/unit/identity/v3/test_access_rule.py +65 -64
  93. openstackclient/tests/unit/identity/v3/test_group.py +4 -10
  94. openstackclient/tests/unit/identity/v3/test_limit.py +2 -2
  95. openstackclient/tests/unit/image/v2/test_metadef_objects.py +1 -2
  96. openstackclient/tests/unit/image/v2/test_metadef_resource_type_association.py +2 -6
  97. openstackclient/tests/unit/integ/base.py +1 -1
  98. openstackclient/tests/unit/network/v2/test_default_security_group_rule.py +3 -3
  99. openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +4 -4
  100. openstackclient/tests/unit/network/v2/test_local_ip_association.py +2 -2
  101. openstackclient/tests/unit/network/v2/test_network_qos_rule.py +12 -13
  102. openstackclient/tests/unit/network/v2/test_network_trunk.py +31 -35
  103. openstackclient/tests/unit/network/v2/test_port.py +40 -17
  104. openstackclient/tests/unit/network/v2/test_subnet_pool.py +1 -1
  105. openstackclient/tests/unit/object/v1/test_object.py +1 -1
  106. openstackclient/tests/unit/utils.py +2 -2
  107. openstackclient/volume/client.py +1 -1
  108. openstackclient/volume/v1/volume.py +2 -2
  109. openstackclient/volume/v1/volume_backup.py +2 -2
  110. openstackclient/volume/v1/volume_snapshot.py +2 -2
  111. openstackclient/volume/v2/volume.py +2 -2
  112. openstackclient/volume/v2/volume_backup.py +2 -2
  113. openstackclient/volume/v2/volume_snapshot.py +2 -2
  114. openstackclient/volume/v2/volume_type.py +4 -4
  115. openstackclient/volume/v3/service.py +0 -1
  116. openstackclient/volume/v3/volume.py +3 -3
  117. openstackclient/volume/v3/volume_backup.py +2 -2
  118. openstackclient/volume/v3/volume_group.py +3 -7
  119. openstackclient/volume/v3/volume_type.py +6 -6
  120. {python_openstackclient-7.1.3.dist-info → python_openstackclient-7.2.0.dist-info}/AUTHORS +3 -0
  121. {python_openstackclient-7.1.3.dist-info → python_openstackclient-7.2.0.dist-info}/METADATA +2 -3
  122. {python_openstackclient-7.1.3.dist-info → python_openstackclient-7.2.0.dist-info}/RECORD +127 -126
  123. python_openstackclient-7.2.0.dist-info/pbr.json +1 -0
  124. python_openstackclient-7.1.3.dist-info/pbr.json +0 -1
  125. {python_openstackclient-7.1.3.dist-info → python_openstackclient-7.2.0.dist-info}/LICENSE +0 -0
  126. {python_openstackclient-7.1.3.dist-info → python_openstackclient-7.2.0.dist-info}/WHEEL +0 -0
  127. {python_openstackclient-7.1.3.dist-info → python_openstackclient-7.2.0.dist-info}/entry_points.txt +0 -0
  128. {python_openstackclient-7.1.3.dist-info → python_openstackclient-7.2.0.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@
12
12
  #
13
13
 
14
14
  """Base API Library"""
15
+
15
16
  from keystoneauth1 import exceptions as ks_exceptions
16
17
  from keystoneauth1 import session as ks_session
17
18
  from osc_lib import exceptions
@@ -306,7 +307,7 @@ class BaseAPI(KeystoneSession):
306
307
  except ks_exceptions.NotFound:
307
308
  kwargs = {attr: value}
308
309
  try:
309
- ret = self.find_one("/%s/detail" % (path), **kwargs)
310
+ ret = self.find_one(f"/{path}/detail", **kwargs)
310
311
  except ks_exceptions.NotFound:
311
312
  msg = _("%s not found") % value
312
313
  raise exceptions.NotFound(msg)
@@ -33,7 +33,7 @@ class APIv2(image_v1.APIv1):
33
33
  private=False,
34
34
  community=False,
35
35
  shared=False,
36
- **filter
36
+ **filter,
37
37
  ):
38
38
  """Get available images
39
39
 
@@ -89,7 +89,7 @@ class APIv1(api.BaseAPI):
89
89
  marker=None,
90
90
  end_marker=None,
91
91
  prefix=None,
92
- **params
92
+ **params,
93
93
  ):
94
94
  """Get containers in an account
95
95
 
@@ -116,7 +116,7 @@ class APIv1(api.BaseAPI):
116
116
  marker=marker,
117
117
  end_marker=end_marker,
118
118
  prefix=prefix,
119
- **params
119
+ **params,
120
120
  )
121
121
  while listing:
122
122
  marker = listing[-1]['name']
@@ -125,7 +125,7 @@ class APIv1(api.BaseAPI):
125
125
  marker=marker,
126
126
  end_marker=end_marker,
127
127
  prefix=prefix,
128
- **params
128
+ **params,
129
129
  )
130
130
  if listing:
131
131
  data.extend(listing)
@@ -256,10 +256,7 @@ class APIv1(api.BaseAPI):
256
256
  # object's name in the container.
257
257
  object_name_str = name if name else object
258
258
 
259
- full_url = "{}/{}".format(
260
- urllib.parse.quote(container),
261
- urllib.parse.quote(object_name_str),
262
- )
259
+ full_url = f"{urllib.parse.quote(container)}/{urllib.parse.quote(object_name_str)}"
263
260
  with open(object, 'rb') as f:
264
261
  response = self.create(
265
262
  full_url,
@@ -293,8 +290,7 @@ class APIv1(api.BaseAPI):
293
290
  return
294
291
 
295
292
  self.delete(
296
- "%s/%s"
297
- % (urllib.parse.quote(container), urllib.parse.quote(object))
293
+ f"{urllib.parse.quote(container)}/{urllib.parse.quote(object)}"
298
294
  )
299
295
 
300
296
  def object_list(
@@ -306,7 +302,7 @@ class APIv1(api.BaseAPI):
306
302
  end_marker=None,
307
303
  delimiter=None,
308
304
  prefix=None,
309
- **params
305
+ **params,
310
306
  ):
311
307
  """List objects in a container
312
308
 
@@ -341,7 +337,7 @@ class APIv1(api.BaseAPI):
341
337
  end_marker=end_marker,
342
338
  prefix=prefix,
343
339
  delimiter=delimiter,
344
- **params
340
+ **params,
345
341
  )
346
342
  while listing:
347
343
  if delimiter:
@@ -355,7 +351,7 @@ class APIv1(api.BaseAPI):
355
351
  end_marker=end_marker,
356
352
  prefix=prefix,
357
353
  delimiter=delimiter,
358
- **params
354
+ **params,
359
355
  )
360
356
  if listing:
361
357
  data.extend(listing)
@@ -395,8 +391,7 @@ class APIv1(api.BaseAPI):
395
391
 
396
392
  response = self._request(
397
393
  'GET',
398
- "%s/%s"
399
- % (urllib.parse.quote(container), urllib.parse.quote(object)),
394
+ f"{urllib.parse.quote(container)}/{urllib.parse.quote(object)}",
400
395
  stream=True,
401
396
  )
402
397
  if response.status_code == 200:
@@ -431,8 +426,7 @@ class APIv1(api.BaseAPI):
431
426
  headers = self._set_properties(properties, 'X-Object-Meta-%s')
432
427
  if headers:
433
428
  self.create(
434
- "%s/%s"
435
- % (urllib.parse.quote(container), urllib.parse.quote(object)),
429
+ f"{urllib.parse.quote(container)}/{urllib.parse.quote(object)}",
436
430
  headers=headers,
437
431
  )
438
432
 
@@ -455,8 +449,7 @@ class APIv1(api.BaseAPI):
455
449
  headers = self._unset_properties(properties, 'X-Remove-Object-Meta-%s')
456
450
  if headers:
457
451
  self.create(
458
- "%s/%s"
459
- % (urllib.parse.quote(container), urllib.parse.quote(object)),
452
+ f"{urllib.parse.quote(container)}/{urllib.parse.quote(object)}",
460
453
  headers=headers,
461
454
  )
462
455
 
@@ -480,8 +473,7 @@ class APIv1(api.BaseAPI):
480
473
 
481
474
  response = self._request(
482
475
  'HEAD',
483
- "%s/%s"
484
- % (urllib.parse.quote(container), urllib.parse.quote(object)),
476
+ f"{urllib.parse.quote(container)}/{urllib.parse.quote(object)}",
485
477
  )
486
478
 
487
479
  data = {
@@ -101,7 +101,7 @@ class ClientManager(clientmanager.ClientManager):
101
101
  # expect, delete fake token and endpoint, then try to
102
102
  # load auth plugin again with user specified options.
103
103
  # We know it looks ugly, but it's necessary.
104
- if self._cli_options.config['auth']['token'] == 'x':
104
+ if self._cli_options.config['auth']['token'] == 'x': # noqa: S105
105
105
  # restore original auth_type
106
106
  self._cli_options.config['auth_type'] = self._original_auth_type
107
107
  del self._cli_options.config['auth']['token']
@@ -111,8 +111,8 @@ class ListModule(command.ShowOne):
111
111
  data[k] = mods[k].version.__version__
112
112
  else:
113
113
  data[k] = mods[k].__version__
114
- except Exception:
114
+ except Exception: # noqa: S110
115
115
  # Catch all exceptions, just skip it
116
- pass # nosec: B110
116
+ pass
117
117
 
118
118
  return zip(*sorted(data.items()))
@@ -176,7 +176,7 @@ def get_network_quotas(
176
176
  default=False,
177
177
  ):
178
178
  def _network_quota_to_dict(network_quota, detail=False):
179
- if type(network_quota) is not dict:
179
+ if not isinstance(network_quota, dict):
180
180
  dict_quota = network_quota.to_dict()
181
181
  else:
182
182
  dict_quota = network_quota
@@ -507,8 +507,8 @@ class SetQuota(common.NetDetectionMixin, command.Command):
507
507
  )
508
508
  for k, v, h in self._build_options_list():
509
509
  parser.add_argument(
510
- '--%s' % v,
511
- metavar='<%s>' % v,
510
+ f'--{v}',
511
+ metavar=f'<{v}>',
512
512
  dest=k,
513
513
  type=int,
514
514
  help=h,
@@ -590,7 +590,7 @@ class SetQuota(common.NetDetectionMixin, command.Command):
590
590
  parsed_args.volume_type
591
591
  and k in IMPACT_VOLUME_TYPE_QUOTAS
592
592
  ):
593
- k = k + '_%s' % parsed_args.volume_type
593
+ k = k + f'_{parsed_args.volume_type}'
594
594
  volume_kwargs[k] = value
595
595
 
596
596
  if self.app.client_manager.is_network_endpoint_enabled():
@@ -577,7 +577,7 @@ class UnsetFlavor(command.Command):
577
577
  parsed_args.flavor, get_extra_specs=True, ignore_missing=False
578
578
  )
579
579
  except sdk_exceptions.ResourceNotFound as e:
580
- raise exceptions.CommandError(_(e.message))
580
+ raise exceptions.CommandError(e.message)
581
581
 
582
582
  result = 0
583
583
  if parsed_args.properties:
@@ -670,7 +670,7 @@ class AddNetwork(command.Command):
670
670
 
671
671
 
672
672
  class AddServerSecurityGroup(command.Command):
673
- _description = _("Add security group to server")
673
+ _description = _("Add security group(s) to server")
674
674
 
675
675
  def get_parser(self, prog_name):
676
676
  parser = super().get_parser(prog_name)
@@ -680,9 +680,13 @@ class AddServerSecurityGroup(command.Command):
680
680
  help=_('Server (name or ID)'),
681
681
  )
682
682
  parser.add_argument(
683
- 'group',
684
- metavar='<group>',
685
- help=_('Security group to add (name or ID)'),
683
+ 'security_groups',
684
+ metavar='<security-group>',
685
+ nargs='+',
686
+ help=_(
687
+ 'Security group(s) to add to the server (name or ID) '
688
+ '(repeat option to add multiple groups)'
689
+ ),
686
690
  )
687
691
  return parser
688
692
 
@@ -694,14 +698,43 @@ class AddServerSecurityGroup(command.Command):
694
698
  )
695
699
  if self.app.client_manager.is_network_endpoint_enabled():
696
700
  # the server handles both names and IDs for neutron SGs, so just
697
- # pass things through
698
- security_group = parsed_args.group
701
+ # pass things through if using neutron
702
+ security_groups = parsed_args.security_groups
699
703
  else:
700
- # however, if using nova-network then it needs a name, not an ID
701
- security_group = compute_v2.find_security_group(
702
- compute_client, parsed_args.group
703
- )['name']
704
- compute_client.add_security_group_to_server(server, security_group)
704
+ # however, if using nova-network then it needs names, not IDs
705
+ security_groups = []
706
+ for security_group in parsed_args.security_groups:
707
+ security_groups.append(
708
+ compute_v2.find_security_group(
709
+ compute_client, security_group
710
+ )['name']
711
+ )
712
+
713
+ errors = 0
714
+ for security_group in security_groups:
715
+ try:
716
+ compute_client.add_security_group_to_server(
717
+ server, security_group
718
+ )
719
+ except sdk_exceptions.HttpException as e:
720
+ errors += 1
721
+ LOG.error(
722
+ _(
723
+ "Failed to add security group with name or ID "
724
+ "'%(security_group)s' to server '%(server)s': %(e)s"
725
+ ),
726
+ {
727
+ 'security_group': security_group,
728
+ 'server': server.id,
729
+ 'e': e,
730
+ },
731
+ )
732
+
733
+ if errors > 0:
734
+ msg = _(
735
+ "%(errors)d of %(total)d security groups were not added."
736
+ ) % {'errors': errors, 'total': len(security_groups)}
737
+ raise exceptions.CommandError(msg)
705
738
 
706
739
 
707
740
  class AddServerVolume(command.ShowOne):
@@ -1328,6 +1361,7 @@ class CreateServer(command.ShowOne):
1328
1361
  metavar='<security-group>',
1329
1362
  action='append',
1330
1363
  default=[],
1364
+ dest='security_groups',
1331
1365
  help=_(
1332
1366
  'Security group to assign to this server (name or ID) '
1333
1367
  '(repeat option to set multiple groups)'
@@ -1504,7 +1538,7 @@ class CreateServer(command.ShowOne):
1504
1538
  def take_action(self, parsed_args):
1505
1539
  def _show_progress(progress):
1506
1540
  if progress:
1507
- self.app.stdout.write('\rProgress: %s' % progress)
1541
+ self.app.stdout.write(f'\rProgress: {progress}')
1508
1542
  self.app.stdout.flush()
1509
1543
 
1510
1544
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -1520,17 +1554,6 @@ class CreateServer(command.ShowOne):
1520
1554
 
1521
1555
  if not image and parsed_args.image_properties:
1522
1556
 
1523
- def emit_duplicated_warning(img):
1524
- img_uuid_list = [str(image.id) for image in img]
1525
- LOG.warning(
1526
- 'Multiple matching images: %(img_uuid_list)s\n'
1527
- 'Using image: %(chosen_one)s',
1528
- {
1529
- 'img_uuid_list': img_uuid_list,
1530
- 'chosen_one': img_uuid_list[0],
1531
- },
1532
- )
1533
-
1534
1557
  def _match_image(image_api, wanted_properties):
1535
1558
  image_list = image_api.images()
1536
1559
  images_matched = []
@@ -1568,7 +1591,15 @@ class CreateServer(command.ShowOne):
1568
1591
 
1569
1592
  images = _match_image(image_client, parsed_args.image_properties)
1570
1593
  if len(images) > 1:
1571
- emit_duplicated_warning(images, parsed_args.image_properties)
1594
+ img_uuid_list = [str(image.id) for image in images]
1595
+ LOG.warning(
1596
+ 'Multiple matching images: %(img_uuid_list)s\n'
1597
+ 'Using image: %(chosen_one)s',
1598
+ {
1599
+ 'img_uuid_list': img_uuid_list,
1600
+ 'chosen_one': img_uuid_list[0],
1601
+ },
1602
+ )
1572
1603
  if images:
1573
1604
  image = images[0]
1574
1605
  else:
@@ -1948,21 +1979,22 @@ class CreateServer(command.ShowOne):
1948
1979
  # 'auto' to maintain legacy behavior if a nic wasn't specified.
1949
1980
  networks = 'auto'
1950
1981
 
1951
- # Check security group exist and convert ID to name
1982
+ # Check security group(s) exist and convert ID to name
1952
1983
  security_groups = []
1953
1984
  if self.app.client_manager.is_network_endpoint_enabled():
1954
1985
  network_client = self.app.client_manager.network
1955
- for each_sg in parsed_args.security_group:
1986
+ for security_group in parsed_args.security_groups:
1956
1987
  sg = network_client.find_security_group(
1957
- each_sg, ignore_missing=False
1988
+ security_group, ignore_missing=False
1958
1989
  )
1959
1990
  # Use security group ID to avoid multiple security group have
1960
1991
  # same name in neutron networking backend
1961
1992
  security_groups.append({'name': sg.id})
1962
- else:
1963
- # Handle nova-network case
1964
- for each_sg in parsed_args.security_group:
1965
- sg = compute_v2.find_security_group(compute_client, each_sg)
1993
+ else: # nova-network
1994
+ for security_group in parsed_args.security_groups:
1995
+ sg = compute_v2.find_security_group(
1996
+ compute_client, security_group
1997
+ )
1966
1998
  security_groups.append({'name': sg['name']})
1967
1999
 
1968
2000
  hints = {}
@@ -2189,7 +2221,7 @@ class DeleteServer(command.Command):
2189
2221
  def take_action(self, parsed_args):
2190
2222
  def _show_progress(progress):
2191
2223
  if progress:
2192
- self.app.stdout.write('\rProgress: %s' % progress)
2224
+ self.app.stdout.write(f'\rProgress: {progress}')
2193
2225
  self.app.stdout.flush()
2194
2226
 
2195
2227
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -2915,10 +2947,10 @@ class ListServer(command.Lister):
2915
2947
  for image_id in image_ids:
2916
2948
  try:
2917
2949
  images[image_id] = image_client.get_image(image_id)
2918
- except Exception:
2950
+ except Exception: # noqa: S110
2919
2951
  # retrieving image names is not crucial, so we swallow
2920
2952
  # any exceptions
2921
- pass # nosec: B110
2953
+ pass
2922
2954
  else:
2923
2955
  try:
2924
2956
  # some deployments can have *loads* of images so we only
@@ -2936,10 +2968,10 @@ class ListServer(command.Lister):
2936
2968
  )
2937
2969
  for i in images_list:
2938
2970
  images[i.id] = i
2939
- except Exception:
2971
+ except Exception: # noqa: S110
2940
2972
  # retrieving image names is not crucial, so we swallow any
2941
2973
  # exceptions
2942
- pass # nosec: B110
2974
+ pass
2943
2975
 
2944
2976
  # create a dict that maps flavor_id to flavor object, which is used
2945
2977
  # to display the "Flavor Name" column. Note that 'flavor.id' is not
@@ -2955,19 +2987,19 @@ class ListServer(command.Lister):
2955
2987
  flavors[f_id] = compute_client.find_flavor(
2956
2988
  f_id, ignore_missing=False
2957
2989
  )
2958
- except Exception:
2990
+ except Exception: # noqa: S110
2959
2991
  # retrieving flavor names is not crucial, so we swallow
2960
2992
  # any exceptions
2961
- pass # nosec: B110
2993
+ pass
2962
2994
  else:
2963
2995
  try:
2964
2996
  flavors_list = compute_client.flavors(is_public=None)
2965
2997
  for i in flavors_list:
2966
2998
  flavors[i.id] = i
2967
- except Exception:
2999
+ except Exception: # noqa: S110
2968
3000
  # retrieving flavor names is not crucial, so we swallow any
2969
3001
  # exceptions
2970
- pass # nosec: B110
3002
+ pass
2971
3003
 
2972
3004
  # Populate image_name, image_id, flavor_name and flavor_id attributes
2973
3005
  # of server objects so that we can display those columns.
@@ -3198,7 +3230,7 @@ revert to release the new server and restart the old one."""
3198
3230
  def take_action(self, parsed_args):
3199
3231
  def _show_progress(progress):
3200
3232
  if progress:
3201
- self.app.stdout.write('\rProgress: %s' % progress)
3233
+ self.app.stdout.write(f'\rProgress: {progress}')
3202
3234
  self.app.stdout.flush()
3203
3235
 
3204
3236
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -3350,7 +3382,7 @@ class RebootServer(command.Command):
3350
3382
  def take_action(self, parsed_args):
3351
3383
  def _show_progress(progress):
3352
3384
  if progress:
3353
- self.app.stdout.write('\rProgress: %s' % progress)
3385
+ self.app.stdout.write(f'\rProgress: {progress}')
3354
3386
  self.app.stdout.flush()
3355
3387
 
3356
3388
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -3555,7 +3587,7 @@ class RebuildServer(command.ShowOne):
3555
3587
  def take_action(self, parsed_args):
3556
3588
  def _show_progress(progress):
3557
3589
  if progress:
3558
- self.app.stdout.write('\rProgress: %s' % progress)
3590
+ self.app.stdout.write(f'\rProgress: {progress}')
3559
3591
  self.app.stdout.flush()
3560
3592
 
3561
3593
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -3816,7 +3848,7 @@ host."""
3816
3848
  def take_action(self, parsed_args):
3817
3849
  def _show_progress(progress):
3818
3850
  if progress:
3819
- self.app.stdout.write('\rProgress: %s' % progress)
3851
+ self.app.stdout.write(f'\rProgress: {progress}')
3820
3852
  self.app.stdout.flush()
3821
3853
 
3822
3854
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -4017,9 +4049,13 @@ class RemoveServerSecurityGroup(command.Command):
4017
4049
  help=_('Server (name or ID)'),
4018
4050
  )
4019
4051
  parser.add_argument(
4020
- 'group',
4021
- metavar='<group>',
4022
- help=_('Security group to remove (name or ID)'),
4052
+ 'security_groups',
4053
+ metavar='<security-group>',
4054
+ nargs='+',
4055
+ help=_(
4056
+ 'Security group(s) to remove from server (name or ID) '
4057
+ '(repeat option to remove multiple groups)'
4058
+ ),
4023
4059
  )
4024
4060
  return parser
4025
4061
 
@@ -4032,15 +4068,42 @@ class RemoveServerSecurityGroup(command.Command):
4032
4068
  if self.app.client_manager.is_network_endpoint_enabled():
4033
4069
  # the server handles both names and IDs for neutron SGs, so just
4034
4070
  # pass things through
4035
- security_group = parsed_args.group
4071
+ security_groups = parsed_args.security_groups
4036
4072
  else:
4037
- # however, if using nova-network then it needs a name, not an ID
4038
- security_group = compute_v2.find_security_group(
4039
- compute_client, parsed_args.group
4040
- )['name']
4041
- compute_client.remove_security_group_from_server(
4042
- server, security_group
4043
- )
4073
+ # however, if using nova-network then it needs names, not IDs
4074
+ security_groups = []
4075
+ for security_group in parsed_args.security_groups:
4076
+ security_groups.append(
4077
+ compute_v2.find_security_group(
4078
+ compute_client, security_group
4079
+ )['name']
4080
+ )
4081
+
4082
+ errors = 0
4083
+ for security_group in security_groups:
4084
+ try:
4085
+ compute_client.remove_security_group_from_server(
4086
+ server, security_group
4087
+ )
4088
+ except sdk_exceptions.HttpException as e:
4089
+ errors += 1
4090
+ LOG.error(
4091
+ _(
4092
+ "Failed to remove security group with name or ID "
4093
+ "'%(security_group)s' from server '%(server)s': %(e)s"
4094
+ ),
4095
+ {
4096
+ 'security_group': security_group,
4097
+ 'server': server.id,
4098
+ 'e': e,
4099
+ },
4100
+ )
4101
+
4102
+ if errors > 0:
4103
+ msg = _(
4104
+ "%(errors)d of %(total)d security groups were not removed."
4105
+ ) % {'errors': errors, 'total': len(security_groups)}
4106
+ raise exceptions.CommandError(msg)
4044
4107
 
4045
4108
 
4046
4109
  class RemoveServerVolume(command.Command):
@@ -4188,7 +4251,7 @@ release the new server and restart the old one."""
4188
4251
  def take_action(self, parsed_args):
4189
4252
  def _show_progress(progress):
4190
4253
  if progress:
4191
- self.app.stdout.write('\rProgress: %s' % progress)
4254
+ self.app.stdout.write(f'\rProgress: {progress}')
4192
4255
  self.app.stdout.flush()
4193
4256
 
4194
4257
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -4584,7 +4647,7 @@ class ShelveServer(command.Command):
4584
4647
  def take_action(self, parsed_args):
4585
4648
  def _show_progress(progress):
4586
4649
  if progress:
4587
- self.app.stdout.write('\rProgress: %s' % progress)
4650
+ self.app.stdout.write(f'\rProgress: {progress}')
4588
4651
  self.app.stdout.flush()
4589
4652
 
4590
4653
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -4872,7 +4935,7 @@ class SshServer(command.Command):
4872
4935
  LOG.debug(f"ssh command: {cmd}")
4873
4936
  # we intentionally pass through user-provided arguments and run this in
4874
4937
  # the user's shell
4875
- os.system(cmd) # nosec: B605
4938
+ os.system(cmd) # noqa: S605
4876
4939
 
4877
4940
 
4878
4941
  class StartServer(command.Command):
@@ -5181,7 +5244,7 @@ class UnshelveServer(command.Command):
5181
5244
  def take_action(self, parsed_args):
5182
5245
  def _show_progress(progress):
5183
5246
  if progress:
5184
- self.app.stdout.write('\rProgress: %s' % progress)
5247
+ self.app.stdout.write(f'\rProgress: {progress}')
5185
5248
  self.app.stdout.flush()
5186
5249
 
5187
5250
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -68,7 +68,7 @@ class CreateServerBackup(command.ShowOne):
68
68
  def take_action(self, parsed_args):
69
69
  def _show_progress(progress):
70
70
  if progress:
71
- self.app.stderr.write('\rProgress: %s' % progress)
71
+ self.app.stderr.write(f'\rProgress: {progress}')
72
72
  self.app.stderr.flush()
73
73
 
74
74
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -69,7 +69,7 @@ class CreateServerImage(command.ShowOne):
69
69
  def take_action(self, parsed_args):
70
70
  def _show_progress(progress):
71
71
  if progress:
72
- self.app.stdout.write('\rProgress: %s' % progress)
72
+ self.app.stdout.write(f'\rProgress: {progress}')
73
73
  self.app.stdout.flush()
74
74
 
75
75
  compute_client = self.app.client_manager.sdk_connection.compute
@@ -253,8 +253,17 @@ def _get_migration_by_uuid(compute_client, server_id, migration_uuid):
253
253
  if migration.uuid == migration_uuid:
254
254
  return migration
255
255
  else:
256
- msg = _('In-progress live migration %s is not found for server %s.')
257
- raise exceptions.CommandError(msg % (migration_uuid, server_id))
256
+ msg = _(
257
+ 'In-progress live migration %(migration)s is not found for '
258
+ 'server %(server)s'
259
+ )
260
+ raise exceptions.CommandError(
261
+ msg
262
+ % {
263
+ 'migration': migration_uuid,
264
+ 'server': server_id,
265
+ }
266
+ )
258
267
 
259
268
 
260
269
  class ShowMigration(command.ShowOne):
@@ -60,7 +60,7 @@ class CountColumn(cliff_columns.FormattableColumn):
60
60
 
61
61
  class FloatColumn(cliff_columns.FormattableColumn):
62
62
  def human_readable(self):
63
- return float("%.2f" % self._value)
63
+ return float(f"{self._value:.2f}")
64
64
 
65
65
 
66
66
  def _formatters(project_cache):
@@ -180,9 +180,9 @@ class ListUsage(command.Lister):
180
180
  try:
181
181
  for p in self.app.client_manager.identity.projects.list():
182
182
  project_cache[p.id] = p
183
- except Exception:
183
+ except Exception: # noqa: S110
184
184
  # Just forget it if there's any trouble
185
- pass # nosec: B110
185
+ pass
186
186
 
187
187
  if parsed_args.formatter == 'table' and len(usage_list) > 0:
188
188
  self.app.stdout.write(
@@ -34,7 +34,7 @@ def find_service_in_list(service_list, service_id):
34
34
  if service.id == service_id:
35
35
  return service
36
36
  raise exceptions.CommandError(
37
- "No service with a type, name or ID of '%s' exists." % service_id
37
+ f"No service with a type, name or ID of '{service_id}' exists."
38
38
  )
39
39
 
40
40
 
@@ -87,7 +87,7 @@ class CreateProject(command.ShowOne):
87
87
  parsed_args.name,
88
88
  description=parsed_args.description,
89
89
  enabled=enabled,
90
- **kwargs
90
+ **kwargs,
91
91
  )
92
92
  except ks_exc.Conflict:
93
93
  if parsed_args.or_show:
@@ -11,7 +11,7 @@
11
11
  # under the License.
12
12
  #
13
13
 
14
- """Identity v2 Assignment action implementations """
14
+ """Identity v2 Assignment action implementations"""
15
15
 
16
16
  from osc_lib.command import command
17
17
  from osc_lib import exceptions
@@ -250,9 +250,9 @@ class ListUser(command.Lister):
250
250
  try:
251
251
  for p in identity_client.tenants.list():
252
252
  project_cache[p.id] = p
253
- except Exception:
253
+ except Exception: # noqa: S110
254
254
  # Just forget it if there's any trouble
255
- pass # nosec: B110
255
+ pass
256
256
  formatters['tenantId'] = functools.partial(
257
257
  ProjectColumn, project_cache=project_cache
258
258
  )