tangle-cli 0.0.1a1__py3-none-any.whl
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.
- tangle_cli/__init__.py +19 -0
- tangle_cli/api_cli.py +787 -0
- tangle_cli/api_schema.py +633 -0
- tangle_cli/api_transport.py +461 -0
- tangle_cli/args_container.py +244 -0
- tangle_cli/artifacts.py +293 -0
- tangle_cli/artifacts_cli.py +108 -0
- tangle_cli/cli.py +57 -0
- tangle_cli/cli_helpers.py +116 -0
- tangle_cli/cli_options.py +52 -0
- tangle_cli/client.py +677 -0
- tangle_cli/component_from_func.py +1856 -0
- tangle_cli/component_generator.py +298 -0
- tangle_cli/component_inspector.py +494 -0
- tangle_cli/component_publisher.py +921 -0
- tangle_cli/components_cli.py +269 -0
- tangle_cli/dynamic_discovery_client.py +296 -0
- tangle_cli/generated_model_extensions.py +405 -0
- tangle_cli/generated_runtime.py +43 -0
- tangle_cli/handler.py +96 -0
- tangle_cli/hydration_trust.py +222 -0
- tangle_cli/logger.py +166 -0
- tangle_cli/models.py +407 -0
- tangle_cli/module_bundler.py +662 -0
- tangle_cli/openapi/__init__.py +0 -0
- tangle_cli/openapi/codegen.py +1090 -0
- tangle_cli/openapi/parser.py +77 -0
- tangle_cli/pipeline_dehydrator.py +720 -0
- tangle_cli/pipeline_hydrator.py +1785 -0
- tangle_cli/pipeline_run_annotations.py +41 -0
- tangle_cli/pipeline_run_details.py +203 -0
- tangle_cli/pipeline_run_manager.py +1994 -0
- tangle_cli/pipeline_run_search.py +712 -0
- tangle_cli/pipeline_runner.py +620 -0
- tangle_cli/pipeline_runs_cli.py +584 -0
- tangle_cli/pipelines.py +581 -0
- tangle_cli/pipelines_cli.py +271 -0
- tangle_cli/published_components_cli.py +373 -0
- tangle_cli/py.typed +0 -0
- tangle_cli/quickstart.py +110 -0
- tangle_cli/secrets.py +156 -0
- tangle_cli/secrets_cli.py +269 -0
- tangle_cli/utils.py +942 -0
- tangle_cli/version_manager.py +470 -0
- tangle_cli-0.0.1a1.dist-info/METADATA +561 -0
- tangle_cli-0.0.1a1.dist-info/RECORD +48 -0
- tangle_cli-0.0.1a1.dist-info/WHEEL +4 -0
- tangle_cli-0.0.1a1.dist-info/entry_points.txt +3 -0
tangle_cli/quickstart.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Native-free quickstart text for the root ``tangle`` CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from textwrap import dedent
|
|
6
|
+
|
|
7
|
+
from cyclopts import App
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
app = App(name="quickstart", help="Print a concise native-free guide to the Tangle CLI.")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
QUICKSTART_TEXT = dedent(
|
|
14
|
+
"""
|
|
15
|
+
Tangle CLI quickstart
|
|
16
|
+
=====================
|
|
17
|
+
|
|
18
|
+
Command families
|
|
19
|
+
----------------
|
|
20
|
+
tangle api ...
|
|
21
|
+
Pure OpenAPI wrappers around a Tangle API. Commands are generated from
|
|
22
|
+
the checked-in official schema and can be extended from a live backend
|
|
23
|
+
schema cache. Use these when you want a direct backend endpoint call.
|
|
24
|
+
|
|
25
|
+
tangle sdk ...
|
|
26
|
+
Hand-written SDK commands for local workflows and compound operations.
|
|
27
|
+
Some commands are local-only (for example pipeline validation/layout and
|
|
28
|
+
component generation); others call the API through the generated client
|
|
29
|
+
while adding domain behavior such as hydration, submit payload shaping,
|
|
30
|
+
version checks, or config batching.
|
|
31
|
+
|
|
32
|
+
Common flags and environment
|
|
33
|
+
----------------------------
|
|
34
|
+
API-backed commands commonly accept:
|
|
35
|
+
--base-url URL API base URL (or TANGLE_API_URL)
|
|
36
|
+
--token TOKEN bearer token (or TANGLE_API_TOKEN)
|
|
37
|
+
--auth-header VALUE full Authorization value, e.g. 'Basic ...' or
|
|
38
|
+
'Bearer ...' (or TANGLE_API_AUTH_HEADER /
|
|
39
|
+
TANGLE_AUTH_HEADER)
|
|
40
|
+
-H, --header 'N: V' extra headers; repeatable (or TANGLE_API_HEADERS)
|
|
41
|
+
--config PATH YAML/JSON defaults; CLI values win over config
|
|
42
|
+
--log-type TYPE progress logs: console, none, file (SDK commands)
|
|
43
|
+
|
|
44
|
+
TANGLE_VERBOSE=1 enables redacted HTTP request/response diagnostics on
|
|
45
|
+
stderr. It is separate from normal progress logging and should not be
|
|
46
|
+
required for routine hydration/publish progress.
|
|
47
|
+
|
|
48
|
+
Protected API examples
|
|
49
|
+
----------------------
|
|
50
|
+
tangle api refresh --base-url https://api.example \\
|
|
51
|
+
--auth-header 'Bearer ...' -H 'X-Gateway-Auth: ...'
|
|
52
|
+
|
|
53
|
+
tangle api pipeline-runs list --base-url https://api.example \\
|
|
54
|
+
--auth-header 'Basic ...' -H 'X-Api-Key: ...'
|
|
55
|
+
|
|
56
|
+
tangle sdk pipeline-runs submit pipeline.yaml --base-url https://api.example \\
|
|
57
|
+
--auth-header 'Bearer ...' --log-type console
|
|
58
|
+
|
|
59
|
+
Local SDK examples
|
|
60
|
+
------------------
|
|
61
|
+
tangle sdk pipelines validate pipeline.yaml
|
|
62
|
+
tangle sdk pipelines hydrate pipeline.yaml --output hydrated.yaml
|
|
63
|
+
tangle sdk components generate from-python component.py --image python:3.12
|
|
64
|
+
tangle sdk components bump-version component.yaml
|
|
65
|
+
|
|
66
|
+
API-backed SDK examples
|
|
67
|
+
-----------------------
|
|
68
|
+
tangle sdk published-components search transformer --base-url https://api.example
|
|
69
|
+
tangle sdk published-components publish component.yaml --dry-run
|
|
70
|
+
tangle sdk pipeline-runs submit pipeline.yaml --dry-run --log-type none
|
|
71
|
+
tangle sdk pipeline-runs status RUN_ID --base-url https://api.example
|
|
72
|
+
|
|
73
|
+
Generated vs hand-written packages
|
|
74
|
+
----------------------------------
|
|
75
|
+
tangle_cli is the hand-written package: CLI wiring, local SDK workflows,
|
|
76
|
+
dynamic schema discovery, codegen, logging, hydrator/resolver logic, and
|
|
77
|
+
extension hooks.
|
|
78
|
+
|
|
79
|
+
tangle_api is the generated/native package: checked-in Pydantic models,
|
|
80
|
+
endpoint operation methods, and the official OpenAPI snapshot. Local-only
|
|
81
|
+
SDK commands and this quickstart do not need it. Static API-backed commands
|
|
82
|
+
need tangle-cli[native] or an equivalent local tangle_api.generated package.
|
|
83
|
+
|
|
84
|
+
Generated model extensions use private generated bases plus stable public
|
|
85
|
+
subclasses, e.g. ComponentSpec(ComponentSpecExtensions,
|
|
86
|
+
_ComponentSpecGenerated). Extension bases are left of the generated base in
|
|
87
|
+
the MRO, and downstream --model-extension-module values can add/override
|
|
88
|
+
behavior while preserving generated fields and stable names.
|
|
89
|
+
|
|
90
|
+
Discover more
|
|
91
|
+
-------------
|
|
92
|
+
tangle --help
|
|
93
|
+
tangle api --help
|
|
94
|
+
tangle api refresh --help
|
|
95
|
+
tangle sdk --help
|
|
96
|
+
tangle sdk pipelines --help
|
|
97
|
+
tangle sdk pipeline-runs submit --help
|
|
98
|
+
|
|
99
|
+
See README.md for codegen/autogen instructions and extension surfaces:
|
|
100
|
+
hydrator resolvers, PipelineRunHooks, ComponentPublishHook, and shared CLI
|
|
101
|
+
options/logging helpers.
|
|
102
|
+
"""
|
|
103
|
+
).strip()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.default
|
|
107
|
+
def quickstart() -> None:
|
|
108
|
+
"""Print a concise native-free guide to the Tangle CLI."""
|
|
109
|
+
|
|
110
|
+
print(QUICKSTART_TEXT)
|
tangle_cli/secrets.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Read/write helpers for Tangle secret metadata and values.
|
|
2
|
+
|
|
3
|
+
Secret values are accepted only for explicit create/update operations and are
|
|
4
|
+
never included in returned metadata dictionaries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import Any, Protocol
|
|
11
|
+
|
|
12
|
+
from .handler import TangleCliHandler
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SecretClient(Protocol):
|
|
16
|
+
"""Subset of the generated static client used by secret commands."""
|
|
17
|
+
|
|
18
|
+
def secrets_list(self) -> Any: ...
|
|
19
|
+
|
|
20
|
+
def secrets_create(
|
|
21
|
+
self,
|
|
22
|
+
secret_name: str,
|
|
23
|
+
secret_value: str,
|
|
24
|
+
description: str | None = None,
|
|
25
|
+
expires_at: str | None = None,
|
|
26
|
+
) -> Any: ...
|
|
27
|
+
|
|
28
|
+
def secrets_update(
|
|
29
|
+
self,
|
|
30
|
+
secret_name: str,
|
|
31
|
+
secret_value: str,
|
|
32
|
+
description: str | None = None,
|
|
33
|
+
expires_at: str | None = None,
|
|
34
|
+
) -> Any: ...
|
|
35
|
+
|
|
36
|
+
def secrets_delete(self, secret_name: str) -> Any: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SecretValueError(ValueError):
|
|
40
|
+
"""Raised when secret value CLI/config inputs are invalid."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SecretsManager(TangleCliHandler):
|
|
44
|
+
"""Secret resource manager with injectable client construction.
|
|
45
|
+
|
|
46
|
+
Downstream packages can inject an authenticated client directly or provide a
|
|
47
|
+
lazy ``client_factory``. Returned dictionaries intentionally omit secret
|
|
48
|
+
values and only include metadata.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
client: SecretClient | None = None,
|
|
54
|
+
*,
|
|
55
|
+
client_factory: Any | None = None,
|
|
56
|
+
logger: Any | None = None,
|
|
57
|
+
**kwargs: Any,
|
|
58
|
+
) -> None:
|
|
59
|
+
super().__init__(client=client, client_factory=client_factory, logger=logger, **kwargs)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def resolve_secret_value(value: str | None, from_env: str | None) -> str:
|
|
63
|
+
"""Resolve the secret value from either ``--value`` or ``--from-env``.
|
|
64
|
+
|
|
65
|
+
Error messages intentionally mention only the option/env-var name and
|
|
66
|
+
never include the secret value.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
if value is not None and from_env is not None:
|
|
70
|
+
raise SecretValueError("specify either --value or --from-env, not both")
|
|
71
|
+
if from_env is not None:
|
|
72
|
+
resolved = os.environ.get(from_env)
|
|
73
|
+
if resolved is None:
|
|
74
|
+
raise SecretValueError(f"environment variable '{from_env}' is not set")
|
|
75
|
+
return resolved
|
|
76
|
+
if value is not None:
|
|
77
|
+
return value
|
|
78
|
+
raise SecretValueError("either --value or --from-env is required")
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def secret_metadata(secret: Any) -> dict[str, Any]:
|
|
82
|
+
"""Return JSON-safe secret metadata, excluding any secret value fields."""
|
|
83
|
+
|
|
84
|
+
entry: dict[str, Any] = {}
|
|
85
|
+
for field in ("secret_name", "created_at", "updated_at", "expires_at", "description"):
|
|
86
|
+
value = _value_from_mapping_or_object(secret, field)
|
|
87
|
+
if value is not None:
|
|
88
|
+
entry[field] = str(value)
|
|
89
|
+
return entry
|
|
90
|
+
|
|
91
|
+
def list(self) -> dict[str, Any]:
|
|
92
|
+
"""List secret metadata without exposing secret values."""
|
|
93
|
+
|
|
94
|
+
response = self._require_client().secrets_list()
|
|
95
|
+
raw_secrets = _value_from_mapping_or_object(response, "secrets", []) or []
|
|
96
|
+
secrets = [self.secret_metadata(secret) for secret in raw_secrets]
|
|
97
|
+
return {"status": "success", "count": len(secrets), "secrets": secrets}
|
|
98
|
+
|
|
99
|
+
def create(
|
|
100
|
+
self,
|
|
101
|
+
secret_name: str,
|
|
102
|
+
*,
|
|
103
|
+
value: str | None = None,
|
|
104
|
+
from_env: str | None = None,
|
|
105
|
+
description: str | None = None,
|
|
106
|
+
expires_at: str | None = None,
|
|
107
|
+
) -> dict[str, Any]:
|
|
108
|
+
"""Create a secret using generated static API operations."""
|
|
109
|
+
|
|
110
|
+
secret_value = self.resolve_secret_value(value, from_env)
|
|
111
|
+
secret = self._require_client().secrets_create(
|
|
112
|
+
secret_name,
|
|
113
|
+
secret_value,
|
|
114
|
+
description=description,
|
|
115
|
+
expires_at=expires_at,
|
|
116
|
+
)
|
|
117
|
+
return {"status": "success", "action": "created", "secret": self.secret_metadata(secret)}
|
|
118
|
+
|
|
119
|
+
def update(
|
|
120
|
+
self,
|
|
121
|
+
secret_name: str,
|
|
122
|
+
*,
|
|
123
|
+
value: str | None = None,
|
|
124
|
+
from_env: str | None = None,
|
|
125
|
+
description: str | None = None,
|
|
126
|
+
expires_at: str | None = None,
|
|
127
|
+
) -> dict[str, Any]:
|
|
128
|
+
"""Update a secret using generated static API operations."""
|
|
129
|
+
|
|
130
|
+
secret_value = self.resolve_secret_value(value, from_env)
|
|
131
|
+
secret = self._require_client().secrets_update(
|
|
132
|
+
secret_name,
|
|
133
|
+
secret_value,
|
|
134
|
+
description=description,
|
|
135
|
+
expires_at=expires_at,
|
|
136
|
+
)
|
|
137
|
+
return {"status": "success", "action": "updated", "secret": self.secret_metadata(secret)}
|
|
138
|
+
|
|
139
|
+
def delete(self, secret_name: str) -> dict[str, Any]:
|
|
140
|
+
"""Delete a secret using generated static API operations."""
|
|
141
|
+
|
|
142
|
+
self._require_client().secrets_delete(secret_name)
|
|
143
|
+
return {"status": "success", "action": "deleted", "secret_name": secret_name}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _value_from_mapping_or_object(value: Any, key: str, default: Any = None) -> Any:
|
|
147
|
+
if isinstance(value, dict):
|
|
148
|
+
return value.get(key, default)
|
|
149
|
+
return getattr(value, key, default)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
__all__ = [
|
|
153
|
+
"SecretClient",
|
|
154
|
+
"SecretValueError",
|
|
155
|
+
"SecretsManager",
|
|
156
|
+
]
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""`tangle sdk secrets` command implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Annotated, Any, Callable
|
|
7
|
+
|
|
8
|
+
from cyclopts import App, Parameter
|
|
9
|
+
|
|
10
|
+
from .args_container import ArgsContainer
|
|
11
|
+
from .cli_helpers import (
|
|
12
|
+
LazyTangleApiClient,
|
|
13
|
+
api_arg_specs,
|
|
14
|
+
include_env_credentials_for_args,
|
|
15
|
+
load_args_or_exit,
|
|
16
|
+
print_json,
|
|
17
|
+
)
|
|
18
|
+
from .cli_options import (
|
|
19
|
+
AuthHeaderOption,
|
|
20
|
+
BaseUrlOption,
|
|
21
|
+
ConfigOption,
|
|
22
|
+
HeaderOption,
|
|
23
|
+
LogTypeOption,
|
|
24
|
+
TokenOption,
|
|
25
|
+
)
|
|
26
|
+
from .logger import Logger, logger_for_log_type
|
|
27
|
+
from .secrets import SecretsManager, SecretValueError
|
|
28
|
+
|
|
29
|
+
ValueOption = Annotated[
|
|
30
|
+
str | None,
|
|
31
|
+
Parameter(
|
|
32
|
+
name="--value",
|
|
33
|
+
alias="-v",
|
|
34
|
+
help="Secret value. Prefer --from-env to avoid shell history exposure.",
|
|
35
|
+
),
|
|
36
|
+
]
|
|
37
|
+
FromEnvOption = Annotated[
|
|
38
|
+
str | None,
|
|
39
|
+
Parameter(
|
|
40
|
+
name="--from-env",
|
|
41
|
+
alias="-e",
|
|
42
|
+
help="Read secret value from this environment variable.",
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
DescriptionOption = Annotated[
|
|
46
|
+
str | None,
|
|
47
|
+
Parameter(name="--description", alias="-d", help="Secret description."),
|
|
48
|
+
]
|
|
49
|
+
ExpiresAtOption = Annotated[
|
|
50
|
+
str | None,
|
|
51
|
+
Parameter(help="Expiration datetime (ISO 8601)."),
|
|
52
|
+
]
|
|
53
|
+
ForceOption = Annotated[
|
|
54
|
+
bool,
|
|
55
|
+
Parameter(help="Skip confirmation prompt."),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
app = App(name="secrets", help="Manage Tangle secrets.")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _client(args: ArgsContainer, *, cli_base_url: str | None, command_name: str) -> LazyTangleApiClient:
|
|
62
|
+
return LazyTangleApiClient(
|
|
63
|
+
base_url=args.base_url,
|
|
64
|
+
token=args.token,
|
|
65
|
+
auth_header=args.auth_header,
|
|
66
|
+
header=args.header,
|
|
67
|
+
include_env_credentials=include_env_credentials_for_args(args, cli_base_url),
|
|
68
|
+
command_name=command_name,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _run_secret_action(
|
|
73
|
+
config: str | None,
|
|
74
|
+
cli_base_url: str | None,
|
|
75
|
+
specs: dict[str, tuple[Any, ...]],
|
|
76
|
+
fn: Callable[[Any, ArgsContainer, Logger], dict[str, Any]],
|
|
77
|
+
) -> None:
|
|
78
|
+
results: list[dict[str, Any]] = []
|
|
79
|
+
for args in load_args_or_exit(config, **specs):
|
|
80
|
+
logger, finalize_logs = logger_for_log_type(getattr(args, "log_type", "console"))
|
|
81
|
+
try:
|
|
82
|
+
client = _client(args, cli_base_url=cli_base_url, command_name="secret commands")
|
|
83
|
+
try:
|
|
84
|
+
results.append(fn(client, args, logger))
|
|
85
|
+
except SecretValueError as exc:
|
|
86
|
+
raise SystemExit(str(exc)) from exc
|
|
87
|
+
finally:
|
|
88
|
+
finalize_logs()
|
|
89
|
+
|
|
90
|
+
print_json(results[0] if len(results) == 1 else {"status": "success", "results": results})
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _secret_mutation_specs(
|
|
94
|
+
*,
|
|
95
|
+
secret_name: str | None,
|
|
96
|
+
value: str | None,
|
|
97
|
+
from_env: str | None,
|
|
98
|
+
description: str | None,
|
|
99
|
+
expires_at: str | None,
|
|
100
|
+
base_url: str | None,
|
|
101
|
+
token: str | None,
|
|
102
|
+
auth_header: str | None,
|
|
103
|
+
header: list[str] | None,
|
|
104
|
+
log_type: str,
|
|
105
|
+
) -> dict[str, tuple[Any, ...]]:
|
|
106
|
+
return {
|
|
107
|
+
"secret_name": ("secret_name", secret_name, None, False, True),
|
|
108
|
+
"value": (value, None),
|
|
109
|
+
"from_env": (from_env, None),
|
|
110
|
+
"description": (description, None),
|
|
111
|
+
"expires_at": (expires_at, None),
|
|
112
|
+
"log_type": (log_type, "console"),
|
|
113
|
+
**api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _confirm_delete(secret_name: str) -> None:
|
|
118
|
+
prompt = f"Are you sure you want to delete secret '{secret_name}'? [y/N]: "
|
|
119
|
+
print(prompt, end="", file=sys.stderr, flush=True)
|
|
120
|
+
try:
|
|
121
|
+
response = input()
|
|
122
|
+
except EOFError as exc: # pragma: no cover - defensive for non-interactive shells
|
|
123
|
+
raise SystemExit("Delete cancelled") from exc
|
|
124
|
+
if response.strip().lower() not in {"y", "yes"}:
|
|
125
|
+
raise SystemExit("Delete cancelled")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@app.command(name="list")
|
|
129
|
+
def secrets_list(
|
|
130
|
+
*,
|
|
131
|
+
base_url: BaseUrlOption = None,
|
|
132
|
+
token: TokenOption = None,
|
|
133
|
+
auth_header: AuthHeaderOption = None,
|
|
134
|
+
header: HeaderOption = None,
|
|
135
|
+
config: ConfigOption = None,
|
|
136
|
+
log_type: LogTypeOption = "console",
|
|
137
|
+
) -> None:
|
|
138
|
+
"""List secret metadata. Secret values are never returned."""
|
|
139
|
+
|
|
140
|
+
specs = {
|
|
141
|
+
"log_type": (log_type, "console"),
|
|
142
|
+
**api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
def action(client: Any, args: ArgsContainer, logger: Logger) -> dict[str, Any]:
|
|
146
|
+
result = SecretsManager(client=client).list()
|
|
147
|
+
logger.info(f"Listed {result['count']} secret(s).")
|
|
148
|
+
return result
|
|
149
|
+
|
|
150
|
+
_run_secret_action(config, base_url, specs, action)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@app.command(name="create")
|
|
154
|
+
def secrets_create(
|
|
155
|
+
secret_name: str | None = None,
|
|
156
|
+
*,
|
|
157
|
+
value: ValueOption = None,
|
|
158
|
+
from_env: FromEnvOption = None,
|
|
159
|
+
description: DescriptionOption = None,
|
|
160
|
+
expires_at: ExpiresAtOption = None,
|
|
161
|
+
base_url: BaseUrlOption = None,
|
|
162
|
+
token: TokenOption = None,
|
|
163
|
+
auth_header: AuthHeaderOption = None,
|
|
164
|
+
header: HeaderOption = None,
|
|
165
|
+
config: ConfigOption = None,
|
|
166
|
+
log_type: LogTypeOption = "console",
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Create a new secret."""
|
|
169
|
+
|
|
170
|
+
specs = _secret_mutation_specs(
|
|
171
|
+
secret_name=secret_name,
|
|
172
|
+
value=value,
|
|
173
|
+
from_env=from_env,
|
|
174
|
+
description=description,
|
|
175
|
+
expires_at=expires_at,
|
|
176
|
+
base_url=base_url,
|
|
177
|
+
token=token,
|
|
178
|
+
auth_header=auth_header,
|
|
179
|
+
header=header,
|
|
180
|
+
log_type=log_type,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def action(client: Any, args: ArgsContainer, logger: Logger) -> dict[str, Any]:
|
|
184
|
+
result = SecretsManager(client=client).create(
|
|
185
|
+
args.secret_name,
|
|
186
|
+
value=args.value,
|
|
187
|
+
from_env=args.from_env,
|
|
188
|
+
description=args.description,
|
|
189
|
+
expires_at=args.expires_at,
|
|
190
|
+
)
|
|
191
|
+
logger.info(f"Created secret: {args.secret_name}")
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
_run_secret_action(config, base_url, specs, action)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@app.command(name="update")
|
|
198
|
+
def secrets_update(
|
|
199
|
+
secret_name: str | None = None,
|
|
200
|
+
*,
|
|
201
|
+
value: ValueOption = None,
|
|
202
|
+
from_env: FromEnvOption = None,
|
|
203
|
+
description: DescriptionOption = None,
|
|
204
|
+
expires_at: ExpiresAtOption = None,
|
|
205
|
+
base_url: BaseUrlOption = None,
|
|
206
|
+
token: TokenOption = None,
|
|
207
|
+
auth_header: AuthHeaderOption = None,
|
|
208
|
+
header: HeaderOption = None,
|
|
209
|
+
config: ConfigOption = None,
|
|
210
|
+
log_type: LogTypeOption = "console",
|
|
211
|
+
) -> None:
|
|
212
|
+
"""Update an existing secret."""
|
|
213
|
+
|
|
214
|
+
specs = _secret_mutation_specs(
|
|
215
|
+
secret_name=secret_name,
|
|
216
|
+
value=value,
|
|
217
|
+
from_env=from_env,
|
|
218
|
+
description=description,
|
|
219
|
+
expires_at=expires_at,
|
|
220
|
+
base_url=base_url,
|
|
221
|
+
token=token,
|
|
222
|
+
auth_header=auth_header,
|
|
223
|
+
header=header,
|
|
224
|
+
log_type=log_type,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def action(client: Any, args: ArgsContainer, logger: Logger) -> dict[str, Any]:
|
|
228
|
+
result = SecretsManager(client=client).update(
|
|
229
|
+
args.secret_name,
|
|
230
|
+
value=args.value,
|
|
231
|
+
from_env=args.from_env,
|
|
232
|
+
description=args.description,
|
|
233
|
+
expires_at=args.expires_at,
|
|
234
|
+
)
|
|
235
|
+
logger.info(f"Updated secret: {args.secret_name}")
|
|
236
|
+
return result
|
|
237
|
+
|
|
238
|
+
_run_secret_action(config, base_url, specs, action)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@app.command(name="delete")
|
|
242
|
+
def secrets_delete(
|
|
243
|
+
secret_name: str | None = None,
|
|
244
|
+
*,
|
|
245
|
+
force: ForceOption = False,
|
|
246
|
+
base_url: BaseUrlOption = None,
|
|
247
|
+
token: TokenOption = None,
|
|
248
|
+
auth_header: AuthHeaderOption = None,
|
|
249
|
+
header: HeaderOption = None,
|
|
250
|
+
config: ConfigOption = None,
|
|
251
|
+
log_type: LogTypeOption = "console",
|
|
252
|
+
) -> None:
|
|
253
|
+
"""Delete a secret. Prompts for confirmation unless ``--force`` is used."""
|
|
254
|
+
|
|
255
|
+
specs = {
|
|
256
|
+
"secret_name": ("secret_name", secret_name, None, False, True),
|
|
257
|
+
"force": (force, False),
|
|
258
|
+
"log_type": (log_type, "console"),
|
|
259
|
+
**api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
def action(client: Any, args: ArgsContainer, logger: Logger) -> dict[str, Any]:
|
|
263
|
+
if not args.force:
|
|
264
|
+
_confirm_delete(args.secret_name)
|
|
265
|
+
result = SecretsManager(client=client).delete(args.secret_name)
|
|
266
|
+
logger.info(f"Deleted secret: {args.secret_name}")
|
|
267
|
+
return result
|
|
268
|
+
|
|
269
|
+
_run_secret_action(config, base_url, specs, action)
|