catocli 2.0.3__py3-none-any.whl → 2.0.5__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.

Potentially problematic release.


This version of catocli might be problematic. Click here for more details.

Files changed (291) hide show
  1. build/lib/catocli/Utils/clidriver.py +268 -0
  2. build/lib/catocli/Utils/profile_manager.py +188 -0
  3. build/lib/catocli/Utils/version_checker.py +192 -0
  4. build/lib/catocli/__init__.py +2 -0
  5. build/lib/catocli/__main__.py +12 -0
  6. build/lib/catocli/parsers/configure/__init__.py +115 -0
  7. build/lib/catocli/parsers/configure/configure.py +307 -0
  8. build/lib/catocli/parsers/custom/__init__.py +57 -0
  9. build/lib/catocli/parsers/custom/customLib.py +561 -0
  10. build/lib/catocli/parsers/custom/export_rules/__init__.py +42 -0
  11. build/lib/catocli/parsers/custom/export_rules/export_rules.py +234 -0
  12. build/lib/catocli/parsers/custom/export_sites/__init__.py +21 -0
  13. build/lib/catocli/parsers/custom/export_sites/export_sites.py +372 -0
  14. build/lib/catocli/parsers/custom/import_rules_to_tf/__init__.py +58 -0
  15. build/lib/catocli/parsers/custom/import_rules_to_tf/import_rules_to_tf.py +451 -0
  16. build/lib/catocli/parsers/custom/import_sites_to_tf/__init__.py +45 -0
  17. build/lib/catocli/parsers/custom/import_sites_to_tf/import_sites_to_tf.py +891 -0
  18. build/lib/catocli/parsers/mutation_accountManagement/__init__.py +48 -0
  19. build/lib/catocli/parsers/mutation_admin/__init__.py +48 -0
  20. build/lib/catocli/parsers/mutation_container/__init__.py +138 -0
  21. build/lib/catocli/parsers/mutation_hardware/__init__.py +22 -0
  22. build/lib/catocli/parsers/mutation_policy/__init__.py +1305 -0
  23. build/lib/catocli/parsers/mutation_sandbox/__init__.py +35 -0
  24. build/lib/catocli/parsers/mutation_site/__init__.py +373 -0
  25. build/lib/catocli/parsers/mutation_sites/__init__.py +373 -0
  26. build/lib/catocli/parsers/mutation_xdr/__init__.py +48 -0
  27. build/lib/catocli/parsers/parserApiClient.py +513 -0
  28. build/lib/catocli/parsers/query_accountBySubdomain/__init__.py +16 -0
  29. build/lib/catocli/parsers/query_accountManagement/__init__.py +16 -0
  30. build/lib/catocli/parsers/query_accountMetrics/__init__.py +16 -0
  31. build/lib/catocli/parsers/query_accountRoles/__init__.py +16 -0
  32. build/lib/catocli/parsers/query_accountSnapshot/__init__.py +16 -0
  33. build/lib/catocli/parsers/query_admin/__init__.py +16 -0
  34. build/lib/catocli/parsers/query_admins/__init__.py +16 -0
  35. build/lib/catocli/parsers/query_appStats/__init__.py +16 -0
  36. build/lib/catocli/parsers/query_appStatsTimeSeries/__init__.py +16 -0
  37. build/lib/catocli/parsers/query_auditFeed/__init__.py +16 -0
  38. build/lib/catocli/parsers/query_catalogs/__init__.py +16 -0
  39. build/lib/catocli/parsers/query_container/__init__.py +16 -0
  40. build/lib/catocli/parsers/query_devices/__init__.py +16 -0
  41. build/lib/catocli/parsers/query_entityLookup/__init__.py +16 -0
  42. build/lib/catocli/parsers/query_events/__init__.py +16 -0
  43. build/lib/catocli/parsers/query_eventsFeed/__init__.py +16 -0
  44. build/lib/catocli/parsers/query_eventsTimeSeries/__init__.py +16 -0
  45. build/lib/catocli/parsers/query_hardware/__init__.py +16 -0
  46. build/lib/catocli/parsers/query_hardwareManagement/__init__.py +16 -0
  47. build/lib/catocli/parsers/query_licensing/__init__.py +16 -0
  48. build/lib/catocli/parsers/query_policy/__init__.py +161 -0
  49. build/lib/catocli/parsers/query_sandbox/__init__.py +16 -0
  50. build/lib/catocli/parsers/query_site/__init__.py +100 -0
  51. build/lib/catocli/parsers/query_siteLocation/__init__.py +13 -0
  52. build/lib/catocli/parsers/query_subDomains/__init__.py +16 -0
  53. build/lib/catocli/parsers/query_xdr/__init__.py +35 -0
  54. build/lib/catocli/parsers/raw/__init__.py +12 -0
  55. build/lib/graphql_client/__init__.py +11 -0
  56. build/lib/graphql_client/api/__init__.py +3 -0
  57. build/lib/graphql_client/api/call_api.py +84 -0
  58. build/lib/graphql_client/api_client.py +192 -0
  59. build/lib/graphql_client/api_client_types.py +409 -0
  60. build/lib/graphql_client/configuration.py +232 -0
  61. build/lib/graphql_client/models/__init__.py +13 -0
  62. build/lib/graphql_client/models/no_schema.py +71 -0
  63. build/lib/schema/catolib.py +1141 -0
  64. build/lib/schema/importSchema.py +60 -0
  65. build/lib/schema/remove_policyid.py +89 -0
  66. build/lib/schema/remove_policyid_mutations.py +89 -0
  67. build/lib/scripts/catolib.py +62 -0
  68. build/lib/scripts/export_if_rules_to_json.py +188 -0
  69. build/lib/scripts/export_wf_rules_to_json.py +111 -0
  70. build/lib/scripts/import_wf_rules_to_tfstate.py +331 -0
  71. build/lib/vendor/certifi/__init__.py +4 -0
  72. build/lib/vendor/certifi/__main__.py +12 -0
  73. build/lib/vendor/certifi/core.py +114 -0
  74. build/lib/vendor/certifi/py.typed +0 -0
  75. build/lib/vendor/six.py +998 -0
  76. build/lib/vendor/urllib3/__init__.py +211 -0
  77. build/lib/vendor/urllib3/_base_connection.py +172 -0
  78. build/lib/vendor/urllib3/_collections.py +483 -0
  79. build/lib/vendor/urllib3/_request_methods.py +278 -0
  80. build/lib/vendor/urllib3/_version.py +16 -0
  81. build/lib/vendor/urllib3/connection.py +1033 -0
  82. build/lib/vendor/urllib3/connectionpool.py +1182 -0
  83. build/lib/vendor/urllib3/contrib/__init__.py +0 -0
  84. build/lib/vendor/urllib3/contrib/emscripten/__init__.py +18 -0
  85. build/lib/vendor/urllib3/contrib/emscripten/connection.py +254 -0
  86. build/lib/vendor/urllib3/contrib/emscripten/fetch.py +418 -0
  87. build/lib/vendor/urllib3/contrib/emscripten/request.py +22 -0
  88. build/lib/vendor/urllib3/contrib/emscripten/response.py +285 -0
  89. build/lib/vendor/urllib3/contrib/pyopenssl.py +552 -0
  90. build/lib/vendor/urllib3/contrib/socks.py +228 -0
  91. build/lib/vendor/urllib3/exceptions.py +321 -0
  92. build/lib/vendor/urllib3/fields.py +341 -0
  93. build/lib/vendor/urllib3/filepost.py +89 -0
  94. build/lib/vendor/urllib3/http2/__init__.py +53 -0
  95. build/lib/vendor/urllib3/http2/connection.py +356 -0
  96. build/lib/vendor/urllib3/http2/probe.py +87 -0
  97. build/lib/vendor/urllib3/poolmanager.py +637 -0
  98. build/lib/vendor/urllib3/py.typed +2 -0
  99. build/lib/vendor/urllib3/response.py +1265 -0
  100. build/lib/vendor/urllib3/util/__init__.py +42 -0
  101. build/lib/vendor/urllib3/util/connection.py +137 -0
  102. build/lib/vendor/urllib3/util/proxy.py +43 -0
  103. build/lib/vendor/urllib3/util/request.py +256 -0
  104. build/lib/vendor/urllib3/util/response.py +101 -0
  105. build/lib/vendor/urllib3/util/retry.py +533 -0
  106. build/lib/vendor/urllib3/util/ssl_.py +513 -0
  107. build/lib/vendor/urllib3/util/ssl_match_hostname.py +159 -0
  108. build/lib/vendor/urllib3/util/ssltransport.py +276 -0
  109. build/lib/vendor/urllib3/util/timeout.py +275 -0
  110. build/lib/vendor/urllib3/util/url.py +471 -0
  111. build/lib/vendor/urllib3/util/util.py +42 -0
  112. build/lib/vendor/urllib3/util/wait.py +124 -0
  113. catocli/Utils/clidriver.py +1 -4
  114. catocli/__init__.py +1 -1
  115. catocli/parsers/custom/export_rules/__init__.py +2 -0
  116. catocli/parsers/custom/export_rules/export_rules.py +29 -5
  117. catocli/parsers/custom/export_sites/__init__.py +1 -0
  118. catocli/parsers/custom/export_sites/export_sites.py +10 -3
  119. catocli/parsers/mutation_container/__init__.py +116 -0
  120. catocli/parsers/mutation_container_fqdn/README.md +7 -0
  121. catocli/parsers/mutation_container_fqdn_addValues/README.md +17 -0
  122. catocli/parsers/mutation_container_fqdn_createFromFile/README.md +17 -0
  123. catocli/parsers/mutation_container_fqdn_removeValues/README.md +17 -0
  124. catocli/parsers/mutation_container_fqdn_updateFromFile/README.md +17 -0
  125. catocli/parsers/mutation_container_ipAddressRange/README.md +7 -0
  126. catocli/parsers/mutation_container_ipAddressRange_addValues/README.md +17 -0
  127. catocli/parsers/mutation_container_ipAddressRange_createFromFile/README.md +17 -0
  128. catocli/parsers/mutation_container_ipAddressRange_removeValues/README.md +17 -0
  129. catocli/parsers/mutation_container_ipAddressRange_updateFromFile/README.md +17 -0
  130. catocli/parsers/mutation_policy_internetFirewall_addRule/README.md +1 -1
  131. catocli/parsers/mutation_policy_internetFirewall_updateRule/README.md +1 -1
  132. catocli/parsers/mutation_policy_wanFirewall_addRule/README.md +1 -1
  133. catocli/parsers/mutation_policy_wanFirewall_updateRule/README.md +1 -1
  134. catocli/parsers/parserApiClient.py +183 -7
  135. catocli/parsers/query_policy/README.md +0 -17
  136. catocli/parsers/query_policy/__init__.py +153 -8
  137. catocli/parsers/query_policy_appTenantRestriction/README.md +7 -0
  138. catocli/parsers/query_policy_appTenantRestriction_policy/README.md +17 -0
  139. catocli/parsers/query_policy_dynamicIpAllocation/README.md +7 -0
  140. catocli/parsers/query_policy_dynamicIpAllocation_policy/README.md +17 -0
  141. catocli/parsers/query_policy_internetFirewall/README.md +7 -0
  142. catocli/parsers/query_policy_internetFirewall_policy/README.md +17 -0
  143. catocli/parsers/query_policy_remotePortFwd/README.md +7 -0
  144. catocli/parsers/query_policy_remotePortFwd_policy/README.md +17 -0
  145. catocli/parsers/query_policy_socketLan/README.md +7 -0
  146. catocli/parsers/query_policy_socketLan_policy/README.md +17 -0
  147. catocli/parsers/query_policy_terminalServer/README.md +7 -0
  148. catocli/parsers/query_policy_terminalServer_policy/README.md +17 -0
  149. catocli/parsers/query_policy_wanFirewall/README.md +7 -0
  150. catocli/parsers/query_policy_wanFirewall_policy/README.md +17 -0
  151. catocli/parsers/query_policy_wanNetwork/README.md +7 -0
  152. catocli/parsers/query_policy_wanNetwork_policy/README.md +17 -0
  153. catocli/parsers/query_site/README.md +0 -16
  154. catocli/parsers/query_site/__init__.py +92 -8
  155. catocli/parsers/query_site_availableVersionList/README.md +17 -0
  156. catocli/parsers/query_site_bgpPeer/README.md +17 -0
  157. catocli/parsers/query_site_bgpPeerList/README.md +17 -0
  158. catocli/parsers/query_site_cloudInterconnectConnectionConnectivity/README.md +17 -0
  159. catocli/parsers/query_site_cloudInterconnectPhysicalConnection/README.md +17 -0
  160. catocli/parsers/query_site_cloudInterconnectPhysicalConnectionId/README.md +17 -0
  161. catocli/parsers/query_site_siteBgpStatus/README.md +17 -0
  162. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/METADATA +1 -1
  163. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/RECORD +291 -121
  164. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/top_level.txt +1 -0
  165. graphql_client/api/call_api.py +4 -3
  166. models/mutation.container.fqdn.addValues.json +866 -0
  167. models/mutation.container.fqdn.createFromFile.json +819 -0
  168. models/mutation.container.fqdn.removeValues.json +866 -0
  169. models/mutation.container.fqdn.updateFromFile.json +1045 -0
  170. models/mutation.container.ipAddressRange.addValues.json +1020 -0
  171. models/mutation.container.ipAddressRange.createFromFile.json +819 -0
  172. models/mutation.container.ipAddressRange.removeValues.json +1020 -0
  173. models/mutation.container.ipAddressRange.updateFromFile.json +1045 -0
  174. models/mutation.policy.appTenantRestriction.addRule.json +8 -8
  175. models/mutation.policy.appTenantRestriction.addSection.json +1 -1
  176. models/mutation.policy.appTenantRestriction.createPolicyRevision.json +2 -2
  177. models/mutation.policy.appTenantRestriction.discardPolicyRevision.json +2 -2
  178. models/mutation.policy.appTenantRestriction.moveRule.json +2 -2
  179. models/mutation.policy.appTenantRestriction.moveSection.json +1 -1
  180. models/mutation.policy.appTenantRestriction.publishPolicyRevision.json +2 -2
  181. models/mutation.policy.appTenantRestriction.removeRule.json +2 -2
  182. models/mutation.policy.appTenantRestriction.removeSection.json +1 -1
  183. models/mutation.policy.appTenantRestriction.updatePolicy.json +2 -2
  184. models/mutation.policy.appTenantRestriction.updateRule.json +8 -8
  185. models/mutation.policy.appTenantRestriction.updateSection.json +1 -1
  186. models/mutation.policy.dynamicIpAllocation.addRule.json +1 -1
  187. models/mutation.policy.dynamicIpAllocation.addSection.json +1 -1
  188. models/mutation.policy.dynamicIpAllocation.createPolicyRevision.json +1 -1
  189. models/mutation.policy.dynamicIpAllocation.discardPolicyRevision.json +1 -1
  190. models/mutation.policy.dynamicIpAllocation.moveRule.json +1 -1
  191. models/mutation.policy.dynamicIpAllocation.moveSection.json +1 -1
  192. models/mutation.policy.dynamicIpAllocation.publishPolicyRevision.json +1 -1
  193. models/mutation.policy.dynamicIpAllocation.removeRule.json +1 -1
  194. models/mutation.policy.dynamicIpAllocation.removeSection.json +1 -1
  195. models/mutation.policy.dynamicIpAllocation.updatePolicy.json +1 -1
  196. models/mutation.policy.dynamicIpAllocation.updateRule.json +1 -1
  197. models/mutation.policy.dynamicIpAllocation.updateSection.json +1 -1
  198. models/mutation.policy.internetFirewall.addRule.json +502 -55
  199. models/mutation.policy.internetFirewall.addSection.json +1 -1
  200. models/mutation.policy.internetFirewall.createPolicyRevision.json +127 -10
  201. models/mutation.policy.internetFirewall.discardPolicyRevision.json +127 -10
  202. models/mutation.policy.internetFirewall.moveRule.json +127 -10
  203. models/mutation.policy.internetFirewall.moveSection.json +1 -1
  204. models/mutation.policy.internetFirewall.publishPolicyRevision.json +127 -10
  205. models/mutation.policy.internetFirewall.removeRule.json +127 -10
  206. models/mutation.policy.internetFirewall.removeSection.json +1 -1
  207. models/mutation.policy.internetFirewall.updatePolicy.json +127 -10
  208. models/mutation.policy.internetFirewall.updateRule.json +493 -55
  209. models/mutation.policy.internetFirewall.updateSection.json +1 -1
  210. models/mutation.policy.remotePortFwd.addRule.json +5 -5
  211. models/mutation.policy.remotePortFwd.addSection.json +1 -1
  212. models/mutation.policy.remotePortFwd.createPolicyRevision.json +2 -2
  213. models/mutation.policy.remotePortFwd.discardPolicyRevision.json +2 -2
  214. models/mutation.policy.remotePortFwd.moveRule.json +2 -2
  215. models/mutation.policy.remotePortFwd.moveSection.json +1 -1
  216. models/mutation.policy.remotePortFwd.publishPolicyRevision.json +2 -2
  217. models/mutation.policy.remotePortFwd.removeRule.json +2 -2
  218. models/mutation.policy.remotePortFwd.removeSection.json +1 -1
  219. models/mutation.policy.remotePortFwd.updatePolicy.json +2 -2
  220. models/mutation.policy.remotePortFwd.updateRule.json +5 -5
  221. models/mutation.policy.remotePortFwd.updateSection.json +1 -1
  222. models/mutation.policy.socketLan.addRule.json +16 -16
  223. models/mutation.policy.socketLan.addSection.json +1 -1
  224. models/mutation.policy.socketLan.createPolicyRevision.json +4 -4
  225. models/mutation.policy.socketLan.discardPolicyRevision.json +4 -4
  226. models/mutation.policy.socketLan.moveRule.json +4 -4
  227. models/mutation.policy.socketLan.moveSection.json +1 -1
  228. models/mutation.policy.socketLan.publishPolicyRevision.json +4 -4
  229. models/mutation.policy.socketLan.removeRule.json +4 -4
  230. models/mutation.policy.socketLan.removeSection.json +1 -1
  231. models/mutation.policy.socketLan.updatePolicy.json +4 -4
  232. models/mutation.policy.socketLan.updateRule.json +16 -16
  233. models/mutation.policy.socketLan.updateSection.json +1 -1
  234. models/mutation.policy.terminalServer.addRule.json +1 -1
  235. models/mutation.policy.terminalServer.addSection.json +1 -1
  236. models/mutation.policy.terminalServer.createPolicyRevision.json +1 -1
  237. models/mutation.policy.terminalServer.discardPolicyRevision.json +1 -1
  238. models/mutation.policy.terminalServer.moveRule.json +1 -1
  239. models/mutation.policy.terminalServer.moveSection.json +1 -1
  240. models/mutation.policy.terminalServer.publishPolicyRevision.json +1 -1
  241. models/mutation.policy.terminalServer.removeRule.json +1 -1
  242. models/mutation.policy.terminalServer.removeSection.json +1 -1
  243. models/mutation.policy.terminalServer.updatePolicy.json +1 -1
  244. models/mutation.policy.terminalServer.updateRule.json +1 -1
  245. models/mutation.policy.terminalServer.updateSection.json +1 -1
  246. models/mutation.policy.wanFirewall.addRule.json +500 -53
  247. models/mutation.policy.wanFirewall.addSection.json +1 -1
  248. models/mutation.policy.wanFirewall.createPolicyRevision.json +128 -11
  249. models/mutation.policy.wanFirewall.discardPolicyRevision.json +128 -11
  250. models/mutation.policy.wanFirewall.moveRule.json +128 -11
  251. models/mutation.policy.wanFirewall.moveSection.json +1 -1
  252. models/mutation.policy.wanFirewall.publishPolicyRevision.json +128 -11
  253. models/mutation.policy.wanFirewall.removeRule.json +128 -11
  254. models/mutation.policy.wanFirewall.removeSection.json +1 -1
  255. models/mutation.policy.wanFirewall.updatePolicy.json +128 -11
  256. models/mutation.policy.wanFirewall.updateRule.json +491 -53
  257. models/mutation.policy.wanFirewall.updateSection.json +1 -1
  258. models/mutation.policy.wanNetwork.addRule.json +13 -13
  259. models/mutation.policy.wanNetwork.addSection.json +1 -1
  260. models/mutation.policy.wanNetwork.createPolicyRevision.json +1 -1
  261. models/mutation.policy.wanNetwork.discardPolicyRevision.json +1 -1
  262. models/mutation.policy.wanNetwork.moveRule.json +1 -1
  263. models/mutation.policy.wanNetwork.moveSection.json +1 -1
  264. models/mutation.policy.wanNetwork.publishPolicyRevision.json +1 -1
  265. models/mutation.policy.wanNetwork.removeRule.json +1 -1
  266. models/mutation.policy.wanNetwork.removeSection.json +1 -1
  267. models/mutation.policy.wanNetwork.updatePolicy.json +1 -1
  268. models/mutation.policy.wanNetwork.updateRule.json +13 -13
  269. models/mutation.policy.wanNetwork.updateSection.json +1 -1
  270. models/query.policy.appTenantRestriction.policy.json +3086 -0
  271. models/query.policy.dynamicIpAllocation.policy.json +1934 -0
  272. models/query.policy.internetFirewall.policy.json +7833 -0
  273. models/query.policy.json +233 -0
  274. models/query.policy.remotePortFwd.policy.json +2387 -0
  275. models/query.policy.socketLan.policy.json +7140 -0
  276. models/query.policy.terminalServer.policy.json +1632 -0
  277. models/query.policy.wanFirewall.policy.json +9212 -0
  278. models/query.policy.wanNetwork.policy.json +8010 -0
  279. models/query.site.availableVersionList.json +365 -0
  280. models/query.site.bgpPeer.json +1917 -0
  281. models/query.site.bgpPeerList.json +2076 -0
  282. models/query.site.cloudInterconnectConnectionConnectivity.json +298 -0
  283. models/query.site.cloudInterconnectPhysicalConnection.json +728 -0
  284. models/query.site.cloudInterconnectPhysicalConnectionId.json +660 -0
  285. models/query.site.siteBgpStatus.json +869 -0
  286. schema/catolib.py +13 -6
  287. schema/remove_policyid.py +89 -0
  288. schema/remove_policyid_mutations.py +89 -0
  289. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/LICENSE +0 -0
  290. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/WHEEL +0 -0
  291. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,471 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import typing
5
+
6
+ from ..exceptions import LocationParseError
7
+ from .util import to_str
8
+
9
+ # We only want to normalize urls with an HTTP(S) scheme.
10
+ # urllib3 infers URLs without a scheme (None) to be http.
11
+ _NORMALIZABLE_SCHEMES = ("http", "https", None)
12
+
13
+ # Almost all of these patterns were derived from the
14
+ # 'rfc3986' module: https://github.com/python-hyper/rfc3986
15
+ _PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}")
16
+ _SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)")
17
+ _URI_RE = re.compile(
18
+ r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?"
19
+ r"(?://([^\\/?#]*))?"
20
+ r"([^?#]*)"
21
+ r"(?:\?([^#]*))?"
22
+ r"(?:#(.*))?$",
23
+ re.UNICODE | re.DOTALL,
24
+ )
25
+
26
+ _IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}"
27
+ _HEX_PAT = "[0-9A-Fa-f]{1,4}"
28
+ _LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=_HEX_PAT, ipv4=_IPV4_PAT)
29
+ _subs = {"hex": _HEX_PAT, "ls32": _LS32_PAT}
30
+ _variations = [
31
+ # 6( h16 ":" ) ls32
32
+ "(?:%(hex)s:){6}%(ls32)s",
33
+ # "::" 5( h16 ":" ) ls32
34
+ "::(?:%(hex)s:){5}%(ls32)s",
35
+ # [ h16 ] "::" 4( h16 ":" ) ls32
36
+ "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s",
37
+ # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
38
+ "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s",
39
+ # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
40
+ "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s",
41
+ # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
42
+ "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s",
43
+ # [ *4( h16 ":" ) h16 ] "::" ls32
44
+ "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s",
45
+ # [ *5( h16 ":" ) h16 ] "::" h16
46
+ "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s",
47
+ # [ *6( h16 ":" ) h16 ] "::"
48
+ "(?:(?:%(hex)s:){0,6}%(hex)s)?::",
49
+ ]
50
+
51
+ _UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~"
52
+ _IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")"
53
+ _ZONE_ID_PAT = "(?:%25|%)(?:[" + _UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+"
54
+ _IPV6_ADDRZ_PAT = r"\[" + _IPV6_PAT + r"(?:" + _ZONE_ID_PAT + r")?\]"
55
+ _REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*"
56
+ _TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$")
57
+
58
+ _IPV4_RE = re.compile("^" + _IPV4_PAT + "$")
59
+ _IPV6_RE = re.compile("^" + _IPV6_PAT + "$")
60
+ _IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT + "$")
61
+ _BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT[2:-2] + "$")
62
+ _ZONE_ID_RE = re.compile("(" + _ZONE_ID_PAT + r")\]$")
63
+
64
+ _HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*?(|0|[1-9][0-9]{0,4}))?$") % (
65
+ _REG_NAME_PAT,
66
+ _IPV4_PAT,
67
+ _IPV6_ADDRZ_PAT,
68
+ )
69
+ _HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL)
70
+
71
+ _UNRESERVED_CHARS = set(
72
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~"
73
+ )
74
+ _SUB_DELIM_CHARS = set("!$&'()*+,;=")
75
+ _USERINFO_CHARS = _UNRESERVED_CHARS | _SUB_DELIM_CHARS | {":"}
76
+ _PATH_CHARS = _USERINFO_CHARS | {"@", "/"}
77
+ _QUERY_CHARS = _FRAGMENT_CHARS = _PATH_CHARS | {"?"}
78
+
79
+
80
+ class Url(
81
+ typing.NamedTuple(
82
+ "Url",
83
+ [
84
+ ("scheme", typing.Optional[str]),
85
+ ("auth", typing.Optional[str]),
86
+ ("host", typing.Optional[str]),
87
+ ("port", typing.Optional[int]),
88
+ ("path", typing.Optional[str]),
89
+ ("query", typing.Optional[str]),
90
+ ("fragment", typing.Optional[str]),
91
+ ],
92
+ )
93
+ ):
94
+ """
95
+ Data structure for representing an HTTP URL. Used as a return value for
96
+ :func:`parse_url`. Both the scheme and host are normalized as they are
97
+ both case-insensitive according to RFC 3986.
98
+ """
99
+
100
+ def __new__( # type: ignore[no-untyped-def]
101
+ cls,
102
+ scheme: str | None = None,
103
+ auth: str | None = None,
104
+ host: str | None = None,
105
+ port: int | None = None,
106
+ path: str | None = None,
107
+ query: str | None = None,
108
+ fragment: str | None = None,
109
+ ):
110
+ if path and not path.startswith("/"):
111
+ path = "/" + path
112
+ if scheme is not None:
113
+ scheme = scheme.lower()
114
+ return super().__new__(cls, scheme, auth, host, port, path, query, fragment)
115
+
116
+ @property
117
+ def hostname(self) -> str | None:
118
+ """For backwards-compatibility with urlparse. We're nice like that."""
119
+ return self.host
120
+
121
+ @property
122
+ def request_uri(self) -> str:
123
+ """Absolute path including the query string."""
124
+ uri = self.path or "/"
125
+
126
+ if self.query is not None:
127
+ uri += "?" + self.query
128
+
129
+ return uri
130
+
131
+ @property
132
+ def authority(self) -> str | None:
133
+ """
134
+ Authority component as defined in RFC 3986 3.2.
135
+ This includes userinfo (auth), host and port.
136
+
137
+ i.e.
138
+ userinfo@host:port
139
+ """
140
+ userinfo = self.auth
141
+ netloc = self.netloc
142
+ if netloc is None or userinfo is None:
143
+ return netloc
144
+ else:
145
+ return f"{userinfo}@{netloc}"
146
+
147
+ @property
148
+ def netloc(self) -> str | None:
149
+ """
150
+ Network location including host and port.
151
+
152
+ If you need the equivalent of urllib.parse's ``netloc``,
153
+ use the ``authority`` property instead.
154
+ """
155
+ if self.host is None:
156
+ return None
157
+ if self.port:
158
+ return f"{self.host}:{self.port}"
159
+ return self.host
160
+
161
+ @property
162
+ def url(self) -> str:
163
+ """
164
+ Convert self into a url
165
+
166
+ This function should more or less round-trip with :func:`.parse_url`. The
167
+ returned url may not be exactly the same as the url inputted to
168
+ :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls
169
+ with a blank port will have : removed).
170
+
171
+ Example:
172
+
173
+ .. code-block:: python
174
+
175
+ import urllib3
176
+
177
+ U = urllib3.util.parse_url("https://google.com/mail/")
178
+
179
+ print(U.url)
180
+ # "https://google.com/mail/"
181
+
182
+ print( urllib3.util.Url("https", "username:password",
183
+ "host.com", 80, "/path", "query", "fragment"
184
+ ).url
185
+ )
186
+ # "https://username:password@host.com:80/path?query#fragment"
187
+ """
188
+ scheme, auth, host, port, path, query, fragment = self
189
+ url = ""
190
+
191
+ # We use "is not None" we want things to happen with empty strings (or 0 port)
192
+ if scheme is not None:
193
+ url += scheme + "://"
194
+ if auth is not None:
195
+ url += auth + "@"
196
+ if host is not None:
197
+ url += host
198
+ if port is not None:
199
+ url += ":" + str(port)
200
+ if path is not None:
201
+ url += path
202
+ if query is not None:
203
+ url += "?" + query
204
+ if fragment is not None:
205
+ url += "#" + fragment
206
+
207
+ return url
208
+
209
+ def __str__(self) -> str:
210
+ return self.url
211
+
212
+
213
+ @typing.overload
214
+ def _encode_invalid_chars(
215
+ component: str, allowed_chars: typing.Container[str]
216
+ ) -> str: # Abstract
217
+ ...
218
+
219
+
220
+ @typing.overload
221
+ def _encode_invalid_chars(
222
+ component: None, allowed_chars: typing.Container[str]
223
+ ) -> None: # Abstract
224
+ ...
225
+
226
+
227
+ def _encode_invalid_chars(
228
+ component: str | None, allowed_chars: typing.Container[str]
229
+ ) -> str | None:
230
+ """Percent-encodes a URI component without reapplying
231
+ onto an already percent-encoded component.
232
+ """
233
+ if component is None:
234
+ return component
235
+
236
+ component = to_str(component)
237
+
238
+ # Normalize existing percent-encoded bytes.
239
+ # Try to see if the component we're encoding is already percent-encoded
240
+ # so we can skip all '%' characters but still encode all others.
241
+ component, percent_encodings = _PERCENT_RE.subn(
242
+ lambda match: match.group(0).upper(), component
243
+ )
244
+
245
+ uri_bytes = component.encode("utf-8", "surrogatepass")
246
+ is_percent_encoded = percent_encodings == uri_bytes.count(b"%")
247
+ encoded_component = bytearray()
248
+
249
+ for i in range(0, len(uri_bytes)):
250
+ # Will return a single character bytestring
251
+ byte = uri_bytes[i : i + 1]
252
+ byte_ord = ord(byte)
253
+ if (is_percent_encoded and byte == b"%") or (
254
+ byte_ord < 128 and byte.decode() in allowed_chars
255
+ ):
256
+ encoded_component += byte
257
+ continue
258
+ encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper()))
259
+
260
+ return encoded_component.decode()
261
+
262
+
263
+ def _remove_path_dot_segments(path: str) -> str:
264
+ # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code
265
+ segments = path.split("/") # Turn the path into a list of segments
266
+ output = [] # Initialize the variable to use to store output
267
+
268
+ for segment in segments:
269
+ # '.' is the current directory, so ignore it, it is superfluous
270
+ if segment == ".":
271
+ continue
272
+ # Anything other than '..', should be appended to the output
273
+ if segment != "..":
274
+ output.append(segment)
275
+ # In this case segment == '..', if we can, we should pop the last
276
+ # element
277
+ elif output:
278
+ output.pop()
279
+
280
+ # If the path starts with '/' and the output is empty or the first string
281
+ # is non-empty
282
+ if path.startswith("/") and (not output or output[0]):
283
+ output.insert(0, "")
284
+
285
+ # If the path starts with '/.' or '/..' ensure we add one more empty
286
+ # string to add a trailing '/'
287
+ if path.endswith(("/.", "/..")):
288
+ output.append("")
289
+
290
+ return "/".join(output)
291
+
292
+
293
+ @typing.overload
294
+ def _normalize_host(host: None, scheme: str | None) -> None:
295
+ ...
296
+
297
+
298
+ @typing.overload
299
+ def _normalize_host(host: str, scheme: str | None) -> str:
300
+ ...
301
+
302
+
303
+ def _normalize_host(host: str | None, scheme: str | None) -> str | None:
304
+ if host:
305
+ if scheme in _NORMALIZABLE_SCHEMES:
306
+ is_ipv6 = _IPV6_ADDRZ_RE.match(host)
307
+ if is_ipv6:
308
+ # IPv6 hosts of the form 'a::b%zone' are encoded in a URL as
309
+ # such per RFC 6874: 'a::b%25zone'. Unquote the ZoneID
310
+ # separator as necessary to return a valid RFC 4007 scoped IP.
311
+ match = _ZONE_ID_RE.search(host)
312
+ if match:
313
+ start, end = match.span(1)
314
+ zone_id = host[start:end]
315
+
316
+ if zone_id.startswith("%25") and zone_id != "%25":
317
+ zone_id = zone_id[3:]
318
+ else:
319
+ zone_id = zone_id[1:]
320
+ zone_id = _encode_invalid_chars(zone_id, _UNRESERVED_CHARS)
321
+ return f"{host[:start].lower()}%{zone_id}{host[end:]}"
322
+ else:
323
+ return host.lower()
324
+ elif not _IPV4_RE.match(host):
325
+ return to_str(
326
+ b".".join([_idna_encode(label) for label in host.split(".")]),
327
+ "ascii",
328
+ )
329
+ return host
330
+
331
+
332
+ def _idna_encode(name: str) -> bytes:
333
+ if not name.isascii():
334
+ try:
335
+ import idna
336
+ except ImportError:
337
+ raise LocationParseError(
338
+ "Unable to parse URL without the 'idna' module"
339
+ ) from None
340
+
341
+ try:
342
+ return idna.encode(name.lower(), strict=True, std3_rules=True)
343
+ except idna.IDNAError:
344
+ raise LocationParseError(
345
+ f"Name '{name}' is not a valid IDNA label"
346
+ ) from None
347
+
348
+ return name.lower().encode("ascii")
349
+
350
+
351
+ def _encode_target(target: str) -> str:
352
+ """Percent-encodes a request target so that there are no invalid characters
353
+
354
+ Pre-condition for this function is that 'target' must start with '/'.
355
+ If that is the case then _TARGET_RE will always produce a match.
356
+ """
357
+ match = _TARGET_RE.match(target)
358
+ if not match: # Defensive:
359
+ raise LocationParseError(f"{target!r} is not a valid request URI")
360
+
361
+ path, query = match.groups()
362
+ encoded_target = _encode_invalid_chars(path, _PATH_CHARS)
363
+ if query is not None:
364
+ query = _encode_invalid_chars(query, _QUERY_CHARS)
365
+ encoded_target += "?" + query
366
+ return encoded_target
367
+
368
+
369
+ def parse_url(url: str) -> Url:
370
+ """
371
+ Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is
372
+ performed to parse incomplete urls. Fields not provided will be None.
373
+ This parser is RFC 3986 and RFC 6874 compliant.
374
+
375
+ The parser logic and helper functions are based heavily on
376
+ work done in the ``rfc3986`` module.
377
+
378
+ :param str url: URL to parse into a :class:`.Url` namedtuple.
379
+
380
+ Partly backwards-compatible with :mod:`urllib.parse`.
381
+
382
+ Example:
383
+
384
+ .. code-block:: python
385
+
386
+ import urllib3
387
+
388
+ print( urllib3.util.parse_url('http://google.com/mail/'))
389
+ # Url(scheme='http', host='google.com', port=None, path='/mail/', ...)
390
+
391
+ print( urllib3.util.parse_url('google.com:80'))
392
+ # Url(scheme=None, host='google.com', port=80, path=None, ...)
393
+
394
+ print( urllib3.util.parse_url('/foo?bar'))
395
+ # Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...)
396
+ """
397
+ if not url:
398
+ # Empty
399
+ return Url()
400
+
401
+ source_url = url
402
+ if not _SCHEME_RE.search(url):
403
+ url = "//" + url
404
+
405
+ scheme: str | None
406
+ authority: str | None
407
+ auth: str | None
408
+ host: str | None
409
+ port: str | None
410
+ port_int: int | None
411
+ path: str | None
412
+ query: str | None
413
+ fragment: str | None
414
+
415
+ try:
416
+ scheme, authority, path, query, fragment = _URI_RE.match(url).groups() # type: ignore[union-attr]
417
+ normalize_uri = scheme is None or scheme.lower() in _NORMALIZABLE_SCHEMES
418
+
419
+ if scheme:
420
+ scheme = scheme.lower()
421
+
422
+ if authority:
423
+ auth, _, host_port = authority.rpartition("@")
424
+ auth = auth or None
425
+ host, port = _HOST_PORT_RE.match(host_port).groups() # type: ignore[union-attr]
426
+ if auth and normalize_uri:
427
+ auth = _encode_invalid_chars(auth, _USERINFO_CHARS)
428
+ if port == "":
429
+ port = None
430
+ else:
431
+ auth, host, port = None, None, None
432
+
433
+ if port is not None:
434
+ port_int = int(port)
435
+ if not (0 <= port_int <= 65535):
436
+ raise LocationParseError(url)
437
+ else:
438
+ port_int = None
439
+
440
+ host = _normalize_host(host, scheme)
441
+
442
+ if normalize_uri and path:
443
+ path = _remove_path_dot_segments(path)
444
+ path = _encode_invalid_chars(path, _PATH_CHARS)
445
+ if normalize_uri and query:
446
+ query = _encode_invalid_chars(query, _QUERY_CHARS)
447
+ if normalize_uri and fragment:
448
+ fragment = _encode_invalid_chars(fragment, _FRAGMENT_CHARS)
449
+
450
+ except (ValueError, AttributeError) as e:
451
+ raise LocationParseError(source_url) from e
452
+
453
+ # For the sake of backwards compatibility we put empty
454
+ # string values for path if there are any defined values
455
+ # beyond the path in the URL.
456
+ # TODO: Remove this when we break backwards compatibility.
457
+ if not path:
458
+ if query is not None or fragment is not None:
459
+ path = ""
460
+ else:
461
+ path = None
462
+
463
+ return Url(
464
+ scheme=scheme,
465
+ auth=auth,
466
+ host=host,
467
+ port=port_int,
468
+ path=path,
469
+ query=query,
470
+ fragment=fragment,
471
+ )
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from types import TracebackType
5
+
6
+
7
+ def to_bytes(
8
+ x: str | bytes, encoding: str | None = None, errors: str | None = None
9
+ ) -> bytes:
10
+ if isinstance(x, bytes):
11
+ return x
12
+ elif not isinstance(x, str):
13
+ raise TypeError(f"not expecting type {type(x).__name__}")
14
+ if encoding or errors:
15
+ return x.encode(encoding or "utf-8", errors=errors or "strict")
16
+ return x.encode()
17
+
18
+
19
+ def to_str(
20
+ x: str | bytes, encoding: str | None = None, errors: str | None = None
21
+ ) -> str:
22
+ if isinstance(x, str):
23
+ return x
24
+ elif not isinstance(x, bytes):
25
+ raise TypeError(f"not expecting type {type(x).__name__}")
26
+ if encoding or errors:
27
+ return x.decode(encoding or "utf-8", errors=errors or "strict")
28
+ return x.decode()
29
+
30
+
31
+ def reraise(
32
+ tp: type[BaseException] | None,
33
+ value: BaseException,
34
+ tb: TracebackType | None = None,
35
+ ) -> typing.NoReturn:
36
+ try:
37
+ if value.__traceback__ is not tb:
38
+ raise value.with_traceback(tb)
39
+ raise value
40
+ finally:
41
+ value = None # type: ignore[assignment]
42
+ tb = None
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ import select
4
+ import socket
5
+ from functools import partial
6
+
7
+ __all__ = ["wait_for_read", "wait_for_write"]
8
+
9
+
10
+ # How should we wait on sockets?
11
+ #
12
+ # There are two types of APIs you can use for waiting on sockets: the fancy
13
+ # modern stateful APIs like epoll/kqueue, and the older stateless APIs like
14
+ # select/poll. The stateful APIs are more efficient when you have a lots of
15
+ # sockets to keep track of, because you can set them up once and then use them
16
+ # lots of times. But we only ever want to wait on a single socket at a time
17
+ # and don't want to keep track of state, so the stateless APIs are actually
18
+ # more efficient. So we want to use select() or poll().
19
+ #
20
+ # Now, how do we choose between select() and poll()? On traditional Unixes,
21
+ # select() has a strange calling convention that makes it slow, or fail
22
+ # altogether, for high-numbered file descriptors. The point of poll() is to fix
23
+ # that, so on Unixes, we prefer poll().
24
+ #
25
+ # On Windows, there is no poll() (or at least Python doesn't provide a wrapper
26
+ # for it), but that's OK, because on Windows, select() doesn't have this
27
+ # strange calling convention; plain select() works fine.
28
+ #
29
+ # So: on Windows we use select(), and everywhere else we use poll(). We also
30
+ # fall back to select() in case poll() is somehow broken or missing.
31
+
32
+
33
+ def select_wait_for_socket(
34
+ sock: socket.socket,
35
+ read: bool = False,
36
+ write: bool = False,
37
+ timeout: float | None = None,
38
+ ) -> bool:
39
+ if not read and not write:
40
+ raise RuntimeError("must specify at least one of read=True, write=True")
41
+ rcheck = []
42
+ wcheck = []
43
+ if read:
44
+ rcheck.append(sock)
45
+ if write:
46
+ wcheck.append(sock)
47
+ # When doing a non-blocking connect, most systems signal success by
48
+ # marking the socket writable. Windows, though, signals success by marked
49
+ # it as "exceptional". We paper over the difference by checking the write
50
+ # sockets for both conditions. (The stdlib selectors module does the same
51
+ # thing.)
52
+ fn = partial(select.select, rcheck, wcheck, wcheck)
53
+ rready, wready, xready = fn(timeout)
54
+ return bool(rready or wready or xready)
55
+
56
+
57
+ def poll_wait_for_socket(
58
+ sock: socket.socket,
59
+ read: bool = False,
60
+ write: bool = False,
61
+ timeout: float | None = None,
62
+ ) -> bool:
63
+ if not read and not write:
64
+ raise RuntimeError("must specify at least one of read=True, write=True")
65
+ mask = 0
66
+ if read:
67
+ mask |= select.POLLIN
68
+ if write:
69
+ mask |= select.POLLOUT
70
+ poll_obj = select.poll()
71
+ poll_obj.register(sock, mask)
72
+
73
+ # For some reason, poll() takes timeout in milliseconds
74
+ def do_poll(t: float | None) -> list[tuple[int, int]]:
75
+ if t is not None:
76
+ t *= 1000
77
+ return poll_obj.poll(t)
78
+
79
+ return bool(do_poll(timeout))
80
+
81
+
82
+ def _have_working_poll() -> bool:
83
+ # Apparently some systems have a select.poll that fails as soon as you try
84
+ # to use it, either due to strange configuration or broken monkeypatching
85
+ # from libraries like eventlet/greenlet.
86
+ try:
87
+ poll_obj = select.poll()
88
+ poll_obj.poll(0)
89
+ except (AttributeError, OSError):
90
+ return False
91
+ else:
92
+ return True
93
+
94
+
95
+ def wait_for_socket(
96
+ sock: socket.socket,
97
+ read: bool = False,
98
+ write: bool = False,
99
+ timeout: float | None = None,
100
+ ) -> bool:
101
+ # We delay choosing which implementation to use until the first time we're
102
+ # called. We could do it at import time, but then we might make the wrong
103
+ # decision if someone goes wild with monkeypatching select.poll after
104
+ # we're imported.
105
+ global wait_for_socket
106
+ if _have_working_poll():
107
+ wait_for_socket = poll_wait_for_socket
108
+ elif hasattr(select, "select"):
109
+ wait_for_socket = select_wait_for_socket
110
+ return wait_for_socket(sock, read, write, timeout)
111
+
112
+
113
+ def wait_for_read(sock: socket.socket, timeout: float | None = None) -> bool:
114
+ """Waits for reading to be available on a given socket.
115
+ Returns True if the socket is readable, or False if the timeout expired.
116
+ """
117
+ return wait_for_socket(sock, read=True, timeout=timeout)
118
+
119
+
120
+ def wait_for_write(sock: socket.socket, timeout: float | None = None) -> bool:
121
+ """Waits for writing to be available on a given socket.
122
+ Returns True if the socket is readable, or False if the timeout expired.
123
+ """
124
+ return wait_for_socket(sock, write=True, timeout=timeout)
@@ -256,9 +256,6 @@ def main(args=None):
256
256
  else:
257
257
  if response!=None:
258
258
  print(json.dumps(response[0], sort_keys=True, indent=4))
259
- except KeyboardInterrupt:
260
- print("\n\nOperation interrupted by user (Ctrl+C). Exiting gracefully...")
261
- exit(130) # Standard exit code for SIGINT
262
259
  except Exception as e:
263
260
  if isinstance(e, AttributeError):
264
261
  print('Missing arguments. Usage: catocli <operation> -h')
@@ -268,4 +265,4 @@ def main(args=None):
268
265
  else:
269
266
  print('ERROR: ',e)
270
267
  traceback.print_exc()
271
- exit(1)
268
+ exit(1)
catocli/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "2.0.3"
1
+ __version__ = "2.0.5"
2
2
  __cato_host__ = "https://api.catonetworks.com/api/v1/graphql2"
@@ -20,6 +20,7 @@ def export_rules_parse(subparsers):
20
20
 
21
21
  if_rules_parser.add_argument('-accountID', help='Account ID to export rules from (uses CATO_ACCOUNT_ID environment variable if not specified)', required=False)
22
22
  if_rules_parser.add_argument('--output-file-path', help='Full path including filename and extension for output file. If not specified, uses default: config_data/all_ifw_rules_and_sections_{account_id}.json')
23
+ if_rules_parser.add_argument('--append-timestamp', action='store_true', help='Append timestamp to the filename after account ID (format: YYYY-MM-DD_HH-MM-SS)')
23
24
  if_rules_parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
24
25
 
25
26
  if_rules_parser.set_defaults(func=export_rules.export_if_rules_to_json)
@@ -33,6 +34,7 @@ def export_rules_parse(subparsers):
33
34
 
34
35
  wf_rules_parser.add_argument('-accountID', help='Account ID to export rules from (uses CATO_ACCOUNT_ID environment variable if not specified)', required=False)
35
36
  wf_rules_parser.add_argument('--output-file-path', help='Full path including filename and extension for output file. If not specified, uses default: config_data/all_wf_rules_and_sections_{account_id}.json')
37
+ wf_rules_parser.add_argument('--append-timestamp', action='store_true', help='Append timestamp to the filename after account ID (format: YYYY-MM-DD_HH-MM-SS)')
36
38
  wf_rules_parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
37
39
 
38
40
  wf_rules_parser.set_defaults(func=export_rules.export_wf_rules_to_json)