python-openstackclient 9.0.0__py3-none-any.whl → 10.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 (284) hide show
  1. openstackclient/__init__.py +2 -6
  2. openstackclient/api/api.py +41 -23
  3. openstackclient/api/compute_v2.py +44 -25
  4. openstackclient/api/object_store_v1.py +75 -97
  5. openstackclient/api/volume_v2.py +2 -1
  6. openstackclient/api/volume_v3.py +2 -1
  7. openstackclient/common/availability_zone.py +58 -42
  8. openstackclient/common/clientmanager.py +56 -29
  9. openstackclient/common/configuration.py +10 -3
  10. openstackclient/common/envvars.py +2 -2
  11. openstackclient/common/extension.py +14 -5
  12. openstackclient/common/limits.py +10 -5
  13. openstackclient/common/module.py +14 -6
  14. openstackclient/common/pagination.py +8 -2
  15. openstackclient/common/progressbar.py +7 -6
  16. openstackclient/common/project_cleanup.py +13 -7
  17. openstackclient/common/quota.py +97 -99
  18. openstackclient/common/versions.py +8 -2
  19. openstackclient/compute/client.py +7 -3
  20. openstackclient/compute/v2/agent.py +17 -10
  21. openstackclient/compute/v2/aggregate.py +36 -22
  22. openstackclient/compute/v2/console.py +14 -8
  23. openstackclient/compute/v2/console_connection.py +11 -3
  24. openstackclient/compute/v2/flavor.py +39 -21
  25. openstackclient/compute/v2/host.py +14 -6
  26. openstackclient/compute/v2/hypervisor.py +14 -5
  27. openstackclient/compute/v2/hypervisor_stats.py +10 -2
  28. openstackclient/compute/v2/keypair.py +29 -14
  29. openstackclient/compute/v2/server.py +249 -169
  30. openstackclient/compute/v2/server_backup.py +10 -4
  31. openstackclient/compute/v2/server_event.py +21 -12
  32. openstackclient/compute/v2/server_group.py +21 -11
  33. openstackclient/compute/v2/server_image.py +19 -10
  34. openstackclient/compute/v2/server_migration.py +24 -10
  35. openstackclient/compute/v2/server_share.py +274 -0
  36. openstackclient/compute/v2/server_volume.py +10 -4
  37. openstackclient/compute/v2/service.py +14 -7
  38. openstackclient/compute/v2/usage.py +26 -21
  39. openstackclient/identity/client.py +8 -3
  40. openstackclient/identity/common.py +78 -47
  41. openstackclient/identity/v2_0/catalog.py +14 -7
  42. openstackclient/identity/v2_0/ec2creds.py +21 -10
  43. openstackclient/identity/v2_0/endpoint.py +23 -11
  44. openstackclient/identity/v2_0/project.py +25 -14
  45. openstackclient/identity/v2_0/role.py +28 -14
  46. openstackclient/identity/v2_0/role_assignment.py +9 -3
  47. openstackclient/identity/v2_0/service.py +23 -11
  48. openstackclient/identity/v2_0/token.py +12 -5
  49. openstackclient/identity/v2_0/user.py +26 -15
  50. openstackclient/identity/v3/access_rule.py +26 -12
  51. openstackclient/identity/v3/application_credential.py +59 -24
  52. openstackclient/identity/v3/catalog.py +14 -7
  53. openstackclient/identity/v3/consumer.py +22 -11
  54. openstackclient/identity/v3/credential.py +36 -16
  55. openstackclient/identity/v3/domain.py +37 -18
  56. openstackclient/identity/v3/ec2creds.py +25 -12
  57. openstackclient/identity/v3/endpoint.py +42 -20
  58. openstackclient/identity/v3/endpoint_group.py +28 -17
  59. openstackclient/identity/v3/federation_protocol.py +38 -16
  60. openstackclient/identity/v3/group.py +55 -32
  61. openstackclient/identity/v3/identity_provider.py +92 -57
  62. openstackclient/identity/v3/implied_role.py +21 -9
  63. openstackclient/identity/v3/limit.py +38 -16
  64. openstackclient/identity/v3/mapping.py +26 -13
  65. openstackclient/identity/v3/policy.py +23 -12
  66. openstackclient/identity/v3/project.py +43 -23
  67. openstackclient/identity/v3/region.py +36 -16
  68. openstackclient/identity/v3/registered_limit.py +40 -16
  69. openstackclient/identity/v3/role.py +61 -31
  70. openstackclient/identity/v3/role_assignment.py +23 -6
  71. openstackclient/identity/v3/service.py +36 -16
  72. openstackclient/identity/v3/service_provider.py +37 -15
  73. openstackclient/identity/v3/tag.py +23 -6
  74. openstackclient/identity/v3/token.py +30 -14
  75. openstackclient/identity/v3/trust.py +32 -14
  76. openstackclient/identity/v3/unscoped_saml.py +10 -2
  77. openstackclient/identity/v3/user.py +49 -26
  78. openstackclient/image/client.py +7 -3
  79. openstackclient/image/v1/image.py +33 -26
  80. openstackclient/image/v2/cache.py +14 -9
  81. openstackclient/image/v2/image.py +74 -48
  82. openstackclient/image/v2/info.py +7 -1
  83. openstackclient/image/v2/metadef_namespaces.py +109 -13
  84. openstackclient/image/v2/metadef_objects.py +28 -15
  85. openstackclient/image/v2/metadef_properties.py +24 -13
  86. openstackclient/image/v2/metadef_resource_type_association.py +14 -7
  87. openstackclient/image/v2/metadef_resource_types.py +7 -1
  88. openstackclient/image/v2/task.py +15 -6
  89. openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po +7 -192
  90. openstackclient/network/client.py +7 -2
  91. openstackclient/network/common.py +16 -241
  92. openstackclient/network/utils.py +36 -22
  93. openstackclient/network/v2/address_group.py +27 -16
  94. openstackclient/network/v2/address_scope.py +24 -13
  95. openstackclient/network/v2/bgpvpn/bgpvpn.py +463 -0
  96. openstackclient/network/v2/bgpvpn/constants.py +30 -0
  97. openstackclient/network/v2/bgpvpn/network_association.py +214 -0
  98. openstackclient/network/v2/bgpvpn/port_association.py +490 -0
  99. openstackclient/network/v2/bgpvpn/router_association.py +288 -0
  100. openstackclient/network/v2/default_security_group_rule.py +19 -10
  101. openstackclient/network/v2/floating_ip.py +110 -159
  102. openstackclient/network/v2/floating_ip_port_forwarding.py +30 -18
  103. openstackclient/network/v2/fwaas/__init__.py +0 -0
  104. openstackclient/network/v2/fwaas/group.py +466 -0
  105. openstackclient/network/v2/fwaas/policy.py +518 -0
  106. openstackclient/network/v2/fwaas/rule.py +574 -0
  107. openstackclient/network/v2/ip_availability.py +13 -5
  108. openstackclient/network/v2/l3_conntrack_helper.py +22 -13
  109. openstackclient/network/v2/local_ip.py +24 -13
  110. openstackclient/network/v2/local_ip_association.py +14 -7
  111. openstackclient/network/v2/ndp_proxy.py +20 -11
  112. openstackclient/network/v2/network.py +129 -196
  113. openstackclient/network/v2/network_agent.py +46 -25
  114. openstackclient/network/v2/network_auto_allocated_topology.py +22 -11
  115. openstackclient/network/v2/network_flavor.py +27 -16
  116. openstackclient/network/v2/network_flavor_profile.py +23 -12
  117. openstackclient/network/v2/network_meter.py +21 -10
  118. openstackclient/network/v2/network_meter_rule.py +21 -11
  119. openstackclient/network/v2/network_qos_policy.py +25 -15
  120. openstackclient/network/v2/network_qos_rule.py +32 -17
  121. openstackclient/network/v2/network_qos_rule_type.py +13 -5
  122. openstackclient/network/v2/network_rbac.py +23 -12
  123. openstackclient/network/v2/network_segment.py +20 -11
  124. openstackclient/network/v2/network_segment_range.py +56 -29
  125. openstackclient/network/v2/network_service_provider.py +7 -1
  126. openstackclient/network/v2/network_trunk.py +38 -22
  127. openstackclient/network/v2/port.py +54 -29
  128. openstackclient/network/v2/router.py +75 -52
  129. openstackclient/network/v2/security_group.py +87 -157
  130. openstackclient/network/v2/security_group_rule.py +100 -280
  131. openstackclient/network/v2/subnet.py +49 -28
  132. openstackclient/network/v2/subnet_pool.py +30 -17
  133. openstackclient/network/v2/taas/tap_flow.py +22 -11
  134. openstackclient/network/v2/taas/tap_mirror.py +22 -11
  135. openstackclient/network/v2/taas/tap_service.py +23 -12
  136. openstackclient/object/client.py +7 -2
  137. openstackclient/object/v1/account.py +13 -6
  138. openstackclient/object/v1/container.py +25 -15
  139. openstackclient/object/v1/object.py +25 -15
  140. openstackclient/py.typed +0 -0
  141. openstackclient/shell.py +46 -10
  142. openstackclient/tests/functional/base.py +55 -20
  143. openstackclient/tests/functional/common/test_extension.py +4 -0
  144. openstackclient/tests/functional/common/test_quota.py +3 -1
  145. openstackclient/tests/functional/compute/v2/common.py +14 -13
  146. openstackclient/tests/functional/compute/v2/test_flavor.py +3 -1
  147. openstackclient/tests/functional/compute/v2/test_server.py +3 -0
  148. openstackclient/tests/functional/identity/v2/common.py +10 -6
  149. openstackclient/tests/functional/identity/v2/test_role.py +4 -4
  150. openstackclient/tests/functional/identity/v3/common.py +25 -19
  151. openstackclient/tests/functional/identity/v3/test_group.py +20 -20
  152. openstackclient/tests/functional/identity/v3/test_idp.py +3 -1
  153. openstackclient/tests/functional/identity/v3/test_project.py +10 -10
  154. openstackclient/tests/functional/identity/v3/test_role.py +18 -18
  155. openstackclient/tests/functional/identity/v3/test_role_assignment.py +12 -12
  156. openstackclient/tests/functional/identity/v3/test_user.py +8 -8
  157. openstackclient/tests/functional/image/base.py +1 -6
  158. openstackclient/tests/functional/network/v2/common.py +5 -2
  159. openstackclient/tests/functional/network/v2/test_floating_ip.py +10 -4
  160. openstackclient/tests/functional/network/v2/test_ip_availability.py +4 -0
  161. openstackclient/tests/functional/network/v2/test_network_meter_rule.py +3 -2
  162. openstackclient/tests/functional/network/v2/test_network_segment.py +5 -0
  163. openstackclient/tests/functional/network/v2/test_subnet.py +13 -9
  164. openstackclient/tests/functional/object/v1/common.py +4 -0
  165. openstackclient/tests/functional/volume/v2/common.py +4 -0
  166. openstackclient/tests/functional/volume/v2/test_volume_snapshot.py +27 -11
  167. openstackclient/tests/functional/volume/v2/test_volume_type.py +2 -2
  168. openstackclient/tests/functional/volume/v3/common.py +4 -0
  169. openstackclient/tests/functional/volume/v3/test_volume_snapshot.py +11 -7
  170. openstackclient/tests/functional/volume/v3/test_volume_type.py +2 -2
  171. openstackclient/tests/unit/common/test_availability_zone.py +35 -49
  172. openstackclient/tests/unit/common/test_extension.py +2 -2
  173. openstackclient/tests/unit/common/test_module.py +12 -7
  174. openstackclient/tests/unit/common/test_project_cleanup.py +3 -1
  175. openstackclient/tests/unit/common/test_quota.py +6 -26
  176. openstackclient/tests/unit/compute/v2/fakes.py +25 -0
  177. openstackclient/tests/unit/compute/v2/test_flavor.py +28 -2
  178. openstackclient/tests/unit/compute/v2/test_keypair.py +6 -6
  179. openstackclient/tests/unit/compute/v2/test_server.py +11 -96
  180. openstackclient/tests/unit/compute/v2/test_server_share.py +287 -0
  181. openstackclient/tests/unit/identity/v3/fakes.py +3 -0
  182. openstackclient/tests/unit/identity/v3/test_group.py +4 -14
  183. openstackclient/tests/unit/identity/v3/test_identity_provider.py +303 -299
  184. openstackclient/tests/unit/identity/v3/test_user.py +4 -4
  185. openstackclient/tests/unit/image/v2/test_image.py +11 -11
  186. openstackclient/tests/unit/image/v2/test_metadef_namespaces.py +105 -6
  187. openstackclient/tests/unit/network/test_common.py +0 -155
  188. openstackclient/tests/unit/network/v2/bgpvpn/__init__.py +0 -0
  189. openstackclient/tests/unit/network/v2/bgpvpn/fakes.py +179 -0
  190. openstackclient/tests/unit/network/v2/bgpvpn/test_bgpvpn.py +584 -0
  191. openstackclient/tests/unit/network/v2/bgpvpn/test_network_association.py +285 -0
  192. openstackclient/tests/unit/network/v2/bgpvpn/test_port_association.py +384 -0
  193. openstackclient/tests/unit/network/v2/bgpvpn/test_router_association.py +297 -0
  194. openstackclient/tests/unit/network/v2/fwaas/__init__.py +0 -0
  195. openstackclient/tests/unit/network/v2/fwaas/test_group.py +897 -0
  196. openstackclient/tests/unit/network/v2/fwaas/test_policy.py +869 -0
  197. openstackclient/tests/unit/network/v2/fwaas/test_rule.py +980 -0
  198. openstackclient/tests/unit/network/v2/taas/{test_osc_tap_flow.py → test_tap_flow.py} +18 -25
  199. openstackclient/tests/unit/network/v2/taas/{test_osc_tap_mirror.py → test_tap_mirror.py} +19 -29
  200. openstackclient/tests/unit/network/v2/taas/{test_osc_tap_service.py → test_tap_service.py} +19 -29
  201. openstackclient/tests/unit/network/v2/test_address_group.py +2 -2
  202. openstackclient/tests/unit/network/v2/{test_floating_ip_network.py → test_floating_ip.py} +3 -2
  203. openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +13 -13
  204. openstackclient/tests/unit/network/v2/test_network_agent.py +8 -4
  205. openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py +3 -3
  206. openstackclient/tests/unit/network/v2/test_network_flavor.py +2 -2
  207. openstackclient/tests/unit/network/v2/test_network_qos_policy.py +1 -1
  208. openstackclient/tests/unit/network/v2/test_network_qos_rule.py +2 -2
  209. openstackclient/tests/unit/network/v2/test_network_rbac.py +1 -1
  210. openstackclient/tests/unit/network/v2/test_network_segment.py +1 -1
  211. openstackclient/tests/unit/network/v2/test_network_segment_range.py +7 -10
  212. openstackclient/tests/unit/network/v2/test_network_trunk.py +1 -1
  213. openstackclient/tests/unit/network/v2/test_router.py +8 -9
  214. openstackclient/tests/unit/network/v2/{test_security_group_network.py → test_security_group.py} +1 -20
  215. openstackclient/tests/unit/network/v2/{test_security_group_rule_network.py → test_security_group_rule.py} +7 -41
  216. openstackclient/tests/unit/network/v2/test_subnet.py +2 -1
  217. openstackclient/tests/unit/network/v2/test_subnet_pool.py +2 -1
  218. openstackclient/tests/unit/object/v1/fakes.py +8 -7
  219. openstackclient/tests/unit/object/v1/test_container.py +65 -101
  220. openstackclient/tests/unit/object/v1/test_container_all.py +8 -1
  221. openstackclient/tests/unit/object/v1/test_object.py +44 -84
  222. openstackclient/tests/unit/object/v1/test_object_all.py +8 -1
  223. openstackclient/tests/unit/test_hacking.py +108 -0
  224. openstackclient/tests/unit/volume/v2/fakes.py +1 -0
  225. openstackclient/tests/unit/volume/v2/test_volume_backup.py +1 -5
  226. openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +2 -1
  227. openstackclient/tests/unit/volume/v2/test_volume_type.py +2 -4
  228. openstackclient/tests/unit/volume/v3/fakes.py +1 -0
  229. openstackclient/tests/unit/volume/v3/test_volume.py +60 -3
  230. openstackclient/tests/unit/volume/v3/test_volume_attachment.py +1 -1
  231. openstackclient/tests/unit/volume/v3/test_volume_backup.py +1 -5
  232. openstackclient/tests/unit/volume/v3/test_volume_snapshot.py +55 -1
  233. openstackclient/tests/unit/volume/v3/test_volume_type.py +2 -4
  234. openstackclient/volume/client.py +7 -3
  235. openstackclient/volume/v2/backup_record.py +15 -6
  236. openstackclient/volume/v2/consistency_group.py +29 -17
  237. openstackclient/volume/v2/consistency_group_snapshot.py +25 -10
  238. openstackclient/volume/v2/qos_specs.py +28 -17
  239. openstackclient/volume/v2/service.py +17 -6
  240. openstackclient/volume/v2/volume.py +57 -29
  241. openstackclient/volume/v2/volume_backend.py +19 -6
  242. openstackclient/volume/v2/volume_backup.py +46 -20
  243. openstackclient/volume/v2/volume_host.py +6 -4
  244. openstackclient/volume/v2/volume_snapshot.py +50 -24
  245. openstackclient/volume/v2/volume_transfer_request.py +31 -13
  246. openstackclient/volume/v2/volume_type.py +43 -24
  247. openstackclient/volume/v3/block_storage_cleanup.py +11 -3
  248. openstackclient/volume/v3/block_storage_cluster.py +19 -7
  249. openstackclient/volume/v3/block_storage_log_level.py +15 -6
  250. openstackclient/volume/v3/block_storage_manage.py +10 -4
  251. openstackclient/volume/v3/block_storage_resource_filter.py +17 -5
  252. openstackclient/volume/v3/service.py +16 -6
  253. openstackclient/volume/v3/volume.py +89 -39
  254. openstackclient/volume/v3/volume_attachment.py +43 -21
  255. openstackclient/volume/v3/volume_backup.py +53 -24
  256. openstackclient/volume/v3/volume_group.py +23 -13
  257. openstackclient/volume/v3/volume_group_snapshot.py +32 -13
  258. openstackclient/volume/v3/volume_group_type.py +26 -13
  259. openstackclient/volume/v3/volume_message.py +15 -7
  260. openstackclient/volume/v3/volume_snapshot.py +69 -32
  261. openstackclient/volume/v3/volume_transfer_request.py +31 -13
  262. openstackclient/volume/v3/volume_type.py +42 -24
  263. {python_openstackclient-9.0.0.dist-info → python_openstackclient-10.0.0.dist-info}/METADATA +6 -6
  264. {python_openstackclient-9.0.0.dist-info → python_openstackclient-10.0.0.dist-info}/RECORD +271 -260
  265. {python_openstackclient-9.0.0.dist-info → python_openstackclient-10.0.0.dist-info}/WHEEL +1 -1
  266. {python_openstackclient-9.0.0.dist-info → python_openstackclient-10.0.0.dist-info}/entry_points.txt +53 -1
  267. {python_openstackclient-9.0.0.dist-info → python_openstackclient-10.0.0.dist-info}/licenses/AUTHORS +4 -0
  268. python_openstackclient-10.0.0.dist-info/pbr.json +1 -0
  269. openstackclient/api/image_v1.py +0 -69
  270. openstackclient/api/image_v2.py +0 -79
  271. openstackclient/network/v2/floating_ip_pool.py +0 -38
  272. openstackclient/tests/functional/image/v1/test_image.py +0 -97
  273. openstackclient/tests/unit/api/test_image_v1.py +0 -96
  274. openstackclient/tests/unit/api/test_image_v2.py +0 -96
  275. openstackclient/tests/unit/network/v2/test_floating_ip_compute.py +0 -248
  276. openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py +0 -49
  277. openstackclient/tests/unit/network/v2/test_floating_ip_pool_network.py +0 -39
  278. openstackclient/tests/unit/network/v2/test_network_compute.py +0 -404
  279. openstackclient/tests/unit/network/v2/test_security_group_compute.py +0 -392
  280. openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +0 -555
  281. python_openstackclient-9.0.0.dist-info/pbr.json +0 -1
  282. /openstackclient/{tests/functional/image/v1 → network/v2/bgpvpn}/__init__.py +0 -0
  283. {python_openstackclient-9.0.0.dist-info → python_openstackclient-10.0.0.dist-info}/licenses/LICENSE +0 -0
  284. {python_openstackclient-9.0.0.dist-info → python_openstackclient-10.0.0.dist-info}/top_level.txt +0 -0
@@ -17,11 +17,12 @@
17
17
 
18
18
  import argparse
19
19
  from base64 import b64encode
20
+ from collections.abc import Iterable, Sequence
20
21
  import copy
21
22
  import logging
22
23
  import os
23
24
  import sys
24
- import typing as ty
25
+ from typing import Any
25
26
  import urllib.parse
26
27
 
27
28
  from openstack import exceptions as sdk_exceptions
@@ -73,7 +74,7 @@ MEMBER_STATUS_CHOICES = ["accepted", "pending", "rejected", "all"]
73
74
  LOG = logging.getLogger(__name__)
74
75
 
75
76
 
76
- def _format_image(image, human_readable=False):
77
+ def _format_image(image: Any, human_readable: bool = False) -> dict[str, Any]:
77
78
  """Format an image to make it more consistent with OSC operations."""
78
79
 
79
80
  info = {}
@@ -137,7 +138,7 @@ _formatters = {
137
138
  }
138
139
 
139
140
 
140
- def _get_member_columns(item):
141
+ def _get_member_columns(item: Any) -> tuple[tuple[str, ...], tuple[str, ...]]:
141
142
  column_map = {'image_id': 'image_id'}
142
143
  hidden_columns = ['id', 'location', 'name']
143
144
  return utils.get_osc_show_columns_for_sdk_resource(
@@ -147,7 +148,7 @@ def _get_member_columns(item):
147
148
  )
148
149
 
149
150
 
150
- def get_data_from_stdin():
151
+ def get_data_from_stdin() -> Any:
151
152
  # distinguish cases where:
152
153
  # (1) stdin is not valid (as in cron jobs):
153
154
  # openstack ... <&-
@@ -166,10 +167,10 @@ def get_data_from_stdin():
166
167
  image = sys.stdin
167
168
  if hasattr(sys.stdin, 'buffer'):
168
169
  image = sys.stdin.buffer
169
- if os.name == "nt":
170
+ if sys.platform == "win32":
170
171
  import msvcrt
171
172
 
172
- msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) # type: ignore
173
+ msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
173
174
 
174
175
  return image
175
176
  else:
@@ -177,7 +178,7 @@ def get_data_from_stdin():
177
178
  return None
178
179
 
179
180
 
180
- def _add_is_protected_args(parser):
181
+ def _add_is_protected_args(parser: argparse.ArgumentParser) -> None:
181
182
  protected_group = parser.add_mutually_exclusive_group()
182
183
  protected_group.add_argument(
183
184
  "--protected",
@@ -195,7 +196,7 @@ def _add_is_protected_args(parser):
195
196
  )
196
197
 
197
198
 
198
- def _add_visibility_args(parser):
199
+ def _add_visibility_args(parser: argparse.ArgumentParser) -> None:
199
200
  public_group = parser.add_mutually_exclusive_group()
200
201
  public_group.add_argument(
201
202
  "--public",
@@ -241,7 +242,7 @@ def _add_visibility_args(parser):
241
242
  class AddProjectToImage(command.ShowOne):
242
243
  _description = _("Associate project with image")
243
244
 
244
- def get_parser(self, prog_name):
245
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
245
246
  parser = super().get_parser(prog_name)
246
247
  parser.add_argument(
247
248
  "image",
@@ -256,7 +257,9 @@ class AddProjectToImage(command.ShowOne):
256
257
  identity_common.add_project_domain_option_to_parser(parser)
257
258
  return parser
258
259
 
259
- def take_action(self, parsed_args):
260
+ def take_action(
261
+ self, parsed_args: argparse.Namespace
262
+ ) -> tuple[Sequence[str], Iterable[Any]]:
260
263
  image_client = self.app.client_manager.image
261
264
  identity_client = self.app.client_manager.identity
262
265
 
@@ -287,7 +290,7 @@ class CreateImage(command.ShowOne):
287
290
 
288
291
  deadopts = ('size', 'location', 'copy-from', 'checksum', 'store')
289
292
 
290
- def get_parser(self, prog_name):
293
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
291
294
  parser = super().get_parser(prog_name)
292
295
  # TODO(bunting): There are additional arguments that v1 supported
293
296
  # that v2 either doesn't support or supports weirdly.
@@ -440,13 +443,15 @@ class CreateImage(command.ShowOne):
440
443
  )
441
444
  return parser
442
445
 
443
- def _take_action_image(self, parsed_args):
446
+ def _take_action_image(
447
+ self, parsed_args: argparse.Namespace
448
+ ) -> dict[str, Any]:
444
449
  identity_client = self.app.client_manager.identity
445
450
  image_client = self.app.client_manager.image
446
451
 
447
452
  # Build an attribute dict from the parsed args, only include
448
453
  # attributes that were actually set on the command line
449
- kwargs: dict[str, ty.Any] = {'allow_duplicates': True}
454
+ kwargs: dict[str, Any] = {'allow_duplicates': True}
450
455
  copy_attrs = (
451
456
  'name',
452
457
  'id',
@@ -592,8 +597,12 @@ class CreateImage(command.ShowOne):
592
597
  image = image_client.get_image(image)
593
598
  return _format_image(image)
594
599
 
595
- def _take_action_volume(self, parsed_args):
596
- volume_client = self.app.client_manager.sdk_connection.volume
600
+ def _take_action_volume(
601
+ self, parsed_args: argparse.Namespace
602
+ ) -> dict[str, Any]:
603
+ volume_client = sdk_utils.ensure_service_version(
604
+ self.app.client_manager.sdk_connection.volume, '3'
605
+ )
597
606
 
598
607
  unsupported_opts = {
599
608
  # 'name', # 'name' is a positional argument and will always exist
@@ -627,7 +636,7 @@ class CreateImage(command.ShowOne):
627
636
  source_volume = volume_client.find_volume(
628
637
  parsed_args.volume, ignore_missing=False
629
638
  )
630
- kwargs: dict[str, ty.Any] = {
639
+ kwargs: dict[str, Any] = {
631
640
  'visibility': None,
632
641
  'protected': None,
633
642
  }
@@ -643,7 +652,7 @@ class CreateImage(command.ShowOne):
643
652
  kwargs['visibility'] = parsed_args.visibility or 'private'
644
653
  kwargs['protected'] = parsed_args.is_protected or False
645
654
 
646
- response = volume_client.upload_volume_to_image(
655
+ response: dict[str, Any] = volume_client.upload_volume_to_image(
647
656
  source_volume.id,
648
657
  parsed_args.name,
649
658
  force=parsed_args.force,
@@ -659,7 +668,9 @@ class CreateImage(command.ShowOne):
659
668
 
660
669
  return info
661
670
 
662
- def take_action(self, parsed_args):
671
+ def take_action(
672
+ self, parsed_args: argparse.Namespace
673
+ ) -> tuple[Sequence[str], Iterable[Any]]:
663
674
  for deadopt in self.deadopts:
664
675
  if getattr(parsed_args, deadopt.replace('-', '_'), None):
665
676
  msg = _(
@@ -673,13 +684,14 @@ class CreateImage(command.ShowOne):
673
684
  else:
674
685
  info = self._take_action_image(parsed_args)
675
686
 
676
- return zip(*sorted(info.items()))
687
+ col_headers, col_data = zip(*sorted(info.items()))
688
+ return col_headers, col_data
677
689
 
678
690
 
679
691
  class DeleteImage(command.Command):
680
692
  _description = _("Delete image(s)")
681
693
 
682
- def get_parser(self, prog_name):
694
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
683
695
  parser = super().get_parser(prog_name)
684
696
  parser.add_argument(
685
697
  "images",
@@ -696,7 +708,7 @@ class DeleteImage(command.Command):
696
708
  )
697
709
  return parser
698
710
 
699
- def take_action(self, parsed_args):
711
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
700
712
  result = 0
701
713
  image_client = self.app.client_manager.image
702
714
  for image in parsed_args.images:
@@ -732,7 +744,7 @@ class DeleteImage(command.Command):
732
744
  class ListImage(command.Lister):
733
745
  _description = _("List available images")
734
746
 
735
- def get_parser(self, prog_name):
747
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
736
748
  parser = super().get_parser(prog_name)
737
749
  public_group = parser.add_mutually_exclusive_group()
738
750
  public_group.add_argument(
@@ -861,7 +873,9 @@ class ListImage(command.Lister):
861
873
  pagination.add_marker_pagination_option_to_parser(parser)
862
874
  return parser
863
875
 
864
- def take_action(self, parsed_args):
876
+ def take_action(
877
+ self, parsed_args: argparse.Namespace
878
+ ) -> tuple[tuple[str, ...], Iterable[tuple[Any, ...]]]:
865
879
  identity_client = self.app.client_manager.identity
866
880
  image_client = self.app.client_manager.image
867
881
 
@@ -962,7 +976,7 @@ class ListImage(command.Lister):
962
976
  class ListImageProjects(command.Lister):
963
977
  _description = _("List projects associated with image")
964
978
 
965
- def get_parser(self, prog_name):
979
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
966
980
  parser = super().get_parser(prog_name)
967
981
  parser.add_argument(
968
982
  "image",
@@ -972,7 +986,9 @@ class ListImageProjects(command.Lister):
972
986
  identity_common.add_project_domain_option_to_parser(parser)
973
987
  return parser
974
988
 
975
- def take_action(self, parsed_args):
989
+ def take_action(
990
+ self, parsed_args: argparse.Namespace
991
+ ) -> tuple[tuple[str, ...], Iterable[tuple[Any, ...]]]:
976
992
  image_client = self.app.client_manager.image
977
993
  columns: tuple[str, ...] = ("Image ID", "Member ID", "Status")
978
994
 
@@ -998,7 +1014,7 @@ class ListImageProjects(command.Lister):
998
1014
  class RemoveProjectImage(command.Command):
999
1015
  _description = _("Disassociate project with image")
1000
1016
 
1001
- def get_parser(self, prog_name):
1017
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
1002
1018
  parser = super().get_parser(prog_name)
1003
1019
  parser.add_argument(
1004
1020
  "image",
@@ -1013,7 +1029,7 @@ class RemoveProjectImage(command.Command):
1013
1029
  identity_common.add_project_domain_option_to_parser(parser)
1014
1030
  return parser
1015
1031
 
1016
- def take_action(self, parsed_args):
1032
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
1017
1033
  image_client = self.app.client_manager.image
1018
1034
  identity_client = self.app.client_manager.identity
1019
1035
 
@@ -1034,7 +1050,7 @@ class RemoveProjectImage(command.Command):
1034
1050
  class ShowProjectImage(command.ShowOne):
1035
1051
  _description = _("Show a particular project associated with image")
1036
1052
 
1037
- def get_parser(self, prog_name):
1053
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
1038
1054
  parser = super().get_parser(prog_name)
1039
1055
  parser.add_argument(
1040
1056
  "image",
@@ -1049,7 +1065,9 @@ class ShowProjectImage(command.ShowOne):
1049
1065
  identity_common.add_project_domain_option_to_parser(parser)
1050
1066
  return parser
1051
1067
 
1052
- def take_action(self, parsed_args):
1068
+ def take_action(
1069
+ self, parsed_args: argparse.Namespace
1070
+ ) -> tuple[Sequence[str], Iterable[Any]]:
1053
1071
  image_client = self.app.client_manager.image
1054
1072
 
1055
1073
  image = image_client.find_image(
@@ -1071,7 +1089,7 @@ class ShowProjectImage(command.ShowOne):
1071
1089
  class SaveImage(command.Command):
1072
1090
  _description = _("Save an image locally")
1073
1091
 
1074
- def get_parser(self, prog_name):
1092
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
1075
1093
  parser = super().get_parser(prog_name)
1076
1094
  parser.add_argument(
1077
1095
  "--chunk-size",
@@ -1096,7 +1114,7 @@ class SaveImage(command.Command):
1096
1114
  )
1097
1115
  return parser
1098
1116
 
1099
- def take_action(self, parsed_args):
1117
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
1100
1118
  image_client = self.app.client_manager.image
1101
1119
  image = image_client.find_image(
1102
1120
  parsed_args.image,
@@ -1120,7 +1138,7 @@ class SetImage(command.Command):
1120
1138
 
1121
1139
  deadopts = ('visibility',)
1122
1140
 
1123
- def get_parser(self, prog_name):
1141
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
1124
1142
  parser = super().get_parser(prog_name)
1125
1143
  # TODO(bunting): There are additional arguments that v1 supported
1126
1144
  # --size - does not exist in v2
@@ -1296,7 +1314,7 @@ class SetImage(command.Command):
1296
1314
  )
1297
1315
  return parser
1298
1316
 
1299
- def take_action(self, parsed_args):
1317
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
1300
1318
  identity_client = self.app.client_manager.identity
1301
1319
  image_client = self.app.client_manager.image
1302
1320
 
@@ -1421,7 +1439,7 @@ class SetImage(command.Command):
1421
1439
  class ShowImage(command.ShowOne):
1422
1440
  _description = _("Display image details")
1423
1441
 
1424
- def get_parser(self, prog_name):
1442
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
1425
1443
  parser = super().get_parser(prog_name)
1426
1444
  parser.add_argument(
1427
1445
  "--human-readable",
@@ -1436,7 +1454,9 @@ class ShowImage(command.ShowOne):
1436
1454
  )
1437
1455
  return parser
1438
1456
 
1439
- def take_action(self, parsed_args):
1457
+ def take_action(
1458
+ self, parsed_args: argparse.Namespace
1459
+ ) -> tuple[Sequence[str], Iterable[Any]]:
1440
1460
  image_client = self.app.client_manager.image
1441
1461
 
1442
1462
  image = image_client.find_image(
@@ -1445,13 +1465,14 @@ class ShowImage(command.ShowOne):
1445
1465
  )
1446
1466
 
1447
1467
  info = _format_image(image, parsed_args.human_readable)
1448
- return zip(*sorted(info.items()))
1468
+ col_headers, col_data = zip(*sorted(info.items()))
1469
+ return col_headers, col_data
1449
1470
 
1450
1471
 
1451
1472
  class UnsetImage(command.Command):
1452
1473
  _description = _("Unset image tags and properties")
1453
1474
 
1454
- def get_parser(self, prog_name):
1475
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
1455
1476
  parser = super().get_parser(prog_name)
1456
1477
  parser.add_argument(
1457
1478
  "image",
@@ -1482,7 +1503,7 @@ class UnsetImage(command.Command):
1482
1503
  )
1483
1504
  return parser
1484
1505
 
1485
- def take_action(self, parsed_args):
1506
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
1486
1507
  image_client = self.app.client_manager.image
1487
1508
  image = image_client.find_image(
1488
1509
  parsed_args.image,
@@ -1501,7 +1522,7 @@ class UnsetImage(command.Command):
1501
1522
  )
1502
1523
  tagret += 1
1503
1524
 
1504
- kwargs: dict[str, ty.Any] = {}
1525
+ kwargs: dict[str, Any] = {}
1505
1526
  if parsed_args.properties:
1506
1527
  for k in parsed_args.properties:
1507
1528
  if k in image:
@@ -1512,7 +1533,7 @@ class UnsetImage(command.Command):
1512
1533
  # out, what was changed inside
1513
1534
  # NOTE: ping gtema to improve that in SDK
1514
1535
  new_props = kwargs.get(
1515
- 'properties', image.get('properties').copy()
1536
+ 'properties', image['properties'].copy()
1516
1537
  )
1517
1538
  new_props.pop(k, None)
1518
1539
  kwargs['properties'] = new_props
@@ -1569,7 +1590,7 @@ class StageImage(command.Command):
1569
1590
  "(Glance 16.0.0 (Queens))"
1570
1591
  )
1571
1592
 
1572
- def get_parser(self, prog_name):
1593
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
1573
1594
  parser = super().get_parser(prog_name)
1574
1595
 
1575
1596
  parser.add_argument(
@@ -1599,7 +1620,7 @@ class StageImage(command.Command):
1599
1620
 
1600
1621
  return parser
1601
1622
 
1602
- def take_action(self, parsed_args):
1623
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
1603
1624
  image_client = self.app.client_manager.image
1604
1625
 
1605
1626
  image = image_client.find_image(
@@ -1619,7 +1640,7 @@ class StageImage(command.Command):
1619
1640
  else:
1620
1641
  fp = get_data_from_stdin()
1621
1642
 
1622
- kwargs: dict[str, ty.Any] = {}
1643
+ kwargs: dict[str, Any] = {}
1623
1644
 
1624
1645
  if parsed_args.progress and parsed_args.filename:
1625
1646
  # NOTE(stephenfin): we only show a progress bar if the user
@@ -1645,7 +1666,7 @@ class ImportImage(command.ShowOne):
1645
1666
  "(Glance 16.0.0 (Queens))"
1646
1667
  )
1647
1668
 
1648
- def get_parser(self, prog_name):
1669
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
1649
1670
  parser = super().get_parser(prog_name)
1650
1671
 
1651
1672
  parser.add_argument(
@@ -1756,7 +1777,9 @@ class ImportImage(command.ShowOne):
1756
1777
  )
1757
1778
  return parser
1758
1779
 
1759
- def take_action(self, parsed_args):
1780
+ def take_action(
1781
+ self, parsed_args: argparse.Namespace
1782
+ ) -> tuple[Sequence[str], Iterable[Any]]:
1760
1783
  image_client = self.app.client_manager.image
1761
1784
 
1762
1785
  try:
@@ -1886,7 +1909,8 @@ class ImportImage(command.ShowOne):
1886
1909
  )
1887
1910
 
1888
1911
  info = _format_image(image)
1889
- return zip(*sorted(info.items()))
1912
+ col_headers, col_data = zip(*sorted(info.items()))
1913
+ return col_headers, col_data
1890
1914
 
1891
1915
 
1892
1916
  class StoresInfo(command.Lister):
@@ -1894,7 +1918,7 @@ class StoresInfo(command.Lister):
1894
1918
  "Get available backends (only valid with Multi-Backend support)"
1895
1919
  )
1896
1920
 
1897
- def get_parser(self, prog_name):
1921
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
1898
1922
  parser = super().get_parser(prog_name)
1899
1923
  parser.add_argument(
1900
1924
  "--detail",
@@ -1907,7 +1931,9 @@ class StoresInfo(command.Lister):
1907
1931
  )
1908
1932
  return parser
1909
1933
 
1910
- def take_action(self, parsed_args):
1934
+ def take_action(
1935
+ self, parsed_args: argparse.Namespace
1936
+ ) -> tuple[tuple[str, ...], Iterable[tuple[Any, ...]]]:
1911
1937
  image_client = self.app.client_manager.image
1912
1938
  try:
1913
1939
  columns: tuple[str, ...] = ("id", "description", "is_default")
@@ -11,6 +11,10 @@
11
11
  # under the License.
12
12
 
13
13
 
14
+ import argparse
15
+ from collections.abc import Iterable, Sequence
16
+ from typing import Any
17
+
14
18
  from osc_lib.cli import format_columns
15
19
 
16
20
  from openstackclient import command
@@ -20,7 +24,9 @@ from openstackclient.i18n import _
20
24
  class ImportInfo(command.ShowOne):
21
25
  _description = _("Show available import methods")
22
26
 
23
- def take_action(self, parsed_args):
27
+ def take_action(
28
+ self, parsed_args: argparse.Namespace
29
+ ) -> tuple[Sequence[str], Iterable[Any]]:
24
30
  image_client = self.app.client_manager.image
25
31
 
26
32
  import_info = image_client.get_import_info()
@@ -15,7 +15,10 @@
15
15
 
16
16
  """Image V2 Action Implementations"""
17
17
 
18
+ import argparse
19
+ from collections.abc import Iterable, Sequence
18
20
  import logging
21
+ from typing import Any
19
22
 
20
23
  from osc_lib.cli import format_columns
21
24
  from osc_lib import exceptions
@@ -31,7 +34,7 @@ _formatters = {
31
34
  LOG = logging.getLogger(__name__)
32
35
 
33
36
 
34
- def _format_namespace(namespace):
37
+ def _format_namespace(namespace: Any) -> dict[str, Any]:
35
38
  info = {}
36
39
 
37
40
  fields_to_show = [
@@ -41,6 +44,7 @@ def _format_namespace(namespace):
41
44
  'namespace',
42
45
  'owner',
43
46
  'protected',
47
+ 'tags',
44
48
  'schema',
45
49
  'updated_at',
46
50
  'visibility',
@@ -65,7 +69,7 @@ def _format_namespace(namespace):
65
69
  class CreateMetadefNamespace(command.ShowOne):
66
70
  _description = _("Create a metadef namespace")
67
71
 
68
- def get_parser(self, prog_name):
72
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
69
73
  parser = super().get_parser(prog_name)
70
74
  parser.add_argument(
71
75
  "namespace",
@@ -114,7 +118,9 @@ class CreateMetadefNamespace(command.ShowOne):
114
118
  )
115
119
  return parser
116
120
 
117
- def take_action(self, parsed_args):
121
+ def take_action(
122
+ self, parsed_args: argparse.Namespace
123
+ ) -> tuple[Sequence[str], Iterable[Any]]:
118
124
  image_client = self.app.client_manager.image
119
125
  filter_keys = ['namespace', 'display_name', 'description']
120
126
  kwargs = {}
@@ -133,13 +139,14 @@ class CreateMetadefNamespace(command.ShowOne):
133
139
  data = image_client.create_metadef_namespace(**kwargs)
134
140
  info = _format_namespace(data)
135
141
 
136
- return zip(*sorted(info.items()))
142
+ col_headers, col_data = zip(*sorted(info.items()))
143
+ return col_headers, col_data
137
144
 
138
145
 
139
146
  class DeleteMetadefNamespace(command.Command):
140
147
  _description = _("Delete metadef namespace")
141
148
 
142
- def get_parser(self, prog_name):
149
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
143
150
  parser = super().get_parser(prog_name)
144
151
  parser.add_argument(
145
152
  "namespace",
@@ -149,7 +156,7 @@ class DeleteMetadefNamespace(command.Command):
149
156
  )
150
157
  return parser
151
158
 
152
- def take_action(self, parsed_args):
159
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
153
160
  image_client = self.app.client_manager.image
154
161
 
155
162
  result = 0
@@ -179,7 +186,7 @@ class DeleteMetadefNamespace(command.Command):
179
186
  class ListMetadefNamespace(command.Lister):
180
187
  _description = _("List metadef namespaces")
181
188
 
182
- def get_parser(self, prog_name):
189
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
183
190
  parser = super().get_parser(prog_name)
184
191
  parser.add_argument(
185
192
  "--resource-types",
@@ -193,7 +200,9 @@ class ListMetadefNamespace(command.Lister):
193
200
  )
194
201
  return parser
195
202
 
196
- def take_action(self, parsed_args):
203
+ def take_action(
204
+ self, parsed_args: argparse.Namespace
205
+ ) -> tuple[Sequence[str], Iterable[tuple[Any, ...]]]:
197
206
  image_client = self.app.client_manager.image
198
207
  filter_keys = ['resource_types', 'visibility']
199
208
  kwargs = {}
@@ -221,7 +230,7 @@ class ListMetadefNamespace(command.Lister):
221
230
  class SetMetadefNamespace(command.Command):
222
231
  _description = _("Set metadef namespace properties")
223
232
 
224
- def get_parser(self, prog_name):
233
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
225
234
  parser = super().get_parser(prog_name)
226
235
  parser.add_argument(
227
236
  "namespace",
@@ -270,9 +279,20 @@ class SetMetadefNamespace(command.Command):
270
279
  dest="is_protected",
271
280
  help=_("Allow metadef namespace to be deleted (default)"),
272
281
  )
282
+ parser.add_argument(
283
+ "--tag",
284
+ metavar="<tag>",
285
+ action='append',
286
+ default=[],
287
+ dest='tags',
288
+ help=_(
289
+ "Set a tag on this metadef namespace "
290
+ "(repeat option to set multiple tags)"
291
+ ),
292
+ )
273
293
  return parser
274
294
 
275
- def take_action(self, parsed_args):
295
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
276
296
  image_client = self.app.client_manager.image
277
297
 
278
298
  namespace = parsed_args.namespace
@@ -293,11 +313,26 @@ class SetMetadefNamespace(command.Command):
293
313
 
294
314
  image_client.update_metadef_namespace(namespace, **kwargs)
295
315
 
316
+ errors = 0
317
+ for tag in parsed_args.tags:
318
+ try:
319
+ image_client.add_tag_to_metadef_namespace(namespace, tag)
320
+ except Exception:
321
+ LOG.error(_("Tag set failed for tag %s"), tag)
322
+ errors += 1
323
+
324
+ if errors > 0:
325
+ msg = _("Failed to set %(errors)s of %(total)s tags.") % {
326
+ 'errors': errors,
327
+ 'total': len(parsed_args.tags),
328
+ }
329
+ raise exceptions.CommandError(msg)
330
+
296
331
 
297
332
  class ShowMetadefNamespace(command.ShowOne):
298
333
  _description = _("Show a metadef namespace")
299
334
 
300
- def get_parser(self, prog_name):
335
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
301
336
  parser = super().get_parser(prog_name)
302
337
  parser.add_argument(
303
338
  "namespace",
@@ -306,7 +341,9 @@ class ShowMetadefNamespace(command.ShowOne):
306
341
  )
307
342
  return parser
308
343
 
309
- def take_action(self, parsed_args):
344
+ def take_action(
345
+ self, parsed_args: argparse.Namespace
346
+ ) -> tuple[Sequence[str], Iterable[Any]]:
310
347
  image_client = self.app.client_manager.image
311
348
 
312
349
  namespace = parsed_args.namespace
@@ -314,4 +351,63 @@ class ShowMetadefNamespace(command.ShowOne):
314
351
  data = image_client.get_metadef_namespace(namespace)
315
352
  info = _format_namespace(data)
316
353
 
317
- return zip(*sorted(info.items()))
354
+ col_headers, col_data = zip(*sorted(info.items()))
355
+ return col_headers, col_data
356
+
357
+
358
+ class UnsetMetadefNamespace(command.Command):
359
+ _description = _("Unset metadef namespace tags")
360
+
361
+ def get_parser(self, prog_name):
362
+ parser = super().get_parser(prog_name)
363
+ parser.add_argument(
364
+ "namespace",
365
+ metavar="<namespace>",
366
+ help=_("Metadef namespace to modify (name)"),
367
+ )
368
+ tag_group = parser.add_mutually_exclusive_group(required=True)
369
+ tag_group.add_argument(
370
+ "--tag",
371
+ metavar="<tag>",
372
+ action='append',
373
+ default=[],
374
+ dest='tags',
375
+ help=_(
376
+ "Unset a tag on this metadef namespace "
377
+ "(repeat option to unset multiple tags)"
378
+ ),
379
+ )
380
+ tag_group.add_argument(
381
+ "--all-tags",
382
+ action="store_true",
383
+ default=False,
384
+ help=_("Unset all metadef tags"),
385
+ )
386
+ return parser
387
+
388
+ def take_action(self, parsed_args):
389
+ image_client = self.app.client_manager.image
390
+
391
+ namespace = image_client.get_metadef_namespace(parsed_args.namespace)
392
+
393
+ errors = 0
394
+ if parsed_args.all_tags:
395
+ namespace = image_client.remove_tags_from_metadef_namespace(
396
+ namespace
397
+ )
398
+ elif parsed_args.tags:
399
+ for tag in parsed_args.tags:
400
+ try:
401
+ image_client.remove_tag_from_metadef_namespace(
402
+ namespace, tag
403
+ )
404
+ except Exception:
405
+ LOG.error(_("tag unset failed for tag %s"), tag)
406
+ errors += 1
407
+
408
+ if errors > 0:
409
+ msg = _("Failed to unset %(errors)s of %(total)s tags.") % {
410
+ 'errors': errors,
411
+ 'total': len(parsed_args.tags),
412
+ }
413
+ raise exceptions.CommandError(msg)