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.
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/PKG-INFO +1 -1
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/cli.py +3 -2
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/cortex_client.py +90 -2
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/pyproject.toml +1 -1
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/LICENSE +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/README.rst +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/command_options.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/api_keys.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/audit_logs.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/backup.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/backup_commands/cortex_export.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/catalog.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/custom_data.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/custom_events.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/custom_metrics.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/dependencies.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/deploys.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/discovery_audit.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/docs.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/entity_relationship_types.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/entity_relationships.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/entity_types.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/gitops_logs.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/groups.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/initiatives.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/aws.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/azure_devops.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/azure_resources.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/circleci.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/coralogix.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/datadog.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/github.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/gitlab.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/incidentio.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/launchdarkly.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/newrelic.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/pagerduty.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/prometheus.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/sonarqube.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/ip_allowlist.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/on_call.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/go.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/java.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/node.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/nuget.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/python.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/plugins.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/queries.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/rest.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/scim.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/scorecards.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/scorecards_commands/exemptions.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/secrets.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/teams.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/workflows.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/models/team.py +0 -0
- {cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/utils.py +0 -0
|
@@ -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(
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/entity_relationship_types.py
RENAMED
|
File without changes
|
{cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/entity_relationships.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/integrations_commands/aws.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/go.py
RENAMED
|
File without changes
|
{cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/java.py
RENAMED
|
File without changes
|
{cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/node.py
RENAMED
|
File without changes
|
{cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/nuget.py
RENAMED
|
File without changes
|
{cortexapps_cli-1.4.0 → cortexapps_cli-1.5.0}/cortexapps_cli/commands/packages_commands/python.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|