Skip to content

Commit 3eb8d62

Browse files
Merge branch 'main' into 163-rate-limiting-fix
2 parents e26121b + 70391c0 commit 3eb8d62

File tree

7 files changed

+203
-302
lines changed

7 files changed

+203
-302
lines changed

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
5454
- name: Generate HISTORY.md
5555
run: |
56-
git-changelog > HISTORY.md
56+
git-changelog -c angular > HISTORY.md
5757
cat HISTORY.md
5858
5959
- name: Commit and Push

HISTORY.md

Lines changed: 44 additions & 301 deletions
Large diffs are not rendered by default.

cortexapps_cli/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import cortexapps_cli.commands.rest as rest
3939
import cortexapps_cli.commands.scim as scim
4040
import cortexapps_cli.commands.scorecards as scorecards
41+
import cortexapps_cli.commands.secrets as secrets
4142
import cortexapps_cli.commands.teams as teams
4243
import cortexapps_cli.commands.workflows as workflows
4344

@@ -74,6 +75,7 @@
7475
app.add_typer(rest.app, name="rest")
7576
app.add_typer(scim.app, name="scim")
7677
app.add_typer(scorecards.app, name="scorecards")
78+
app.add_typer(secrets.app, name="secrets")
7779
app.add_typer(teams.app, name="teams")
7880
app.add_typer(workflows.app, name="workflows")
7981

cortexapps_cli/commands/secrets.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import typer
2+
import json
3+
from typing_extensions import Annotated
4+
from cortexapps_cli.utils import print_output_with_context
5+
from cortexapps_cli.command_options import ListCommandOptions
6+
7+
app = typer.Typer(
8+
help="Secrets commands",
9+
no_args_is_help=True
10+
)
11+
12+
@app.command()
13+
def list(
14+
ctx: typer.Context,
15+
page: ListCommandOptions.page = None,
16+
page_size: ListCommandOptions.page_size = 250,
17+
table_output: ListCommandOptions.table_output = False,
18+
csv_output: ListCommandOptions.csv_output = False,
19+
columns: ListCommandOptions.columns = [],
20+
no_headers: ListCommandOptions.no_headers = False,
21+
filters: ListCommandOptions.filters = [],
22+
sort: ListCommandOptions.sort = [],
23+
):
24+
"""
25+
List secrets
26+
"""
27+
client = ctx.obj["client"]
28+
29+
params = {
30+
"page": page,
31+
"pageSize": page_size
32+
}
33+
34+
if (table_output or csv_output) and not ctx.params.get('columns'):
35+
ctx.params['columns'] = [
36+
"ID=id",
37+
"Name=name",
38+
"Tag=tag",
39+
]
40+
41+
# remove any params that are None
42+
params = {k: v for k, v in params.items() if v is not None}
43+
44+
if page is None:
45+
r = client.fetch("api/v1/secrets", params=params)
46+
else:
47+
r = client.get("api/v1/secrets", params=params)
48+
print_output_with_context(ctx, r)
49+
50+
@app.command()
51+
def get(
52+
ctx: typer.Context,
53+
tag_or_id: str = typer.Option(..., "--tag-or-id", "-t", help="Secret tag or ID"),
54+
):
55+
"""
56+
Get a secret by tag or ID
57+
"""
58+
client = ctx.obj["client"]
59+
r = client.get(f"api/v1/secrets/{tag_or_id}")
60+
print_output_with_context(ctx, r)
61+
62+
@app.command()
63+
def create(
64+
ctx: typer.Context,
65+
file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="File containing secret definition (name, secret, tag); can be passed as stdin with -, example: -f-")] = ...,
66+
):
67+
"""
68+
Create a secret
69+
70+
Provide a JSON file with the secret definition including required fields:
71+
- name: human-readable label for the secret
72+
- secret: the actual secret value
73+
- tag: unique identifier for the secret
74+
"""
75+
client = ctx.obj["client"]
76+
data = json.loads("".join([line for line in file_input]))
77+
r = client.post("api/v1/secrets", data=data)
78+
print_output_with_context(ctx, r)
79+
80+
@app.command()
81+
def update(
82+
ctx: typer.Context,
83+
tag_or_id: str = typer.Option(..., "--tag-or-id", "-t", help="Secret tag or ID"),
84+
file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="File containing fields to update (name, secret); can be passed as stdin with -, example: -f-")] = ...,
85+
):
86+
"""
87+
Update a secret
88+
89+
Provide a JSON file with the fields to update (name and/or secret are optional).
90+
"""
91+
client = ctx.obj["client"]
92+
data = json.loads("".join([line for line in file_input]))
93+
r = client.put(f"api/v1/secrets/{tag_or_id}", data=data)
94+
print_output_with_context(ctx, r)
95+
96+
@app.command()
97+
def delete(
98+
ctx: typer.Context,
99+
tag_or_id: str = typer.Option(..., "--tag-or-id", "-t", help="Secret tag or ID"),
100+
):
101+
"""
102+
Delete a secret
103+
"""
104+
client = ctx.obj["client"]
105+
client.delete(f"api/v1/secrets/{tag_or_id}")

data/run-time/secret-create.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"tag": "cli_test_secret",
3+
"name": "CLI Test Secret",
4+
"secret": "test-secret-value-12345"
5+
}

data/run-time/secret-update.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "Updated CLI Test Secret",
3+
"secret": "updated-secret-value-67890"
4+
}

tests/test_secrets.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from tests.helpers.utils import *
2+
import pytest
3+
4+
def test():
5+
# Skip test if API key doesn't have secrets permissions
6+
# The Secrets API may require special permissions or may not be available in all environments
7+
try:
8+
# Try to list secrets first to check if we have permission
9+
response = cli(["secrets", "list"], return_type=ReturnType.RAW)
10+
if response.exit_code != 0 and "403" in response.stdout:
11+
pytest.skip("API key does not have permission to access Secrets API")
12+
except Exception as e:
13+
if "403" in str(e) or "Forbidden" in str(e):
14+
pytest.skip("API key does not have permission to access Secrets API")
15+
16+
# Create a secret
17+
response = cli(["secrets", "create", "-f", "data/run-time/secret-create.json"])
18+
assert response['tag'] == 'cli_test_secret', "Should create secret with tag cli_test_secret"
19+
assert response['name'] == 'CLI Test Secret', "Should have correct name"
20+
21+
# List secrets and verify it exists
22+
response = cli(["secrets", "list"])
23+
assert any(secret['tag'] == 'cli_test_secret' for secret in response['secrets']), "Should find secret with tag cli_test_secret"
24+
25+
# Get the secret
26+
response = cli(["secrets", "get", "-t", "cli_test_secret"])
27+
assert response['tag'] == 'cli_test_secret', "Should get secret with correct tag"
28+
assert response['name'] == 'CLI Test Secret', "Should have correct name"
29+
30+
# Update the secret
31+
cli(["secrets", "update", "-t", "cli_test_secret", "-f", "data/run-time/secret-update.json"])
32+
33+
# Verify the update
34+
response = cli(["secrets", "get", "-t", "cli_test_secret"])
35+
assert response['name'] == 'Updated CLI Test Secret', "Should have updated name"
36+
37+
# Delete the secret
38+
cli(["secrets", "delete", "-t", "cli_test_secret"])
39+
40+
# Verify deletion by checking list
41+
response = cli(["secrets", "list"])
42+
assert not any(secret['tag'] == 'cli_test_secret' for secret in response['secrets']), "Should not find deleted secret"

0 commit comments

Comments
 (0)