inthub-cli 0.1.4__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.
- inthub/__init__.py +0 -0
- inthub/__main__.py +4 -0
- inthub/bulk/__init__.py +0 -0
- inthub/bulk/csv_state.py +118 -0
- inthub/bulk/ddl_converter.py +86 -0
- inthub/bulk/gcp_secrets.py +23 -0
- inthub/bulk/snowflake_client.py +61 -0
- inthub/bulk/sqlserver.py +51 -0
- inthub/cli.py +27 -0
- inthub/client.py +85 -0
- inthub/commands/__init__.py +0 -0
- inthub/commands/auth.py +57 -0
- inthub/commands/bulk/__init__.py +11 -0
- inthub/commands/bulk/manage.py +98 -0
- inthub/commands/bulk/progress.py +46 -0
- inthub/commands/bulk/src_sqlserver_sink_snowflake/__init__.py +123 -0
- inthub/commands/bulk/src_sqlserver_sink_snowflake/connectors.py +134 -0
- inthub/commands/bulk/src_sqlserver_sink_snowflake/orchestrator.py +326 -0
- inthub/commands/connectors.py +186 -0
- inthub/config.py +48 -0
- inthub_cli-0.1.4.dist-info/METADATA +17 -0
- inthub_cli-0.1.4.dist-info/RECORD +24 -0
- inthub_cli-0.1.4.dist-info/WHEEL +4 -0
- inthub_cli-0.1.4.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
import json as json_mod
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from inthub import config
|
|
12
|
+
from inthub.client import InthubClient
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="Manage connectors.", add_completion=False)
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OutputFormat(str, Enum):
|
|
19
|
+
table = "table"
|
|
20
|
+
json = "json"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _client(debug: bool = False) -> InthubClient:
|
|
24
|
+
return InthubClient(config.require_token(), debug=debug)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command("list")
|
|
28
|
+
def list_connectors(
|
|
29
|
+
tag: Annotated[
|
|
30
|
+
list[str] | None,
|
|
31
|
+
typer.Option("--tag", help="Filter by tag (repeatable, all must match)."),
|
|
32
|
+
] = None,
|
|
33
|
+
output: OutputFormat = typer.Option(OutputFormat.table, "--output", "-o"),
|
|
34
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
35
|
+
) -> None:
|
|
36
|
+
"""List connectors, optionally filtered by tags."""
|
|
37
|
+
path = "/connectors"
|
|
38
|
+
if tag:
|
|
39
|
+
path += "?tags=" + ",".join(tag)
|
|
40
|
+
|
|
41
|
+
body = _client(debug).get(path).json()
|
|
42
|
+
connectors: list[dict[str, object]] = (
|
|
43
|
+
body.get("connectors", []) if isinstance(body, dict) else body
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if output == OutputFormat.json:
|
|
47
|
+
print(json_mod.dumps(connectors))
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if not connectors:
|
|
51
|
+
console.print("No connectors found.")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
table = Table(show_header=True, header_style="bold", show_lines=True, expand=True)
|
|
55
|
+
table.add_column("ID", style="dim", overflow="fold")
|
|
56
|
+
table.add_column("NAME", overflow="fold")
|
|
57
|
+
table.add_column("STATE", no_wrap=True)
|
|
58
|
+
table.add_column("PLUGIN", overflow="fold")
|
|
59
|
+
table.add_column("KCC", overflow="fold")
|
|
60
|
+
table.add_column("ARGO HEALTH")
|
|
61
|
+
table.add_column("ARGO SYNC")
|
|
62
|
+
table.add_column("CREATED")
|
|
63
|
+
table.add_column("UPDATED")
|
|
64
|
+
|
|
65
|
+
for c in connectors:
|
|
66
|
+
state = str(c.get("state", ""))
|
|
67
|
+
state_cell = f"[green]{state}[/]" if state == "DEPLOYED" else f"[yellow]{state}[/]"
|
|
68
|
+
|
|
69
|
+
health = str(c.get("argoHealth", "") or "")
|
|
70
|
+
health_cell = (
|
|
71
|
+
f"[green]{health}[/]" if health == "Healthy"
|
|
72
|
+
else f"[red]{health}[/]" if health in ("Degraded", "Missing")
|
|
73
|
+
else health
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
sync = str(c.get("argoSync", "") or "")
|
|
77
|
+
sync_cell = (
|
|
78
|
+
f"[green]{sync}[/]" if sync == "Synced"
|
|
79
|
+
else f"[yellow]{sync}[/]" if sync == "OutOfSync"
|
|
80
|
+
else sync
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
table.add_row(
|
|
84
|
+
str(c.get("id", "")),
|
|
85
|
+
str(c.get("name", "")),
|
|
86
|
+
state_cell,
|
|
87
|
+
str(c.get("plugin", "")),
|
|
88
|
+
str(c.get("kafkaConnectCluster", "")),
|
|
89
|
+
health_cell,
|
|
90
|
+
sync_cell,
|
|
91
|
+
_fmt_date(str(c.get("createdAt", ""))),
|
|
92
|
+
_fmt_date(str(c.get("updatedAt", ""))),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
console.print(table)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _fmt_date(value: str) -> str:
|
|
99
|
+
if not value:
|
|
100
|
+
return ""
|
|
101
|
+
try:
|
|
102
|
+
dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
103
|
+
return dt.astimezone(UTC).strftime("%Y-%m-%d %H:%M")
|
|
104
|
+
except ValueError:
|
|
105
|
+
return value
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@app.command("create")
|
|
109
|
+
def create_connector(
|
|
110
|
+
name: str = typer.Option(..., "--name", help="Connector name (lowercase, no spaces)."),
|
|
111
|
+
description: str = typer.Option("", "--description"),
|
|
112
|
+
kafka_connect_cluster: str = typer.Option(..., "--kafka-connect-cluster"),
|
|
113
|
+
plugin: str = typer.Option(..., "--plugin"),
|
|
114
|
+
configs: str = typer.Option(..., "--configs", help="Connector configs as a JSON string."),
|
|
115
|
+
tasks_max: int = typer.Option(1, "--tasks-max"),
|
|
116
|
+
output: OutputFormat = typer.Option(OutputFormat.table, "--output", "-o"),
|
|
117
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Create a new connector."""
|
|
120
|
+
body = _client(debug).post("/connectors", json={
|
|
121
|
+
"name": name,
|
|
122
|
+
"description": description,
|
|
123
|
+
"kafkaConnectCluster": kafka_connect_cluster,
|
|
124
|
+
"plugin": plugin,
|
|
125
|
+
"configs": configs,
|
|
126
|
+
"tasksMax": tasks_max,
|
|
127
|
+
}).json()
|
|
128
|
+
|
|
129
|
+
if output == OutputFormat.json:
|
|
130
|
+
print(json_mod.dumps(body))
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
console.print(
|
|
134
|
+
f"[green]Created[/] [bold]{body['name']}[/] (id: {body['id']}, state: {body['state']})"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@app.command("clone")
|
|
139
|
+
def clone_connector(
|
|
140
|
+
id: str = typer.Argument(..., help="ID of the connector to clone."),
|
|
141
|
+
output: OutputFormat = typer.Option(OutputFormat.table, "--output", "-o"),
|
|
142
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Clone an existing connector."""
|
|
145
|
+
body = _client(debug).post(f"/connectors/{id}/clone").json()
|
|
146
|
+
|
|
147
|
+
if output == OutputFormat.json:
|
|
148
|
+
print(json_mod.dumps(body))
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
console.print(f"[green]Cloned[/] → [bold]{body['name']}[/] (id: {body['id']})")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@app.command("delete")
|
|
155
|
+
def delete_connector(
|
|
156
|
+
id: str = typer.Argument(..., help="ID of the connector to delete."),
|
|
157
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Delete a connector."""
|
|
160
|
+
_client(debug).delete(f"/connectors/{id}")
|
|
161
|
+
console.print(f"Connector {id} deleted.")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@app.command("publish")
|
|
165
|
+
def publish_connector(
|
|
166
|
+
id: str = typer.Argument(..., help="ID of the connector to publish."),
|
|
167
|
+
branch: str = typer.Option("main", "--branch"),
|
|
168
|
+
commit_message: str = typer.Option(
|
|
169
|
+
"Publish connector via inthub CLI", "--commit-message"
|
|
170
|
+
),
|
|
171
|
+
author: str = typer.Option(default_factory=getpass.getuser),
|
|
172
|
+
output: OutputFormat = typer.Option(OutputFormat.table, "--output", "-o"),
|
|
173
|
+
debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Publish (apply) a connector to git/ArgoCD."""
|
|
176
|
+
resp = _client(debug).post(f"/connectors/{id}/apply", json={
|
|
177
|
+
"branch": branch,
|
|
178
|
+
"commitMessage": commit_message,
|
|
179
|
+
"authorName": author,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
if output == OutputFormat.json:
|
|
183
|
+
print(resp.text)
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
console.print(f"[green]Published[/] connector {id} → branch [bold]{branch}[/]")
|
inthub/config.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
CONFIG_PATH = Path.home() / ".inthub" / "config.json"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def save(token: str, company_id: str, company_slug: str) -> None:
|
|
10
|
+
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
11
|
+
CONFIG_PATH.write_text(
|
|
12
|
+
json.dumps(
|
|
13
|
+
{"token": token, "company_id": company_id, "company_slug": company_slug}, indent=2
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load() -> dict[str, str]:
|
|
19
|
+
if not CONFIG_PATH.exists():
|
|
20
|
+
return {}
|
|
21
|
+
try:
|
|
22
|
+
return json.loads(CONFIG_PATH.read_text()) # type: ignore[no-any-return]
|
|
23
|
+
except (json.JSONDecodeError, OSError):
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def require_token() -> str:
|
|
28
|
+
"""Return the stored token or exit with a clear message."""
|
|
29
|
+
token = os.environ.get("INTHUB_TOKEN") or load().get("token")
|
|
30
|
+
if not token:
|
|
31
|
+
from rich.console import Console
|
|
32
|
+
Console(stderr=True).print(
|
|
33
|
+
"[bold red]Not logged in.[/] Run [bold]inthub login[/] first."
|
|
34
|
+
)
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
return token
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def require_company_slug() -> str:
|
|
40
|
+
"""Return the stored company slug or exit with a clear message."""
|
|
41
|
+
slug = os.environ.get("INTHUB_COMPANY_SLUG") or load().get("company_slug")
|
|
42
|
+
if not slug:
|
|
43
|
+
from rich.console import Console
|
|
44
|
+
Console(stderr=True).print(
|
|
45
|
+
"[bold red]Not logged in.[/] Run [bold]inthub login[/] first."
|
|
46
|
+
)
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
return slug
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: inthub-cli
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: inthub CLI — manage your inthub.io resources from the terminal
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: google-cloud-secret-manager==2.21.0
|
|
7
|
+
Requires-Dist: httpx==0.27.2
|
|
8
|
+
Requires-Dist: pyodbc==5.2.0
|
|
9
|
+
Requires-Dist: python-ulid==2.7.0
|
|
10
|
+
Requires-Dist: rich==13.9.4
|
|
11
|
+
Requires-Dist: snowflake-connector-python==3.12.0
|
|
12
|
+
Requires-Dist: typer==0.15.4
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: mypy==1.11.2; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest-mock==3.14.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest==8.3.3; extra == 'dev'
|
|
17
|
+
Requires-Dist: ruff==0.6.9; extra == 'dev'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
inthub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
inthub/__main__.py,sha256=nQtjBZyq-GNUnbPcMDw4aUfHliff6uy8eTvqdI0fqfc,67
|
|
3
|
+
inthub/cli.py,sha256=n_5mKY59l64K3oMYrwp2Bx8GZvzN57EJpNqc_0afWUc,716
|
|
4
|
+
inthub/client.py,sha256=pJSWaz_Pe7B_aFd-yn1E2ycdNV-FgDfnRyFv0hKt8Y4,2927
|
|
5
|
+
inthub/config.py,sha256=mPCIs5P-m5bHOG0aSh6K6G7SIr46uGG6Cr8Fmkj_atc,1413
|
|
6
|
+
inthub/bulk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
inthub/bulk/csv_state.py,sha256=akJi_w5UG0L_vLI-MYzuI_-aDlGlmnOOUIVqLaK5sGc,3206
|
|
8
|
+
inthub/bulk/ddl_converter.py,sha256=LW22keUqTnlUeprs58YaTP7pwGFy9-P_oQ-pb0ghx-M,2590
|
|
9
|
+
inthub/bulk/gcp_secrets.py,sha256=EU4QFKRn-NbM1Yi4IpXt5B87QgoJV76S-YvSuX3DBN8,839
|
|
10
|
+
inthub/bulk/snowflake_client.py,sha256=q5-c7cT-jypqvtSWpaT54hzdh7NqWCJ4CftvIPBSPPk,1724
|
|
11
|
+
inthub/bulk/sqlserver.py,sha256=kka5gpoPX5LoH_mq0C5p3zfaEkzWSPyReTMYkDQXpfU,1435
|
|
12
|
+
inthub/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
inthub/commands/auth.py,sha256=7BZFAxu45DeYFWNU0nkRYzbr12FJW5sVr8sgY2ebyUA,1765
|
|
14
|
+
inthub/commands/connectors.py,sha256=ig479BiUmldBQd8sNM4xtdcLyWHye6v0TAQKVVr5MAo,6134
|
|
15
|
+
inthub/commands/bulk/__init__.py,sha256=t66st9_IA_Z0a5gMsrwno6czwcSuci2M7vEtU2HBR1U,459
|
|
16
|
+
inthub/commands/bulk/manage.py,sha256=Pq_ONwmM-7UxOg30Un3h9VZcyuVOufQZ3iNiXuhu3oY,2938
|
|
17
|
+
inthub/commands/bulk/progress.py,sha256=zdcXujx-Kv7mXaXyuWm9OchVkZMbnOx6yKzc7kNRWQM,1314
|
|
18
|
+
inthub/commands/bulk/src_sqlserver_sink_snowflake/__init__.py,sha256=MNZXDi08CyXZg-fanmjSiv3_M8XKKl34__MuVr-bKcY,4357
|
|
19
|
+
inthub/commands/bulk/src_sqlserver_sink_snowflake/connectors.py,sha256=nEEFP7UVAhYxZab5FCLAjm4o3GJ_CdMYlgYK8fsCdTQ,4548
|
|
20
|
+
inthub/commands/bulk/src_sqlserver_sink_snowflake/orchestrator.py,sha256=nl_gyv3KM4xtnD3fDnK4htsyv4jXbXFT05-37Ics35o,12821
|
|
21
|
+
inthub_cli-0.1.4.dist-info/METADATA,sha256=7K1hE3_GCFII7fFLz5m4tCYpoc8-WyWabNlcM9NWF74,605
|
|
22
|
+
inthub_cli-0.1.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
23
|
+
inthub_cli-0.1.4.dist-info/entry_points.txt,sha256=V6U8-d1J5_N3GP-1k1LHTjdZvnZoykJBRCItrWvEiPg,43
|
|
24
|
+
inthub_cli-0.1.4.dist-info/RECORD,,
|