cortexapps-cli 1.4.0__tar.gz → 1.5.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 (59) hide show
  1. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/PKG-INFO +1 -1
  2. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/cli.py +3 -2
  3. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/cortex_client.py +90 -2
  4. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/pyproject.toml +1 -1
  5. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/LICENSE +0 -0
  6. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/README.rst +0 -0
  7. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/command_options.py +0 -0
  8. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/api_keys.py +0 -0
  9. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/audit_logs.py +0 -0
  10. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/backup.py +0 -0
  11. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/backup_commands/cortex_export.py +0 -0
  12. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/catalog.py +0 -0
  13. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/custom_data.py +0 -0
  14. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/custom_events.py +0 -0
  15. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/custom_metrics.py +0 -0
  16. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/dependencies.py +0 -0
  17. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/deploys.py +0 -0
  18. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/discovery_audit.py +0 -0
  19. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/docs.py +0 -0
  20. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/entity_relationship_types.py +0 -0
  21. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/entity_relationships.py +0 -0
  22. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/entity_types.py +0 -0
  23. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/gitops_logs.py +0 -0
  24. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/groups.py +0 -0
  25. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/initiatives.py +0 -0
  26. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations.py +0 -0
  27. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/aws.py +0 -0
  28. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/azure_devops.py +0 -0
  29. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/azure_resources.py +0 -0
  30. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/circleci.py +0 -0
  31. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/coralogix.py +0 -0
  32. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/datadog.py +0 -0
  33. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/github.py +0 -0
  34. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/gitlab.py +0 -0
  35. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/incidentio.py +0 -0
  36. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/launchdarkly.py +0 -0
  37. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/newrelic.py +0 -0
  38. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/pagerduty.py +0 -0
  39. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/prometheus.py +0 -0
  40. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/sonarqube.py +0 -0
  41. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/ip_allowlist.py +0 -0
  42. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/on_call.py +0 -0
  43. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages.py +0 -0
  44. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/go.py +0 -0
  45. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/java.py +0 -0
  46. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/node.py +0 -0
  47. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/nuget.py +0 -0
  48. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/python.py +0 -0
  49. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/plugins.py +0 -0
  50. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/queries.py +0 -0
  51. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/rest.py +0 -0
  52. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/scim.py +0 -0
  53. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/scorecards.py +0 -0
  54. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/scorecards_commands/exemptions.py +0 -0
  55. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/secrets.py +0 -0
  56. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/teams.py +0 -0
  57. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/workflows.py +0 -0
  58. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/models/team.py +0 -0
  59. {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.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.4.0
3
+ Version: 1.5.0
4
4
  Summary: Command Line Interface for cortexapps
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -87,7 +87,8 @@ def global_callback(
87
87
  url: str = typer.Option(None, "--url", "-u", help="Base URL for the API", envvar="CORTEX_BASE_URL"),
88
88
  config_file: str = typer.Option(os.path.join(os.path.expanduser('~'), '.cortex', 'config'), "--config", "-c", help="Config file path", envvar="CORTEX_CONFIG"),
89
89
  tenant: str = typer.Option("default", "--tenant", "-t", help="Tenant alias", envvar="CORTEX_TENANT_ALIAS"),
90
- log_level: Annotated[str, typer.Option("--log-level", "-l", help="Set the logging level")] = "INFO"
90
+ log_level: Annotated[str, typer.Option("--log-level", "-l", help="Set the logging level")] = "INFO",
91
+ rate_limit: int = typer.Option(None, "--rate-limit", "-r", help="API rate limit in requests per minute (default: 1000)", envvar="CORTEX_RATE_LIMIT")
91
92
  ):
92
93
  if not ctx.obj:
93
94
  ctx.obj = {}
@@ -135,7 +136,7 @@ def global_callback(
135
136
  api_key = api_key.strip('"\' ')
136
137
  url = url.strip('"\' /')
137
138
 
138
- ctx.obj["client"] = CortexClient(api_key, tenant, numeric_level, url)
139
+ ctx.obj["client"] = CortexClient(api_key, tenant, numeric_level, url, rate_limit)
139
140
 
140
141
  @app.command()
141
142
  def version():
@@ -9,12 +9,64 @@ from rich.markdown import Markdown
9
9
  from rich.console import Console
10
10
  import logging
11
11
  import urllib.parse
12
+ import time
13
+ import threading
14
+ import os
12
15
 
13
16
  from cortexapps_cli.utils import guess_data_key
14
17
 
15
18
 
19
+ class TokenBucket:
20
+ """
21
+ Token bucket rate limiter for client-side rate limiting.
22
+
23
+ Allows bursts up to bucket capacity while enforcing long-term rate limit.
24
+ Thread-safe for concurrent use.
25
+ """
26
+ def __init__(self, rate, capacity=None):
27
+ """
28
+ Args:
29
+ rate: Tokens per second (e.g., 1000 req/min = 16.67 req/sec)
30
+ capacity: Maximum tokens in bucket (default: rate, allows 1 second burst)
31
+ """
32
+ self.rate = rate
33
+ self.capacity = capacity or rate
34
+ self.tokens = self.capacity
35
+ self.last_update = time.time()
36
+ self.lock = threading.Lock()
37
+
38
+ def acquire(self, tokens=1):
39
+ """
40
+ Acquire tokens, blocking until available.
41
+
42
+ Args:
43
+ tokens: Number of tokens to acquire (default: 1)
44
+ """
45
+ with self.lock:
46
+ while True:
47
+ now = time.time()
48
+ elapsed = now - self.last_update
49
+
50
+ # Refill tokens based on elapsed time
51
+ self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
52
+ self.last_update = now
53
+
54
+ if self.tokens >= tokens:
55
+ self.tokens -= tokens
56
+ return
57
+
58
+ # Calculate wait time for next token
59
+ tokens_needed = tokens - self.tokens
60
+ wait_time = tokens_needed / self.rate
61
+
62
+ # Release lock and sleep
63
+ self.lock.release()
64
+ time.sleep(min(wait_time, 0.1)) # Sleep in small increments
65
+ self.lock.acquire()
66
+
67
+
16
68
  class CortexClient:
17
- def __init__(self, api_key, tenant, numeric_level, base_url='https://api.getcortexapp.com'):
69
+ def __init__(self, api_key, tenant, numeric_level, base_url='https://api.getcortexapp.com', rate_limit=None):
18
70
  self.api_key = api_key
19
71
  self.tenant = tenant
20
72
  self.base_url = base_url
@@ -22,6 +74,19 @@ class CortexClient:
22
74
  logging.basicConfig(level=numeric_level)
23
75
  self.logger = logging.getLogger(__name__)
24
76
 
77
+ # Enable urllib3 retry logging to see when retries occur
78
+ urllib3_logger = logging.getLogger('urllib3.util.retry')
79
+ urllib3_logger.setLevel(logging.DEBUG)
80
+
81
+ # Read rate limit from environment variable or use default
82
+ if rate_limit is None:
83
+ rate_limit = int(os.environ.get('CORTEX_RATE_LIMIT', '1000'))
84
+
85
+ # Client-side rate limiter (default: 1000 req/min = 16.67 req/sec)
86
+ # Allows bursting up to 50 requests, then enforces rate limit
87
+ self.rate_limiter = TokenBucket(rate=rate_limit/60.0, capacity=50)
88
+ self.logger.info(f"Rate limiter initialized: {rate_limit} req/min (burst: 50)")
89
+
25
90
  # Create a session with connection pooling for better performance
26
91
  self.session = requests.Session()
27
92
 
@@ -31,7 +96,13 @@ class CortexClient:
31
96
  adapter = HTTPAdapter(
32
97
  pool_connections=10,
33
98
  pool_maxsize=50,
34
- max_retries=Retry(total=3, backoff_factor=0.3, status_forcelist=[500, 502, 503, 504])
99
+ max_retries=Retry(
100
+ total=3,
101
+ backoff_factor=0.3,
102
+ status_forcelist=[500, 502, 503, 504], # Removed 429 - we avoid it with rate limiting
103
+ allowed_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
104
+ respect_retry_after_header=True
105
+ )
35
106
  )
36
107
  self.session.mount('https://', adapter)
37
108
  self.session.mount('http://', adapter)
@@ -50,13 +121,30 @@ class CortexClient:
50
121
  req_data = json.dumps(data)
51
122
 
52
123
  # Use session for connection pooling and reuse
124
+ # Acquire rate limit token before making request (blocks if needed)
125
+ self.rate_limiter.acquire()
126
+
127
+ start_time = time.time()
53
128
  response = self.session.request(method, url, params=params, headers=req_headers, data=req_data)
129
+ duration = time.time() - start_time
130
+
131
+ # Log slow requests or non-200 responses (likely retries happened)
132
+ if duration > 2.0 or response.status_code != 200:
133
+ self.logger.info(f"{method} {endpoint} -> {response.status_code} ({duration:.1f}s)")
134
+
135
+ # Log if retries likely occurred (duration suggests backoff delays)
136
+ if duration > 5.0:
137
+ self.logger.warning(f"⚠️ Slow request ({duration:.1f}s) - likely retries occurred")
54
138
 
55
139
  self.logger.debug(f"Request Headers: {response.request.headers}")
56
140
  self.logger.debug(f"Response Status Code: {response.status_code}")
57
141
  self.logger.debug(f"Response Headers: {response.headers}")
58
142
  self.logger.debug(f"Response Content: {response.text}")
59
143
 
144
+ # Check if response is OK. Note: urllib3 Retry with status_forcelist should have already
145
+ # retried any 429/500/502/503/504 errors. If we're here with one of those status codes,
146
+ # it means retries were exhausted.
147
+
60
148
  if not response.ok:
61
149
  try:
62
150
  # try to parse the error message
@@ -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.4.0"
4
+ version = "1.5.0"
5
5
  description = "Command Line Interface for cortexapps"
6
6
  license = "MIT"
7
7
  authors = [
File without changes