Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ rh-envault store list
rh-envault store list --prefix /production/
rh-envault store get DB_PASSWORD --store my-vault
rh-envault store set DB_PASSWORD new_value --store my-vault
rh-envault store delete DB_PASSWORD --store my-vault
```

### `rh-envault audit`
Expand Down
21 changes: 21 additions & 0 deletions src/envault/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,27 @@ def store_set(
console.print(f"[green]✓[/green] Set {key}")


@store_app.command("delete")
def store_delete(
key: str = typer.Argument(..., help="Key to delete"),
store_name: str | None = typer.Option(None, "--store", "-s", help="Store name from config"),
config_path: str = typer.Option("", "--config", "-c", help="Config file path"),
):
"""Delete a secret from a secret store."""
config = load_config(config_path)

if store_name and store_name in config.stores:
store_instance = get_store(config.stores[store_name])
else:
store_instance = get_store(config_path)

if store_instance.delete(key):
console.print(f"[green]✓[/green] Deleted {key}")
else:
err_console.print(f"[red]Error:[/red] Key '{key}' not found in store")
raise typer.Exit(1)


# ── Encrypt / Decrypt ──────────────────────────────────────────────────────────

@app.command()
Expand Down
74 changes: 74 additions & 0 deletions tests/test_envault.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,80 @@ def test_store_factory_unknown():
get_store(config)


# ── Store Delete CLI Tests ──────────────────────────────────────────────────


def test_cli_store_delete_ok(tmp_path):
"""store delete exits 0 and removes the key via config'd store."""
from typer.testing import CliRunner
from envault.cli import app
import yaml

# Create valid .envault.yml with a local store pointing at our env file
env_file = tmp_path / ".env"
env_file.write_text("MY_KEY=my_value\nOTHER=keep\n")

envault_config = {
"project": "test",
"stores": {
"local": {
"type": "local",
"path_prefix": str(env_file),
}
}
}
config_path = tmp_path / ".envault.yml"
with open(config_path, "w") as f:
yaml.dump(envault_config, f)

runner = CliRunner()
result = runner.invoke(app, [
"store", "delete", "MY_KEY",
"--store", "local",
"-c", str(config_path),
])
assert result.exit_code == 0, f"stdout: {result.stdout}"
assert "Deleted" in result.stdout

# Verify key was actually deleted
from envault.stores import LocalEnvStore
store = LocalEnvStore(str(env_file))
assert store.get("MY_KEY") is None
assert store.get("OTHER") == "keep"


def test_cli_store_delete_not_found(tmp_path):
"""store delete exits 1 when key doesn't exist."""
from typer.testing import CliRunner
from envault.cli import app
import yaml

env_file = tmp_path / ".env"
env_file.write_text("OTHER=value\n")

envault_config = {
"project": "test",
"stores": {
"local": {
"type": "local",
"path_prefix": str(env_file),
}
}
}
config_path = tmp_path / ".envault.yml"
with open(config_path, "w") as f:
yaml.dump(envault_config, f)

runner = CliRunner()
result = runner.invoke(app, [
"store", "delete", "NONEXISTENT",
"--store", "local",
"-c", str(config_path),
])
assert result.exit_code == 1
assert "not found" in result.output.lower() or "Error" in result.output


# ── Encrypt / Decrypt ───────────────────────────────────────────────────────


Expand Down