catocli 3.0.18__py3-none-any.whl → 3.0.22__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.
- catocli/Utils/clidriver.py +16 -8
- catocli/Utils/formatter_account_metrics.py +544 -0
- catocli/Utils/formatter_app_stats.py +184 -0
- catocli/Utils/formatter_app_stats_timeseries.py +377 -0
- catocli/Utils/formatter_events_timeseries.py +459 -0
- catocli/Utils/formatter_socket_port_metrics.py +189 -0
- catocli/Utils/formatter_socket_port_metrics_timeseries.py +339 -0
- catocli/Utils/formatter_utils.py +251 -0
- catocli/__init__.py +1 -1
- catocli/clisettings.json +37 -5
- catocli/parsers/customParserApiClient.py +206 -65
- catocli/parsers/mutation_policy/__init__.py +405 -405
- catocli/parsers/mutation_site/__init__.py +15 -15
- catocli/parsers/mutation_sites/__init__.py +15 -15
- catocli/parsers/query_accountMetrics/README.md +90 -0
- catocli/parsers/query_accountMetrics/__init__.py +6 -0
- catocli/parsers/query_appStats/README.md +2 -2
- catocli/parsers/query_appStats/__init__.py +4 -2
- catocli/parsers/query_appStatsTimeSeries/__init__.py +4 -2
- catocli/parsers/query_eventsTimeSeries/README.md +280 -0
- catocli/parsers/query_eventsTimeSeries/__init__.py +6 -0
- catocli/parsers/query_policy/__init__.py +42 -42
- catocli/parsers/query_socketPortMetrics/README.md +44 -0
- catocli/parsers/query_socketPortMetrics/__init__.py +6 -0
- catocli/parsers/query_socketPortMetricsTimeSeries/README.md +83 -0
- catocli/parsers/query_socketPortMetricsTimeSeries/__init__.py +4 -2
- {catocli-3.0.18.dist-info → catocli-3.0.22.dist-info}/METADATA +1 -1
- {catocli-3.0.18.dist-info → catocli-3.0.22.dist-info}/RECORD +179 -177
- {catocli-3.0.18.dist-info → catocli-3.0.22.dist-info}/top_level.txt +0 -1
- models/mutation.policy.antiMalwareFileHash.addRule.json +20 -0
- models/mutation.policy.antiMalwareFileHash.addSection.json +103 -0
- models/mutation.policy.antiMalwareFileHash.createPolicyRevision.json +123 -0
- models/mutation.policy.antiMalwareFileHash.discardPolicyRevision.json +123 -0
- models/mutation.policy.antiMalwareFileHash.moveRule.json +20 -0
- models/mutation.policy.antiMalwareFileHash.moveSection.json +103 -0
- models/mutation.policy.antiMalwareFileHash.publishPolicyRevision.json +123 -0
- models/mutation.policy.antiMalwareFileHash.removeRule.json +20 -0
- models/mutation.policy.antiMalwareFileHash.removeSection.json +103 -0
- models/mutation.policy.antiMalwareFileHash.updatePolicy.json +123 -0
- models/mutation.policy.antiMalwareFileHash.updateRule.json +20 -0
- models/mutation.policy.antiMalwareFileHash.updateSection.json +103 -0
- models/mutation.policy.appTenantRestriction.addRule.json +20 -0
- models/mutation.policy.appTenantRestriction.addSection.json +103 -0
- models/mutation.policy.appTenantRestriction.createPolicyRevision.json +123 -0
- models/mutation.policy.appTenantRestriction.discardPolicyRevision.json +123 -0
- models/mutation.policy.appTenantRestriction.moveRule.json +20 -0
- models/mutation.policy.appTenantRestriction.moveSection.json +103 -0
- models/mutation.policy.appTenantRestriction.publishPolicyRevision.json +123 -0
- models/mutation.policy.appTenantRestriction.removeRule.json +20 -0
- models/mutation.policy.appTenantRestriction.removeSection.json +103 -0
- models/mutation.policy.appTenantRestriction.updatePolicy.json +123 -0
- models/mutation.policy.appTenantRestriction.updateRule.json +20 -0
- models/mutation.policy.appTenantRestriction.updateSection.json +103 -0
- models/mutation.policy.applicationControl.addRule.json +20 -0
- models/mutation.policy.applicationControl.addSection.json +103 -0
- models/mutation.policy.applicationControl.createPolicyRevision.json +123 -0
- models/mutation.policy.applicationControl.discardPolicyRevision.json +123 -0
- models/mutation.policy.applicationControl.moveRule.json +20 -0
- models/mutation.policy.applicationControl.moveSection.json +103 -0
- models/mutation.policy.applicationControl.publishPolicyRevision.json +123 -0
- models/mutation.policy.applicationControl.removeRule.json +20 -0
- models/mutation.policy.applicationControl.removeSection.json +103 -0
- models/mutation.policy.applicationControl.updatePolicy.json +123 -0
- models/mutation.policy.applicationControl.updateRule.json +20 -0
- models/mutation.policy.applicationControl.updateSection.json +103 -0
- models/mutation.policy.dynamicIpAllocation.addRule.json +20 -0
- models/mutation.policy.dynamicIpAllocation.addSection.json +103 -0
- models/mutation.policy.dynamicIpAllocation.createPolicyRevision.json +123 -0
- models/mutation.policy.dynamicIpAllocation.discardPolicyRevision.json +123 -0
- models/mutation.policy.dynamicIpAllocation.moveRule.json +20 -0
- models/mutation.policy.dynamicIpAllocation.moveSection.json +103 -0
- models/mutation.policy.dynamicIpAllocation.publishPolicyRevision.json +123 -0
- models/mutation.policy.dynamicIpAllocation.removeRule.json +20 -0
- models/mutation.policy.dynamicIpAllocation.removeSection.json +103 -0
- models/mutation.policy.dynamicIpAllocation.updatePolicy.json +123 -0
- models/mutation.policy.dynamicIpAllocation.updateRule.json +20 -0
- models/mutation.policy.dynamicIpAllocation.updateSection.json +103 -0
- models/mutation.policy.internetFirewall.addRule.json +20 -0
- models/mutation.policy.internetFirewall.addSection.json +103 -0
- models/mutation.policy.internetFirewall.createPolicyRevision.json +123 -0
- models/mutation.policy.internetFirewall.discardPolicyRevision.json +123 -0
- models/mutation.policy.internetFirewall.moveRule.json +20 -0
- models/mutation.policy.internetFirewall.moveSection.json +103 -0
- models/mutation.policy.internetFirewall.publishPolicyRevision.json +123 -0
- models/mutation.policy.internetFirewall.removeRule.json +20 -0
- models/mutation.policy.internetFirewall.removeSection.json +103 -0
- models/mutation.policy.internetFirewall.updatePolicy.json +123 -0
- models/mutation.policy.internetFirewall.updateRule.json +20 -0
- models/mutation.policy.internetFirewall.updateSection.json +103 -0
- models/mutation.policy.remotePortFwd.addRule.json +20 -0
- models/mutation.policy.remotePortFwd.addSection.json +103 -0
- models/mutation.policy.remotePortFwd.createPolicyRevision.json +123 -0
- models/mutation.policy.remotePortFwd.discardPolicyRevision.json +123 -0
- models/mutation.policy.remotePortFwd.moveRule.json +20 -0
- models/mutation.policy.remotePortFwd.moveSection.json +103 -0
- models/mutation.policy.remotePortFwd.publishPolicyRevision.json +123 -0
- models/mutation.policy.remotePortFwd.removeRule.json +20 -0
- models/mutation.policy.remotePortFwd.removeSection.json +103 -0
- models/mutation.policy.remotePortFwd.updatePolicy.json +123 -0
- models/mutation.policy.remotePortFwd.updateRule.json +20 -0
- models/mutation.policy.remotePortFwd.updateSection.json +103 -0
- models/mutation.policy.socketLan.addRule.json +40 -0
- models/mutation.policy.socketLan.addSection.json +103 -0
- models/mutation.policy.socketLan.createPolicyRevision.json +143 -0
- models/mutation.policy.socketLan.discardPolicyRevision.json +143 -0
- models/mutation.policy.socketLan.moveRule.json +40 -0
- models/mutation.policy.socketLan.moveSection.json +103 -0
- models/mutation.policy.socketLan.publishPolicyRevision.json +143 -0
- models/mutation.policy.socketLan.removeRule.json +40 -0
- models/mutation.policy.socketLan.removeSection.json +103 -0
- models/mutation.policy.socketLan.updatePolicy.json +143 -0
- models/mutation.policy.socketLan.updateRule.json +40 -0
- models/mutation.policy.socketLan.updateSection.json +103 -0
- models/mutation.policy.terminalServer.addRule.json +20 -0
- models/mutation.policy.terminalServer.addSection.json +103 -0
- models/mutation.policy.terminalServer.createPolicyRevision.json +123 -0
- models/mutation.policy.terminalServer.discardPolicyRevision.json +123 -0
- models/mutation.policy.terminalServer.moveRule.json +20 -0
- models/mutation.policy.terminalServer.moveSection.json +103 -0
- models/mutation.policy.terminalServer.publishPolicyRevision.json +123 -0
- models/mutation.policy.terminalServer.removeRule.json +20 -0
- models/mutation.policy.terminalServer.removeSection.json +103 -0
- models/mutation.policy.terminalServer.updatePolicy.json +123 -0
- models/mutation.policy.terminalServer.updateRule.json +20 -0
- models/mutation.policy.terminalServer.updateSection.json +103 -0
- models/mutation.policy.tlsInspect.addRule.json +20 -0
- models/mutation.policy.tlsInspect.addSection.json +103 -0
- models/mutation.policy.tlsInspect.createPolicyRevision.json +123 -0
- models/mutation.policy.tlsInspect.discardPolicyRevision.json +123 -0
- models/mutation.policy.tlsInspect.moveRule.json +20 -0
- models/mutation.policy.tlsInspect.moveSection.json +103 -0
- models/mutation.policy.tlsInspect.publishPolicyRevision.json +123 -0
- models/mutation.policy.tlsInspect.removeRule.json +20 -0
- models/mutation.policy.tlsInspect.removeSection.json +103 -0
- models/mutation.policy.tlsInspect.updatePolicy.json +123 -0
- models/mutation.policy.tlsInspect.updateRule.json +20 -0
- models/mutation.policy.tlsInspect.updateSection.json +103 -0
- models/mutation.policy.wanFirewall.addRule.json +20 -0
- models/mutation.policy.wanFirewall.addSection.json +103 -0
- models/mutation.policy.wanFirewall.createPolicyRevision.json +123 -0
- models/mutation.policy.wanFirewall.discardPolicyRevision.json +123 -0
- models/mutation.policy.wanFirewall.moveRule.json +20 -0
- models/mutation.policy.wanFirewall.moveSection.json +103 -0
- models/mutation.policy.wanFirewall.publishPolicyRevision.json +123 -0
- models/mutation.policy.wanFirewall.removeRule.json +20 -0
- models/mutation.policy.wanFirewall.removeSection.json +103 -0
- models/mutation.policy.wanFirewall.updatePolicy.json +123 -0
- models/mutation.policy.wanFirewall.updateRule.json +20 -0
- models/mutation.policy.wanFirewall.updateSection.json +103 -0
- models/mutation.policy.wanNetwork.addRule.json +20 -0
- models/mutation.policy.wanNetwork.addSection.json +103 -0
- models/mutation.policy.wanNetwork.createPolicyRevision.json +123 -0
- models/mutation.policy.wanNetwork.discardPolicyRevision.json +123 -0
- models/mutation.policy.wanNetwork.moveRule.json +20 -0
- models/mutation.policy.wanNetwork.moveSection.json +103 -0
- models/mutation.policy.wanNetwork.publishPolicyRevision.json +123 -0
- models/mutation.policy.wanNetwork.removeRule.json +20 -0
- models/mutation.policy.wanNetwork.removeSection.json +103 -0
- models/mutation.policy.wanNetwork.updatePolicy.json +123 -0
- models/mutation.policy.wanNetwork.updateRule.json +20 -0
- models/mutation.policy.wanNetwork.updateSection.json +103 -0
- models/mutation.xdr.analystFeedback.json +822 -87
- models/query.policy.antiMalwareFileHash.policy.json +123 -0
- models/query.policy.appTenantRestriction.policy.json +123 -0
- models/query.policy.applicationControl.policy.json +123 -0
- models/query.policy.dynamicIpAllocation.policy.json +123 -0
- models/query.policy.internetFirewall.policy.json +123 -0
- models/query.policy.remotePortFwd.policy.json +123 -0
- models/query.policy.socketLan.policy.json +143 -0
- models/query.policy.terminalServer.policy.json +123 -0
- models/query.policy.tlsInspect.policy.json +123 -0
- models/query.policy.wanFirewall.policy.json +123 -0
- models/query.policy.wanNetwork.policy.json +123 -0
- models/query.xdr.stories.json +822 -87
- models/query.xdr.story.json +822 -87
- schema/catolib.py +25 -8
- catocli/Utils/csv_formatter.py +0 -663
- scripts/catolib.py +0 -62
- scripts/export_if_rules_to_json.py +0 -188
- scripts/export_wf_rules_to_json.py +0 -111
- scripts/import_wf_rules_to_tfstate.py +0 -331
- {catocli-3.0.18.dist-info → catocli-3.0.22.dist-info}/WHEEL +0 -0
- {catocli-3.0.18.dist-info → catocli-3.0.22.dist-info}/entry_points.txt +0 -0
- {catocli-3.0.18.dist-info → catocli-3.0.22.dist-info}/licenses/LICENSE +0 -0
|
@@ -129,22 +129,50 @@ def createRequest(args, configuration):
|
|
|
129
129
|
print(f"ERROR: Failed to load operation model for {operation_name}: {e}")
|
|
130
130
|
return None
|
|
131
131
|
|
|
132
|
-
# Load
|
|
132
|
+
# Load configuration for output formatting
|
|
133
133
|
csv_function = None
|
|
134
|
-
output_format = getattr(args, 'format',
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
output_format = getattr(args, 'format', None)
|
|
135
|
+
raw_output = getattr(args, 'raw_output', False)
|
|
136
|
+
default_override = None
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
settings = loadJSON("clisettings.json")
|
|
140
|
+
csv_supported_operations = settings.get("queryOperationCsvOutput", {})
|
|
141
|
+
default_overrides = settings.get("queryOperationDefaultFormatOverrides", {})
|
|
142
|
+
default_override = default_overrides.get(operation_name)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
csv_supported_operations = {}
|
|
145
|
+
default_override = None
|
|
146
|
+
|
|
147
|
+
# Determine effective format
|
|
148
|
+
# Priority: -raw flag -> raw/original; -f -> explicit; config override -> default; fallback -> json
|
|
149
|
+
effective_format = 'json'
|
|
150
|
+
if raw_output:
|
|
151
|
+
effective_format = 'raw'
|
|
152
|
+
elif output_format in ['json', 'csv']:
|
|
153
|
+
effective_format = output_format
|
|
154
|
+
elif default_override and default_override.get('enabled'):
|
|
155
|
+
effective_format = default_override.get('default_format', 'json')
|
|
156
|
+
|
|
157
|
+
# If CSV requested, validate support - check both new and legacy systems
|
|
158
|
+
if effective_format == 'csv':
|
|
159
|
+
# First check new format override system
|
|
160
|
+
if default_override and default_override.get('format_function'):
|
|
161
|
+
csv_function = default_override.get('format_function')
|
|
162
|
+
else:
|
|
163
|
+
# Fallback to legacy CSV system for backward compatibility
|
|
140
164
|
csv_function = csv_supported_operations.get(operation_name)
|
|
141
|
-
except Exception as e:
|
|
142
|
-
print(f"WARNING: Could not load CSV settings: {e}")
|
|
143
|
-
csv_function = None
|
|
144
165
|
|
|
145
166
|
if not csv_function:
|
|
167
|
+
# List all operations that support CSV from both systems
|
|
168
|
+
supported_ops = list(csv_supported_operations.keys())
|
|
169
|
+
if default_overrides:
|
|
170
|
+
for op_name, config in default_overrides.items():
|
|
171
|
+
if config.get('enabled') and config.get('format_function'):
|
|
172
|
+
supported_ops.append(op_name)
|
|
173
|
+
|
|
146
174
|
print(f"ERROR: CSV output not supported for operation '{operation_name}'")
|
|
147
|
-
print(f"Supported CSV operations: {list(
|
|
175
|
+
print(f"Supported CSV operations: {sorted(list(set(supported_ops))) if supported_ops else 'none'}")
|
|
148
176
|
return None
|
|
149
177
|
|
|
150
178
|
variables_obj = {}
|
|
@@ -235,79 +263,192 @@ def createRequest(args, configuration):
|
|
|
235
263
|
)
|
|
236
264
|
print(f"Sentinel API response code: {result_code}")
|
|
237
265
|
|
|
238
|
-
# Apply
|
|
239
|
-
if
|
|
266
|
+
# Apply formatting based on effective format
|
|
267
|
+
if effective_format == 'raw' or not default_override:
|
|
268
|
+
# Raw format - return only the data portion, not HTTP headers/status
|
|
269
|
+
if isinstance(response, (list, tuple)) and len(response) > 0:
|
|
270
|
+
return response[0] # Extract just the data portion
|
|
271
|
+
else:
|
|
272
|
+
return response
|
|
273
|
+
elif effective_format == 'csv' and csv_function and response:
|
|
274
|
+
# CSV formatting requested
|
|
240
275
|
try:
|
|
241
|
-
# Import the CSV formatter dynamically
|
|
242
276
|
# Get the response data (handle both list and tuple responses)
|
|
243
277
|
if isinstance(response, (list, tuple)) and len(response) > 0:
|
|
244
278
|
response_data = response[0]
|
|
245
279
|
else:
|
|
246
280
|
response_data = response
|
|
247
281
|
|
|
248
|
-
# Add Utils directory to path and import
|
|
282
|
+
# Add Utils directory to path and import formatter_utils
|
|
249
283
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
250
284
|
utils_dir = os.path.join(os.path.dirname(current_dir), 'Utils')
|
|
251
285
|
if utils_dir not in sys.path:
|
|
252
286
|
sys.path.insert(0, utils_dir)
|
|
253
287
|
|
|
254
|
-
# Import the
|
|
255
|
-
import
|
|
288
|
+
# Import the formatter_utils module
|
|
289
|
+
import formatter_utils
|
|
256
290
|
|
|
257
|
-
#
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
291
|
+
# Use the centralized format_to_csv function
|
|
292
|
+
csv_output = None
|
|
293
|
+
if hasattr(formatter_utils, 'format_to_csv'):
|
|
294
|
+
# Use format_to_csv with the operation name
|
|
295
|
+
csv_output = formatter_utils.format_to_csv(response_data, operation_name)
|
|
296
|
+
elif hasattr(formatter_utils, csv_function):
|
|
297
|
+
# Fallback to direct function call for legacy support
|
|
298
|
+
csv_formatter_func = getattr(formatter_utils, csv_function)
|
|
261
299
|
|
|
262
|
-
if
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
os.makedirs(reports_dir)
|
|
267
|
-
|
|
268
|
-
# Default filename is the operation name (second segment) lowercased
|
|
269
|
-
op_base = operation_name.split('.')[-1].lower()
|
|
270
|
-
default_filename = f"{op_base}.csv"
|
|
271
|
-
filename = default_filename
|
|
272
|
-
|
|
273
|
-
# Override filename if provided
|
|
274
|
-
if hasattr(args, 'csv_filename') and getattr(args, 'csv_filename'):
|
|
275
|
-
filename = getattr(args, 'csv_filename')
|
|
276
|
-
# Ensure .csv extension
|
|
277
|
-
if not filename.lower().endswith('.csv'):
|
|
278
|
-
filename += '.csv'
|
|
279
|
-
|
|
280
|
-
# Append timestamp if requested
|
|
281
|
-
if hasattr(args, 'append_timestamp') and getattr(args, 'append_timestamp'):
|
|
282
|
-
ts = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
|
283
|
-
name, ext = os.path.splitext(filename)
|
|
284
|
-
filename = f"{name}_{ts}{ext}"
|
|
285
|
-
|
|
286
|
-
output_path = os.path.join(reports_dir, filename)
|
|
287
|
-
|
|
288
|
-
# Write CSV to file
|
|
289
|
-
try:
|
|
290
|
-
with open(output_path, 'w', encoding='utf-8', newline='') as f:
|
|
291
|
-
f.write(csv_output)
|
|
292
|
-
except Exception as write_err:
|
|
293
|
-
print(f"ERROR: Failed to write CSV to file {output_path}: {write_err}")
|
|
294
|
-
# Fallback: return CSV to stdout behavior
|
|
295
|
-
return [{"__csv_output__": csv_output}]
|
|
296
|
-
|
|
297
|
-
if params.get('v'):
|
|
298
|
-
print(f"Saved CSV report to: {output_path}")
|
|
299
|
-
|
|
300
|
-
# Return structured response similar to export functions
|
|
301
|
-
return [{"success": True, "output_file": output_path, "operation": operation_name}]
|
|
300
|
+
# Check if this is a new format function that takes output_format parameter
|
|
301
|
+
if default_override and default_override.get('format_function') == csv_function:
|
|
302
|
+
# New format functions that support output_format parameter
|
|
303
|
+
csv_output = csv_formatter_func(response_data, output_format='csv')
|
|
302
304
|
else:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
+
# Legacy CSV functions that only take response_data
|
|
306
|
+
csv_output = csv_formatter_func(response_data)
|
|
305
307
|
else:
|
|
308
|
+
# CSV formatter function not found
|
|
306
309
|
print(f"ERROR: CSV formatter function '{csv_function}' not found")
|
|
307
|
-
|
|
310
|
+
# Return clean error response instead of raw response with HTTP headers
|
|
311
|
+
return [{"error": f"CSV formatter function '{csv_function}' not found", "operation": operation_name, "success": False}]
|
|
312
|
+
|
|
313
|
+
# Handle CSV output regardless of which formatter path was used
|
|
314
|
+
if csv_output:
|
|
315
|
+
# Determine output directory (reports) in current folder
|
|
316
|
+
reports_dir = os.path.join(os.getcwd(), 'reports')
|
|
317
|
+
if not os.path.exists(reports_dir):
|
|
318
|
+
os.makedirs(reports_dir)
|
|
319
|
+
|
|
320
|
+
# Default filename is the operation name (second segment) lowercased
|
|
321
|
+
op_base = operation_name.split('.')[-1].lower()
|
|
322
|
+
default_filename = f"{op_base}.csv"
|
|
323
|
+
filename = default_filename
|
|
324
|
+
|
|
325
|
+
# Override filename if provided
|
|
326
|
+
if hasattr(args, 'csv_filename') and getattr(args, 'csv_filename'):
|
|
327
|
+
filename = getattr(args, 'csv_filename')
|
|
328
|
+
# Ensure .csv extension
|
|
329
|
+
if not filename.lower().endswith('.csv'):
|
|
330
|
+
filename += '.csv'
|
|
331
|
+
|
|
332
|
+
# Append timestamp if requested
|
|
333
|
+
if hasattr(args, 'append_timestamp') and getattr(args, 'append_timestamp'):
|
|
334
|
+
ts = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
|
335
|
+
name, ext = os.path.splitext(filename)
|
|
336
|
+
filename = f"{name}_{ts}{ext}"
|
|
337
|
+
|
|
338
|
+
output_path = os.path.join(reports_dir, filename)
|
|
339
|
+
|
|
340
|
+
# Write CSV to file
|
|
341
|
+
try:
|
|
342
|
+
with open(output_path, 'w', encoding='utf-8', newline='') as f:
|
|
343
|
+
f.write(csv_output)
|
|
344
|
+
except Exception as write_err:
|
|
345
|
+
print(f"ERROR: Failed to write CSV to file {output_path}: {write_err}")
|
|
346
|
+
# Fallback: return CSV to stdout behavior
|
|
347
|
+
return [{"__csv_output__": csv_output}]
|
|
348
|
+
|
|
349
|
+
if params.get('v'):
|
|
350
|
+
print(f"Saved CSV report to: {output_path}")
|
|
351
|
+
|
|
352
|
+
# Return structured response similar to export functions
|
|
353
|
+
return [{"success": True, "output_file": output_path, "operation": operation_name}]
|
|
354
|
+
elif csv_output is None:
|
|
355
|
+
# Formatter returned None, indicating we should fall back to raw response
|
|
356
|
+
print("INFO: No processable data found, returning raw API response")
|
|
357
|
+
# Extract just the data portion to avoid HTTP headers
|
|
358
|
+
if isinstance(response, (list, tuple)) and len(response) > 0:
|
|
359
|
+
return response[0]
|
|
360
|
+
else:
|
|
361
|
+
return response
|
|
362
|
+
else:
|
|
363
|
+
print("WARNING: CSV formatter returned empty result - no data available for the specified criteria")
|
|
364
|
+
# Return clean error response instead of raw response with HTTP headers
|
|
365
|
+
return [{"error": "No data available for CSV export", "operation": operation_name, "success": False}]
|
|
308
366
|
except Exception as e:
|
|
309
367
|
print(f"ERROR: Failed to format CSV output: {e}")
|
|
310
|
-
|
|
368
|
+
# Return clean error response instead of raw response with HTTP headers
|
|
369
|
+
return [{"error": f"Failed to format CSV output: {str(e)}", "operation": operation_name, "success": False}]
|
|
370
|
+
elif effective_format == 'json' and default_override and default_override.get('format_function') and response:
|
|
371
|
+
# Enhanced JSON formatting requested
|
|
372
|
+
try:
|
|
373
|
+
# Get the response data (handle both list and tuple responses)
|
|
374
|
+
if isinstance(response, (list, tuple)) and len(response) > 0:
|
|
375
|
+
response_data = response[0]
|
|
376
|
+
else:
|
|
377
|
+
response_data = response
|
|
378
|
+
|
|
379
|
+
# Add Utils directory to path and import formatter_utils module for format functions
|
|
380
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
381
|
+
utils_dir = os.path.join(os.path.dirname(current_dir), 'Utils')
|
|
382
|
+
if utils_dir not in sys.path:
|
|
383
|
+
sys.path.insert(0, utils_dir)
|
|
384
|
+
|
|
385
|
+
# Import the formatter_utils module (which contains format functions)
|
|
386
|
+
import formatter_utils
|
|
387
|
+
|
|
388
|
+
# Use the centralized format_to_csv function which can handle JSON output too
|
|
389
|
+
format_function = default_override.get('format_function')
|
|
390
|
+
if hasattr(formatter_utils, 'format_to_csv'):
|
|
391
|
+
# The individual formatters handle JSON output internally when called from format_to_csv
|
|
392
|
+
# We need to directly call the specific formatter for JSON output
|
|
393
|
+
try:
|
|
394
|
+
# Dynamic import of the specific formatter function
|
|
395
|
+
if format_function == 'format_app_stats':
|
|
396
|
+
from formatter_app_stats import format_app_stats
|
|
397
|
+
formatted_output = format_app_stats(response_data, output_format='json')
|
|
398
|
+
elif format_function == 'format_app_stats_timeseries':
|
|
399
|
+
from formatter_app_stats_timeseries import format_app_stats_timeseries
|
|
400
|
+
formatted_output = format_app_stats_timeseries(response_data, output_format='json')
|
|
401
|
+
elif format_function == 'format_account_metrics':
|
|
402
|
+
from formatter_account_metrics import format_account_metrics
|
|
403
|
+
formatted_output = format_account_metrics(response_data, output_format='json')
|
|
404
|
+
elif format_function == 'format_socket_port_metrics_timeseries':
|
|
405
|
+
from formatter_socket_port_metrics_timeseries import format_socket_port_metrics_timeseries
|
|
406
|
+
formatted_output = format_socket_port_metrics_timeseries(response_data, output_format='json')
|
|
407
|
+
elif format_function == 'format_events_timeseries':
|
|
408
|
+
from formatter_events_timeseries import format_events_timeseries
|
|
409
|
+
formatted_output = format_events_timeseries(response_data, output_format='json')
|
|
410
|
+
else:
|
|
411
|
+
formatted_output = None
|
|
412
|
+
except ImportError:
|
|
413
|
+
formatted_output = None
|
|
414
|
+
|
|
415
|
+
# Handle the formatted output from individual formatters
|
|
416
|
+
if formatted_output is None:
|
|
417
|
+
# Formatter returned None, indicating we should fall back to raw response
|
|
418
|
+
print("INFO: No processable data found, returning raw API response")
|
|
419
|
+
return response_data
|
|
420
|
+
else:
|
|
421
|
+
# Pretty print the formatted JSON directly to stdout
|
|
422
|
+
print(formatted_output)
|
|
423
|
+
return None # Return None to prevent further processing/output
|
|
424
|
+
elif hasattr(formatter_utils, format_function):
|
|
425
|
+
formatter_func = getattr(formatter_utils, format_function)
|
|
426
|
+
formatted_output = formatter_func(response_data, output_format='json')
|
|
427
|
+
|
|
428
|
+
if formatted_output is None:
|
|
429
|
+
# Formatter returned None, indicating we should fall back to raw response
|
|
430
|
+
print("INFO: No processable data found, returning raw API response")
|
|
431
|
+
return response_data
|
|
432
|
+
else:
|
|
433
|
+
# Pretty print the formatted JSON directly to stdout
|
|
434
|
+
print(formatted_output)
|
|
435
|
+
return None # Return None to prevent further processing/output
|
|
436
|
+
else:
|
|
437
|
+
print(f"WARNING: Formatter function '{format_function}' not found, using original response")
|
|
438
|
+
return response
|
|
439
|
+
except Exception as e:
|
|
440
|
+
print(f"ERROR: Failed to apply enhanced JSON formatting: {e}")
|
|
441
|
+
# Instead of returning the raw response which may contain non-serializable objects,
|
|
442
|
+
# extract just the JSON data portion to avoid HTTPHeaderDict serialization errors
|
|
443
|
+
try:
|
|
444
|
+
if isinstance(response, (list, tuple)) and len(response) > 0:
|
|
445
|
+
response_data = response[0]
|
|
446
|
+
else:
|
|
447
|
+
response_data = response
|
|
448
|
+
return response_data
|
|
449
|
+
except Exception:
|
|
450
|
+
# If we can't extract data safely, return a minimal error response
|
|
451
|
+
return [{"error": "Failed to format response and fallback failed", "original_error": str(e)}]
|
|
311
452
|
|
|
312
453
|
return response
|
|
313
454
|
|