catocli 1.0.21__py3-none-any.whl → 2.0.1__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 (139) hide show
  1. catocli/Utils/clidriver.py +112 -25
  2. catocli/Utils/profile_manager.py +188 -0
  3. catocli/Utils/version_checker.py +192 -0
  4. catocli/__init__.py +1 -1
  5. catocli/parsers/configure/__init__.py +115 -0
  6. catocli/parsers/configure/configure.py +307 -0
  7. catocli/parsers/custom/__init__.py +8 -0
  8. catocli/parsers/custom/export_rules/__init__.py +36 -0
  9. catocli/parsers/custom/export_rules/export_rules.py +361 -0
  10. catocli/parsers/custom/import_rules_to_tf/__init__.py +58 -0
  11. catocli/parsers/custom/import_rules_to_tf/import_rules_to_tf.py +577 -0
  12. catocli/parsers/mutation_admin_addAdmin/README.md +1 -1
  13. catocli/parsers/mutation_hardware/README.md +7 -0
  14. catocli/parsers/mutation_hardware/__init__.py +23 -0
  15. catocli/parsers/mutation_hardware_updateHardwareShipping/README.md +17 -0
  16. catocli/parsers/mutation_site_addBgpPeer/README.md +1 -1
  17. catocli/parsers/mutation_site_addNetworkRange/README.md +1 -1
  18. catocli/parsers/mutation_site_updateBgpPeer/README.md +1 -1
  19. catocli/parsers/mutation_site_updateNetworkRange/README.md +1 -1
  20. catocli/parsers/mutation_sites_addBgpPeer/README.md +1 -1
  21. catocli/parsers/mutation_sites_addNetworkRange/README.md +1 -1
  22. catocli/parsers/mutation_sites_updateBgpPeer/README.md +1 -1
  23. catocli/parsers/mutation_sites_updateNetworkRange/README.md +1 -1
  24. catocli/parsers/query_auditFeed/README.md +1 -1
  25. catocli/parsers/query_catalogs/README.md +19 -0
  26. catocli/parsers/query_catalogs/__init__.py +17 -0
  27. catocli/parsers/query_devices/README.md +19 -0
  28. catocli/parsers/query_devices/__init__.py +17 -0
  29. catocli/parsers/query_eventsFeed/README.md +1 -1
  30. catocli/parsers/query_hardware/README.md +17 -0
  31. catocli/parsers/query_hardware/__init__.py +17 -0
  32. catocli/parsers/query_sandbox/README.md +1 -1
  33. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/METADATA +1 -1
  34. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/RECORD +139 -114
  35. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/top_level.txt +1 -0
  36. graphql_client/api/call_api.py +4 -0
  37. graphql_client/api_client_types.py +4 -3
  38. graphql_client/configuration.py +2 -0
  39. models/mutation.admin.addAdmin.json +130 -0
  40. models/mutation.hardware.updateHardwareShipping.json +2506 -0
  41. models/mutation.policy.appTenantRestriction.addRule.json +11 -11
  42. models/mutation.policy.appTenantRestriction.createPolicyRevision.json +11 -11
  43. models/mutation.policy.appTenantRestriction.discardPolicyRevision.json +11 -11
  44. models/mutation.policy.appTenantRestriction.moveRule.json +11 -11
  45. models/mutation.policy.appTenantRestriction.publishPolicyRevision.json +11 -11
  46. models/mutation.policy.appTenantRestriction.removeRule.json +11 -11
  47. models/mutation.policy.appTenantRestriction.updatePolicy.json +11 -11
  48. models/mutation.policy.appTenantRestriction.updateRule.json +11 -11
  49. models/mutation.policy.dynamicIpAllocation.addRule.json +4 -4
  50. models/mutation.policy.dynamicIpAllocation.createPolicyRevision.json +4 -4
  51. models/mutation.policy.dynamicIpAllocation.discardPolicyRevision.json +4 -4
  52. models/mutation.policy.dynamicIpAllocation.moveRule.json +4 -4
  53. models/mutation.policy.dynamicIpAllocation.publishPolicyRevision.json +4 -4
  54. models/mutation.policy.dynamicIpAllocation.removeRule.json +4 -4
  55. models/mutation.policy.dynamicIpAllocation.updatePolicy.json +4 -4
  56. models/mutation.policy.dynamicIpAllocation.updateRule.json +4 -4
  57. models/mutation.policy.internetFirewall.addRule.json +63 -63
  58. models/mutation.policy.internetFirewall.createPolicyRevision.json +45 -45
  59. models/mutation.policy.internetFirewall.discardPolicyRevision.json +45 -45
  60. models/mutation.policy.internetFirewall.moveRule.json +45 -45
  61. models/mutation.policy.internetFirewall.publishPolicyRevision.json +45 -45
  62. models/mutation.policy.internetFirewall.removeRule.json +45 -45
  63. models/mutation.policy.internetFirewall.updatePolicy.json +45 -45
  64. models/mutation.policy.internetFirewall.updateRule.json +63 -63
  65. models/mutation.policy.remotePortFwd.addRule.json +5 -5
  66. models/mutation.policy.remotePortFwd.createPolicyRevision.json +5 -5
  67. models/mutation.policy.remotePortFwd.discardPolicyRevision.json +5 -5
  68. models/mutation.policy.remotePortFwd.moveRule.json +5 -5
  69. models/mutation.policy.remotePortFwd.publishPolicyRevision.json +5 -5
  70. models/mutation.policy.remotePortFwd.removeRule.json +5 -5
  71. models/mutation.policy.remotePortFwd.updatePolicy.json +5 -5
  72. models/mutation.policy.remotePortFwd.updateRule.json +5 -5
  73. models/mutation.policy.socketLan.addRule.json +3580 -125
  74. models/mutation.policy.socketLan.createPolicyRevision.json +3580 -125
  75. models/mutation.policy.socketLan.discardPolicyRevision.json +3580 -125
  76. models/mutation.policy.socketLan.moveRule.json +3580 -125
  77. models/mutation.policy.socketLan.publishPolicyRevision.json +3580 -125
  78. models/mutation.policy.socketLan.removeRule.json +3580 -125
  79. models/mutation.policy.socketLan.updatePolicy.json +3580 -125
  80. models/mutation.policy.socketLan.updateRule.json +3580 -125
  81. models/mutation.policy.wanFirewall.addRule.json +77 -77
  82. models/mutation.policy.wanFirewall.createPolicyRevision.json +59 -59
  83. models/mutation.policy.wanFirewall.discardPolicyRevision.json +59 -59
  84. models/mutation.policy.wanFirewall.moveRule.json +59 -59
  85. models/mutation.policy.wanFirewall.publishPolicyRevision.json +59 -59
  86. models/mutation.policy.wanFirewall.removeRule.json +59 -59
  87. models/mutation.policy.wanFirewall.updatePolicy.json +59 -59
  88. models/mutation.policy.wanFirewall.updateRule.json +77 -77
  89. models/mutation.policy.wanNetwork.addRule.json +49 -49
  90. models/mutation.policy.wanNetwork.createPolicyRevision.json +49 -49
  91. models/mutation.policy.wanNetwork.discardPolicyRevision.json +49 -49
  92. models/mutation.policy.wanNetwork.moveRule.json +49 -49
  93. models/mutation.policy.wanNetwork.publishPolicyRevision.json +49 -49
  94. models/mutation.policy.wanNetwork.removeRule.json +49 -49
  95. models/mutation.policy.wanNetwork.updatePolicy.json +49 -49
  96. models/mutation.policy.wanNetwork.updateRule.json +49 -49
  97. models/mutation.site.addBgpPeer.json +2812 -217
  98. models/mutation.site.addNetworkRange.json +114 -0
  99. models/mutation.site.addSocketSite.json +18 -0
  100. models/mutation.site.removeBgpPeer.json +667 -1
  101. models/mutation.site.updateBgpPeer.json +3152 -559
  102. models/mutation.site.updateNetworkRange.json +114 -0
  103. models/mutation.sites.addBgpPeer.json +2812 -217
  104. models/mutation.sites.addNetworkRange.json +114 -0
  105. models/mutation.sites.addSocketSite.json +18 -0
  106. models/mutation.sites.removeBgpPeer.json +667 -1
  107. models/mutation.sites.updateBgpPeer.json +3152 -559
  108. models/mutation.sites.updateNetworkRange.json +114 -0
  109. models/mutation.xdr.addStoryComment.json +2 -2
  110. models/mutation.xdr.analystFeedback.json +182 -42
  111. models/mutation.xdr.deleteStoryComment.json +2 -2
  112. models/query.accountMetrics.json +112 -0
  113. models/query.accountSnapshot.json +62 -0
  114. models/query.admin.json +46 -0
  115. models/query.admins.json +46 -0
  116. models/query.appStats.json +528 -0
  117. models/query.appStatsTimeSeries.json +396 -0
  118. models/query.auditFeed.json +273 -3336
  119. models/query.catalogs.json +9840 -0
  120. models/query.devices.json +15469 -0
  121. models/query.events.json +4606 -4318
  122. models/query.eventsFeed.json +1167 -1095
  123. models/query.eventsTimeSeries.json +3459 -3243
  124. models/query.hardware.json +5730 -0
  125. models/query.hardwareManagement.json +8 -2
  126. models/query.licensing.json +3 -3
  127. models/query.policy.json +3743 -298
  128. models/query.sandbox.json +6 -4
  129. models/query.site.json +1329 -4
  130. models/query.xdr.stories.json +182 -42
  131. models/query.xdr.story.json +182 -42
  132. schema/catolib.py +105 -28
  133. scripts/catolib.py +62 -0
  134. scripts/export_if_rules_to_json.py +188 -0
  135. scripts/export_wf_rules_to_json.py +111 -0
  136. scripts/import_wf_rules_to_tfstate.py +331 -0
  137. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/LICENSE +0 -0
  138. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/WHEEL +0 -0
  139. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,361 @@
1
+ import os
2
+ import json
3
+ import sys
4
+ from graphql_client.api.call_api import ApiClient, CallApi
5
+ from graphql_client.api_client import ApiException
6
+
7
+
8
+ def strip_ids_recursive(data):
9
+ """Recursively strip id attributes from data structure, but only from objects that contain only 'id' and 'name' keys"""
10
+ try:
11
+ if isinstance(data, dict):
12
+ # Check if this dict should have its 'id' stripped
13
+ # Only strip 'id' if the object contains only 'id' and 'name' keys
14
+ dict_keys = set(data.keys())
15
+ should_strip_id = dict_keys == {'id', 'name'} or dict_keys == {'name', 'id'}
16
+
17
+ result = {}
18
+ for k, v in data.items():
19
+ if k == 'id' and should_strip_id:
20
+ # Skip this 'id' key only if this object contains only id and name
21
+ continue
22
+ else:
23
+ # Keep the key and recursively process the value
24
+ result[k] = strip_ids_recursive(v)
25
+ return result
26
+ elif isinstance(data, list):
27
+ return [strip_ids_recursive(item) for item in data]
28
+ else:
29
+ return data
30
+ except Exception as e:
31
+ print(f"Error in strip_ids_recursive: {e}, data type: {type(data)}, data: {str(data)[:100]}")
32
+ raise
33
+
34
+ def export_if_rules_to_json(args, configuration):
35
+ """
36
+ Export Internet Firewall rules to JSON format
37
+ Adapted from scripts/export_if_rules_to_json.py
38
+ """
39
+ try:
40
+ # Get account ID from args, configuration, or environment variable
41
+ account_id = None
42
+ if hasattr(args, 'accountID') and args.accountID:
43
+ account_id = args.accountID
44
+ elif hasattr(configuration, 'accountID') and configuration.accountID:
45
+ account_id = configuration.accountID
46
+ else:
47
+ account_id = os.getenv('CATO_ACCOUNT_ID')
48
+
49
+ if not account_id:
50
+ raise ValueError("Account ID is required. Provide it using the -accountID flag or set CATO_ACCOUNT_ID environment variable.")
51
+
52
+ # Set up output file path
53
+ if hasattr(args, 'output_file_path') and args.output_file_path:
54
+ # Use output file path if provided
55
+ output_file = args.output_file_path
56
+ destination_dir = os.path.dirname(output_file)
57
+ if hasattr(args, 'verbose') and args.verbose:
58
+ print(f"Using output file path: {output_file}")
59
+ else:
60
+ # Use default path and filename
61
+ destination_dir = 'config_data'
62
+ json_output_file = f"all_ifw_rules_and_sections_{account_id}.json"
63
+ output_file = os.path.join(destination_dir, json_output_file)
64
+ if hasattr(args, 'verbose') and args.verbose:
65
+ print(f"Using default path: {output_file}")
66
+
67
+ # Create destination directory if it doesn't exist
68
+ if destination_dir and not os.path.exists(destination_dir):
69
+ if hasattr(args, 'verbose') and args.verbose:
70
+ print(f"Creating directory: {destination_dir}")
71
+ os.makedirs(destination_dir)
72
+
73
+ # Define the GraphQL query
74
+ policy_query = {
75
+ "query": "query policy ( $accountId:ID! ) { policy ( accountId:$accountId ) { internetFirewall { policy { enabled rules { audit { updatedTime updatedBy } rule { id name description index section { id name } enabled source { ip host { id name } site { id name } subnet ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } connectionOrigin country { id name } device { id name } deviceOS deviceAttributes { category type model manufacturer os osVersion } destination { application { id name } customApp { id name } appCategory { id name } customCategory { id name } sanctionedAppsCategory { id name } country { id name } domain fqdn ip subnet ipRange { from to } globalIpRange { id name } remoteAsn containers { fqdnContainer { id name } ipAddressRangeContainer { id name } } } service { standard { id name } custom { port portRange { from to } protocol } } action tracking { event { enabled } alert { enabled frequency subscriptionGroup { id name } webhook { id name } mailingList { id name } } } schedule { activeOn customTimeframePolicySchedule: customTimeframe { from to } customRecurringPolicySchedule: customRecurring { from to days } } exceptions { name source { ip host { id name } site { id name } subnet ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } deviceOS country { id name } device { id name } deviceAttributes { category type model manufacturer os osVersion } destination { application { id name } customApp { id name } appCategory { id name } customCategory { id name } sanctionedAppsCategory { id name } country { id name } domain fqdn ip subnet ipRange { from to } globalIpRange { id name } remoteAsn containers { fqdnContainer { id name } ipAddressRangeContainer { id name } } } service { standard { id name } custom { port portRangeCustomService: portRange { from to } protocol } } connectionOrigin } } properties } sections { audit { updatedTime updatedBy } section { id name } properties } audit { publishedTime publishedBy } revision { id name description changes createdTime updatedTime } } } } }",
76
+ "variables": {
77
+ "accountId": account_id
78
+ },
79
+ "operationName": "policy"
80
+ }
81
+
82
+ # Create API client instance with params
83
+ instance = CallApi(ApiClient(configuration))
84
+ params = {
85
+ 'v': hasattr(args, 'verbose') and args.verbose, # verbose mode
86
+ 'f': 'json', # format
87
+ 'p': False, # pretty print
88
+ 't': False # test mode
89
+ }
90
+
91
+ try:
92
+ # Call the API directly
93
+ # NOTE: The API client (graphql_client/api_client_types.py lines 106-108)
94
+ # automatically prints error responses and exits on GraphQL errors.
95
+ # This means our custom error handling below may not be reached if there are GraphQL errors.
96
+ response = instance.call_api(policy_query, params)
97
+ all_ifw_rules = response[0] if response else {}
98
+
99
+ # Show raw API response in verbose mode
100
+ if hasattr(args, 'verbose') and args.verbose:
101
+ print("\n" + "=" * 80)
102
+ print("RAW API RESPONSE:")
103
+ print("=" * 80)
104
+ print(json.dumps(all_ifw_rules, indent=2))
105
+ print("=" * 80 + "\n")
106
+
107
+ # Check for GraphQL errors first (may not be reached due to API client behavior)
108
+ if 'errors' in all_ifw_rules:
109
+ error_messages = [error.get('message', 'Unknown error') for error in all_ifw_rules['errors']]
110
+ raise Exception(f"API returned errors: {', '.join(error_messages)}")
111
+
112
+ if not all_ifw_rules or 'data' not in all_ifw_rules:
113
+ raise ValueError("Failed to retrieve data from API")
114
+
115
+ except ApiException as e:
116
+ raise Exception(f"API call failed - {e}")
117
+ except Exception as e:
118
+ raise Exception(f"Unexpected error during API call - {e}")
119
+
120
+ # IMPORTANT: Preserve section IDs BEFORE stripping them
121
+ section_id_map = {}
122
+ section_to_start_after_id = None
123
+ sections_with_ids = all_ifw_rules['data']['policy']['internetFirewall']['policy']['sections']
124
+ for index, section_data in enumerate(sections_with_ids):
125
+ section_name = section_data['section']['name']
126
+ section_id = section_data['section']['id']
127
+ if index == 0:
128
+ section_to_start_after_id = section_id
129
+ else:
130
+ section_id_map[section_name] = section_id
131
+
132
+ # Processing data to strip id attributes
133
+ processed_data = strip_ids_recursive(all_ifw_rules)
134
+
135
+ # Filter out rules with properties[0]=="SYSTEM"
136
+ filtered_rules = []
137
+ for rule_data in processed_data['data']['policy']['internetFirewall']['policy']['rules']:
138
+ rule_properties = rule_data.get('properties', [])
139
+ # Skip rules where the first property is "SYSTEM"
140
+ if rule_properties and rule_properties[0] == "SYSTEM":
141
+ if hasattr(args, 'verbose') and args.verbose:
142
+ print(f"Excluding SYSTEM rule: {rule_data['rule']['name']}")
143
+ else:
144
+ filtered_rules.append(rule_data)
145
+
146
+ processed_data['data']['policy']['internetFirewall']['policy']['rules'] = filtered_rules
147
+
148
+ # Add index_in_section to each rule
149
+ # Group rules by section and add index_in_section
150
+ section_counters = {}
151
+ for rule_data in processed_data['data']['policy']['internetFirewall']['policy']['rules']:
152
+ section_name = rule_data['rule']['section']['name']
153
+ if section_name not in section_counters:
154
+ section_counters[section_name] = 0
155
+ section_counters[section_name] += 1
156
+ rule_data['rule']['index_in_section'] = section_counters[section_name]
157
+
158
+ # Create rules_in_sections array
159
+ rules_in_sections = []
160
+ for rule_data in processed_data['data']['policy']['internetFirewall']['policy']['rules']:
161
+ rule_info = rule_data['rule']
162
+ rules_in_sections.append({
163
+ "index_in_section": rule_info['index_in_section'],
164
+ "section_name": rule_info['section']['name'],
165
+ "rule_name": rule_info['name']
166
+ })
167
+ rule_info.pop("index_in_section", None)
168
+ rule_info.pop("index", None)
169
+ # rule_info["enabled"] = True
170
+
171
+ # Add rules_in_sections to the policy structure
172
+ processed_data['data']['policy']['internetFirewall']['policy']['rules_in_sections'] = rules_in_sections
173
+
174
+ # Reformat sections array to have index, section_id and section_name structure
175
+ # Exclude the first section from export
176
+ processed_sections = []
177
+ for index, section_data in enumerate(processed_data['data']['policy']['internetFirewall']['policy']['sections']):
178
+ if index > 0: # Skip the first section (index 0)
179
+ processed_sections.append({
180
+ "section_index": index,
181
+ "section_name": section_data['section']['name']
182
+ })
183
+
184
+ # Add preserved section IDs and section_to_start_after_id
185
+ processed_data['data']['policy']['internetFirewall']['policy']['section_ids'] = section_id_map
186
+ if section_to_start_after_id:
187
+ processed_data['data']['policy']['internetFirewall']['policy']['section_to_start_after_id'] = section_to_start_after_id
188
+
189
+ # Replace the original sections array with the reformatted one
190
+ processed_data['data']['policy']['internetFirewall']['policy']['sections'] = processed_sections
191
+
192
+ # Write the processed data to the new file
193
+ with open(output_file, 'w', encoding='utf-8') as f:
194
+ json.dump(processed_data, f, indent=4, ensure_ascii=False)
195
+
196
+ return [{"success": True, "output_file": output_file, "account_id": account_id}]
197
+
198
+ except Exception as e:
199
+ print(f"ERROR: {str(e)}")
200
+ return [{"success": False, "error": str(e)}]
201
+
202
+
203
+ def export_wf_rules_to_json(args, configuration):
204
+ """
205
+ Export WAN Firewall rules to JSON format
206
+ Adapted from scripts/export_wf_rules_to_json.py
207
+ """
208
+ try:
209
+ # Get account ID from args, configuration, or environment variable
210
+ account_id = None
211
+ if hasattr(args, 'accountID') and args.accountID:
212
+ account_id = args.accountID
213
+ elif hasattr(configuration, 'accountID') and configuration.accountID:
214
+ account_id = configuration.accountID
215
+ else:
216
+ account_id = os.getenv('CATO_ACCOUNT_ID')
217
+
218
+ if not account_id:
219
+ raise ValueError("Account ID is required. Provide it using the -accountID flag or set CATO_ACCOUNT_ID environment variable.")
220
+
221
+ # Set up output file path
222
+ if hasattr(args, 'output_file_path') and args.output_file_path:
223
+ # Use output file path if provided
224
+ output_file = args.output_file_path
225
+ destination_dir = os.path.dirname(output_file)
226
+ if hasattr(args, 'verbose') and args.verbose:
227
+ print(f"Using output file path: {output_file}")
228
+ else:
229
+ # Use default path and filename
230
+ destination_dir = 'config_data'
231
+ json_output_file = f"all_wf_rules_and_sections_{account_id}.json"
232
+ output_file = os.path.join(destination_dir, json_output_file)
233
+ if hasattr(args, 'verbose') and args.verbose:
234
+ print(f"Using default path: {output_file}")
235
+
236
+ # Create destination directory if it doesn't exist
237
+ if destination_dir and not os.path.exists(destination_dir):
238
+ if hasattr(args, 'verbose') and args.verbose:
239
+ print(f"Creating directory: {destination_dir}")
240
+ os.makedirs(destination_dir)
241
+
242
+ # Define the GraphQL query for WAN Firewall
243
+ policy_query = {
244
+ "query": "query policy ( $accountId:ID! ) { policy ( accountId:$accountId ) { wanFirewall { policy { enabled rules { audit { updatedTime updatedBy } rule { id name description index section { id name } enabled source { host { id name } site { id name } subnet ip ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } connectionOrigin country { id name } device { id name } deviceOS deviceAttributes { category type model manufacturer os osVersion } destination { host { id name } site { id name } subnet ip ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } application { application { id name } appCategory { id name } customApp { id name } customCategory { id name } sanctionedAppsCategory { id name } domain fqdn ip subnet ipRange { from to } globalIpRange { id name } } service { standard { id name } custom { port portRange { from to } protocol } } action tracking { event { enabled } alert { enabled frequency subscriptionGroup { id name } webhook { id name } mailingList { id name } } } schedule { activeOn customTimeframePolicySchedule: customTimeframe { from to } customRecurringPolicySchedule: customRecurring { from to days } } direction exceptions { name source { host { id name } site { id name } subnet ip ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } deviceOS destination { host { id name } site { id name } subnet ip ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } country { id name } device { id name } deviceAttributes { category type model manufacturer os osVersion } application { application { id name } appCategory { id name } customApp { id name } customCategory { id name } sanctionedAppsCategory { id name } domain fqdn ip subnet ipRange { from to } globalIpRange { id name } } service { standard { id name } custom { port portRangeCustomService: portRange { from to } protocol } } connectionOrigin direction } } properties } sections { audit { updatedTime updatedBy } section { id name } properties } audit { publishedTime publishedBy } revision { id name description changes createdTime updatedTime } } } } }",
245
+ "variables": {
246
+ "accountId": account_id
247
+ },
248
+ "operationName": "policy"
249
+ }
250
+
251
+ # Create API client instance with params
252
+ instance = CallApi(ApiClient(configuration))
253
+ params = {
254
+ 'v': hasattr(args, 'verbose') and args.verbose, # verbose mode
255
+ 'f': 'json', # format
256
+ 'p': False, # pretty print
257
+ 't': False # test mode
258
+ }
259
+
260
+ try:
261
+ # Call the API directly
262
+ response = instance.call_api(policy_query, params)
263
+ all_wf_rules = response[0] if response else {}
264
+
265
+ # Show raw API response in verbose mode
266
+ if hasattr(args, 'verbose') and args.verbose:
267
+ print("\n" + "=" * 80)
268
+ print("RAW API RESPONSE:")
269
+ print("=" * 80)
270
+ print(json.dumps(all_wf_rules, indent=2))
271
+ print("=" * 80 + "\n")
272
+
273
+ # Check for GraphQL errors first
274
+ if 'errors' in all_wf_rules:
275
+ error_messages = [error.get('message', 'Unknown error') for error in all_wf_rules['errors']]
276
+ raise Exception(f"API returned errors: {', '.join(error_messages)}")
277
+
278
+ if not all_wf_rules or 'data' not in all_wf_rules:
279
+ raise ValueError("Failed to retrieve data from API")
280
+
281
+ except ApiException as e:
282
+ raise Exception(f"API call failed - {e}")
283
+ except Exception as e:
284
+ raise Exception(f"Unexpected error during API call - {e}")
285
+
286
+ # IMPORTANT: Preserve section IDs BEFORE stripping them
287
+ section_id_map = {}
288
+ section_to_start_after_id = None
289
+ sections_with_ids = all_wf_rules['data']['policy']['wanFirewall']['policy']['sections']
290
+ for index, section_data in enumerate(sections_with_ids):
291
+ section_name = section_data['section']['name']
292
+ section_id = section_data['section']['id']
293
+ section_id_map[section_name] = section_id
294
+
295
+ # Processing data to strip id attributes
296
+ processed_data = strip_ids_recursive(all_wf_rules)
297
+
298
+ # Filter out rules with properties[0]=="SYSTEM"
299
+ filtered_rules = []
300
+ for rule_data in processed_data['data']['policy']['wanFirewall']['policy']['rules']:
301
+ rule_properties = rule_data.get('properties', [])
302
+ # Skip rules where the first property is "SYSTEM"
303
+ if rule_properties and rule_properties[0] == "SYSTEM":
304
+ if hasattr(args, 'verbose') and args.verbose:
305
+ print(f"Excluding SYSTEM rule: {rule_data['rule']['name']}")
306
+ else:
307
+ filtered_rules.append(rule_data)
308
+
309
+ processed_data['data']['policy']['wanFirewall']['policy']['rules'] = filtered_rules
310
+
311
+ # Add index_in_section to each rule
312
+ section_counters = {}
313
+ for rule_data in processed_data['data']['policy']['wanFirewall']['policy']['rules']:
314
+ section_name = rule_data['rule']['section']['name']
315
+ if section_name not in section_counters:
316
+ section_counters[section_name] = 0
317
+ section_counters[section_name] += 1
318
+ rule_data['rule']['index_in_section'] = section_counters[section_name]
319
+
320
+ # Create rules_in_sections array
321
+ rules_in_sections = []
322
+ for rule_data in processed_data['data']['policy']['wanFirewall']['policy']['rules']:
323
+ rule_info = rule_data['rule']
324
+ rules_in_sections.append({
325
+ "index_in_section": rule_info['index_in_section'],
326
+ "section_name": rule_info['section']['name'],
327
+ "rule_name": rule_info['name']
328
+ })
329
+ # rule_info.pop("index_in_section", None)
330
+ # rule_info.pop("index", None)
331
+ # rule_info["enabled"] = True
332
+
333
+ # Add rules_in_sections to the policy structure
334
+ processed_data['data']['policy']['wanFirewall']['policy']['rules_in_sections'] = rules_in_sections
335
+
336
+ # Reformat sections array to have index, section_id and section_name structure
337
+ # Exclude the first section from export
338
+ processed_sections = []
339
+ for index, section_data in enumerate(processed_data['data']['policy']['wanFirewall']['policy']['sections']):
340
+ processed_sections.append({
341
+ "section_index": index,
342
+ "section_name": section_data['section']['name']
343
+ })
344
+
345
+ # Add preserved section IDs and section_to_start_after_id
346
+ processed_data['data']['policy']['wanFirewall']['policy']['section_ids'] = section_id_map
347
+ if section_to_start_after_id:
348
+ processed_data['data']['policy']['wanFirewall']['policy']['section_to_start_after_id'] = section_to_start_after_id
349
+
350
+ # Replace the original sections array with the reformatted one
351
+ processed_data['data']['policy']['wanFirewall']['policy']['sections'] = processed_sections
352
+
353
+ # Write the processed data to the new file
354
+ with open(output_file, 'w', encoding='utf-8') as f:
355
+ json.dump(processed_data, f, indent=4, ensure_ascii=False)
356
+
357
+ return [{"success": True, "output_file": output_file, "account_id": account_id}]
358
+
359
+ except Exception as e:
360
+ print(f"ERROR: {str(e)}")
361
+ return [{"success": False, "error": str(e)}]
@@ -0,0 +1,58 @@
1
+ import catocli.parsers.custom.import_rules_to_tf.import_rules_to_tf as import_rules_to_tf
2
+
3
+ def import_parse(subparsers):
4
+ """Create import command parsers"""
5
+
6
+ # Create the main import parser
7
+ import_parser = subparsers.add_parser('import', help='Import data from various sources', usage='catocli import <operation> [options]')
8
+ import_subparsers = import_parser.add_subparsers(description='valid import operations', help='additional help')
9
+
10
+ # Add if_rules_to_tf command
11
+ if_rules_parser = import_subparsers.add_parser(
12
+ 'if_rules_to_tf',
13
+ help='Import Internet Firewall rules to Terraform state',
14
+ usage='catocli import if_rules_to_tf <json_file> --module-name <module_name> [options]'
15
+ )
16
+
17
+ if_rules_parser.add_argument('json_file', help='Path to the JSON file containing IFW rules and sections')
18
+ if_rules_parser.add_argument('--module-name', required=True,
19
+ help='Terraform module name to import resources into')
20
+ if_rules_parser.add_argument('-accountID', help='Account ID (required by CLI framework but not used for import)', required=False)
21
+ if_rules_parser.add_argument('--batch-size', type=int, default=10,
22
+ help='Number of imports per batch (default: 10)')
23
+ if_rules_parser.add_argument('--delay', type=int, default=2,
24
+ help='Delay between batches in seconds (default: 2)')
25
+ if_rules_parser.add_argument('--rules-only', action='store_true',
26
+ help='Import only rules, skip sections')
27
+ if_rules_parser.add_argument('--sections-only', action='store_true',
28
+ help='Import only sections, skip rules')
29
+ if_rules_parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
30
+ if_rules_parser.add_argument('--auto-approve', action='store_true', help='Skip confirmation prompt and proceed automatically')
31
+
32
+ if_rules_parser.set_defaults(func=import_rules_to_tf.import_if_rules_to_tf)
33
+
34
+ # Add wf_rules_to_tf command
35
+ wf_rules_parser = import_subparsers.add_parser(
36
+ 'wf_rules_to_tf',
37
+ help='Import WAN Firewall rules to Terraform state',
38
+ usage='catocli import wf_rules_to_tf <json_file> --module-name <module_name> [options]'
39
+ )
40
+
41
+ wf_rules_parser.add_argument('json_file', help='Path to the JSON file containing WF rules and sections')
42
+ wf_rules_parser.add_argument('--module-name', required=True,
43
+ help='Terraform module name to import resources into')
44
+ wf_rules_parser.add_argument('-accountID', help='Account ID (required by CLI framework but not used for import)', required=False)
45
+ wf_rules_parser.add_argument('--batch-size', type=int, default=10,
46
+ help='Number of imports per batch (default: 10)')
47
+ wf_rules_parser.add_argument('--delay', type=int, default=2,
48
+ help='Delay between batches in seconds (default: 2)')
49
+ wf_rules_parser.add_argument('--rules-only', action='store_true',
50
+ help='Import only rules, skip sections')
51
+ wf_rules_parser.add_argument('--sections-only', action='store_true',
52
+ help='Import only sections, skip rules')
53
+ wf_rules_parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
54
+ wf_rules_parser.add_argument('--auto-approve', action='store_true', help='Skip confirmation prompt and proceed automatically')
55
+
56
+ wf_rules_parser.set_defaults(func=import_rules_to_tf.import_wf_rules_to_tf)
57
+
58
+ return import_parser