inthub-cli 0.1.4__tar.gz → 0.1.6__tar.gz
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.
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/PKG-INFO +1 -1
- inthub_cli-0.1.6/inthub/bulk/gcp_secrets.py +52 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/cli.py +8 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/bulk/__init__.py +8 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/bulk/manage.py +19 -8
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/bulk/progress.py +6 -1
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/bulk/src_sqlserver_sink_snowflake/__init__.py +9 -7
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/bulk/src_sqlserver_sink_snowflake/orchestrator.py +2 -2
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/connectors.py +25 -10
- inthub_cli-0.1.6/inthub/state.py +1 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/pyproject.toml +1 -1
- inthub_cli-0.1.4/inthub/bulk/gcp_secrets.py +0 -23
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/.gitignore +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/Makefile +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/README.md +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/__init__.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/__main__.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/bulk/__init__.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/bulk/csv_state.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/bulk/ddl_converter.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/bulk/snowflake_client.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/bulk/sqlserver.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/client.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/__init__.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/auth.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/bulk/src_sqlserver_sink_snowflake/connectors.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/config.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/plan.md +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/plan_bulk.md +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/tests/__init__.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/tests/bulk/__init__.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/tests/bulk/test_csv_state.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/tests/bulk/test_ddl_converter.py +0 -0
- {inthub_cli-0.1.4 → inthub_cli-0.1.6}/tests/test_config.py +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import http.client
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from google.cloud import secretmanager
|
|
6
|
+
|
|
7
|
+
from inthub import state
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _enable_gcp_debug_logging() -> None:
|
|
11
|
+
logging.basicConfig(
|
|
12
|
+
level=logging.DEBUG,
|
|
13
|
+
format="%(asctime)s %(name)s %(levelname)s %(message)s",
|
|
14
|
+
)
|
|
15
|
+
http.client.HTTPConnection.debuglevel = 2
|
|
16
|
+
for name in ("urllib3", "urllib3.connectionpool", "google.auth", "google.api_core"):
|
|
17
|
+
logging.getLogger(name).setLevel(logging.DEBUG)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _make_client() -> secretmanager.SecretManagerServiceClient:
|
|
21
|
+
if state.debug:
|
|
22
|
+
_enable_gcp_debug_logging()
|
|
23
|
+
return secretmanager.SecretManagerServiceClient(transport="rest")
|
|
24
|
+
return secretmanager.SecretManagerServiceClient()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def fetch_secret(project_id: str, secret_name: str) -> dict[str, str]:
|
|
28
|
+
client = _make_client()
|
|
29
|
+
secret_path = f"projects/{project_id}/secrets/{secret_name}"
|
|
30
|
+
versions = list(
|
|
31
|
+
client.list_secret_versions(
|
|
32
|
+
request={"parent": secret_path, "filter": "state=ENABLED"}
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
if not versions:
|
|
36
|
+
raise ValueError(f"No enabled versions found for secret '{secret_name}'")
|
|
37
|
+
latest = max(versions, key=lambda v: int(v.name.rsplit("/", 1)[-1]))
|
|
38
|
+
response = client.access_secret_version(request={"name": latest.name})
|
|
39
|
+
payload = response.payload.data.decode("UTF-8")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
data: dict[str, str] = json.loads(payload)
|
|
43
|
+
except json.JSONDecodeError as exc:
|
|
44
|
+
raise ValueError(f"Secret '{secret_name}' is not valid JSON") from exc
|
|
45
|
+
|
|
46
|
+
missing = {"username", "password"} - set(data.keys())
|
|
47
|
+
if missing:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"Secret '{secret_name}' missing required fields: {', '.join(sorted(missing))}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return {"username": data["username"], "password": data["password"]}
|
|
@@ -3,6 +3,7 @@ from importlib.metadata import version as pkg_version
|
|
|
3
3
|
|
|
4
4
|
import typer
|
|
5
5
|
|
|
6
|
+
from inthub import state
|
|
6
7
|
from inthub.commands import connectors
|
|
7
8
|
from inthub.commands.auth import login
|
|
8
9
|
from inthub.commands.bulk import app as bulk_app
|
|
@@ -15,6 +16,13 @@ app.add_typer(connectors.app, name="connectors")
|
|
|
15
16
|
app.add_typer(bulk_app, name="bulk")
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
@app.callback()
|
|
20
|
+
def _root(
|
|
21
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
22
|
+
) -> None:
|
|
23
|
+
state.debug = state.debug or debug
|
|
24
|
+
|
|
25
|
+
|
|
18
26
|
@app.command("version")
|
|
19
27
|
def version() -> None:
|
|
20
28
|
"""Show the CLI version."""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
|
|
3
|
+
from inthub import state
|
|
3
4
|
from inthub.commands.bulk import manage, progress
|
|
4
5
|
from inthub.commands.bulk.src_sqlserver_sink_snowflake import app as _src_sf_app
|
|
5
6
|
|
|
@@ -9,3 +10,10 @@ app.command("list")(manage.list_bulks)
|
|
|
9
10
|
app.command("delete")(manage.delete_bulk)
|
|
10
11
|
app.command("cleanup")(manage.cleanup)
|
|
11
12
|
app.add_typer(_src_sf_app, name="src-sqlserver-sink-snowflake")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.callback()
|
|
16
|
+
def _bulk(
|
|
17
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
18
|
+
) -> None:
|
|
19
|
+
state.debug = state.debug or debug
|
|
@@ -5,6 +5,7 @@ import typer
|
|
|
5
5
|
from rich.console import Console
|
|
6
6
|
from rich.table import Table
|
|
7
7
|
|
|
8
|
+
from inthub import state
|
|
8
9
|
from inthub.bulk.csv_state import BULK_DIR, _progress_path, _state_path, load_rows
|
|
9
10
|
|
|
10
11
|
console = Console()
|
|
@@ -36,8 +37,11 @@ def _delete_bulk(ulid: str) -> bool:
|
|
|
36
37
|
return found
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
def list_bulks(
|
|
40
|
+
def list_bulks(
|
|
41
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
42
|
+
) -> None:
|
|
40
43
|
"""List all bulk runs."""
|
|
44
|
+
state.debug = state.debug or debug
|
|
41
45
|
entries = _state_files()
|
|
42
46
|
if not entries:
|
|
43
47
|
console.print("No bulk runs found.")
|
|
@@ -53,7 +57,7 @@ def list_bulks() -> None:
|
|
|
53
57
|
table.add_column("ERROR", justify="right")
|
|
54
58
|
table.add_column("PENDING", justify="right")
|
|
55
59
|
|
|
56
|
-
for ulid,
|
|
60
|
+
for ulid, entry in entries:
|
|
57
61
|
counts: Counter[str] = Counter()
|
|
58
62
|
csv_file = _progress_path(ulid)
|
|
59
63
|
if csv_file.exists():
|
|
@@ -64,10 +68,10 @@ def list_bulks() -> None:
|
|
|
64
68
|
|
|
65
69
|
table.add_row(
|
|
66
70
|
ulid,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
entry.get("gcp_project", ""),
|
|
72
|
+
entry.get("sqlserver_source_plugin", ""),
|
|
73
|
+
entry.get("snowflake_sink_plugin", ""),
|
|
74
|
+
entry.get("tags", ""),
|
|
71
75
|
f"[green]{counts['done']}[/]" if counts["done"] else "0",
|
|
72
76
|
f"[red]{counts['error']}[/]" if counts["error"] else "0",
|
|
73
77
|
str(counts["pending"]),
|
|
@@ -76,8 +80,12 @@ def list_bulks() -> None:
|
|
|
76
80
|
console.print(table)
|
|
77
81
|
|
|
78
82
|
|
|
79
|
-
def delete_bulk(
|
|
83
|
+
def delete_bulk(
|
|
84
|
+
ulid: str = typer.Argument(..., help="ULID of the bulk run to delete."),
|
|
85
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
86
|
+
) -> None:
|
|
80
87
|
"""Delete a bulk run (CSV + state)."""
|
|
88
|
+
state.debug = state.debug or debug
|
|
81
89
|
if _delete_bulk(ulid):
|
|
82
90
|
console.print(f"Deleted bulk run [bold]{ulid}[/].")
|
|
83
91
|
else:
|
|
@@ -85,8 +93,11 @@ def delete_bulk(ulid: str = typer.Argument(..., help="ULID of the bulk run to de
|
|
|
85
93
|
raise typer.Exit(1)
|
|
86
94
|
|
|
87
95
|
|
|
88
|
-
def cleanup(
|
|
96
|
+
def cleanup(
|
|
97
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
98
|
+
) -> None:
|
|
89
99
|
"""Delete all bulk runs."""
|
|
100
|
+
state.debug = state.debug or debug
|
|
90
101
|
entries = _state_files()
|
|
91
102
|
if not entries:
|
|
92
103
|
console.print("Nothing to clean up.")
|
|
@@ -2,6 +2,7 @@ import typer
|
|
|
2
2
|
from rich.console import Console
|
|
3
3
|
from rich.table import Table
|
|
4
4
|
|
|
5
|
+
from inthub import state
|
|
5
6
|
from inthub.bulk.csv_state import _progress_path, load_rows
|
|
6
7
|
|
|
7
8
|
console = Console()
|
|
@@ -14,8 +15,12 @@ _STATUS_STYLE = {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
def progress(
|
|
18
|
+
def progress(
|
|
19
|
+
ulid: str = typer.Argument(..., help="ULID of the bulk run."),
|
|
20
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
21
|
+
) -> None:
|
|
18
22
|
"""Show the current status of a bulk provisioning run."""
|
|
23
|
+
state.debug = state.debug or debug
|
|
19
24
|
path = _progress_path(ulid)
|
|
20
25
|
if not path.exists():
|
|
21
26
|
console.print(f"[bold red]File not found:[/] {path}")
|
{inthub_cli-0.1.4 → inthub_cli-0.1.6}/inthub/commands/bulk/src_sqlserver_sink_snowflake/__init__.py
RENAMED
|
@@ -4,7 +4,7 @@ from typing import Annotated
|
|
|
4
4
|
import typer
|
|
5
5
|
from ulid import ULID
|
|
6
6
|
|
|
7
|
-
from inthub import config
|
|
7
|
+
from inthub import config, state
|
|
8
8
|
from inthub.bulk import csv_state
|
|
9
9
|
from inthub.client import InthubClient
|
|
10
10
|
from inthub.commands.bulk.src_sqlserver_sink_snowflake import orchestrator
|
|
@@ -16,12 +16,13 @@ app = typer.Typer(
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def _client() -> InthubClient:
|
|
19
|
-
return InthubClient(config.require_token())
|
|
19
|
+
return InthubClient(config.require_token(), debug=state.debug)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@app.callback(invoke_without_command=True)
|
|
23
23
|
def src_sqlserver_sink_snowflake(
|
|
24
24
|
ctx: typer.Context,
|
|
25
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
25
26
|
csv_file: Annotated[Path | None, typer.Option("--csv", help="Path to input CSV.")] = None,
|
|
26
27
|
sqlserver_source_plugin: Annotated[
|
|
27
28
|
str | None, typer.Option("--sqlserver-source-plugin")
|
|
@@ -40,6 +41,7 @@ def src_sqlserver_sink_snowflake(
|
|
|
40
41
|
] = None,
|
|
41
42
|
) -> None:
|
|
42
43
|
"""Provision SQL Server source and Snowflake sink connectors from a CSV."""
|
|
44
|
+
state.debug = state.debug or debug
|
|
43
45
|
if ctx.invoked_subcommand is not None:
|
|
44
46
|
return
|
|
45
47
|
|
|
@@ -54,7 +56,7 @@ def src_sqlserver_sink_snowflake(
|
|
|
54
56
|
|
|
55
57
|
if resume:
|
|
56
58
|
try:
|
|
57
|
-
|
|
59
|
+
saved = csv_state.load_state(resume)
|
|
58
60
|
except FileNotFoundError:
|
|
59
61
|
typer.echo(f"Error: state for ULID '{resume}' not found in ~/.inthub/.", err=True)
|
|
60
62
|
raise typer.Exit(1)
|
|
@@ -62,10 +64,10 @@ def src_sqlserver_sink_snowflake(
|
|
|
62
64
|
if not progress_path.exists():
|
|
63
65
|
typer.echo(f"Error: progress file '{progress_path}' not found.", err=True)
|
|
64
66
|
raise typer.Exit(1)
|
|
65
|
-
source_plugin =
|
|
66
|
-
sink_plugin =
|
|
67
|
-
tag_list = [t for t in
|
|
68
|
-
resolved_gcp_project =
|
|
67
|
+
source_plugin = saved["sqlserver_source_plugin"]
|
|
68
|
+
sink_plugin = saved["snowflake_sink_plugin"]
|
|
69
|
+
tag_list = [t for t in saved.get("tags", "").split(",") if t]
|
|
70
|
+
resolved_gcp_project = saved["gcp_project"]
|
|
69
71
|
else:
|
|
70
72
|
missing = [
|
|
71
73
|
name for name, val in [
|
|
@@ -143,14 +143,14 @@ def process_row(
|
|
|
143
143
|
_msg2 = "Fetching GCP secrets"
|
|
144
144
|
try:
|
|
145
145
|
ss_creds = gcp_secrets.fetch_secret(
|
|
146
|
-
gcp_project,
|
|
146
|
+
gcp_project, row["sqlserver_gcp_secret"]
|
|
147
147
|
)
|
|
148
148
|
except Exception as exc:
|
|
149
149
|
fail(2, _msg2, str(exc))
|
|
150
150
|
return
|
|
151
151
|
try:
|
|
152
152
|
sf_creds = gcp_secrets.fetch_secret(
|
|
153
|
-
gcp_project,
|
|
153
|
+
gcp_project, row["snowflake_gcp_secret"]
|
|
154
154
|
)
|
|
155
155
|
except Exception as exc:
|
|
156
156
|
fail(2, _msg2, str(exc))
|
|
@@ -8,7 +8,7 @@ import typer
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from rich.table import Table
|
|
10
10
|
|
|
11
|
-
from inthub import config
|
|
11
|
+
from inthub import config, state
|
|
12
12
|
from inthub.client import InthubClient
|
|
13
13
|
|
|
14
14
|
app = typer.Typer(help="Manage connectors.", add_completion=False)
|
|
@@ -20,8 +20,15 @@ class OutputFormat(str, Enum):
|
|
|
20
20
|
json = "json"
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
@app.callback()
|
|
24
|
+
def _connectors(
|
|
25
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
26
|
+
) -> None:
|
|
27
|
+
state.debug = state.debug or debug
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _client() -> InthubClient:
|
|
31
|
+
return InthubClient(config.require_token(), debug=state.debug)
|
|
25
32
|
|
|
26
33
|
|
|
27
34
|
@app.command("list")
|
|
@@ -34,11 +41,12 @@ def list_connectors(
|
|
|
34
41
|
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
35
42
|
) -> None:
|
|
36
43
|
"""List connectors, optionally filtered by tags."""
|
|
44
|
+
state.debug = state.debug or debug
|
|
37
45
|
path = "/connectors"
|
|
38
46
|
if tag:
|
|
39
47
|
path += "?tags=" + ",".join(tag)
|
|
40
48
|
|
|
41
|
-
body = _client(
|
|
49
|
+
body = _client().get(path).json()
|
|
42
50
|
connectors: list[dict[str, object]] = (
|
|
43
51
|
body.get("connectors", []) if isinstance(body, dict) else body
|
|
44
52
|
)
|
|
@@ -63,8 +71,11 @@ def list_connectors(
|
|
|
63
71
|
table.add_column("UPDATED")
|
|
64
72
|
|
|
65
73
|
for c in connectors:
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
connector_state = str(c.get("state", ""))
|
|
75
|
+
if connector_state == "DEPLOYED":
|
|
76
|
+
state_cell = f"[green]{connector_state}[/]"
|
|
77
|
+
else:
|
|
78
|
+
state_cell = f"[yellow]{connector_state}[/]"
|
|
68
79
|
|
|
69
80
|
health = str(c.get("argoHealth", "") or "")
|
|
70
81
|
health_cell = (
|
|
@@ -117,7 +128,8 @@ def create_connector(
|
|
|
117
128
|
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
118
129
|
) -> None:
|
|
119
130
|
"""Create a new connector."""
|
|
120
|
-
|
|
131
|
+
state.debug = state.debug or debug
|
|
132
|
+
body = _client().post("/connectors", json={
|
|
121
133
|
"name": name,
|
|
122
134
|
"description": description,
|
|
123
135
|
"kafkaConnectCluster": kafka_connect_cluster,
|
|
@@ -142,7 +154,8 @@ def clone_connector(
|
|
|
142
154
|
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
143
155
|
) -> None:
|
|
144
156
|
"""Clone an existing connector."""
|
|
145
|
-
|
|
157
|
+
state.debug = state.debug or debug
|
|
158
|
+
body = _client().post(f"/connectors/{id}/clone").json()
|
|
146
159
|
|
|
147
160
|
if output == OutputFormat.json:
|
|
148
161
|
print(json_mod.dumps(body))
|
|
@@ -157,7 +170,8 @@ def delete_connector(
|
|
|
157
170
|
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
158
171
|
) -> None:
|
|
159
172
|
"""Delete a connector."""
|
|
160
|
-
|
|
173
|
+
state.debug = state.debug or debug
|
|
174
|
+
_client().delete(f"/connectors/{id}")
|
|
161
175
|
console.print(f"Connector {id} deleted.")
|
|
162
176
|
|
|
163
177
|
|
|
@@ -173,7 +187,8 @@ def publish_connector(
|
|
|
173
187
|
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
174
188
|
) -> None:
|
|
175
189
|
"""Publish (apply) a connector to git/ArgoCD."""
|
|
176
|
-
|
|
190
|
+
state.debug = state.debug or debug
|
|
191
|
+
resp = _client().post(f"/connectors/{id}/apply", json={
|
|
177
192
|
"branch": branch,
|
|
178
193
|
"commitMessage": commit_message,
|
|
179
194
|
"authorName": author,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
debug: bool = False
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
from google.cloud import secretmanager
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def fetch_secret(project_id: str, secret_name: str) -> dict[str, str]:
|
|
7
|
-
client = secretmanager.SecretManagerServiceClient()
|
|
8
|
-
resource = f"projects/{project_id}/secrets/{secret_name}/versions/latest"
|
|
9
|
-
response = client.access_secret_version(request={"name": resource})
|
|
10
|
-
payload = response.payload.data.decode("UTF-8")
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
data: dict[str, str] = json.loads(payload)
|
|
14
|
-
except json.JSONDecodeError as exc:
|
|
15
|
-
raise ValueError(f"Secret '{secret_name}' is not valid JSON") from exc
|
|
16
|
-
|
|
17
|
-
missing = {"username", "password"} - set(data.keys())
|
|
18
|
-
if missing:
|
|
19
|
-
raise ValueError(
|
|
20
|
-
f"Secret '{secret_name}' missing required fields: {', '.join(sorted(missing))}"
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
return {"username": data["username"], "password": data["password"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|