Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0d99232
perf: optimize test scheduling with --dist loadfile for 25% faster te…
jeff-schnitter Nov 5, 2025
32c9f45
feat: add support for Cortex Secrets API (#161)
jeff-schnitter Nov 6, 2025
6fc38bb
feat: add entity relationships API support with optimized backup/rest…
jeff-schnitter Nov 6, 2025
e54dca3
fix: add client-side rate limiting and make tests idempotent (#165) #…
jeff-schnitter Nov 13, 2025
283fc3b
Merge branch 'main' into staging
jeff-schnitter Nov 13, 2025
5741d35
fix: remove rate limiter initialization log message (#168)
jeff-schnitter Nov 14, 2025
7b20357
Merge branch 'main' into staging
jeff-schnitter Nov 14, 2025
8e58ea0
fix: change default logging level from INFO to WARNING
jeff-schnitter Nov 18, 2025
b095f88
Merge branch '170-default-logging-none' into staging
jeff-schnitter Nov 18, 2025
4165886
fix: initialize results and failed_count before directory check in im…
jeff-schnitter Nov 18, 2025
5be1748
Merge branch '171-import-partial-export' into staging
jeff-schnitter Nov 18, 2025
b292e67
fix: only retry on 429 rate limit errors, not 5xx server errors
jeff-schnitter Nov 18, 2025
052796e
Merge branch '172-retry-only-429' into staging
jeff-schnitter Nov 18, 2025
470664d
Revert: Undo direct merges to staging (#176)
jeff-schnitter Nov 19, 2025
d7e6963
fix: change default logging level from INFO to WARNING (#177)
jeff-schnitter Nov 19, 2025
b5f0c5a
fix: initialize results and failed_count before directory check in im…
jeff-schnitter Nov 19, 2025
a037024
fix: only retry on 429 rate limit errors, not 5xx server errors (#179)
jeff-schnitter Nov 19, 2025
65b2c2d
Add tests for iconTag parameter in entity-types create (#183)
jeff-schnitter Jan 10, 2026
fffe6b8
Fix backup import/export error handling (#185)
jeff-schnitter Jan 10, 2026
7799716
Merge branch 'main' into staging
jeff-schnitter Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,18 @@ Commits should be prefixed with:

Only commits with these prefixes appear in the auto-generated `HISTORY.md`.

### HISTORY.md Merge Conflicts
The `HISTORY.md` file is auto-generated when `staging` is merged to `main`. This means:
- `main` always has the latest HISTORY.md
- `staging` lags behind until the next release
- Feature branches created from `main` have the updated history

When merging feature branches to `staging`, conflicts in HISTORY.md are expected. Resolve by accepting the incoming version:
```bash
git checkout --theirs HISTORY.md
git add HISTORY.md
```

### GitHub Actions
- **`publish.yml`**: Triggered on push to `main`, handles versioning and multi-platform publishing
- **`test-pr.yml`**: Runs tests on pull requests
Expand Down
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

<!-- insertion marker -->

## [1.7.0](https://github.com/cortexapps/cli/releases/tag/1.7.0) - 2025-11-19

<small>[Compare with 1.6.0](https://github.com/cortexapps/cli/compare/1.6.0...1.7.0)</small>
Expand All @@ -18,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

- remove rate limiter initialization log message (#169) #patch ([015107a](https://github.com/cortexapps/cli/commit/015107aca15d5a4cf4eb746834bcbb7dac607e1d) by Jeff Schnitter).


## [1.5.0](https://github.com/cortexapps/cli/releases/tag/1.5.0) - 2025-11-13

<small>[Compare with 1.4.0](https://github.com/cortexapps/cli/compare/1.4.0...1.5.0)</small>
Expand Down
4 changes: 4 additions & 0 deletions cortexapps_cli/commands/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,3 +698,7 @@ def import_tenant(
print(f"cortex scorecards create -f \"{file_path}\"")
elif import_type == "workflows":
print(f"cortex workflows create -f \"{file_path}\"")

# Exit with non-zero code if any imports failed
if total_failed > 0:
raise typer.Exit(1)
8 changes: 6 additions & 2 deletions cortexapps_cli/cortex_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,12 @@ def request(self, method, endpoint, params={}, headers={}, data=None, raw_body=F
print(error_str)
raise typer.Exit(code=1)
except json.JSONDecodeError:
# if we can't parse the error message, just raise the HTTP error
response.raise_for_status()
# if we can't parse the error message, print a clean error and exit
status = response.status_code
reason = response.reason or 'Unknown error'
error_str = f'[red][bold]HTTP Error {status}[/bold][/red]: {reason}'
print(error_str)
raise typer.Exit(code=1)

if raw_response:
return response
Expand Down
1 change: 1 addition & 0 deletions data/import/entity-types/cli-test.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"description": "This is a test entity type definition.",
"iconTag": "Cortex-builtin::Basketball",
"name": "CLI Test With Empty Schema",
"schema": {},
"type": "cli-test"
Expand Down
7 changes: 7 additions & 0 deletions data/run-time/entity-type-invalid-icon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"description": "This is a test entity type definition with invalid icon.",
"iconTag": "invalidIcon",
"name": "CLI Test With Invalid Icon",
"schema": {},
"type": "cli-test-invalid-icon"
}
40 changes: 40 additions & 0 deletions tests/test_backup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from tests.helpers.utils import *
import os
import tempfile

def test_backup_import_invalid_api_key(monkeypatch):
"""
Test that backup import exits with non-zero return code when API calls fail.
"""
monkeypatch.setenv("CORTEX_API_KEY", "invalidKey")

# Create a temp directory with a catalog subdirectory and a simple yaml file
with tempfile.TemporaryDirectory() as tmpdir:
catalog_dir = os.path.join(tmpdir, "catalog")
os.makedirs(catalog_dir)

# Create a minimal catalog entity file
entity_file = os.path.join(catalog_dir, "test-entity.yaml")
with open(entity_file, "w") as f:
f.write("""
info:
x-cortex-tag: test-entity
title: Test Entity
x-cortex-type: service
""")

result = cli(["backup", "import", "-d", tmpdir], return_type=ReturnType.RAW)
assert result.exit_code != 0, f"backup import should exit with non-zero code on failure, got exit_code={result.exit_code}"


def test_backup_export_invalid_api_key(monkeypatch):
"""
Test that backup export exits with non-zero return code and clean error message when API calls fail.
"""
monkeypatch.setenv("CORTEX_API_KEY", "invalidKey")

with tempfile.TemporaryDirectory() as tmpdir:
result = cli(["backup", "export", "-d", tmpdir], return_type=ReturnType.RAW)
assert result.exit_code != 0, f"backup export should exit with non-zero code on failure, got exit_code={result.exit_code}"
assert "HTTP Error 401" in result.stdout, "Should show HTTP 401 error message"
assert "Traceback" not in result.stdout, "Should not show Python traceback"
4 changes: 2 additions & 2 deletions tests/test_config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ def test_config_file_bad_api_key(monkeypatch, tmp_path):
monkeypatch.setattr('sys.stdin', io.StringIO('y'))
f = tmp_path / "test-config-bad-api-key.txt"
response = cli(["-c", str(f), "-k", "invalidApiKey", "scorecards", "list"], return_type=ReturnType.RAW)
assert "401 Client Error: Unauthorized" in str(response), "should get Unauthorized error"
assert "HTTP Error 401" in response.stdout, "should get Unauthorized error"

def test_environment_variable_invalid_key(monkeypatch):
monkeypatch.setenv("CORTEX_API_KEY", "invalidKey")
response = cli(["scorecards", "list"], return_type=ReturnType.RAW)
assert "401 Client Error: Unauthorized" in str(response), "should get Unauthorized error"
assert "HTTP Error 401" in response.stdout, "should get Unauthorized error"

def test_config_file_bad_url(monkeypatch, tmp_path):
monkeypatch.delenv("CORTEX_BASE_URL")
Expand Down
14 changes: 13 additions & 1 deletion tests/test_entity_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ def test_resource_definitions(capsys):
response = cli(["entity-types", "list"])
assert any(definition['type'] == 'cli-test' for definition in response['definitions']), "Should find entity type named 'cli-test'"

cli(["entity-types", "get", "-t", "cli-test"])
# Verify iconTag was set correctly
response = cli(["entity-types", "get", "-t", "cli-test"])
assert response.get('iconTag') == "Cortex-builtin::Basketball", "iconTag should be set to Cortex-builtin::Basketball"

cli(["entity-types", "update", "-t", "cli-test", "-f", "data/run-time/entity-type-update.json"])


def test_resource_definitions_invalid_icon():
# API does not reject invalid iconTag values - it uses a default icon instead
# This test verifies that behavior and will catch if the API changes to reject invalid icons
response = cli(["entity-types", "create", "-f", "data/run-time/entity-type-invalid-icon.json"], return_type=ReturnType.RAW)
assert response.exit_code == 0, "Creation should succeed even with invalid iconTag (API uses default icon)"

# Clean up the test entity type
cli(["entity-types", "delete", "-t", "cli-test-invalid-icon"])
1 change: 1 addition & 0 deletions tests/test_scim.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from urllib.error import HTTPError
import pytest

@pytest.mark.skip(reason="Disabled until CET-23082 is resolved.")
def test():
response = cli(["scim", "list"], ReturnType.STDOUT)

Expand Down