cortexapps-cli 1.1.0__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/PKG-INFO +1 -1
  2. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/cli.py +4 -0
  3. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/backup.py +160 -35
  4. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/cortex_client.py +18 -1
  5. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/pyproject.toml +1 -1
  6. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/LICENSE +0 -0
  7. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/README.rst +0 -0
  8. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/command_options.py +0 -0
  9. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/api_keys.py +0 -0
  10. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/audit_logs.py +0 -0
  11. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/backup_commands/cortex_export.py +0 -0
  12. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/catalog.py +0 -0
  13. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/custom_data.py +0 -0
  14. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/custom_events.py +0 -0
  15. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/custom_metrics.py +0 -0
  16. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/dependencies.py +0 -0
  17. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/deploys.py +0 -0
  18. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/discovery_audit.py +0 -0
  19. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/docs.py +0 -0
  20. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/entity_types.py +0 -0
  21. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/gitops_logs.py +0 -0
  22. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/groups.py +0 -0
  23. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/initiatives.py +0 -0
  24. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations.py +0 -0
  25. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/aws.py +0 -0
  26. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/azure_devops.py +0 -0
  27. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/azure_resources.py +0 -0
  28. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/circleci.py +0 -0
  29. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/coralogix.py +0 -0
  30. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/datadog.py +0 -0
  31. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/github.py +0 -0
  32. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/gitlab.py +0 -0
  33. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/incidentio.py +0 -0
  34. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/launchdarkly.py +0 -0
  35. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/newrelic.py +0 -0
  36. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/pagerduty.py +0 -0
  37. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/prometheus.py +0 -0
  38. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/integrations_commands/sonarqube.py +0 -0
  39. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/ip_allowlist.py +0 -0
  40. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/on_call.py +0 -0
  41. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/packages.py +0 -0
  42. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/packages_commands/go.py +0 -0
  43. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/packages_commands/java.py +0 -0
  44. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/packages_commands/node.py +0 -0
  45. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/packages_commands/nuget.py +0 -0
  46. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/packages_commands/python.py +0 -0
  47. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/plugins.py +0 -0
  48. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/queries.py +0 -0
  49. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/rest.py +0 -0
  50. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/scim.py +0 -0
  51. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/scorecards.py +0 -0
  52. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/scorecards_commands/exemptions.py +0 -0
  53. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/teams.py +0 -0
  54. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/commands/workflows.py +0 -0
  55. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/models/team.py +0 -0
  56. {cortexapps_cli-1.1.0 → cortexapps_cli-1.3.0}/cortexapps_cli/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cortexapps-cli
3
- Version: 1.1.0
3
+ Version: 1.3.0
4
4
  Summary: Command Line Interface for cortexapps
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -121,6 +121,10 @@ def global_callback(
121
121
  else:
122
122
  url = "https://api.getcortexapp.com"
123
123
 
124
+ # Set default URL if not provided
125
+ if not url:
126
+ url = "https://api.getcortexapp.com"
127
+
124
128
  # strip any quotes or spaces from the api_key and url
125
129
  api_key = api_key.strip('"\' ')
126
130
  url = url.strip('"\' /')
@@ -9,6 +9,7 @@ from rich import print, print_json
9
9
  from rich.console import Console
10
10
  from enum import Enum
11
11
  import yaml
12
+ from concurrent.futures import ThreadPoolExecutor, as_completed
12
13
 
13
14
  import cortexapps_cli.commands.scorecards as scorecards
14
15
  import cortexapps_cli.commands.catalog as catalog
@@ -60,7 +61,7 @@ def _file_name(directory, tag, content, extension):
60
61
  def _write_file(content, file, is_json=False):
61
62
  with open(file, 'w') as f:
62
63
  if is_json:
63
- print(content, file=f)
64
+ json.dump(content, f, indent=2)
64
65
  else:
65
66
  f.write(str(content) + "\n")
66
67
  f.close()
@@ -108,9 +109,27 @@ def _export_plugins(ctx, directory):
108
109
  list = plugins.list(ctx, _print=False, include_drafts="true", page=None, page_size=None)
109
110
  tags = [plugin["tag"] for plugin in list["plugins"]]
110
111
  tags_sorted = sorted(tags)
111
- for tag in tags_sorted:
112
- content = plugins.get(ctx, tag_or_id=tag, include_blob="true", _print=False)
113
- _file_name(directory, tag, content, "json")
112
+
113
+ def fetch_plugin(tag):
114
+ try:
115
+ content = plugins.get(ctx, tag_or_id=tag, include_blob="true", _print=False)
116
+ return (tag, content, None)
117
+ except Exception as e:
118
+ return (tag, None, str(e))
119
+
120
+ # Fetch all plugins in parallel
121
+ with ThreadPoolExecutor(max_workers=30) as executor:
122
+ futures = {executor.submit(fetch_plugin, tag): tag for tag in tags_sorted}
123
+ results = []
124
+ for future in as_completed(futures):
125
+ results.append(future.result())
126
+
127
+ # Sort results alphabetically and write in order
128
+ for tag, content, error in sorted(results, key=lambda x: x[0]):
129
+ if error:
130
+ print(f"Failed to export plugin {tag}: {error}")
131
+ else:
132
+ _file_name(directory, tag, content, "json")
114
133
 
115
134
  def _export_scorecards(ctx, directory):
116
135
  directory = _directory_name(directory, "scorecards")
@@ -118,22 +137,44 @@ def _export_scorecards(ctx, directory):
118
137
  list = scorecards.list(ctx, show_drafts=True, page=None, page_size=None, _print=False)
119
138
  tags = [scorecard["tag"] for scorecard in list["scorecards"]]
120
139
  tags_sorted = sorted(tags)
121
- for tag in tags_sorted:
122
- content = scorecards.descriptor(ctx, scorecard_tag=tag, _print=False)
123
- _file_name(directory, tag, content, "yaml")
140
+
141
+ def fetch_scorecard(tag):
142
+ try:
143
+ content = scorecards.descriptor(ctx, scorecard_tag=tag, _print=False)
144
+ return (tag, content, None)
145
+ except Exception as e:
146
+ return (tag, None, str(e))
147
+
148
+ # Fetch all scorecards in parallel
149
+ with ThreadPoolExecutor(max_workers=30) as executor:
150
+ futures = {executor.submit(fetch_scorecard, tag): tag for tag in tags_sorted}
151
+ results = []
152
+ for future in as_completed(futures):
153
+ results.append(future.result())
154
+
155
+ # Sort results alphabetically and write in order
156
+ for tag, content, error in sorted(results, key=lambda x: x[0]):
157
+ if error:
158
+ print(f"Failed to export scorecard {tag}: {error}")
159
+ else:
160
+ _file_name(directory, tag, content, "yaml")
124
161
 
125
162
  def _export_workflows(ctx, directory):
126
163
  directory = _directory_name(directory, "workflows")
127
164
 
128
- list = workflows.list(ctx, _print=False, include_actions="false", page=None, page_size=None, search_query=None)
129
- tags = [workflow["tag"] for workflow in list["workflows"]]
130
- tags_sorted = sorted(tags)
131
- for tag in tags_sorted:
165
+ # Get all workflows with actions in one API call
166
+ list = workflows.list(ctx, _print=False, include_actions="true", page=None, page_size=None, search_query=None)
167
+ workflows_data = sorted(list["workflows"], key=lambda x: x["tag"])
168
+
169
+ # Convert JSON workflows to YAML and write them
170
+ for workflow in workflows_data:
171
+ tag = workflow["tag"]
132
172
  try:
133
- content = workflows.get(ctx, tag=tag, yaml="true", _print=False)
134
- _file_name(directory, tag, content, "yaml")
135
- except:
136
- print("failed for " + tag)
173
+ # Convert the JSON workflow data to YAML format
174
+ workflow_yaml = yaml.dump(workflow, default_flow_style=False, sort_keys=False)
175
+ _file_name(directory, tag, workflow_yaml, "yaml")
176
+ except Exception as e:
177
+ print(f"Failed to export workflow {tag}: {e}")
137
178
 
138
179
  backupTypes = {
139
180
  "catalog",
@@ -257,38 +298,122 @@ def _import_entity_types(ctx, force, directory):
257
298
  def _import_catalog(ctx, directory):
258
299
  if os.path.isdir(directory):
259
300
  print("Processing: " + directory)
260
- for filename in sorted(os.listdir(directory)):
261
- file_path = os.path.join(directory, filename)
262
- if os.path.isfile(file_path):
263
- print(" Importing: " + filename)
264
- catalog.create(ctx, file_input=open(file_path), _print=False)
301
+ files = [(filename, os.path.join(directory, filename))
302
+ for filename in sorted(os.listdir(directory))
303
+ if os.path.isfile(os.path.join(directory, filename))]
304
+
305
+ def import_catalog_file(file_info):
306
+ filename, file_path = file_info
307
+ try:
308
+ with open(file_path) as f:
309
+ catalog.create(ctx, file_input=f, _print=False)
310
+ return (filename, None)
311
+ except Exception as e:
312
+ return (filename, str(e))
313
+
314
+ # Import all files in parallel
315
+ with ThreadPoolExecutor(max_workers=30) as executor:
316
+ futures = {executor.submit(import_catalog_file, file_info): file_info[0] for file_info in files}
317
+ results = []
318
+ for future in as_completed(futures):
319
+ results.append(future.result())
320
+
321
+ # Print results in alphabetical order
322
+ for filename, error in sorted(results, key=lambda x: x[0]):
323
+ if error:
324
+ print(f" Failed to import {filename}: {error}")
325
+ else:
326
+ print(f" Importing: {filename}")
265
327
 
266
328
  def _import_plugins(ctx, directory):
267
329
  if os.path.isdir(directory):
268
330
  print("Processing: " + directory)
269
- for filename in sorted(os.listdir(directory)):
270
- file_path = os.path.join(directory, filename)
271
- if os.path.isfile(file_path):
272
- print(" Importing: " + filename)
273
- plugins.create(ctx, file_input=open(file_path), force=True)
331
+ files = [(filename, os.path.join(directory, filename))
332
+ for filename in sorted(os.listdir(directory))
333
+ if os.path.isfile(os.path.join(directory, filename))]
334
+
335
+ def import_plugin_file(file_info):
336
+ filename, file_path = file_info
337
+ try:
338
+ with open(file_path) as f:
339
+ plugins.create(ctx, file_input=f, force=True)
340
+ return (filename, None)
341
+ except Exception as e:
342
+ return (filename, str(e))
343
+
344
+ # Import all files in parallel
345
+ with ThreadPoolExecutor(max_workers=30) as executor:
346
+ futures = {executor.submit(import_plugin_file, file_info): file_info[0] for file_info in files}
347
+ results = []
348
+ for future in as_completed(futures):
349
+ results.append(future.result())
350
+
351
+ # Print results in alphabetical order
352
+ for filename, error in sorted(results, key=lambda x: x[0]):
353
+ if error:
354
+ print(f" Failed to import {filename}: {error}")
355
+ else:
356
+ print(f" Importing: {filename}")
274
357
 
275
358
  def _import_scorecards(ctx, directory):
276
359
  if os.path.isdir(directory):
277
360
  print("Processing: " + directory)
278
- for filename in sorted(os.listdir(directory)):
279
- file_path = os.path.join(directory, filename)
280
- if os.path.isfile(file_path):
281
- print(" Importing: " + filename)
282
- scorecards.create(ctx, file_input=open(file_path), dry_run=False)
361
+ files = [(filename, os.path.join(directory, filename))
362
+ for filename in sorted(os.listdir(directory))
363
+ if os.path.isfile(os.path.join(directory, filename))]
364
+
365
+ def import_scorecard_file(file_info):
366
+ filename, file_path = file_info
367
+ try:
368
+ with open(file_path) as f:
369
+ scorecards.create(ctx, file_input=f, dry_run=False)
370
+ return (filename, None)
371
+ except Exception as e:
372
+ return (filename, str(e))
373
+
374
+ # Import all files in parallel
375
+ with ThreadPoolExecutor(max_workers=30) as executor:
376
+ futures = {executor.submit(import_scorecard_file, file_info): file_info[0] for file_info in files}
377
+ results = []
378
+ for future in as_completed(futures):
379
+ results.append(future.result())
380
+
381
+ # Print results in alphabetical order
382
+ for filename, error in sorted(results, key=lambda x: x[0]):
383
+ if error:
384
+ print(f" Failed to import {filename}: {error}")
385
+ else:
386
+ print(f" Importing: {filename}")
283
387
 
284
388
  def _import_workflows(ctx, directory):
285
389
  if os.path.isdir(directory):
286
390
  print("Processing: " + directory)
287
- for filename in sorted(os.listdir(directory)):
288
- file_path = os.path.join(directory, filename)
289
- if os.path.isfile(file_path):
290
- print(" Importing: " + filename)
291
- workflows.create(ctx, file_input=open(file_path))
391
+ files = [(filename, os.path.join(directory, filename))
392
+ for filename in sorted(os.listdir(directory))
393
+ if os.path.isfile(os.path.join(directory, filename))]
394
+
395
+ def import_workflow_file(file_info):
396
+ filename, file_path = file_info
397
+ try:
398
+ with open(file_path) as f:
399
+ workflows.create(ctx, file_input=f)
400
+ return (filename, None)
401
+ except Exception as e:
402
+ return (filename, str(e))
403
+
404
+ # Import all files in parallel
405
+ with ThreadPoolExecutor(max_workers=30) as executor:
406
+ futures = {executor.submit(import_workflow_file, file_info): file_info[0] for file_info in files}
407
+ results = []
408
+ for future in as_completed(futures):
409
+ results.append(future.result())
410
+
411
+ # Print results in alphabetical order
412
+ for filename, error in sorted(results, key=lambda x: x[0]):
413
+ if error:
414
+ print(f" Failed to import {filename}: {error}")
415
+ else:
416
+ print(f" Importing: {filename}")
292
417
 
293
418
  @app.command("import")
294
419
  def import_tenant(
@@ -1,4 +1,6 @@
1
1
  import requests
2
+ from requests.adapters import HTTPAdapter
3
+ from urllib3.util.retry import Retry
2
4
  import json
3
5
  import typer
4
6
  from rich import print
@@ -20,6 +22,20 @@ class CortexClient:
20
22
  logging.basicConfig(level=numeric_level)
21
23
  self.logger = logging.getLogger(__name__)
22
24
 
25
+ # Create a session with connection pooling for better performance
26
+ self.session = requests.Session()
27
+
28
+ # Configure connection pool to support concurrent requests
29
+ # pool_connections: number of connection pools to cache
30
+ # pool_maxsize: maximum number of connections to save in the pool
31
+ adapter = HTTPAdapter(
32
+ pool_connections=10,
33
+ pool_maxsize=50,
34
+ max_retries=Retry(total=3, backoff_factor=0.3, status_forcelist=[500, 502, 503, 504])
35
+ )
36
+ self.session.mount('https://', adapter)
37
+ self.session.mount('http://', adapter)
38
+
23
39
  def request(self, method, endpoint, params={}, headers={}, data=None, raw_body=False, raw_response=False, content_type='application/json'):
24
40
  req_headers = {
25
41
  'Authorization': f'Bearer {self.api_key}',
@@ -33,7 +49,8 @@ class CortexClient:
33
49
  if content_type == 'application/json' and isinstance(data, dict):
34
50
  req_data = json.dumps(data)
35
51
 
36
- response = requests.request(method, url, params=params, headers=req_headers, data=req_data)
52
+ # Use session for connection pooling and reuse
53
+ response = self.session.request(method, url, params=params, headers=req_headers, data=req_data)
37
54
 
38
55
  self.logger.debug(f"Request Headers: {response.request.headers}")
39
56
  self.logger.debug(f"Response Status Code: {response.status_code}")
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "cortexapps-cli"
3
3
  # version will be incremented via command line as part of github actions build
4
- version = "1.1.0"
4
+ version = "1.3.0"
5
5
  description = "Command Line Interface for cortexapps"
6
6
  license = "MIT"
7
7
  authors = [
File without changes