From ff9ca35042c418df7b54dd67104c7b22cf7a6a2a Mon Sep 17 00:00:00 2001 From: Jeff Schnitter Date: Mon, 5 Oct 2026 13:07:34 -0700 Subject: [PATCH 1/3] fix: remove catalog delete-by-type command to prevent accidental mass entity deletion Co-Authored-By: Claude Sonnet 4.6 --- cortexapps_cli/commands/catalog.py | 18 ----------------- .../catalog-delete-by-type-entity.yaml | 5 ----- data/run-time/entity-type-delete-by-type.json | 6 ------ tests/test_catalog_delete_by_type.py | 20 ------------------- 4 files changed, 49 deletions(-) delete mode 100644 data/run-time/catalog-delete-by-type-entity.yaml delete mode 100644 data/run-time/entity-type-delete-by-type.json delete mode 100644 tests/test_catalog_delete_by_type.py diff --git a/cortexapps_cli/commands/catalog.py b/cortexapps_cli/commands/catalog.py index 2717b891..c5e575f1 100644 --- a/cortexapps_cli/commands/catalog.py +++ b/cortexapps_cli/commands/catalog.py @@ -122,24 +122,6 @@ def delete( client.delete("api/v1/catalog/" + tag) -@app.command() -def delete_by_type( - ctx: typer.Context, - types: CatalogCommandOptions.types = None, -): - """ - Dangerous operation that will delete all entities that are of the given type - """ - client = ctx.obj["client"] - - #TODO: check if types is a regex of form: ([-A-Za-z]+,)+ - - params = { - "types": types - } - - client.delete("api/v1/catalog", params=params) - @app.command() def descriptor( ctx: typer.Context, diff --git a/data/run-time/catalog-delete-by-type-entity.yaml b/data/run-time/catalog-delete-by-type-entity.yaml deleted file mode 100644 index b33be910..00000000 --- a/data/run-time/catalog-delete-by-type-entity.yaml +++ /dev/null @@ -1,5 +0,0 @@ -openapi: 3.0.1 -info: - title: Delete By Type Test Entity - x-cortex-tag: cli-test-delete-by-type-entity - x-cortex-type: cli-test-delete-by-type diff --git a/data/run-time/entity-type-delete-by-type.json b/data/run-time/entity-type-delete-by-type.json deleted file mode 100644 index 8af27dbb..00000000 --- a/data/run-time/entity-type-delete-by-type.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "description": "Temporary entity type for testing delete-by-type.", - "name": "CLI Test Delete By Type", - "schema": {}, - "type": "cli-test-delete-by-type" -} diff --git a/tests/test_catalog_delete_by_type.py b/tests/test_catalog_delete_by_type.py deleted file mode 100644 index 0fe5e796..00000000 --- a/tests/test_catalog_delete_by_type.py +++ /dev/null @@ -1,20 +0,0 @@ -from tests.helpers.utils import * - -#@pytest.mark.skip(reason="Disabled until CET-24425 is resolved.") -def test(): - # Create the entity type - cli(["entity-types", "create", "-f", "data/run-time/entity-type-delete-by-type.json"], ReturnType.RAW) - - # Create an entity of that type - cli(["catalog", "create", "-f", "data/run-time/catalog-delete-by-type-entity.yaml"]) - - # Verify the entity exists - response = cli(["catalog", "list", "-t", "cli-test-delete-by-type"]) - assert response['total'] > 0, "Should find at least 1 entity of type 'cli-test-delete-by-type'" - - # Delete all entities of that type - cli(["catalog", "delete-by-type", "-t", "cli-test-delete-by-type"]) - - # Verify 0 entities remain of that type - response = cli(["catalog", "list", "-t", "cli-test-delete-by-type"]) - assert response['total'] == 0, f"Expected 0 entities of x-cortex-type:cli-test-delete-by-type, but found: {response['total']}" From 9a3c05780b76c573e0d0c6ae404776a5eb055308 Mon Sep 17 00:00:00 2001 From: Jeff Schnitter Date: Mon, 12 Oct 2026 08:33:40 -0700 Subject: [PATCH 2/3] feat: add catalog delete-by-id command Replaces the removed delete-by-type with the safer delete-entities-by-id endpoint, which requires explicit CIDs rather than a broad type filter. Co-Authored-By: Claude Sonnet 4.6 --- cortexapps_cli/commands/catalog.py | 12 ++++++++++++ data/run-time/catalog-delete-by-id-entity.yaml | 5 +++++ tests/test_catalog_delete_by_id.py | 17 +++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 data/run-time/catalog-delete-by-id-entity.yaml create mode 100644 tests/test_catalog_delete_by_id.py diff --git a/cortexapps_cli/commands/catalog.py b/cortexapps_cli/commands/catalog.py index c5e575f1..da8d1392 100644 --- a/cortexapps_cli/commands/catalog.py +++ b/cortexapps_cli/commands/catalog.py @@ -122,6 +122,18 @@ def delete( client.delete("api/v1/catalog/" + tag) +@app.command() +def delete_by_id( + ctx: typer.Context, + ids: Optional[List[str]] = typer.Option(..., "--id", "-i", help="CID of an entity to delete (e.g. en2da8159dbeefb974). Can be specified multiple times.", show_default=False), +): + """ + Delete entities by CID. Accepts one or more --id flags. + """ + client = ctx.obj["client"] + + client.delete("api/v1/catalog", params={"ids": ids}) + @app.command() def descriptor( ctx: typer.Context, diff --git a/data/run-time/catalog-delete-by-id-entity.yaml b/data/run-time/catalog-delete-by-id-entity.yaml new file mode 100644 index 00000000..57980df8 --- /dev/null +++ b/data/run-time/catalog-delete-by-id-entity.yaml @@ -0,0 +1,5 @@ +openapi: 3.0.1 +info: + title: Delete By ID Test Entity + x-cortex-tag: cli-test-delete-by-id-entity + x-cortex-type: service diff --git a/tests/test_catalog_delete_by_id.py b/tests/test_catalog_delete_by_id.py new file mode 100644 index 00000000..93b17a8e --- /dev/null +++ b/tests/test_catalog_delete_by_id.py @@ -0,0 +1,17 @@ +from tests.helpers.utils import * + +def test(): + # Create an entity + cli(["catalog", "create", "-f", "data/run-time/catalog-delete-by-id-entity.yaml"]) + + # Get its CID + response = cli(["catalog", "details", "-t", "cli-test-delete-by-id-entity"]) + cid = response['id'] + assert cid.startswith("en"), f"Expected a CID starting with 'en', got: {cid}" + + # Delete by CID + cli(["catalog", "delete-by-id", "--id", cid]) + + # Verify it's gone + response = cli(["catalog", "details", "-t", "cli-test-delete-by-id-entity"], ReturnType.RAW) + assert "HTTP Error 404:" in response.stdout, "Entity should be deleted (404 expected)" From e84d7098d01bd983bc81a3d1903ffe66eaa1252b Mon Sep 17 00:00:00 2001 From: Jeff Schnitter Date: Mon, 12 Oct 2026 08:35:35 -0700 Subject: [PATCH 3/3] feat: support file input and batching in catalog delete-by-id - Accepts --file/-f with one CID per line as an alternative to --id/-i flags - Hidden --batch-size param (default 100) controls API call batching for large files - Validates that exactly one of --id or --file is provided Co-Authored-By: Claude Sonnet 4.6 --- cortexapps_cli/commands/catalog.py | 19 +++++- .../run-time/catalog-delete-by-id-entity.yaml | 5 -- tests/test_catalog_delete_by_id.py | 66 +++++++++++++++---- 3 files changed, 70 insertions(+), 20 deletions(-) delete mode 100644 data/run-time/catalog-delete-by-id-entity.yaml diff --git a/cortexapps_cli/commands/catalog.py b/cortexapps_cli/commands/catalog.py index da8d1392..26d73f00 100644 --- a/cortexapps_cli/commands/catalog.py +++ b/cortexapps_cli/commands/catalog.py @@ -125,14 +125,27 @@ def delete( @app.command() def delete_by_id( ctx: typer.Context, - ids: Optional[List[str]] = typer.Option(..., "--id", "-i", help="CID of an entity to delete (e.g. en2da8159dbeefb974). Can be specified multiple times.", show_default=False), + ids: Optional[List[str]] = typer.Option(None, "--id", "-i", help="CID of an entity to delete (e.g. en2da8159dbeefb974). Can be specified multiple times.", show_default=False), + file_input: Annotated[Optional[typer.FileText], typer.Option("--file", "-f", help="File containing one CID per line; can be passed as stdin with -, example: -f-")] = None, + batch_size: int = typer.Option(100, "--batch-size", hidden=True, help="Number of IDs per API call when processing file input"), ): """ - Delete entities by CID. Accepts one or more --id flags. + Delete entities by CID. Accepts one or more --id flags, or a file with one CID per line. """ + if ids and file_input: + raise typer.BadParameter("Specify either --id or --file, not both") + + if not ids and not file_input: + raise typer.BadParameter("One of --id or --file is required") + client = ctx.obj["client"] - client.delete("api/v1/catalog", params={"ids": ids}) + if file_input: + all_ids = [line.strip() for line in file_input if line.strip()] + for i in range(0, len(all_ids), batch_size): + client.delete("api/v1/catalog", params={"ids": all_ids[i:i + batch_size]}) + else: + client.delete("api/v1/catalog", params={"ids": ids}) @app.command() def descriptor( diff --git a/data/run-time/catalog-delete-by-id-entity.yaml b/data/run-time/catalog-delete-by-id-entity.yaml deleted file mode 100644 index 57980df8..00000000 --- a/data/run-time/catalog-delete-by-id-entity.yaml +++ /dev/null @@ -1,5 +0,0 @@ -openapi: 3.0.1 -info: - title: Delete By ID Test Entity - x-cortex-tag: cli-test-delete-by-id-entity - x-cortex-type: service diff --git a/tests/test_catalog_delete_by_id.py b/tests/test_catalog_delete_by_id.py index 93b17a8e..5b12343d 100644 --- a/tests/test_catalog_delete_by_id.py +++ b/tests/test_catalog_delete_by_id.py @@ -1,17 +1,59 @@ from tests.helpers.utils import * +import tempfile +import os -def test(): - # Create an entity - cli(["catalog", "create", "-f", "data/run-time/catalog-delete-by-id-entity.yaml"]) +def _create_entity(tag): + content = f"""openapi: 3.0.1 +info: + title: Delete By ID Test Entity + x-cortex-tag: {tag} + x-cortex-type: service +""" + with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: + f.write(content) + return f.name - # Get its CID - response = cli(["catalog", "details", "-t", "cli-test-delete-by-id-entity"]) - cid = response['id'] - assert cid.startswith("en"), f"Expected a CID starting with 'en', got: {cid}" +def test_delete_by_id_flag(): + tag = "cli-test-delete-by-id-flag" + path = _create_entity(tag) + try: + cli(["catalog", "create", "-f", path]) - # Delete by CID - cli(["catalog", "delete-by-id", "--id", cid]) + response = cli(["catalog", "details", "-t", tag]) + cid = response['id'] + assert cid.startswith("en"), f"Expected a CID starting with 'en', got: {cid}" - # Verify it's gone - response = cli(["catalog", "details", "-t", "cli-test-delete-by-id-entity"], ReturnType.RAW) - assert "HTTP Error 404:" in response.stdout, "Entity should be deleted (404 expected)" + cli(["catalog", "delete-by-id", "--id", cid]) + + response = cli(["catalog", "details", "-t", tag], ReturnType.RAW) + assert "HTTP Error 404:" in response.stdout, "Entity should be deleted (404 expected)" + finally: + os.unlink(path) + +def test_delete_by_id_file(): + tag1 = "cli-test-delete-by-id-file-1" + tag2 = "cli-test-delete-by-id-file-2" + path1 = _create_entity(tag1) + path2 = _create_entity(tag2) + try: + cli(["catalog", "create", "-f", path1]) + cli(["catalog", "create", "-f", path2]) + + cid1 = cli(["catalog", "details", "-t", tag1])['id'] + cid2 = cli(["catalog", "details", "-t", tag2])['id'] + + with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f: + f.write(f"{cid1}\n{cid2}\n") + ids_file = f.name + + try: + cli(["catalog", "delete-by-id", "--file", ids_file]) + + for tag in [tag1, tag2]: + response = cli(["catalog", "details", "-t", tag], ReturnType.RAW) + assert "HTTP Error 404:" in response.stdout, f"Entity {tag} should be deleted (404 expected)" + finally: + os.unlink(ids_file) + finally: + os.unlink(path1) + os.unlink(path2)