videosdkagent-cli 0.0.1__py2.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.
- videosdk_cli/__init__.py +0 -0
- videosdk_cli/auth.py +57 -0
- videosdk_cli/build.py +430 -0
- videosdk_cli/main.py +89 -0
- videosdk_cli/projects.py +117 -0
- videosdk_cli/run_agent.py +88 -0
- videosdk_cli/secret_set.py +82 -0
- videosdk_cli/templates.py +168 -0
- videosdk_cli/utils/__init__.py +0 -0
- videosdk_cli/utils/analytics.py +61 -0
- videosdk_cli/utils/api_client.py +107 -0
- videosdk_cli/utils/apis/deployment_client.py +172 -0
- videosdk_cli/utils/apis/error.py +10 -0
- videosdk_cli/utils/apis/videosdk_auth_api_client.py +58 -0
- videosdk_cli/utils/auth_api_client.py +49 -0
- videosdk_cli/utils/config_manager.py +66 -0
- videosdk_cli/utils/exceptions.py +3 -0
- videosdk_cli/utils/manager/agent_manager.py +381 -0
- videosdk_cli/utils/project_config.py +87 -0
- videosdk_cli/utils/template_helper.py +204 -0
- videosdk_cli/utils/template_options.py +51 -0
- videosdk_cli/utils/ui/kv_blocks.py +8 -0
- videosdk_cli/utils/ui/progress_runner.py +53 -0
- videosdk_cli/utils/videosdk_yaml_helper.py +75 -0
- videosdkagent_cli-0.0.1.dist-info/METADATA +15 -0
- videosdkagent_cli-0.0.1.dist-info/RECORD +28 -0
- videosdkagent_cli-0.0.1.dist-info/WHEEL +5 -0
- videosdkagent_cli-0.0.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import aiohttp
|
|
3
|
+
from videosdk_cli.utils.config_manager import get_config_value
|
|
4
|
+
from videosdk_cli.utils.apis.error import AuthenticationError, APIRequestError
|
|
5
|
+
|
|
6
|
+
class VideoSDKAsyncClient:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.auth_token = get_config_value("VIDEOSDK_AUTH_TOKEN") or ""
|
|
9
|
+
self.base_url = os.environ.get(
|
|
10
|
+
"API_BASE_URL", "https://api.videosdk.live"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
self.headers = {
|
|
14
|
+
"Authorization": self.auth_token,
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async def _request(
|
|
19
|
+
self,
|
|
20
|
+
method: str,
|
|
21
|
+
endpoint: str,
|
|
22
|
+
*,
|
|
23
|
+
session: aiohttp.ClientSession | None = None,
|
|
24
|
+
**kwargs,
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Common aiohttp request handler
|
|
28
|
+
"""
|
|
29
|
+
url = endpoint if endpoint.startswith("http") else f"{self.base_url}{endpoint}"
|
|
30
|
+
close_session = False
|
|
31
|
+
|
|
32
|
+
if session is None:
|
|
33
|
+
session = aiohttp.ClientSession(headers=self.headers)
|
|
34
|
+
close_session = True
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
async with session.request(method, url, **kwargs) as resp:
|
|
38
|
+
# 🔐 Common auth handling
|
|
39
|
+
if resp.status in (401, 403):
|
|
40
|
+
raise AuthenticationError(
|
|
41
|
+
"Authentication failed. Please check your VIDEOSDK_AUTH_TOKEN."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if resp.status >= 400:
|
|
45
|
+
text = await resp.text()
|
|
46
|
+
raise APIRequestError(
|
|
47
|
+
f"Request failed [{resp.status}]: {text}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# auto-detect JSON vs text
|
|
51
|
+
content_type = resp.headers.get("Content-Type", "")
|
|
52
|
+
if "application/json" in content_type:
|
|
53
|
+
return await resp.json()
|
|
54
|
+
return await resp.text()
|
|
55
|
+
|
|
56
|
+
finally:
|
|
57
|
+
if close_session:
|
|
58
|
+
await session.close()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AuthAPIClient:
|
|
5
|
+
"""
|
|
6
|
+
Client for VideoSDK CLI authentication flow.
|
|
7
|
+
Responsible ONLY for talking to your backend auth APIs.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, base_url: str = "http://localhost:8000"):
|
|
11
|
+
self.base_url = base_url.rstrip("/")
|
|
12
|
+
|
|
13
|
+
async def start_auth(self) -> dict:
|
|
14
|
+
"""
|
|
15
|
+
Starts the auth flow.
|
|
16
|
+
|
|
17
|
+
Expected response:
|
|
18
|
+
{
|
|
19
|
+
"verify_url": "...",
|
|
20
|
+
"request_id": "...",
|
|
21
|
+
"expires_in": 600
|
|
22
|
+
}
|
|
23
|
+
"""
|
|
24
|
+
async with aiohttp.ClientSession() as session:
|
|
25
|
+
async with session.post(f"{self.base_url}/v2/cli/request/start") as resp:
|
|
26
|
+
resp.raise_for_status()
|
|
27
|
+
return await resp.json()
|
|
28
|
+
|
|
29
|
+
async def check_status(self, requestId: str) -> dict:
|
|
30
|
+
"""
|
|
31
|
+
Polls auth status.
|
|
32
|
+
|
|
33
|
+
Expected approved response:
|
|
34
|
+
{
|
|
35
|
+
"status": "approved",
|
|
36
|
+
"project_token": "VIDEOSDK_AUTH_TOKEN"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Other responses:
|
|
40
|
+
{ "status": "pending" }
|
|
41
|
+
{ "status": "expired" }
|
|
42
|
+
"""
|
|
43
|
+
async with aiohttp.ClientSession() as session:
|
|
44
|
+
async with session.get(
|
|
45
|
+
f"{self.base_url}/v2/cli/request/status",
|
|
46
|
+
params={"requestId": requestId},
|
|
47
|
+
) as resp:
|
|
48
|
+
resp.raise_for_status()
|
|
49
|
+
return await resp.json()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import click
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
CONFIG_DIR = Path(click.get_app_dir("videosdk_config"))
|
|
6
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_config():
|
|
11
|
+
"""
|
|
12
|
+
Load the configuration from the standard user config directory.
|
|
13
|
+
For Windows : C:/Users/User_name/AppData/Roaming/videosdk_config/config.json
|
|
14
|
+
For mac/linux : ~/.config/videosdk_config/config.json
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
dict or None: Configuration dictionary, or None if file is empty or missing.
|
|
18
|
+
"""
|
|
19
|
+
if not CONFIG_FILE.is_file():
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
with open(CONFIG_FILE, "r") as f:
|
|
24
|
+
content = f.read().strip()
|
|
25
|
+
if not content:
|
|
26
|
+
return None
|
|
27
|
+
return json.loads(content)
|
|
28
|
+
|
|
29
|
+
except json.JSONDecodeError:
|
|
30
|
+
print(f"[WARNING] Config file at {CONFIG_FILE} is invalid. A new one will be created on next save.")
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
except Exception as e:
|
|
34
|
+
print(f"[ERROR] An unexpected error occurred while loading the config file: {e}")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def save_config(config: dict):
|
|
41
|
+
"""Save the configuration to the standard user config directory."""
|
|
42
|
+
try:
|
|
43
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
with open(CONFIG_FILE, "w") as f:
|
|
45
|
+
json.dump(config, f, indent=2)
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
print(f"[ERROR] Unable to save config to {CONFIG_FILE}: {e}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_config_value(key: str, default=None):
|
|
53
|
+
"""A helper function to safely get a single value from the config."""
|
|
54
|
+
config = load_config()
|
|
55
|
+
|
|
56
|
+
if not config:
|
|
57
|
+
return default
|
|
58
|
+
return config.get(key, default)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def set_config_value(key: str, value):
|
|
63
|
+
"""A helper function to safely set a single value in the config."""
|
|
64
|
+
config = load_config() or {}
|
|
65
|
+
config[key] = value
|
|
66
|
+
save_config(config)
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
from genericpath import exists
|
|
2
|
+
import click
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from dotenv import dotenv_values
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from videosdk_cli.utils.apis.deployment_client import DeploymentClient
|
|
9
|
+
from videosdk_cli.utils.videosdk_yaml_helper import update_agent_config
|
|
10
|
+
from videosdk_cli.utils.videosdk_yaml_helper import AgentConfig
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from InquirerPy import inquirer
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from videosdk_cli.utils.ui.kv_blocks import print_kv_blocks
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
async def init_agent(app_dir: Path, name: Optional[str] = None):
|
|
18
|
+
client = DeploymentClient()
|
|
19
|
+
|
|
20
|
+
# Call backend
|
|
21
|
+
response = await client.agent_init(name)
|
|
22
|
+
|
|
23
|
+
agent_id = response.get("agentId")
|
|
24
|
+
agent_name = response.get("name") or "agent-test"
|
|
25
|
+
|
|
26
|
+
if not agent_id or not agent_name:
|
|
27
|
+
raise RuntimeError("Invalid response from agent_init API")
|
|
28
|
+
|
|
29
|
+
# Build AgentConfig dataclass
|
|
30
|
+
agent_config = AgentConfig(
|
|
31
|
+
id=agent_id,
|
|
32
|
+
name=agent_name,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Update videosdk.yaml
|
|
36
|
+
update_agent_config(
|
|
37
|
+
app_dir=app_dir,
|
|
38
|
+
agent_config=agent_config,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return response
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def list_deployment(deployment_id: Optional[str] = None,head: Optional[int] = None,tail: Optional[int] = None):
|
|
45
|
+
client = DeploymentClient()
|
|
46
|
+
try:
|
|
47
|
+
response = await client.agent_list(deployment_id,head,tail)
|
|
48
|
+
deployments = response.get("deployments", [])
|
|
49
|
+
|
|
50
|
+
if not deployments:
|
|
51
|
+
console.print("[bold red]No deployments found.[/bold red]")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
table = Table(title="Deployments", show_lines=True)
|
|
55
|
+
|
|
56
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
57
|
+
table.add_column("Region", style="green")
|
|
58
|
+
table.add_column("Profile", style="magenta")
|
|
59
|
+
table.add_column("Deployment ID", style="yellow",no_wrap=True)
|
|
60
|
+
table.add_column("Created At", style="white")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
for d in deployments:
|
|
64
|
+
created = d["createdAt"].replace("T", " ").replace("Z", "")
|
|
65
|
+
|
|
66
|
+
table.add_row(
|
|
67
|
+
d["name"],
|
|
68
|
+
d["region"],
|
|
69
|
+
d["profile"],
|
|
70
|
+
d["deploymentId"],
|
|
71
|
+
created
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
console.print(table)
|
|
75
|
+
return response
|
|
76
|
+
except Exception as e:
|
|
77
|
+
raise e
|
|
78
|
+
|
|
79
|
+
async def list_agents_manager(agent_id: Optional[str] = None,head: Optional[int] = None,tail: Optional[int] = None):
|
|
80
|
+
client = DeploymentClient()
|
|
81
|
+
try:
|
|
82
|
+
print(agent_id)
|
|
83
|
+
response = await client.agent_list(agent_id,head,tail)
|
|
84
|
+
deployments = response.get("deployments", [])
|
|
85
|
+
|
|
86
|
+
if not deployments:
|
|
87
|
+
console.print("[bold red]No deployments found.[/bold red]")
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
table = Table(title="Deployments", show_lines=True)
|
|
91
|
+
|
|
92
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
93
|
+
table.add_column("Region", style="green")
|
|
94
|
+
table.add_column("Profile", style="magenta")
|
|
95
|
+
table.add_column("Deployment ID", style="yellow",no_wrap=True)
|
|
96
|
+
table.add_column("Created At", style="white")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
for d in deployments:
|
|
100
|
+
created = d["createdAt"].replace("T", " ").replace("Z", "")
|
|
101
|
+
|
|
102
|
+
table.add_row(
|
|
103
|
+
d["name"],
|
|
104
|
+
d["region"],
|
|
105
|
+
d["profile"],
|
|
106
|
+
d["deploymentId"],
|
|
107
|
+
created
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
console.print(table)
|
|
111
|
+
return response
|
|
112
|
+
except Exception as e:
|
|
113
|
+
raise e
|
|
114
|
+
|
|
115
|
+
async def describe_agent_manager(agent_id:str,deployment_id:Optional[str] = None):
|
|
116
|
+
client = DeploymentClient()
|
|
117
|
+
try:
|
|
118
|
+
response = await client.agent_describe(agent_id,deployment_id)
|
|
119
|
+
data_to_print = {}
|
|
120
|
+
if response:
|
|
121
|
+
data_to_print = {
|
|
122
|
+
"Agent ID": response.get("agentId",'N/A'),
|
|
123
|
+
"Name": response.get("name",'N/A'),
|
|
124
|
+
"Region": response.get("region",'N/A'),
|
|
125
|
+
"Profile": response.get("profile",'N/A'),
|
|
126
|
+
"Deployment ID": response.get("deploymentId",'N/A'),
|
|
127
|
+
"Created At": response.get("createdAt",'N/A').replace("T", " ").replace("Z", ""),
|
|
128
|
+
"minReplica": response.get("minReplica",'N/A'),
|
|
129
|
+
"maxReplica": response.get("maxReplica",'N/A'),
|
|
130
|
+
"imageCR": response.get("image",{}).get("imageCR",'N/A'),
|
|
131
|
+
"imageUrl": response.get("image",{}).get("imageUrl",'N/A'),
|
|
132
|
+
"Active": response.get("active",'N/A'),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
print_kv_blocks([data_to_print])
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
raise e
|
|
139
|
+
|
|
140
|
+
def parse_env_file(env_file: Path) -> dict:
|
|
141
|
+
if not env_file.exists():
|
|
142
|
+
raise click.ClickException("Env file does not exist")
|
|
143
|
+
|
|
144
|
+
env_vars = dotenv_values(env_file)
|
|
145
|
+
|
|
146
|
+
if not env_vars:
|
|
147
|
+
raise click.ClickException("Env file is empty or invalid")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Convert OrderedDict -> dict
|
|
151
|
+
return dict(env_vars)
|
|
152
|
+
|
|
153
|
+
async def secret_set_manager(name:str,file:Optional[str] = None):
|
|
154
|
+
client = DeploymentClient()
|
|
155
|
+
try:
|
|
156
|
+
console.print(f"Secret Name: [green]{name}[/green]")
|
|
157
|
+
|
|
158
|
+
# 🔒 Validate secret name
|
|
159
|
+
if not re.fullmatch(r"[a-z0-9_-]+", name):
|
|
160
|
+
console.print(
|
|
161
|
+
"❌ Invalid secret name.\n"
|
|
162
|
+
"Rules:\n"
|
|
163
|
+
" • must be lowercase\n"
|
|
164
|
+
" • no spaces\n"
|
|
165
|
+
" • allowed characters: a-z, 0-9, -, _",
|
|
166
|
+
)
|
|
167
|
+
raise click.Abort()
|
|
168
|
+
|
|
169
|
+
secrets = {}
|
|
170
|
+
|
|
171
|
+
if file:
|
|
172
|
+
console.print(f"File: [green]{file}[/green]")
|
|
173
|
+
secrets = parse_env_file(Path(os.getcwd()) / file)
|
|
174
|
+
|
|
175
|
+
else:
|
|
176
|
+
while True:
|
|
177
|
+
key = click.prompt("Enter key", type=str)
|
|
178
|
+
if not re.fullmatch(r"^[A-Za-z0-9_-]+$", key):
|
|
179
|
+
console.print(
|
|
180
|
+
"❌ Invalid key.\n"
|
|
181
|
+
"Rules:\n"
|
|
182
|
+
" • must be lowercase\n"
|
|
183
|
+
" • no spaces\n"
|
|
184
|
+
" • allowed characters: a-z, 0-9, -, _",
|
|
185
|
+
)
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
value = click.prompt("Enter value", type=str, hide_input=True)
|
|
189
|
+
|
|
190
|
+
secrets[key] = value
|
|
191
|
+
|
|
192
|
+
choice = inquirer.select(
|
|
193
|
+
message="Add another secret?",
|
|
194
|
+
choices=["Yes", "No"],
|
|
195
|
+
default="No",
|
|
196
|
+
).execute()
|
|
197
|
+
|
|
198
|
+
if choice == "No":
|
|
199
|
+
break
|
|
200
|
+
|
|
201
|
+
if not secrets:
|
|
202
|
+
console.print("No secrets provided. Aborting.")
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
console.print("\nSecrets to be saved:")
|
|
206
|
+
for k in secrets:
|
|
207
|
+
console.print(f"- {k}: ******")
|
|
208
|
+
|
|
209
|
+
action = inquirer.select(
|
|
210
|
+
message="Confirm action",
|
|
211
|
+
choices=["Save secrets", "Cancel"],
|
|
212
|
+
default="Save secrets",
|
|
213
|
+
).execute()
|
|
214
|
+
|
|
215
|
+
if action == "Cancel":
|
|
216
|
+
console.print("[yellow]Cancelled. No secrets were saved.[/yellow]")
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
console.print("\nSaving secrets...")
|
|
220
|
+
response = await client.secret_set(name, secrets)
|
|
221
|
+
console.print("Secrets saved successfully.")
|
|
222
|
+
except Exception as e:
|
|
223
|
+
raise e
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
async def list_secret_manager():
|
|
227
|
+
client = DeploymentClient()
|
|
228
|
+
try:
|
|
229
|
+
console.print("[bold blue]Listing secrets...[/bold blue]")
|
|
230
|
+
response = await client.secret_list()
|
|
231
|
+
data = response.get("data", [])
|
|
232
|
+
|
|
233
|
+
if not data:
|
|
234
|
+
console.print("[bold red]No secrets found.[/bold red]")
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
table = Table(title="Secrets", show_lines=True)
|
|
238
|
+
|
|
239
|
+
table.add_column("name", style="cyan", no_wrap=True)
|
|
240
|
+
table.add_column("secretId", style="green")
|
|
241
|
+
table.add_column("type", style="magenta")
|
|
242
|
+
|
|
243
|
+
for d in data:
|
|
244
|
+
table.add_row(
|
|
245
|
+
d.get('name','N/A'),
|
|
246
|
+
d.get('secretId','N/A'),
|
|
247
|
+
d.get('type','N/A'),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
console.print(table)
|
|
251
|
+
console.print("Secrets listed successfully.")
|
|
252
|
+
except Exception as e:
|
|
253
|
+
raise e
|
|
254
|
+
|
|
255
|
+
async def remove_secret_set(name: str):
|
|
256
|
+
client = DeploymentClient()
|
|
257
|
+
try:
|
|
258
|
+
console.print("[bold blue]Removing secret...[/bold blue]")
|
|
259
|
+
response = await client.secret_remove(name)
|
|
260
|
+
console.print("Secret removed successfully.")
|
|
261
|
+
except Exception as e:
|
|
262
|
+
raise e
|
|
263
|
+
|
|
264
|
+
async def describe_secret_set(name: str):
|
|
265
|
+
client = DeploymentClient()
|
|
266
|
+
try:
|
|
267
|
+
console.print("[bold blue]Getting secret...[/bold blue]")
|
|
268
|
+
response = await client.secret_get(name)
|
|
269
|
+
data_to_print = {}
|
|
270
|
+
data=response.get("data", {})
|
|
271
|
+
if data:
|
|
272
|
+
data_to_print = {
|
|
273
|
+
"Name": data.get("name",'N/A'),
|
|
274
|
+
"Secret ID": data.get("secretId",'N/A'),
|
|
275
|
+
"type": data.get("type",'N/A'),
|
|
276
|
+
}
|
|
277
|
+
print_kv_blocks([data_to_print])
|
|
278
|
+
# 2️⃣ Keys table
|
|
279
|
+
keys = data.get("keys", {})
|
|
280
|
+
|
|
281
|
+
if not keys:
|
|
282
|
+
console.print("\n[dim]No keys found[/dim]")
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
table = Table(title="Secret Keys", show_header=True, header_style="bold")
|
|
286
|
+
|
|
287
|
+
table.add_column("Key", style="cyan", no_wrap=True)
|
|
288
|
+
table.add_column("Value", style="green")
|
|
289
|
+
|
|
290
|
+
for k in keys:
|
|
291
|
+
table.add_row(
|
|
292
|
+
k,
|
|
293
|
+
keys.get(k,'N/A')
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
console.print(table)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
raise e
|
|
299
|
+
|
|
300
|
+
async def add_secret_set(name: str):
|
|
301
|
+
client = DeploymentClient()
|
|
302
|
+
try:
|
|
303
|
+
console.print("[bold blue]Adding secret...[/bold blue]")
|
|
304
|
+
secrets = {}
|
|
305
|
+
|
|
306
|
+
while True:
|
|
307
|
+
key = click.prompt("Enter key", type=str)
|
|
308
|
+
value = click.prompt("Enter value", type=str, hide_input=True)
|
|
309
|
+
|
|
310
|
+
secrets[key] = value
|
|
311
|
+
|
|
312
|
+
choice = inquirer.select(
|
|
313
|
+
message="Add another secret?",
|
|
314
|
+
choices=["Yes", "No"],
|
|
315
|
+
default="No",
|
|
316
|
+
).execute()
|
|
317
|
+
|
|
318
|
+
if choice == "No":
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
if not secrets:
|
|
322
|
+
console.print("No secrets provided. Aborting.")
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
console.print("\nSecrets to be saved:")
|
|
326
|
+
for k in secrets:
|
|
327
|
+
console.print(f"- {k}: ******")
|
|
328
|
+
|
|
329
|
+
action = inquirer.select(
|
|
330
|
+
message="Confirm action",
|
|
331
|
+
choices=["Save secrets", "Cancel"],
|
|
332
|
+
default="Save secrets",
|
|
333
|
+
).execute()
|
|
334
|
+
|
|
335
|
+
if action == "Cancel":
|
|
336
|
+
console.print("Cancelled. No secrets were saved.", fg="yellow")
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
response = await client.secret_add_key(name, secrets)
|
|
340
|
+
|
|
341
|
+
console.print("Secret added successfully.")
|
|
342
|
+
except Exception as e:
|
|
343
|
+
raise e
|
|
344
|
+
|
|
345
|
+
async def remove_secret_set_key(name: str):
|
|
346
|
+
client = DeploymentClient()
|
|
347
|
+
try:
|
|
348
|
+
console.print("[bold blue]Removing secret...[/bold blue]")
|
|
349
|
+
keys=[]
|
|
350
|
+
while True:
|
|
351
|
+
key = click.prompt("Enter key", type=str)
|
|
352
|
+
keys.append(key)
|
|
353
|
+
|
|
354
|
+
choice = inquirer.select(
|
|
355
|
+
message="Remove another key?",
|
|
356
|
+
choices=["Yes", "No"],
|
|
357
|
+
default="No",
|
|
358
|
+
).execute()
|
|
359
|
+
|
|
360
|
+
if choice == "No":
|
|
361
|
+
break
|
|
362
|
+
if not keys:
|
|
363
|
+
console.print("No keys provided.")
|
|
364
|
+
return
|
|
365
|
+
response = await client.secret_remove_key(name,keys)
|
|
366
|
+
console.print("Secret removed successfully.")
|
|
367
|
+
except Exception as e:
|
|
368
|
+
raise e
|
|
369
|
+
|
|
370
|
+
async def image_pull_secret_manager(name: str):
|
|
371
|
+
client = DeploymentClient()
|
|
372
|
+
try:
|
|
373
|
+
console.print("[bold blue]Setting image pull secret...[/bold blue]")
|
|
374
|
+
console.print(f"Secret Name: [green]{name}[/green]")
|
|
375
|
+
server =click.prompt("Enter server name", type=str)
|
|
376
|
+
username =click.prompt("Enter username", type=str)
|
|
377
|
+
password =click.prompt("Enter password", type=str,hide_input=True)
|
|
378
|
+
response = await client.secret_set(name,{"serverName":server,"USERNAME":username,"PASSWORD":password},type="IMAGE_PULL")
|
|
379
|
+
console.print("Image pull secret set successfully.")
|
|
380
|
+
except Exception as e:
|
|
381
|
+
raise e
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
import click
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def load_project_config(search_path=None):
|
|
7
|
+
"""
|
|
8
|
+
Load the project configuration from videosdk.yaml or videosdk.yml.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
search_path (Path, optional): Directory to search in. Defaults to current working directory.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
dict or None: Configuration dictionary if found, None otherwise.
|
|
15
|
+
"""
|
|
16
|
+
if search_path is None:
|
|
17
|
+
search_path = Path(os.getcwd())
|
|
18
|
+
else:
|
|
19
|
+
search_path = Path(search_path)
|
|
20
|
+
|
|
21
|
+
config_path = search_path / "videosdk.yaml"
|
|
22
|
+
if not config_path.exists():
|
|
23
|
+
config_path = search_path / "videosdk.yml"
|
|
24
|
+
|
|
25
|
+
if not config_path.exists():
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
with open(config_path, 'r') as f:
|
|
30
|
+
return yaml.safe_load(f)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
# We perform a lenient load here. Validation errors should generally be handled
|
|
33
|
+
# by the caller or result in None/empty dict depending on strictness required.
|
|
34
|
+
# But per requirements we usually just want the config if valid.
|
|
35
|
+
# For now, if it fails to parse, we might return None or let the error bubble?
|
|
36
|
+
# The prompt said "if not there in yaml we will throw error" only if required config is missing.
|
|
37
|
+
# So if the file exists but is invalid, that's trickier.
|
|
38
|
+
# Let's print a warning and return None for now to be safe,
|
|
39
|
+
# preventing a crash if the file is just empty or garbage.
|
|
40
|
+
click.echo(f"[WARNING] Failed to load config from {config_path}: {e}", err=True)
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def get_config_option(cli_value, config_keys, default=None, required=True, fail_message=None):
|
|
44
|
+
"""
|
|
45
|
+
Get a configuration option resolving priority: CLI > videosdk.yaml > Error (if required) > Default.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
cli_value: The value provided via CLI (e.g. from click.option).
|
|
49
|
+
config_keys (list[str]): List of keys to navigate the config dict (e.g. ['agent', 'image']).
|
|
50
|
+
default: Default value if not found.
|
|
51
|
+
required (bool): If True, raise UsageError if value is not found in CLI or config.
|
|
52
|
+
fail_message (str, optional): Custom error message for UsageError.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The resolved value.
|
|
56
|
+
"""
|
|
57
|
+
# 1. Check CLI value
|
|
58
|
+
if cli_value is not None:
|
|
59
|
+
return cli_value
|
|
60
|
+
|
|
61
|
+
# 2. Check Project Config
|
|
62
|
+
config = load_project_config()
|
|
63
|
+
if config:
|
|
64
|
+
value = config
|
|
65
|
+
for key in config_keys:
|
|
66
|
+
if isinstance(value, dict):
|
|
67
|
+
value = value.get(key)
|
|
68
|
+
else:
|
|
69
|
+
value = None
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
if value is not None:
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
# 3. Check Default
|
|
76
|
+
if default is not None:
|
|
77
|
+
return default
|
|
78
|
+
|
|
79
|
+
# 4. Fail if required/no default
|
|
80
|
+
if required:
|
|
81
|
+
if fail_message:
|
|
82
|
+
raise click.UsageError(fail_message)
|
|
83
|
+
else:
|
|
84
|
+
key_path = ".".join(config_keys)
|
|
85
|
+
raise click.UsageError(f"Missing required configuration: '{key_path}'. Please provide it via CLI or videosdk.yaml.")
|
|
86
|
+
|
|
87
|
+
return None
|