cisco-sccfm-devkit 0.39.3__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 (241) hide show
  1. cisco_sccfm_cli/__init__.py +3 -0
  2. cisco_sccfm_cli/cli.py +66 -0
  3. cisco_sccfm_cli/commands/__init__.py +3 -0
  4. cisco_sccfm_cli/commands/base.py +337 -0
  5. cisco_sccfm_cli/commands/configure.py +123 -0
  6. cisco_sccfm_cli/commands/inventory/__init__.py +7 -0
  7. cisco_sccfm_cli/commands/inventory/command.py +38 -0
  8. cisco_sccfm_cli/commands/inventory/devices/__init__.py +7 -0
  9. cisco_sccfm_cli/commands/inventory/devices/asa/__init__.py +7 -0
  10. cisco_sccfm_cli/commands/inventory/devices/asa/change_boot_image/__init__.py +9 -0
  11. cisco_sccfm_cli/commands/inventory/devices/asa/change_boot_image/command.py +225 -0
  12. cisco_sccfm_cli/commands/inventory/devices/asa/cli/__init__.py +7 -0
  13. cisco_sccfm_cli/commands/inventory/devices/asa/cli/command.py +36 -0
  14. cisco_sccfm_cli/commands/inventory/devices/asa/cli/execute/__init__.py +7 -0
  15. cisco_sccfm_cli/commands/inventory/devices/asa/cli/execute/command.py +129 -0
  16. cisco_sccfm_cli/commands/inventory/devices/asa/cli_result_renderer.py +66 -0
  17. cisco_sccfm_cli/commands/inventory/devices/asa/command.py +76 -0
  18. cisco_sccfm_cli/commands/inventory/devices/asa/disk/__init__.py +7 -0
  19. cisco_sccfm_cli/commands/inventory/devices/asa/disk/command.py +38 -0
  20. cisco_sccfm_cli/commands/inventory/devices/asa/disk/list_files/__init__.py +9 -0
  21. cisco_sccfm_cli/commands/inventory/devices/asa/disk/list_files/command.py +122 -0
  22. cisco_sccfm_cli/commands/inventory/devices/asa/ha_check/__init__.py +7 -0
  23. cisco_sccfm_cli/commands/inventory/devices/asa/ha_check/command.py +153 -0
  24. cisco_sccfm_cli/commands/inventory/devices/asa/list_boot_registry/__init__.py +9 -0
  25. cisco_sccfm_cli/commands/inventory/devices/asa/list_boot_registry/command.py +211 -0
  26. cisco_sccfm_cli/commands/inventory/devices/asa/list_local_users/__init__.py +11 -0
  27. cisco_sccfm_cli/commands/inventory/devices/asa/list_local_users/command.py +123 -0
  28. cisco_sccfm_cli/commands/inventory/devices/asa/list_not_on_version/__init__.py +9 -0
  29. cisco_sccfm_cli/commands/inventory/devices/asa/list_not_on_version/command.py +194 -0
  30. cisco_sccfm_cli/commands/inventory/devices/asa/onboard/__init__.py +7 -0
  31. cisco_sccfm_cli/commands/inventory/devices/asa/onboard/command.py +277 -0
  32. cisco_sccfm_cli/commands/inventory/devices/asa/shared.py +243 -0
  33. cisco_sccfm_cli/commands/inventory/devices/asa/shun/__init__.py +7 -0
  34. cisco_sccfm_cli/commands/inventory/devices/asa/shun/add/__init__.py +7 -0
  35. cisco_sccfm_cli/commands/inventory/devices/asa/shun/add/command.py +254 -0
  36. cisco_sccfm_cli/commands/inventory/devices/asa/shun/clear/__init__.py +7 -0
  37. cisco_sccfm_cli/commands/inventory/devices/asa/shun/clear/command.py +97 -0
  38. cisco_sccfm_cli/commands/inventory/devices/asa/shun/command.py +46 -0
  39. cisco_sccfm_cli/commands/inventory/devices/asa/shun/remove/__init__.py +7 -0
  40. cisco_sccfm_cli/commands/inventory/devices/asa/shun/remove/command.py +114 -0
  41. cisco_sccfm_cli/commands/inventory/devices/asa/shun/show/__init__.py +7 -0
  42. cisco_sccfm_cli/commands/inventory/devices/asa/shun/show/command.py +230 -0
  43. cisco_sccfm_cli/commands/inventory/devices/asa/smartlicense/__init__.py +3 -0
  44. cisco_sccfm_cli/commands/inventory/devices/asa/smartlicense/command.py +294 -0
  45. cisco_sccfm_cli/commands/inventory/devices/asa/upgrade/__init__.py +7 -0
  46. cisco_sccfm_cli/commands/inventory/devices/asa/upgrade/command.py +42 -0
  47. cisco_sccfm_cli/commands/inventory/devices/asa/upgrade/compatible_versions/__init__.py +9 -0
  48. cisco_sccfm_cli/commands/inventory/devices/asa/upgrade/compatible_versions/command.py +216 -0
  49. cisco_sccfm_cli/commands/inventory/devices/asa/upgrade/trigger/__init__.py +9 -0
  50. cisco_sccfm_cli/commands/inventory/devices/asa/upgrade/trigger/command.py +391 -0
  51. cisco_sccfm_cli/commands/inventory/devices/asa/user/__init__.py +7 -0
  52. cisco_sccfm_cli/commands/inventory/devices/asa/user/change_password/__init__.py +9 -0
  53. cisco_sccfm_cli/commands/inventory/devices/asa/user/change_password/command.py +164 -0
  54. cisco_sccfm_cli/commands/inventory/devices/asa/user/command.py +38 -0
  55. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/__init__.py +9 -0
  56. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/cli/__init__.py +9 -0
  57. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/cli/command.py +38 -0
  58. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/cli/execute/__init__.py +9 -0
  59. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/cli/execute/command.py +90 -0
  60. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/cli_result_renderer.py +57 -0
  61. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/command.py +56 -0
  62. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/configure_manager/__init__.py +9 -0
  63. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/configure_manager/command.py +259 -0
  64. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/deploy/__init__.py +9 -0
  65. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/deploy/command.py +218 -0
  66. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/onboard/__init__.py +9 -0
  67. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/onboard/command.py +222 -0
  68. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/onboard_ztp/__init__.py +9 -0
  69. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/onboard_ztp/command.py +227 -0
  70. cisco_sccfm_cli/commands/inventory/devices/cdfmc_managed_ftd/shared.py +220 -0
  71. cisco_sccfm_cli/commands/inventory/devices/command.py +44 -0
  72. cisco_sccfm_cli/commands/inventory/devices/ftd/__init__.py +7 -0
  73. cisco_sccfm_cli/commands/inventory/devices/ftd/command.py +51 -0
  74. cisco_sccfm_cli/commands/inventory/devices/ftd/list_not_on_version/__init__.py +9 -0
  75. cisco_sccfm_cli/commands/inventory/devices/ftd/list_not_on_version/command.py +318 -0
  76. cisco_sccfm_cli/commands/inventory/devices/ftd/shared.py +243 -0
  77. cisco_sccfm_cli/commands/inventory/devices/ftd/upgrade/__init__.py +7 -0
  78. cisco_sccfm_cli/commands/inventory/devices/ftd/upgrade/command.py +42 -0
  79. cisco_sccfm_cli/commands/inventory/devices/ftd/upgrade/compatible_versions/__init__.py +9 -0
  80. cisco_sccfm_cli/commands/inventory/devices/ftd/upgrade/compatible_versions/command.py +228 -0
  81. cisco_sccfm_cli/commands/inventory/devices/ftd/upgrade/trigger/__init__.py +9 -0
  82. cisco_sccfm_cli/commands/inventory/devices/ftd/upgrade/trigger/command.py +316 -0
  83. cisco_sccfm_cli/commands/inventory/devices/list/__init__.py +7 -0
  84. cisco_sccfm_cli/commands/inventory/devices/list/command.py +46 -0
  85. cisco_sccfm_cli/commands/inventory/devices/rendering.py +129 -0
  86. cisco_sccfm_cli/commands/inventory/manager/__init__.py +7 -0
  87. cisco_sccfm_cli/commands/inventory/manager/access_policies/__init__.py +7 -0
  88. cisco_sccfm_cli/commands/inventory/manager/access_policies/command.py +40 -0
  89. cisco_sccfm_cli/commands/inventory/manager/access_policies/list/__init__.py +9 -0
  90. cisco_sccfm_cli/commands/inventory/manager/access_policies/list/command.py +71 -0
  91. cisco_sccfm_cli/commands/inventory/manager/command.py +40 -0
  92. cisco_sccfm_cli/commands/inventory/manager/list/__init__.py +7 -0
  93. cisco_sccfm_cli/commands/inventory/manager/list/command.py +82 -0
  94. cisco_sccfm_cli/commands/inventory/options.py +53 -0
  95. cisco_sccfm_cli/commands/objects/__init__.py +7 -0
  96. cisco_sccfm_cli/commands/objects/add_override/__init__.py +7 -0
  97. cisco_sccfm_cli/commands/objects/add_override/command.py +96 -0
  98. cisco_sccfm_cli/commands/objects/apply_override_as_default/__init__.py +9 -0
  99. cisco_sccfm_cli/commands/objects/apply_override_as_default/command.py +88 -0
  100. cisco_sccfm_cli/commands/objects/command.py +60 -0
  101. cisco_sccfm_cli/commands/objects/delete_override/__init__.py +7 -0
  102. cisco_sccfm_cli/commands/objects/delete_override/command.py +88 -0
  103. cisco_sccfm_cli/commands/objects/edit_override/__init__.py +7 -0
  104. cisco_sccfm_cli/commands/objects/edit_override/command.py +96 -0
  105. cisco_sccfm_cli/commands/objects/network/__init__.py +7 -0
  106. cisco_sccfm_cli/commands/objects/network/command.py +46 -0
  107. cisco_sccfm_cli/commands/objects/network/create/__init__.py +7 -0
  108. cisco_sccfm_cli/commands/objects/network/create/command.py +93 -0
  109. cisco_sccfm_cli/commands/objects/network/delete/__init__.py +9 -0
  110. cisco_sccfm_cli/commands/objects/network/delete/command.py +65 -0
  111. cisco_sccfm_cli/commands/objects/network/list/__init__.py +7 -0
  112. cisco_sccfm_cli/commands/objects/network/list/command.py +72 -0
  113. cisco_sccfm_cli/commands/objects/network/update/__init__.py +7 -0
  114. cisco_sccfm_cli/commands/objects/network/update/command.py +119 -0
  115. cisco_sccfm_cli/commands/objects/network_group/__init__.py +7 -0
  116. cisco_sccfm_cli/commands/objects/network_group/add_member/__init__.py +9 -0
  117. cisco_sccfm_cli/commands/objects/network_group/add_member/command.py +161 -0
  118. cisco_sccfm_cli/commands/objects/network_group/command.py +52 -0
  119. cisco_sccfm_cli/commands/objects/network_group/create/__init__.py +7 -0
  120. cisco_sccfm_cli/commands/objects/network_group/create/command.py +121 -0
  121. cisco_sccfm_cli/commands/objects/network_group/delete/__init__.py +11 -0
  122. cisco_sccfm_cli/commands/objects/network_group/delete/command.py +65 -0
  123. cisco_sccfm_cli/commands/objects/network_group/list/__init__.py +7 -0
  124. cisco_sccfm_cli/commands/objects/network_group/list/command.py +80 -0
  125. cisco_sccfm_cli/commands/objects/network_group/remove_member/__init__.py +9 -0
  126. cisco_sccfm_cli/commands/objects/network_group/remove_member/command.py +161 -0
  127. cisco_sccfm_cli/commands/objects/network_group/update/__init__.py +9 -0
  128. cisco_sccfm_cli/commands/objects/network_group/update/command.py +142 -0
  129. cisco_sccfm_cli/commands/objects/options.py +319 -0
  130. cisco_sccfm_cli/commands/objects/show/__init__.py +7 -0
  131. cisco_sccfm_cli/commands/objects/show/command.py +105 -0
  132. cisco_sccfm_cli/commands/objects/update_default/__init__.py +7 -0
  133. cisco_sccfm_cli/commands/objects/update_default/command.py +88 -0
  134. cisco_sccfm_cli/commands/objects/utils.py +237 -0
  135. cisco_sccfm_cli/commands/policies/__init__.py +7 -0
  136. cisco_sccfm_cli/commands/policies/access_group/__init__.py +7 -0
  137. cisco_sccfm_cli/commands/policies/access_group/command.py +42 -0
  138. cisco_sccfm_cli/commands/policies/access_group/get/__init__.py +7 -0
  139. cisco_sccfm_cli/commands/policies/access_group/get/command.py +71 -0
  140. cisco_sccfm_cli/commands/policies/access_group/list/__init__.py +7 -0
  141. cisco_sccfm_cli/commands/policies/access_group/list/command.py +86 -0
  142. cisco_sccfm_cli/commands/policies/access_rule/__init__.py +7 -0
  143. cisco_sccfm_cli/commands/policies/access_rule/command.py +48 -0
  144. cisco_sccfm_cli/commands/policies/access_rule/create/__init__.py +7 -0
  145. cisco_sccfm_cli/commands/policies/access_rule/create/command.py +258 -0
  146. cisco_sccfm_cli/commands/policies/access_rule/delete/__init__.py +7 -0
  147. cisco_sccfm_cli/commands/policies/access_rule/delete/command.py +50 -0
  148. cisco_sccfm_cli/commands/policies/access_rule/get/__init__.py +7 -0
  149. cisco_sccfm_cli/commands/policies/access_rule/get/command.py +81 -0
  150. cisco_sccfm_cli/commands/policies/access_rule/list/__init__.py +7 -0
  151. cisco_sccfm_cli/commands/policies/access_rule/list/command.py +96 -0
  152. cisco_sccfm_cli/commands/policies/access_rule/update/__init__.py +7 -0
  153. cisco_sccfm_cli/commands/policies/access_rule/update/command.py +178 -0
  154. cisco_sccfm_cli/commands/policies/command.py +40 -0
  155. cisco_sccfm_cli/commands/schema/__init__.py +7 -0
  156. cisco_sccfm_cli/commands/schema/command.py +88 -0
  157. cisco_sccfm_cli/commands/shared_options.py +77 -0
  158. cisco_sccfm_cli/commands/status.py +70 -0
  159. cisco_sccfm_cli/commands/transaction.py +89 -0
  160. cisco_sccfm_cli/interactive.py +426 -0
  161. cisco_sccfm_cli/interactive_commands.py +125 -0
  162. cisco_sccfm_cli/models/__init__.py +7 -0
  163. cisco_sccfm_cli/models/config.py +9 -0
  164. cisco_sccfm_cli/option_metadata.py +26 -0
  165. cisco_sccfm_cli/py.typed +0 -0
  166. cisco_sccfm_cli/schema.py +871 -0
  167. cisco_sccfm_cli/services/__init__.py +9 -0
  168. cisco_sccfm_cli/services/config_service.py +9 -0
  169. cisco_sccfm_cli/utils/__init__.py +11 -0
  170. cisco_sccfm_cli/utils/json_output.py +36 -0
  171. cisco_sccfm_cli/utils/redaction.py +50 -0
  172. cisco_sccfm_cli/utils/spinner.py +52 -0
  173. cisco_sccfm_core/__init__.py +106 -0
  174. cisco_sccfm_core/constants.py +76 -0
  175. cisco_sccfm_core/errors.py +137 -0
  176. cisco_sccfm_core/factories/__init__.py +7 -0
  177. cisco_sccfm_core/factories/api_client_factory.py +28 -0
  178. cisco_sccfm_core/models/asa_boot_image_change_result.py +19 -0
  179. cisco_sccfm_core/models/asa_boot_registry.py +19 -0
  180. cisco_sccfm_core/models/asa_disk_file.py +41 -0
  181. cisco_sccfm_core/models/asa_failover_status.py +62 -0
  182. cisco_sccfm_core/models/asa_local_user.py +20 -0
  183. cisco_sccfm_core/models/asa_password_change_result.py +16 -0
  184. cisco_sccfm_core/models/asa_shun_entry.py +38 -0
  185. cisco_sccfm_core/models/asa_upgrade_version.py +22 -0
  186. cisco_sccfm_core/models/cdo_transaction_status.py +20 -0
  187. cisco_sccfm_core/models/ftd_cli_result.py +26 -0
  188. cisco_sccfm_core/models/ftd_upgrade_version.py +24 -0
  189. cisco_sccfm_core/models/profile.py +16 -0
  190. cisco_sccfm_core/parsers/__init__.py +28 -0
  191. cisco_sccfm_core/parsers/asa_boot_registry_parser.py +75 -0
  192. cisco_sccfm_core/parsers/asa_cli_table_parser.py +53 -0
  193. cisco_sccfm_core/parsers/asa_disk_file_parser.py +45 -0
  194. cisco_sccfm_core/parsers/asa_failover_parser.py +216 -0
  195. cisco_sccfm_core/parsers/asa_local_user_parser.py +49 -0
  196. cisco_sccfm_core/parsers/asa_shun_parser.py +82 -0
  197. cisco_sccfm_core/py.typed +0 -0
  198. cisco_sccfm_core/services/__init__.py +70 -0
  199. cisco_sccfm_core/services/health_service.py +35 -0
  200. cisco_sccfm_core/services/inventory/__init__.py +84 -0
  201. cisco_sccfm_core/services/inventory/asa_boot_image_service.py +505 -0
  202. cisco_sccfm_core/services/inventory/asa_boot_registry_service.py +89 -0
  203. cisco_sccfm_core/services/inventory/asa_cli_service.py +65 -0
  204. cisco_sccfm_core/services/inventory/asa_disk_file_service.py +48 -0
  205. cisco_sccfm_core/services/inventory/asa_ha_check_service.py +393 -0
  206. cisco_sccfm_core/services/inventory/asa_onboard_service.py +42 -0
  207. cisco_sccfm_core/services/inventory/asa_shun_service.py +208 -0
  208. cisco_sccfm_core/services/inventory/asa_upgrade_service.py +81 -0
  209. cisco_sccfm_core/services/inventory/asa_upgrade_version_service.py +113 -0
  210. cisco_sccfm_core/services/inventory/asa_user_password_service.py +205 -0
  211. cisco_sccfm_core/services/inventory/cdfmc_access_policy_service.py +68 -0
  212. cisco_sccfm_core/services/inventory/ftd_cli_service.py +262 -0
  213. cisco_sccfm_core/services/inventory/ftd_configure_manager_service.py +399 -0
  214. cisco_sccfm_core/services/inventory/ftd_deploy_service.py +69 -0
  215. cisco_sccfm_core/services/inventory/ftd_onboard_service.py +37 -0
  216. cisco_sccfm_core/services/inventory/ftd_register_service.py +40 -0
  217. cisco_sccfm_core/services/inventory/ftd_upgrade_service.py +73 -0
  218. cisco_sccfm_core/services/inventory/ftd_upgrade_version_service.py +81 -0
  219. cisco_sccfm_core/services/inventory/ftd_ztp_onboard_service.py +37 -0
  220. cisco_sccfm_core/services/inventory/inventory_service.py +25 -0
  221. cisco_sccfm_core/services/object_management/__init__.py +41 -0
  222. cisco_sccfm_core/services/object_management/network_group_service.py +690 -0
  223. cisco_sccfm_core/services/object_management/network_object_service.py +316 -0
  224. cisco_sccfm_core/services/object_management/object_api_helper.py +52 -0
  225. cisco_sccfm_core/services/object_management/object_override_service.py +583 -0
  226. cisco_sccfm_core/services/object_management/utils.py +82 -0
  227. cisco_sccfm_core/services/policy/__init__.py +23 -0
  228. cisco_sccfm_core/services/policy/access_group_service.py +117 -0
  229. cisco_sccfm_core/services/policy/access_rule_service.py +340 -0
  230. cisco_sccfm_core/services/policy/policy_api_helper.py +54 -0
  231. cisco_sccfm_core/services/profile_service.py +634 -0
  232. cisco_sccfm_core/services/transaction_service.py +54 -0
  233. cisco_sccfm_core/types.py +15 -0
  234. cisco_sccfm_core/utils/__init__.py +7 -0
  235. cisco_sccfm_core/utils/validation.py +45 -0
  236. cisco_sccfm_devkit-0.39.3.dist-info/METADATA +257 -0
  237. cisco_sccfm_devkit-0.39.3.dist-info/RECORD +241 -0
  238. cisco_sccfm_devkit-0.39.3.dist-info/WHEEL +4 -0
  239. cisco_sccfm_devkit-0.39.3.dist-info/entry_points.txt +4 -0
  240. cisco_sccfm_devkit-0.39.3.dist-info/licenses/LICENSE +201 -0
  241. cisco_sccfm_devkit-0.39.3.dist-info/licenses/LICENSES/Apache-2.0.txt +201 -0
@@ -0,0 +1,3 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
cisco_sccfm_cli/cli.py ADDED
@@ -0,0 +1,66 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+ import click
8
+ from rich.console import Console
9
+
10
+ from cisco_sccfm_cli.commands.base import BaseCommand
11
+ from cisco_sccfm_cli.commands.configure import ConfigureCommand
12
+ from cisco_sccfm_cli.commands.inventory import InventoryCommand
13
+ from cisco_sccfm_cli.commands.objects import ObjectsCommand
14
+ from cisco_sccfm_cli.commands.policies import PoliciesCommand
15
+ from cisco_sccfm_cli.commands.schema import SchemaCommand
16
+ from cisco_sccfm_cli.commands.status import StatusCommand
17
+ from cisco_sccfm_cli.commands.transaction import TransactionCommand
18
+
19
+
20
+ def _build_commands(console: Console) -> list[BaseCommand]:
21
+ return [
22
+ ConfigureCommand(console=console),
23
+ StatusCommand(console=console),
24
+ TransactionCommand(console=console),
25
+ InventoryCommand(console=console),
26
+ ObjectsCommand(console=console),
27
+ PoliciesCommand(console=console),
28
+ SchemaCommand(console=console),
29
+ ]
30
+
31
+
32
+ def _build_cli() -> click.Group:
33
+ console = Console()
34
+
35
+ @click.group(help="SCC Firewall Manager CLI")
36
+ @click.option(
37
+ "--profile",
38
+ default="default",
39
+ show_default=True,
40
+ help="Configuration profile to use",
41
+ )
42
+ @click.option(
43
+ "--silent",
44
+ is_flag=True,
45
+ default=False,
46
+ help="Suppress progress indicators (useful for piping output)",
47
+ )
48
+ @click.pass_context
49
+ def group(ctx: click.Context, profile: str, silent: bool) -> None:
50
+ ctx.ensure_object(dict)
51
+ ctx.obj["profile"] = profile
52
+ ctx.obj["silent"] = silent
53
+
54
+ for command in _build_commands(console):
55
+ command.register(group)
56
+ return group
57
+
58
+
59
+ cli = _build_cli()
60
+
61
+
62
+ if __name__ == "__main__":
63
+ try:
64
+ cli()
65
+ except KeyboardInterrupt:
66
+ raise SystemExit(130)
@@ -0,0 +1,3 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,337 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import shlex
9
+ import subprocess
10
+ import sys
11
+ from abc import ABC, abstractmethod
12
+ from pathlib import Path
13
+ from typing import Any, Sequence, cast
14
+
15
+ import click
16
+ from rich.console import Console
17
+ from rich.live import Live
18
+ from rich.spinner import Spinner
19
+ from scc_firewall_manager_sdk import ApiException, CdoTransaction, ConnectivityState, Device
20
+
21
+ from cisco_sccfm_cli.option_metadata import is_sensitive_option
22
+ from cisco_sccfm_cli.services import ConfigService
23
+ from cisco_sccfm_cli.utils import print_json, redact_data, redact_text
24
+ from cisco_sccfm_core import SccApiError
25
+ from cisco_sccfm_core.constants import DEFAULT_POLLING_INTERVAL_SEC, DEFAULT_TRANSACTION_TIMEOUT_SEC
26
+ from cisco_sccfm_core.models.cdo_transaction_status import CdoTransactionStatus
27
+ from cisco_sccfm_core.services.transaction_service import TransactionService
28
+ from cisco_sccfm_core.types import ConfigLike
29
+
30
+ _WINDOWS_SHELL = sys.platform == "win32"
31
+
32
+
33
+ def _join_shell_command(arguments: Sequence[str]) -> str:
34
+ """Render arguments for the platform's default command shell."""
35
+ if _WINDOWS_SHELL:
36
+ return subprocess.list2cmdline(arguments)
37
+ return shlex.join(arguments)
38
+
39
+
40
+ class BaseCommand(ABC):
41
+ """Base class implementing the command pattern for CLI commands."""
42
+
43
+ _SENSITIVE_VALUES_META_KEY = "sccfm_sensitive_values"
44
+
45
+ def __init__(self, console: Console) -> None:
46
+ self._console = console
47
+
48
+ @property
49
+ @abstractmethod
50
+ def name(self) -> str:
51
+ """Return the Click command name."""
52
+
53
+ @property
54
+ def help_text(self) -> str:
55
+ return ""
56
+
57
+ def register(self, group: click.Group) -> None:
58
+ group.add_command(self.build())
59
+
60
+ def build(self) -> click.Command:
61
+ return click.Command(
62
+ name=self.name,
63
+ help=self.help_text,
64
+ callback=self._dispatch,
65
+ params=list(self.build_params()),
66
+ )
67
+
68
+ def get_profile(self, ctx: click.Context, **kwargs: Any) -> ConfigLike:
69
+ profile = ctx.obj["profile"]
70
+ config_path = cast(Path | None, kwargs.get("config_path"))
71
+ config_service = ConfigService(path=config_path)
72
+ config = config_service.load(profile)
73
+ if not config:
74
+ setup_arguments = ["sccfm-cli", "--profile", profile, "configure"]
75
+ if config_path is not None:
76
+ setup_arguments.extend(["--config-path", str(config_path)])
77
+ setup_command = _join_shell_command(setup_arguments)
78
+ raise click.ClickException(
79
+ f"Profile '{profile}' not found. Run this command to set it up:\n{setup_command}"
80
+ )
81
+ self._register_sensitive_value(ctx, config.api_token)
82
+ return cast(ConfigLike, cast(object, config))
83
+
84
+ def build_params(self) -> Sequence[click.Parameter]:
85
+ return []
86
+
87
+ def _dispatch(self, **kwargs: Any) -> None:
88
+ ctx = click.get_current_context()
89
+ self._register_sensitive_parameters(ctx, kwargs)
90
+ exit_code: int | None = None
91
+ click_exception: click.ClickException | None = None
92
+ try:
93
+ self.handle(ctx=ctx, **kwargs)
94
+ except ApiException as e:
95
+ sensitive_values = self._sensitive_values(ctx)
96
+ output_format = cast(str | None, kwargs.get("format"))
97
+ error = SccApiError.from_exception(e)
98
+
99
+ if output_format == "json":
100
+ print_json(redact_data(error.to_dict(), sensitive_values))
101
+ else:
102
+ self.console.print(
103
+ "[yellow]Error executing operation using the SCC Firewall Manager API. "
104
+ "If you think you should not be getting this error, please file a Github issue"
105
+ " with the details below.[/yellow]"
106
+ )
107
+ self.console.print(
108
+ f"[bold]Error message:[/bold] "
109
+ f"{redact_text(error.message, sensitive_values)}"
110
+ )
111
+ error_code = redact_text(str(error.error_code), sensitive_values)
112
+ self.console.print(f"[bold]Error Code:[/bold] {error_code}")
113
+ error_details = redact_data(error.details, sensitive_values)
114
+ self.console.print(
115
+ f"[bold]Error Details:[/bold]\n{json.dumps(error_details, indent=2)}"
116
+ )
117
+ exit_code = -1
118
+ except click.ClickException as exc:
119
+ # Preserve Click's default error handling so usage/help is shown for user errors.
120
+ exc.message = redact_text(str(exc.message), self._sensitive_values(ctx))
121
+ exc.args = (exc.message,)
122
+ exc.__context__ = None
123
+ exc.__cause__ = None
124
+ exc.__suppress_context__ = True
125
+ exc.__traceback__ = None
126
+ click_exception = exc
127
+ except (click.Abort, click.exceptions.Exit):
128
+ raise
129
+ except KeyboardInterrupt:
130
+ sys.exit(130)
131
+ except Exception as e:
132
+ message = redact_text(str(e), self._sensitive_values(ctx))
133
+ self.console.print(f"[red]Error: {message}[/red]")
134
+ exit_code = -1
135
+
136
+ if click_exception is not None:
137
+ raise click_exception
138
+ if exit_code is not None:
139
+ sys.exit(exit_code)
140
+
141
+ def _register_sensitive_value(self, ctx: click.Context, value: str) -> None:
142
+ """Register a secret for command-scoped output and exception redaction."""
143
+ if not value:
144
+ return
145
+ values = self._sensitive_values(ctx)
146
+ if value not in values:
147
+ ctx.meta[self._SENSITIVE_VALUES_META_KEY] = (*values, value)
148
+
149
+ def _register_sensitive_parameters(self, ctx: click.Context, kwargs: dict[str, Any]) -> None:
150
+ """Register values from Click options explicitly marked as sensitive."""
151
+ for parameter in ctx.command.params:
152
+ if not isinstance(parameter, click.Option) or not is_sensitive_option(parameter):
153
+ continue
154
+ value = kwargs.get(parameter.name or "")
155
+ if isinstance(value, str):
156
+ self._register_sensitive_value(ctx, value)
157
+
158
+ def _sensitive_values(self, ctx: click.Context) -> tuple[str, ...]:
159
+ """Return secrets registered for the active Click command context."""
160
+ raw_values = ctx.meta.get(self._SENSITIVE_VALUES_META_KEY, ())
161
+ if not isinstance(raw_values, tuple):
162
+ return ()
163
+ return tuple(value for value in raw_values if isinstance(value, str) and value)
164
+
165
+ def _prompt_sensitive(
166
+ self,
167
+ text: str,
168
+ *,
169
+ default: str | None = None,
170
+ show_default: bool = True,
171
+ ) -> str:
172
+ """Prompt without echoing and immediately register the acquired secret."""
173
+ value = cast(
174
+ str,
175
+ click.prompt(
176
+ text,
177
+ default=default,
178
+ hide_input=True,
179
+ show_default=show_default,
180
+ ),
181
+ )
182
+ self._register_sensitive_value(click.get_current_context(), value)
183
+ return value
184
+
185
+ def _active_sensitive_values(
186
+ self, sensitive_values: Sequence[str] | None = None
187
+ ) -> Sequence[str]:
188
+ """Resolve explicit secrets or inherit the active command registry."""
189
+ if sensitive_values is not None:
190
+ return sensitive_values
191
+ ctx = click.get_current_context(silent=True)
192
+ return self._sensitive_values(ctx) if ctx is not None else ()
193
+
194
+ @abstractmethod
195
+ def handle(self, ctx: click.Context, **kwargs: Any) -> None:
196
+ """Execute the command logic."""
197
+
198
+ @property
199
+ def console(self) -> Console:
200
+ return self._console
201
+
202
+ def filter_online_devices(self, devices: list[Device]) -> list[Device]:
203
+ """Return only devices with ``ONLINE`` connectivity state.
204
+
205
+ Prints a warning for each skipped device so the operator knows
206
+ which devices were excluded. Raises :class:`click.ClickException`
207
+ if *no* devices are online.
208
+ """
209
+ online: list[Device] = []
210
+ offline: list[Device] = []
211
+ for device in devices:
212
+ if device.connectivity_state == ConnectivityState.ONLINE:
213
+ online.append(device)
214
+ else:
215
+ offline.append(device)
216
+
217
+ for device in offline:
218
+ self.console.print(
219
+ f"[yellow]Skipping '{device.name}' "
220
+ f"(state: {device.connectivity_state}) "
221
+ f"— CLI commands require ONLINE devices.[/yellow]"
222
+ )
223
+
224
+ if not online:
225
+ raise click.ClickException(
226
+ "No online devices found. CLI commands can only be "
227
+ "executed on devices with connectivity state ONLINE."
228
+ )
229
+ return online
230
+
231
+ def wait_for_transaction(
232
+ self,
233
+ *,
234
+ config: ConfigLike,
235
+ transaction: CdoTransaction,
236
+ polling_interval_sec: int = DEFAULT_POLLING_INTERVAL_SEC,
237
+ spinner_text: str | None = None,
238
+ **kwargs: Any,
239
+ ) -> CdoTransaction:
240
+ """Poll a transaction until it reaches a terminal state, printing status on each poll.
241
+
242
+ Reads ``wait`` (default False) and ``timeout`` (default 3600) from
243
+ *kwargs* so callers can forward CLI kwargs directly.
244
+ Returns the original transaction unchanged when ``wait`` is False.
245
+
246
+ When *spinner_text* is provided, a checkmark line is printed
247
+ (e.g. ``\u2713 Triggering ASA upgrade\u2026``) and a live spinner
248
+ shows the latest transaction status in-place. All human-facing
249
+ output is written to *stderr* so that stdout remains clean for
250
+ machine-parseable formats like JSON.
251
+ """
252
+ wait = cast(bool, kwargs.get("wait", False))
253
+ if not wait:
254
+ return transaction
255
+ if transaction.transaction_uid is None:
256
+ raise click.ClickException("Transaction UID missing \u2014 cannot poll for status.")
257
+
258
+ ctx = click.get_current_context()
259
+ silent = (ctx.obj or {}).get("silent", False)
260
+
261
+ stderr_console = Console(stderr=True)
262
+
263
+ if spinner_text and not silent:
264
+ stderr_console.print(f"[green]\u2713[/green] {spinner_text}")
265
+ stderr_console.print(f" [bold]Transaction UID:[/bold] {transaction.transaction_uid}")
266
+ if transaction.transaction_polling_url:
267
+ stderr_console.print(
268
+ f" [bold]Polling URL:[/bold] {transaction.transaction_polling_url}"
269
+ )
270
+
271
+ timeout_sec = cast(int, kwargs.get("timeout", DEFAULT_TRANSACTION_TIMEOUT_SEC))
272
+ service = TransactionService(config=config)
273
+
274
+ if silent:
275
+ return service.wait_for_transaction_to_finish(
276
+ transaction_uid=transaction.transaction_uid,
277
+ timeout_sec=timeout_sec,
278
+ polling_interval_sec=polling_interval_sec,
279
+ )
280
+
281
+ spinner = Spinner("dots", text="Polling transaction status...")
282
+
283
+ def _update_status(t: CdoTransaction) -> None:
284
+ spinner.text = f"Status: {t.cdo_transaction_status}"
285
+
286
+ with Live(spinner, console=stderr_console, refresh_per_second=10, transient=True):
287
+ return service.wait_for_transaction_to_finish(
288
+ transaction_uid=transaction.transaction_uid,
289
+ timeout_sec=timeout_sec,
290
+ polling_interval_sec=polling_interval_sec,
291
+ on_poll=_update_status,
292
+ )
293
+
294
+ @staticmethod
295
+ def is_failed_transaction(transaction: CdoTransaction) -> bool:
296
+ """Return True if the transaction reached a terminal failure state."""
297
+ return transaction.cdo_transaction_status in (
298
+ CdoTransactionStatus.ERROR,
299
+ CdoTransactionStatus.CANCELLED,
300
+ )
301
+
302
+ def print_failed_transaction_details(
303
+ self,
304
+ cdo_transaction: CdoTransaction,
305
+ format: str = "table",
306
+ *,
307
+ sensitive_values: Sequence[str] | None = None,
308
+ ) -> None:
309
+ sensitive_values = self._active_sensitive_values(sensitive_values)
310
+ if format == "json":
311
+ print_json(redact_data(cdo_transaction.to_dict(), sensitive_values))
312
+ else:
313
+ transaction_uid = redact_text(str(cdo_transaction.transaction_uid), sensitive_values)
314
+ transaction_status = redact_text(
315
+ str(cdo_transaction.cdo_transaction_status), sensitive_values
316
+ )
317
+ error_message = redact_text(str(cdo_transaction.error_message), sensitive_values)
318
+ transaction_details = redact_data(cdo_transaction.transaction_details, sensitive_values)
319
+ self.console.print("[yellow]The execution failed. Transaction Details:[/yellow]")
320
+ self.console.print("[bold]Transaction UID: [/bold]" f"{transaction_uid}")
321
+ self.console.print("[bold]Transaction Status: [/bold]" f"{transaction_status}")
322
+ self.console.print("[bold]Transaction Error Message: [/bold]" f"{error_message}")
323
+ self.console.print(
324
+ "[bold]Transaction Details: [/bold]\n" f"{json.dumps(transaction_details)}"
325
+ )
326
+ sys.exit(-1)
327
+
328
+ def print_submitted_transaction(
329
+ self, cdo_transaction: CdoTransaction, format: str = "table"
330
+ ) -> None:
331
+ """Render a submitted (not-yet-completed) transaction."""
332
+ if format == "json":
333
+ print_json(cdo_transaction.to_dict())
334
+ else:
335
+ self.console.print("[green]✓[/green] Transaction submitted.")
336
+ self.console.print(f"[bold]Transaction UID:[/bold] {cdo_transaction.transaction_uid}")
337
+ self.console.print(f"[bold]Status:[/bold] {cdo_transaction.cdo_transaction_status}")
@@ -0,0 +1,123 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any, Final, Sequence, cast
10
+
11
+ import click
12
+ from click.core import ParameterSource
13
+ from click_option_group import GroupedOption, OptionGroup
14
+ from rich.console import Console
15
+
16
+ from cisco_sccfm_cli.commands.base import BaseCommand
17
+ from cisco_sccfm_cli.models import Config
18
+ from cisco_sccfm_cli.option_metadata import sensitive_option
19
+ from cisco_sccfm_cli.services import ConfigService
20
+ from cisco_sccfm_core.constants import SCCFM_REGION_CHOICES, SCCFM_REGIONS, normalize_sccfm_region
21
+
22
+
23
+ class ConfigureCommand(BaseCommand):
24
+ _API_TOKEN_ENVVAR: Final[str] = "SCCFM_API_TOKEN"
25
+
26
+ def __init__(
27
+ self,
28
+ console: Console,
29
+ config_service: ConfigService | None = None,
30
+ ) -> None:
31
+ super().__init__(console)
32
+ self._config_service = config_service
33
+
34
+ @property
35
+ def name(self) -> str:
36
+ return "configure"
37
+
38
+ @property
39
+ def help_text(self) -> str:
40
+ return "Configure API connectivity."
41
+
42
+ def build_params(self) -> Sequence[click.Parameter]:
43
+ profile_group = OptionGroup("Profile", help="Profile storage overrides.")
44
+ credential_group = OptionGroup(
45
+ "Credentials",
46
+ help="Region and API token settings.",
47
+ )
48
+ return [
49
+ GroupedOption(
50
+ ["--config-path"],
51
+ type=click.Path(path_type=Path, resolve_path=False),
52
+ default=None,
53
+ envvar="SCCFM_CONFIG",
54
+ show_default=False,
55
+ help=("Path to the configuration file " "(defaults to ~/.sccfm-cli/config.json)."),
56
+ group=profile_group,
57
+ ),
58
+ GroupedOption(
59
+ ["--region"],
60
+ type=click.Choice(SCCFM_REGION_CHOICES, case_sensitive=False),
61
+ help=f"SCCFM region ({', '.join(SCCFM_REGIONS)})",
62
+ group=credential_group,
63
+ required=True,
64
+ ),
65
+ sensitive_option(
66
+ GroupedOption(
67
+ ["--api-token"],
68
+ type=str,
69
+ default=None,
70
+ envvar=self._API_TOKEN_ENVVAR,
71
+ show_envvar=True,
72
+ hide_input=True,
73
+ help=(
74
+ "API token for the chosen region. Passing it directly is supported for "
75
+ "compatibility but may expose it in process listings and shell history; "
76
+ f"prefer {self._API_TOKEN_ENVVAR} or the hidden prompt."
77
+ ),
78
+ group=credential_group,
79
+ required=False,
80
+ ),
81
+ ),
82
+ ]
83
+
84
+ def handle(self, ctx: click.Context, **kwargs: Any) -> None:
85
+ profile = ctx.obj["profile"]
86
+ region = kwargs["region"]
87
+ api_token = self._resolve_api_token(ctx=ctx, **kwargs)
88
+ config_path = kwargs["config_path"]
89
+
90
+ config_service = ConfigService(config_path)
91
+ normalized_region = normalize_sccfm_region(region)
92
+ assert normalized_region
93
+ config = Config(profile=profile, region=normalized_region, api_token=api_token)
94
+ config_service.save(config)
95
+ self.console.print(f"[green]Profile '{profile}' updated[/green]")
96
+
97
+ def _resolve_api_token(self, ctx: click.Context, **kwargs: Any) -> str:
98
+ api_token = cast(str | None, kwargs.get("api_token"))
99
+ source = ctx.get_parameter_source("api_token")
100
+
101
+ if api_token is None:
102
+ if not self._can_prompt():
103
+ ctx.fail(
104
+ "An API token is required. Set "
105
+ f"{self._API_TOKEN_ENVVAR} or run interactively for a hidden prompt."
106
+ )
107
+ api_token = self._prompt_sensitive("API token")
108
+ elif source is ParameterSource.COMMANDLINE:
109
+ click.echo(
110
+ "Warning: passing --api-token directly may expose it in process listings and "
111
+ f"shell history; prefer {self._API_TOKEN_ENVVAR} or the hidden prompt.",
112
+ err=True,
113
+ )
114
+
115
+ return self._validate_api_token(ctx=ctx, api_token=api_token)
116
+
117
+ def _validate_api_token(self, ctx: click.Context, api_token: str) -> str:
118
+ if not api_token.strip():
119
+ ctx.fail("The API token cannot be empty.")
120
+ return api_token
121
+
122
+ def _can_prompt(self) -> bool:
123
+ return sys.stdin.isatty()
@@ -0,0 +1,7 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from cisco_sccfm_cli.commands.inventory.command import InventoryCommand
6
+
7
+ __all__ = ["InventoryCommand"]
@@ -0,0 +1,38 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any
8
+
9
+ import click
10
+ from rich.console import Console
11
+
12
+ from cisco_sccfm_cli.commands.base import BaseCommand
13
+ from cisco_sccfm_cli.commands.inventory.devices import DevicesCommand
14
+ from cisco_sccfm_cli.commands.inventory.manager import ManagersCommand
15
+
16
+
17
+ class InventoryCommand(BaseCommand):
18
+ def __init__(self, console: Console) -> None:
19
+ super().__init__(console)
20
+ self._devices_command = DevicesCommand(console)
21
+ self._managers_command = ManagersCommand(console)
22
+
23
+ @property
24
+ def name(self) -> str:
25
+ return "inventory"
26
+
27
+ @property
28
+ def help_text(self) -> str:
29
+ return "Browse SCC Firewall Management inventory."
30
+
31
+ def build(self) -> click.Command:
32
+ group = click.Group(name=self.name, help=self.help_text)
33
+ group.add_command(self._devices_command.build())
34
+ group.add_command(self._managers_command.build())
35
+ return group
36
+
37
+ def handle(self, ctx: click.Context, **kwargs: Any) -> None: # pragma: no cover
38
+ ctx.fail("Specify a subcommand: devices or manager")
@@ -0,0 +1,7 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from cisco_sccfm_cli.commands.inventory.devices.command import DevicesCommand
6
+
7
+ __all__ = ["DevicesCommand"]
@@ -0,0 +1,7 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ __all__ = ["AsaCommand"]
6
+
7
+ from cisco_sccfm_cli.commands.inventory.devices.asa.command import AsaCommand
@@ -0,0 +1,9 @@
1
+ # Copyright 2026 Cisco Systems, Inc. and its affiliates
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ __all__ = ["AsaChangeBootImageCommand"]
6
+
7
+ from cisco_sccfm_cli.commands.inventory.devices.asa.change_boot_image.command import (
8
+ AsaChangeBootImageCommand,
9
+ )