agentstack-cli 0.5.2rc3__py3-none-any.whl → 0.6.0rc1__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.
- agentstack_cli/__init__.py +17 -7
- agentstack_cli/auth_manager.py +3 -2
- agentstack_cli/commands/connector.py +301 -0
- agentstack_cli/commands/platform/__init__.py +26 -16
- agentstack_cli/commands/platform/base_driver.py +32 -24
- agentstack_cli/commands/platform/wsl_driver.py +2 -0
- agentstack_cli/commands/server.py +7 -4
- agentstack_cli/commands/user.py +0 -3
- agentstack_cli/configuration.py +3 -2
- agentstack_cli/data/helm-chart.tgz +0 -0
- agentstack_cli/utils.py +48 -1
- {agentstack_cli-0.5.2rc3.dist-info → agentstack_cli-0.6.0rc1.dist-info}/METADATA +2 -2
- agentstack_cli-0.6.0rc1.dist-info/RECORD +25 -0
- agentstack_cli/commands/platform/istio.py +0 -186
- agentstack_cli-0.5.2rc3.dist-info/RECORD +0 -25
- {agentstack_cli-0.5.2rc3.dist-info → agentstack_cli-0.6.0rc1.dist-info}/WHEEL +0 -0
- {agentstack_cli-0.5.2rc3.dist-info → agentstack_cli-0.6.0rc1.dist-info}/entry_points.txt +0 -0
agentstack_cli/__init__.py
CHANGED
|
@@ -10,11 +10,13 @@ import typer
|
|
|
10
10
|
|
|
11
11
|
import agentstack_cli.commands.agent
|
|
12
12
|
import agentstack_cli.commands.build
|
|
13
|
+
import agentstack_cli.commands.connector
|
|
13
14
|
import agentstack_cli.commands.model
|
|
14
15
|
import agentstack_cli.commands.platform
|
|
15
16
|
import agentstack_cli.commands.self
|
|
16
17
|
import agentstack_cli.commands.server
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
# import agentstack_cli.commands.user
|
|
18
20
|
from agentstack_cli.async_typer import AsyncTyper
|
|
19
21
|
from agentstack_cli.configuration import Configuration
|
|
20
22
|
|
|
@@ -43,6 +45,7 @@ Usage: agentstack [OPTIONS] COMMAND [ARGS]...
|
|
|
43
45
|
╰────────────────────────────────────────────────────────────────────────────╯
|
|
44
46
|
|
|
45
47
|
╭─ Platform & Configuration ─────────────────────────────────────────────────╮
|
|
48
|
+
| connector Manage connectors to external services │
|
|
46
49
|
│ model Configure 15+ LLM providers [Admin only] │
|
|
47
50
|
│ platform Start, stop, or delete local platform [Local only] │
|
|
48
51
|
│ server Connect to remote Agent Stack servers │
|
|
@@ -82,6 +85,12 @@ app.add_typer(
|
|
|
82
85
|
no_args_is_help=True,
|
|
83
86
|
help="Manage agents. Some commands are [Admin only].",
|
|
84
87
|
)
|
|
88
|
+
app.add_typer(
|
|
89
|
+
agentstack_cli.commands.connector.app,
|
|
90
|
+
name="connector",
|
|
91
|
+
no_args_is_help=True,
|
|
92
|
+
help="Manage connectors to external services.",
|
|
93
|
+
)
|
|
85
94
|
app.add_typer(
|
|
86
95
|
agentstack_cli.commands.platform.app,
|
|
87
96
|
name="platform",
|
|
@@ -102,12 +111,13 @@ app.add_typer(
|
|
|
102
111
|
help="Manage Agent Stack installation.",
|
|
103
112
|
hidden=True,
|
|
104
113
|
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
# TODO: Implement keycloak integration
|
|
115
|
+
# app.add_typer(
|
|
116
|
+
# agentstack_cli.commands.user.app,
|
|
117
|
+
# name="user",
|
|
118
|
+
# no_args_is_help=True,
|
|
119
|
+
# help="Manage users. [Admin only]",
|
|
120
|
+
# )
|
|
111
121
|
|
|
112
122
|
|
|
113
123
|
agent_alias = deepcopy(agentstack_cli.commands.agent.app)
|
agentstack_cli/auth_manager.py
CHANGED
|
@@ -80,6 +80,7 @@ class AuthManager:
|
|
|
80
80
|
"""
|
|
81
81
|
This method exchanges a refresh token for a new access token.
|
|
82
82
|
"""
|
|
83
|
+
|
|
83
84
|
async with httpx.AsyncClient(headers={"Accept": "application/json"}) as client:
|
|
84
85
|
resp = None
|
|
85
86
|
try:
|
|
@@ -107,8 +108,8 @@ class AuthManager:
|
|
|
107
108
|
"refresh_token": token.refresh_token,
|
|
108
109
|
"scope": token.scope,
|
|
109
110
|
"client_id": client_id,
|
|
110
|
-
|
|
111
|
-
},
|
|
111
|
+
}
|
|
112
|
+
| ({"client_secret": client_secret} if client_secret else {}),
|
|
112
113
|
)
|
|
113
114
|
resp.raise_for_status()
|
|
114
115
|
new_token = resp.json()
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import asyncio
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
import pydantic
|
|
7
|
+
import typer
|
|
8
|
+
from agentstack_sdk.platform.connector import Connector, ConnectorState
|
|
9
|
+
from agentstack_sdk.platform.types import Metadata
|
|
10
|
+
from InquirerPy import inquirer
|
|
11
|
+
from InquirerPy.base.control import Choice
|
|
12
|
+
|
|
13
|
+
from agentstack_cli import configuration
|
|
14
|
+
from agentstack_cli.async_typer import AsyncTyper
|
|
15
|
+
from agentstack_cli.configuration import Configuration
|
|
16
|
+
from agentstack_cli.console import console
|
|
17
|
+
from agentstack_cli.utils import (
|
|
18
|
+
announce_server_action,
|
|
19
|
+
confirm_server_action,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
app = AsyncTyper()
|
|
23
|
+
config = Configuration()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.command("create")
|
|
27
|
+
async def create_connector(
|
|
28
|
+
url: typing.Annotated[str, typer.Argument(help="Agent location (public docker image or github url)")],
|
|
29
|
+
client_id: typing.Annotated[
|
|
30
|
+
str | None,
|
|
31
|
+
typer.Option("--client-id", help="Client ID for authentication, acquired from env if not supplied"),
|
|
32
|
+
] = None,
|
|
33
|
+
client_secret: typing.Annotated[
|
|
34
|
+
str | None,
|
|
35
|
+
typer.Option("--client-secret", help="Client secret for authentication, acquired from env if not supplied"),
|
|
36
|
+
] = None,
|
|
37
|
+
metadata: typing.Annotated[str | None, typer.Option("--metadata", help="Metadata as JSON string")] = None,
|
|
38
|
+
match_preset: typing.Annotated[
|
|
39
|
+
bool, typer.Option("--match-preset", help="Use preset configuration for given url if it exists")
|
|
40
|
+
] = True,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Create a connector to an external service."""
|
|
43
|
+
async with configuration.use_platform_client():
|
|
44
|
+
connector = await Connector.create(
|
|
45
|
+
url,
|
|
46
|
+
client_id=client_id if client_id else config.client_id,
|
|
47
|
+
client_secret=client_secret if client_secret else config.client_secret,
|
|
48
|
+
metadata=pydantic.TypeAdapter(Metadata).validate_json(metadata if metadata else "{}"),
|
|
49
|
+
match_preset=match_preset,
|
|
50
|
+
)
|
|
51
|
+
console.success(
|
|
52
|
+
f"Created connector for URL [blue]{connector.url}[/blue] with id: [green]{connector.id}[/green]\n"
|
|
53
|
+
f"Connector status: [yellow]{connector.state}[/yellow]"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def search_path_match_connectors(search_path: str, connectors: list[Connector]) -> list[Connector]:
|
|
58
|
+
return [
|
|
59
|
+
c for c in connectors if (search_path in str(c.id) or search_path.lower() in c.url.unicode_string().lower())
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def select_connectors_multi(
|
|
64
|
+
search_path: str, connectors: list[Connector], operation_name: str = "remove"
|
|
65
|
+
) -> list[Connector]:
|
|
66
|
+
"""Select multiple connectors matching the search path."""
|
|
67
|
+
connector_candidates = search_path_match_connectors(search_path, connectors)
|
|
68
|
+
if not connector_candidates:
|
|
69
|
+
raise ValueError(f"No matching connectors found for '{search_path}'")
|
|
70
|
+
|
|
71
|
+
if len(connector_candidates) == 1:
|
|
72
|
+
return connector_candidates
|
|
73
|
+
|
|
74
|
+
# Multiple matches - show selection menu
|
|
75
|
+
choices = [Choice(value=c, name=f"{c.url} - {c.id} ({c.state})") for c in connector_candidates]
|
|
76
|
+
|
|
77
|
+
selected_connectors = await inquirer.checkbox( # pyright: ignore[reportPrivateImportUsage]
|
|
78
|
+
message=f"Select connectors to {operation_name} (use ↑/↓ to navigate, Space to select):", choices=choices
|
|
79
|
+
).execute_async()
|
|
80
|
+
|
|
81
|
+
return selected_connectors or []
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command("remove | rm | delete")
|
|
85
|
+
async def remove_connector(
|
|
86
|
+
search_path: typing.Annotated[
|
|
87
|
+
str, typer.Argument(help="Short ID or connector url, supports partial matching")
|
|
88
|
+
] = "",
|
|
89
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
90
|
+
all: typing.Annotated[bool, typer.Option("--all", "-a", help="Remove all connectors without selection.")] = False,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Remove connectors."""
|
|
93
|
+
|
|
94
|
+
async def _delete_and_wait_for_completion(connector: Connector) -> None:
|
|
95
|
+
await connector.delete()
|
|
96
|
+
await connector.wait_for_deletion()
|
|
97
|
+
|
|
98
|
+
if search_path and all:
|
|
99
|
+
console.error(
|
|
100
|
+
"[red]Cannot specify both --all and a search path. Use --all to remove all connectors, or provide a search path for specific connectors.[/red]"
|
|
101
|
+
)
|
|
102
|
+
raise typer.Exit(1)
|
|
103
|
+
|
|
104
|
+
async with configuration.use_platform_client():
|
|
105
|
+
connectors_list = await Connector.list()
|
|
106
|
+
connectors = connectors_list.items
|
|
107
|
+
if len(connectors) == 0:
|
|
108
|
+
console.info("[yellow]No connectors found.[/yellow]")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
if all:
|
|
112
|
+
selected_connectors = connectors
|
|
113
|
+
else:
|
|
114
|
+
selected_connectors = await select_connectors_multi(search_path, connectors, operation_name="remove")
|
|
115
|
+
|
|
116
|
+
if not selected_connectors:
|
|
117
|
+
console.info("[yellow]No connectors selected, exiting.[/yellow]")
|
|
118
|
+
return
|
|
119
|
+
else:
|
|
120
|
+
connector_names = "\n".join([f" - {c.url} - {c.id}" for c in selected_connectors])
|
|
121
|
+
|
|
122
|
+
message = f"\n[bold]Selected connectors to remove:[/bold]\n{connector_names}\n from "
|
|
123
|
+
|
|
124
|
+
url = announce_server_action(message)
|
|
125
|
+
await confirm_server_action("Proceed with removing these connectors from", url=url, yes=yes)
|
|
126
|
+
|
|
127
|
+
with console.status("Removing connector(s)...", spinner="dots"):
|
|
128
|
+
delete_tasks = [_delete_and_wait_for_completion(connector) for connector in selected_connectors]
|
|
129
|
+
results = await asyncio.gather(*delete_tasks, return_exceptions=True)
|
|
130
|
+
|
|
131
|
+
# Check results for exceptions
|
|
132
|
+
successful_deletions = []
|
|
133
|
+
for connector, result in zip(selected_connectors, results, strict=True):
|
|
134
|
+
if isinstance(result, Exception):
|
|
135
|
+
console.error(f"[red]Failed to delete {connector.url}:[/red] {result}")
|
|
136
|
+
else:
|
|
137
|
+
successful_deletions.append(connector)
|
|
138
|
+
|
|
139
|
+
# Wait for successful deletions to complete
|
|
140
|
+
for connector in successful_deletions:
|
|
141
|
+
console.success(f"[green]Successfully deleted connector {connector.url}[/green]")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@app.command("list")
|
|
145
|
+
async def list_connectors() -> None:
|
|
146
|
+
"""List all connectors."""
|
|
147
|
+
async with configuration.use_platform_client():
|
|
148
|
+
connectors = await Connector.list()
|
|
149
|
+
message = f"Found [green]{connectors.total_count}[/green] connectors"
|
|
150
|
+
if connectors.total_count > 0:
|
|
151
|
+
message += ":"
|
|
152
|
+
for item in connectors.items:
|
|
153
|
+
message += f"\n- {item.id}: {item.url} ({item.state})"
|
|
154
|
+
|
|
155
|
+
console.success(message)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@app.command("list-presets")
|
|
159
|
+
async def list_connector_presets() -> None:
|
|
160
|
+
"""List connector presets."""
|
|
161
|
+
async with configuration.use_platform_client():
|
|
162
|
+
presets = await Connector.presets()
|
|
163
|
+
message = f"Found [green]{presets.total_count}[/green] connector presets:"
|
|
164
|
+
for item in presets.items:
|
|
165
|
+
message += f"\n- {item}"
|
|
166
|
+
|
|
167
|
+
console.success(message)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def find_matching_connector(search_path: str, connectors: list[Connector]) -> Connector:
|
|
171
|
+
connector_candidates = search_path_match_connectors(search_path, connectors)
|
|
172
|
+
if len(connector_candidates) != 1:
|
|
173
|
+
message = f"Found {len(connector_candidates)} matching connectors"
|
|
174
|
+
connector_list = [f" - {c.url} - {c.id} ({c.state})" for c in connector_candidates]
|
|
175
|
+
connectors_detail = ":\n" + "\n".join(connector_list) if connector_list else ""
|
|
176
|
+
raise ValueError(message + connectors_detail)
|
|
177
|
+
[selected_connector] = connector_candidates
|
|
178
|
+
return selected_connector
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def select_connector(search_path: str) -> Connector | None:
|
|
182
|
+
connectors_list = await Connector.list()
|
|
183
|
+
connectors = connectors_list.items
|
|
184
|
+
if connectors_list.total_count == 0:
|
|
185
|
+
console.info("[yellow]No connectors found.[/yellow]")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
selected_connector = find_matching_connector(search_path, connectors)
|
|
190
|
+
return selected_connector
|
|
191
|
+
except ValueError as e:
|
|
192
|
+
console.error(e.__str__())
|
|
193
|
+
console.hint("Please refine your input to match exactly one connector id or url.")
|
|
194
|
+
raise typer.Exit(code=1) from None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@app.command("get")
|
|
198
|
+
async def get_connector(
|
|
199
|
+
search_path: typing.Annotated[str, typer.Argument(help="Short ID or connector url, supports partial matching")],
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Get connector details."""
|
|
202
|
+
async with configuration.use_platform_client():
|
|
203
|
+
selected_connector = await select_connector(search_path)
|
|
204
|
+
if not selected_connector:
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
connector = await Connector.get(selected_connector.id)
|
|
208
|
+
connector_data = connector.model_dump()
|
|
209
|
+
message = "Connector details:"
|
|
210
|
+
for key, value in connector_data.items():
|
|
211
|
+
if key in ["auth_request"]:
|
|
212
|
+
continue # Skip auth_request details
|
|
213
|
+
message += f"\n- {key}: {value}"
|
|
214
|
+
|
|
215
|
+
console.success(message)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@app.command("connect")
|
|
219
|
+
async def connect(
|
|
220
|
+
search_path: typing.Annotated[str, typer.Argument(help="Short ID or connector url, supports partial matching")],
|
|
221
|
+
) -> None:
|
|
222
|
+
"""Connect a connector (e.g., start OAuth flow)."""
|
|
223
|
+
async with configuration.use_platform_client():
|
|
224
|
+
selected_connector = await select_connector(search_path)
|
|
225
|
+
if not selected_connector:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
with console.status("Connecting connector...", spinner="dots"):
|
|
230
|
+
connector = await selected_connector.connect()
|
|
231
|
+
connector = await connector.wait_for_state(state=ConnectorState.connected)
|
|
232
|
+
|
|
233
|
+
console.success(
|
|
234
|
+
f"[green]Connector connected successfully:[/green] {connector.url} (state: {connector.state})"
|
|
235
|
+
)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
console.error(f"[red]Failed to connect connector:[/red] {e}")
|
|
238
|
+
raise typer.Exit(code=1) from None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@app.command("disconnect")
|
|
242
|
+
async def disconnect(
|
|
243
|
+
search_path: typing.Annotated[
|
|
244
|
+
str, typer.Argument(help="Short ID or connector url, supports partial matching")
|
|
245
|
+
] = "",
|
|
246
|
+
yes: typing.Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
247
|
+
all: typing.Annotated[
|
|
248
|
+
bool, typer.Option("--all", "-a", help="Deisconnect all connectors without selection.")
|
|
249
|
+
] = False,
|
|
250
|
+
) -> None:
|
|
251
|
+
"""Disconnect one or more connectors."""
|
|
252
|
+
|
|
253
|
+
async def _discionnect_and_wait_for_completion(connector: Connector) -> None:
|
|
254
|
+
await connector.disconnect()
|
|
255
|
+
await connector.wait_for_state(state=ConnectorState.disconnected)
|
|
256
|
+
|
|
257
|
+
if search_path and all:
|
|
258
|
+
console.error(
|
|
259
|
+
"[red]Cannot specify both --all and a search path. Use --all to remove all connectors, or provide a search path for specific connectors.[/red]"
|
|
260
|
+
)
|
|
261
|
+
raise typer.Exit(1)
|
|
262
|
+
|
|
263
|
+
async with configuration.use_platform_client():
|
|
264
|
+
connectors_list = await Connector.list()
|
|
265
|
+
connectors = connectors_list.items
|
|
266
|
+
if len(connectors) == 0:
|
|
267
|
+
console.info("[yellow]No connectors found.[/yellow]")
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
if all:
|
|
271
|
+
selected_connectors = connectors
|
|
272
|
+
else:
|
|
273
|
+
selected_connectors = await select_connectors_multi(search_path, connectors, operation_name="disconnect")
|
|
274
|
+
|
|
275
|
+
if not selected_connectors:
|
|
276
|
+
console.info("[yellow]No connectors selected, exiting.[/yellow]")
|
|
277
|
+
return
|
|
278
|
+
else:
|
|
279
|
+
connector_names = "\n".join([f" - {c.url} - {c.id}" for c in selected_connectors])
|
|
280
|
+
|
|
281
|
+
message = f"\n[bold]Selected connectors to disconnect:[/bold]\n{connector_names}\n from "
|
|
282
|
+
|
|
283
|
+
url = announce_server_action(message)
|
|
284
|
+
await confirm_server_action("Proceed with disconnecting these connectors from", url=url, yes=yes)
|
|
285
|
+
|
|
286
|
+
with console.status("Disconnecting connectors...", spinner="dots"):
|
|
287
|
+
disconnect_tasks = [_discionnect_and_wait_for_completion(connector) for connector in selected_connectors]
|
|
288
|
+
results = await asyncio.gather(*disconnect_tasks, return_exceptions=True)
|
|
289
|
+
|
|
290
|
+
# Check results for exceptions
|
|
291
|
+
successful_disconnections = []
|
|
292
|
+
for connector, result in zip(selected_connectors, results, strict=True):
|
|
293
|
+
if isinstance(result, Exception):
|
|
294
|
+
console.error(f"[red]Failed to disconnect {connector.url}:[/red] {result}")
|
|
295
|
+
console.hint("Check that the selected connector is currently connected.")
|
|
296
|
+
else:
|
|
297
|
+
successful_disconnections.append(connector)
|
|
298
|
+
|
|
299
|
+
# Wait for successful disconnections to complete
|
|
300
|
+
for connector in successful_disconnections:
|
|
301
|
+
console.success(f"[green]Successfully disconnected connector[/green] {connector.url}")
|
|
@@ -14,18 +14,20 @@ import typing
|
|
|
14
14
|
|
|
15
15
|
import httpx
|
|
16
16
|
import typer
|
|
17
|
-
from agentstack_sdk.platform import Provider
|
|
18
17
|
from tenacity import AsyncRetrying, retry_if_exception_type, stop_after_delay, wait_fixed
|
|
19
18
|
|
|
20
19
|
from agentstack_cli.async_typer import AsyncTyper
|
|
21
20
|
from agentstack_cli.commands.platform.base_driver import BaseDriver
|
|
22
21
|
from agentstack_cli.commands.platform.lima_driver import LimaDriver
|
|
23
22
|
from agentstack_cli.commands.platform.wsl_driver import WSLDriver
|
|
23
|
+
from agentstack_cli.configuration import Configuration
|
|
24
24
|
from agentstack_cli.console import console
|
|
25
25
|
from agentstack_cli.utils import verbosity
|
|
26
26
|
|
|
27
27
|
app = AsyncTyper()
|
|
28
28
|
|
|
29
|
+
configuration = Configuration()
|
|
30
|
+
|
|
29
31
|
|
|
30
32
|
@functools.cache
|
|
31
33
|
def get_driver(vm_name: str = "agentstack") -> BaseDriver:
|
|
@@ -80,6 +82,9 @@ async def start(
|
|
|
80
82
|
] = None,
|
|
81
83
|
vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
|
|
82
84
|
verbose: typing.Annotated[bool, typer.Option("-v", "--verbose", help="Show verbose output")] = False,
|
|
85
|
+
skip_pull: typing.Annotated[bool, typer.Option(hidden=True)] = False,
|
|
86
|
+
skip_restart_deployments: typing.Annotated[bool, typer.Option(hidden=True)] = False,
|
|
87
|
+
no_wait_for_platform: typing.Annotated[bool, typer.Option(hidden=True)] = False,
|
|
83
88
|
):
|
|
84
89
|
import agentstack_cli.commands.server
|
|
85
90
|
|
|
@@ -98,23 +103,28 @@ async def start(
|
|
|
98
103
|
values_file=values_file_path,
|
|
99
104
|
import_images=import_images,
|
|
100
105
|
pull_on_host=pull_on_host,
|
|
106
|
+
skip_pull=skip_pull,
|
|
107
|
+
skip_restart_deployments=skip_restart_deployments,
|
|
101
108
|
)
|
|
102
109
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
async
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
if not no_wait_for_platform:
|
|
111
|
+
with console.status("Waiting for Agent Stack platform to be ready...", spinner="dots"):
|
|
112
|
+
timeout = datetime.timedelta(minutes=20)
|
|
113
|
+
async with httpx.AsyncClient() as client:
|
|
114
|
+
try:
|
|
115
|
+
async for attempt in AsyncRetrying(
|
|
116
|
+
stop=stop_after_delay(timeout),
|
|
117
|
+
wait=wait_fixed(datetime.timedelta(seconds=1)),
|
|
118
|
+
retry=retry_if_exception_type((httpx.HTTPError, ConnectionError)),
|
|
119
|
+
reraise=True,
|
|
120
|
+
):
|
|
121
|
+
with attempt:
|
|
122
|
+
resp = await client.get("http://localhost:8333/healthcheck")
|
|
123
|
+
resp.raise_for_status()
|
|
124
|
+
except Exception as ex:
|
|
125
|
+
raise ConnectionError(
|
|
126
|
+
f"Server did not start in {timeout}. Please check your internet connection."
|
|
127
|
+
) from ex
|
|
118
128
|
|
|
119
129
|
console.success("Agent Stack platform started successfully!")
|
|
120
130
|
|
|
@@ -13,9 +13,8 @@ import anyio
|
|
|
13
13
|
import yaml
|
|
14
14
|
from tenacity import AsyncRetrying, stop_after_attempt
|
|
15
15
|
|
|
16
|
-
import agentstack_cli.commands.platform.istio
|
|
17
16
|
from agentstack_cli.configuration import Configuration
|
|
18
|
-
from agentstack_cli.utils import run_command
|
|
17
|
+
from agentstack_cli.utils import merge, run_command
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
class BaseDriver(abc.ABC):
|
|
@@ -106,24 +105,33 @@ class BaseDriver(abc.ABC):
|
|
|
106
105
|
values_file: pathlib.Path | None = None,
|
|
107
106
|
import_images: list[str] | None = None,
|
|
108
107
|
pull_on_host: bool = False,
|
|
108
|
+
skip_pull: bool = False,
|
|
109
|
+
skip_restart_deployments: bool = False,
|
|
109
110
|
) -> None:
|
|
110
|
-
await self.run_in_vm(
|
|
111
|
+
_ = await self.run_in_vm(
|
|
111
112
|
["sh", "-c", "mkdir -p /tmp/agentstack && cat >/tmp/agentstack/chart.tgz"],
|
|
112
113
|
"Preparing Helm chart",
|
|
113
114
|
input=(importlib.resources.files("agentstack_cli") / "data" / "helm-chart.tgz").read_bytes(),
|
|
114
115
|
)
|
|
115
116
|
values = {
|
|
116
117
|
**{svc: {"service": {"type": "LoadBalancer"}} for svc in ["collector", "docling", "ui", "phoenix"]},
|
|
117
|
-
"
|
|
118
|
+
"service": {"type": "LoadBalancer"},
|
|
118
119
|
"externalRegistries": {"public_github": str(Configuration().agent_registry)},
|
|
119
120
|
"encryptionKey": "Ovx8qImylfooq4-HNwOzKKDcXLZCB3c_m0JlB9eJBxc=",
|
|
121
|
+
"trustProxyHeaders": True,
|
|
122
|
+
"keycloak": {
|
|
123
|
+
"uiClientSecret": "agentstack-ui-secret",
|
|
124
|
+
"serverClientSecret": "agentstack-server-secret",
|
|
125
|
+
"service": {"type": "LoadBalancer"},
|
|
126
|
+
"auth": {"adminPassword": "admin"},
|
|
127
|
+
},
|
|
120
128
|
"features": {"uiLocalSetup": True},
|
|
121
129
|
"providerBuilds": {"enabled": True},
|
|
122
130
|
"localDockerRegistry": {"enabled": True},
|
|
123
131
|
"auth": {"enabled": False},
|
|
124
132
|
}
|
|
125
133
|
if values_file:
|
|
126
|
-
values
|
|
134
|
+
values = merge(values, yaml.safe_load(values_file.read_text()))
|
|
127
135
|
await self.run_in_vm(
|
|
128
136
|
["sh", "-c", "cat >/tmp/agentstack/values.yaml"],
|
|
129
137
|
"Preparing Helm values",
|
|
@@ -150,29 +158,29 @@ class BaseDriver(abc.ABC):
|
|
|
150
158
|
images_to_import = {canonify(tag) for tag in import_images or []}
|
|
151
159
|
images_to_pull = required_images - images_to_import
|
|
152
160
|
|
|
153
|
-
if
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
if not skip_pull:
|
|
162
|
+
if pull_on_host:
|
|
163
|
+
for image in images_to_pull:
|
|
164
|
+
await run_command(["docker", "pull", image], f"Pulling image {image} on host")
|
|
165
|
+
images_to_import = required_images
|
|
166
|
+
images_to_pull = set[str]()
|
|
158
167
|
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
if images_to_import:
|
|
169
|
+
await self.import_images(*images_to_import)
|
|
161
170
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
for image in images_to_pull:
|
|
172
|
+
async for attempt in AsyncRetrying(stop=stop_after_attempt(5)):
|
|
173
|
+
with attempt:
|
|
174
|
+
attempt_num = attempt.retry_state.attempt_number
|
|
175
|
+
await self.run_in_vm(
|
|
176
|
+
["k3s", "ctr", "image", "pull", image],
|
|
177
|
+
f"Pulling image {image}" + (f" (attempt {attempt_num})" if attempt_num > 1 else ""),
|
|
178
|
+
)
|
|
179
|
+
elif images_to_import:
|
|
180
|
+
await self.import_images(*images_to_import)
|
|
170
181
|
|
|
171
182
|
self.loaded_images = required_images
|
|
172
183
|
|
|
173
|
-
if any("auth.oidc.enabled=true" in value.lower() for value in set_values_list):
|
|
174
|
-
await agentstack_cli.commands.platform.istio.install(driver=self)
|
|
175
|
-
|
|
176
184
|
kubeconfig_path = anyio.Path(Configuration().lima_home) / self.vm_name / "copied-from-guest" / "kubeconfig.yaml"
|
|
177
185
|
await kubeconfig_path.parent.mkdir(parents=True, exist_ok=True)
|
|
178
186
|
await kubeconfig_path.write_text(
|
|
@@ -202,7 +210,7 @@ class BaseDriver(abc.ABC):
|
|
|
202
210
|
"Deploying Agent Stack platform with Helm",
|
|
203
211
|
)
|
|
204
212
|
|
|
205
|
-
if import_images:
|
|
213
|
+
if import_images and not skip_restart_deployments:
|
|
206
214
|
await self.run_in_vm(
|
|
207
215
|
["k3s", "kubectl", "rollout", "restart", "deployment"],
|
|
208
216
|
"Restarting deployments to load imported images",
|
|
@@ -132,6 +132,8 @@ class WSLDriver(BaseDriver):
|
|
|
132
132
|
values_file: pathlib.Path | None = None,
|
|
133
133
|
import_images: list[str] | None = None,
|
|
134
134
|
pull_on_host: bool = False,
|
|
135
|
+
skip_pull: bool = False,
|
|
136
|
+
skip_restart_deployments: bool = False,
|
|
135
137
|
) -> None:
|
|
136
138
|
if pull_on_host:
|
|
137
139
|
raise NotImplementedError("Pulling on host is not supported on this platform.")
|
|
@@ -198,10 +198,13 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
|
|
|
198
198
|
console.warning(f" Dynamic client registration failed. Proceed with manual input. {e!s}")
|
|
199
199
|
|
|
200
200
|
if not client_id:
|
|
201
|
-
client_id =
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
client_id = (
|
|
202
|
+
await inquirer.text( # type: ignore
|
|
203
|
+
message="Enter Client ID (default agentstack-cli):",
|
|
204
|
+
instruction=f"(Redirect URI: {REDIRECT_URI})",
|
|
205
|
+
).execute_async()
|
|
206
|
+
or "agentstack-cli"
|
|
207
|
+
)
|
|
205
208
|
if not client_id:
|
|
206
209
|
raise RuntimeError("Client ID is mandatory. Action cancelled.")
|
|
207
210
|
client_secret = (
|
agentstack_cli/commands/user.py
CHANGED
|
@@ -43,21 +43,18 @@ async def list_users(
|
|
|
43
43
|
Column("Email"),
|
|
44
44
|
Column("Role"),
|
|
45
45
|
Column("Created"),
|
|
46
|
-
Column("Role Updated"),
|
|
47
46
|
no_wrap=True,
|
|
48
47
|
) as table:
|
|
49
48
|
for user in items:
|
|
50
49
|
role_display = ROLE_DISPLAY.get(user.role, user.role)
|
|
51
50
|
|
|
52
51
|
created_at = _format_date(user.created_at)
|
|
53
|
-
role_updated_at = _format_date(user.role_updated_at) if user.role_updated_at else "-"
|
|
54
52
|
|
|
55
53
|
table.add_row(
|
|
56
54
|
user.id,
|
|
57
55
|
user.email,
|
|
58
56
|
role_display,
|
|
59
57
|
created_at,
|
|
60
|
-
role_updated_at,
|
|
61
58
|
)
|
|
62
59
|
|
|
63
60
|
console.print()
|
agentstack_cli/configuration.py
CHANGED
|
@@ -35,7 +35,8 @@ class Configuration(pydantic_settings.BaseSettings):
|
|
|
35
35
|
agent_registry: pydantic.AnyUrl = HttpUrl(
|
|
36
36
|
f"https://github.com/i-am-bee/agentstack@v{version()}#path=agent-registry.yaml"
|
|
37
37
|
)
|
|
38
|
-
|
|
38
|
+
username: str = "admin"
|
|
39
|
+
password: SecretStr | None = None
|
|
39
40
|
server_metadata_ttl: int = 86400
|
|
40
41
|
|
|
41
42
|
oidc_enabled: bool = False
|
|
@@ -64,7 +65,7 @@ class Configuration(pydantic_settings.BaseSettings):
|
|
|
64
65
|
)
|
|
65
66
|
sys.exit(1)
|
|
66
67
|
async with use_platform_client(
|
|
67
|
-
auth=(
|
|
68
|
+
auth=(self.username, self.password.get_secret_value()) if self.password else None,
|
|
68
69
|
auth_token=await self.auth_manager.load_auth_token(),
|
|
69
70
|
base_url=self.auth_manager.active_server + "/",
|
|
70
71
|
) as client:
|
|
Binary file
|
agentstack_cli/utils.py
CHANGED
|
@@ -8,7 +8,8 @@ import os
|
|
|
8
8
|
import re
|
|
9
9
|
import subprocess
|
|
10
10
|
import sys
|
|
11
|
-
from collections
|
|
11
|
+
from collections import Counter
|
|
12
|
+
from collections.abc import AsyncIterator, Mapping, MutableMapping
|
|
12
13
|
from contextlib import asynccontextmanager
|
|
13
14
|
from contextvars import ContextVar
|
|
14
15
|
from copy import deepcopy
|
|
@@ -340,3 +341,49 @@ def is_github_url(url: str) -> bool:
|
|
|
340
341
|
$
|
|
341
342
|
"""
|
|
342
343
|
return bool(re.match(pattern, url, re.VERBOSE))
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# Inspired by: https://github.com/clarketm/mergedeep/blob/master/mergedeep/mergedeep.py
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _is_recursive_merge(a: Any, b: Any) -> bool:
|
|
350
|
+
both_mapping = isinstance(a, Mapping) and isinstance(b, Mapping)
|
|
351
|
+
both_counter = isinstance(a, Counter) and isinstance(b, Counter)
|
|
352
|
+
return both_mapping and not both_counter
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _handle_merge_replace(destination, source, key):
|
|
356
|
+
if isinstance(destination[key], Counter) and isinstance(source[key], Counter):
|
|
357
|
+
# Merge both destination and source `Counter` as if they were a standard dict.
|
|
358
|
+
_deepmerge(destination[key], source[key])
|
|
359
|
+
else:
|
|
360
|
+
# If a key exists in both objects and the values are `different`, the value from the `source` object will be used.
|
|
361
|
+
destination[key] = deepcopy(source[key])
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _deepmerge(dst, src):
|
|
365
|
+
for key in src:
|
|
366
|
+
if key in dst:
|
|
367
|
+
if _is_recursive_merge(dst[key], src[key]):
|
|
368
|
+
# If the key for both `dst` and `src` are both Mapping types (e.g. dict), then recurse.
|
|
369
|
+
_deepmerge(dst[key], src[key])
|
|
370
|
+
elif dst[key] is src[key]:
|
|
371
|
+
# If a key exists in both objects and the values are `same`, the value from the `dst` object will be used.
|
|
372
|
+
pass
|
|
373
|
+
else:
|
|
374
|
+
_handle_merge_replace(dst, src, key)
|
|
375
|
+
else:
|
|
376
|
+
# If the key exists only in `src`, the value from the `src` object will be used.
|
|
377
|
+
dst[key] = deepcopy(src[key])
|
|
378
|
+
return dst
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def merge(destination: MutableMapping[str, Any], *sources: Mapping[str, Any]) -> MutableMapping[str, Any]:
|
|
382
|
+
"""
|
|
383
|
+
A deep merge function for 🐍.
|
|
384
|
+
|
|
385
|
+
:param destination: The destination mapping.
|
|
386
|
+
:param sources: The source mappings.
|
|
387
|
+
:return:
|
|
388
|
+
"""
|
|
389
|
+
return functools.reduce(_deepmerge, sources, destination)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: agentstack-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0rc1
|
|
4
4
|
Summary: Agent Stack CLI
|
|
5
5
|
Author: IBM Corp.
|
|
6
|
-
Requires-Dist: agentstack-sdk==0.
|
|
6
|
+
Requires-Dist: agentstack-sdk==0.6.0rc1
|
|
7
7
|
Requires-Dist: a2a-sdk==0.3.21
|
|
8
8
|
Requires-Dist: annotated-doc==0.0.4
|
|
9
9
|
Requires-Dist: annotated-types==0.7.0
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
agentstack_cli/__init__.py,sha256=VML7VgV0JvGJ4xDnQ-zPulWQKXZWe56Qd19Hp7moq-8,7019
|
|
2
|
+
agentstack_cli/api.py,sha256=3bjdlSp2vaEWv94Aa5z_s0nW1euFP_etQ2bWZ4G7Gdg,5619
|
|
3
|
+
agentstack_cli/async_typer.py,sha256=mTc1B6fMd4EAT60nj5KuJMuIKEfRGRbPHMFo_7fcVt0,4354
|
|
4
|
+
agentstack_cli/auth_manager.py,sha256=kr4tIVGW-fUROU3oJT1l2B1bb1spRbL4iiSrh7u4ZIo,10076
|
|
5
|
+
agentstack_cli/configuration.py,sha256=Na0lkoLw2RlfORb3x8QwHRfU8V3plPHlJF5NTTRj4mk,2734
|
|
6
|
+
agentstack_cli/console.py,sha256=x7YdbrUtc6gnFMViI8gEVLjF2ns7EKNybanW3h8Q1qU,710
|
|
7
|
+
agentstack_cli/utils.py,sha256=Cevvx7SAb-f07g_sfz8EXZoGnhodaLVUR20Tc4WHr0s,14187
|
|
8
|
+
agentstack_cli/commands/__init__.py,sha256=jr8otByt8HFY9OWptuWdpq4WHE7A1srCSRXZV066FrI,94
|
|
9
|
+
agentstack_cli/commands/agent.py,sha256=6wSPKvM3SYpJRkMpjl8iwpGaWCqtbRA8y-YaHVi_QZE,57685
|
|
10
|
+
agentstack_cli/commands/build.py,sha256=7oRwd_2-vpUnKHvwykPHqqMBMyHGCZ99lIRdg1yDPVE,9002
|
|
11
|
+
agentstack_cli/commands/connector.py,sha256=GnY0MeHnA53PlIti-iIibsIw6caOXyTgs8DVo1LZX6M,12497
|
|
12
|
+
agentstack_cli/commands/model.py,sha256=9htp0J03u4Pg9qroxWAT-RF5ADPMpm264nzIhg13tso,30695
|
|
13
|
+
agentstack_cli/commands/self.py,sha256=1zX9Qu0ma146LUiHWrNOXXIj32i44Cvi9O030PiGCXg,9363
|
|
14
|
+
agentstack_cli/commands/server.py,sha256=IXjCWEKPqnwqQNVvEwBOFITEJ79WpcW1RSBp32sd88s,12157
|
|
15
|
+
agentstack_cli/commands/user.py,sha256=6Hr_UIybDMJgM4QN73zM4J6mP_4BZnW_C2Neu0f2yPE,2893
|
|
16
|
+
agentstack_cli/commands/platform/__init__.py,sha256=rzWgMds-aDA2wCxw3lEG4KywDpW1Wrb6AgLY8zYtM8M,8970
|
|
17
|
+
agentstack_cli/commands/platform/base_driver.py,sha256=oyfqCjL5xsD9gzZ9QhF3U7MPF9RECDplg0CRIFUwjt8,7725
|
|
18
|
+
agentstack_cli/commands/platform/lima_driver.py,sha256=a30flm1EEbsCPbzMugGHjShnljqS1kBu9aspJDY3SgY,11020
|
|
19
|
+
agentstack_cli/commands/platform/wsl_driver.py,sha256=wbQaf9wyuVsXJfqTKddbcgB3KeRSZrO-kwSMi7DUyxE,9422
|
|
20
|
+
agentstack_cli/data/.gitignore,sha256=W0urBl_DVtzJyKHGXo53wMVzMPJseiK9RMn46No6i70,14
|
|
21
|
+
agentstack_cli/data/helm-chart.tgz,sha256=od7mjrLX7pa0pE6FBlHbdcdrIDNY1vhT2po9UW9IfOs,316477
|
|
22
|
+
agentstack_cli-0.6.0rc1.dist-info/METADATA,sha256=_nGoPQUw9g7ezoNYHn62EDXc3GbyKx1141cZLcy0eTg,3766
|
|
23
|
+
agentstack_cli-0.6.0rc1.dist-info/WHEEL,sha256=M6du7VZflc4UPsGphmOXHANdgk8zessdJG0DBUuoA-U,78
|
|
24
|
+
agentstack_cli-0.6.0rc1.dist-info/entry_points.txt,sha256=g3RAenJ90a0mLfx8x-ETzsr3uEdFnvuhTX3Amy1AUbA,87
|
|
25
|
+
agentstack_cli-0.6.0rc1.dist-info/RECORD,,
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import typing
|
|
5
|
-
|
|
6
|
-
import yaml
|
|
7
|
-
|
|
8
|
-
if typing.TYPE_CHECKING:
|
|
9
|
-
from agentstack_cli.commands.platform.base_driver import BaseDriver
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
async def install(driver: "BaseDriver"):
|
|
13
|
-
# Gateway API
|
|
14
|
-
await driver.run_in_vm(
|
|
15
|
-
[
|
|
16
|
-
"k3s",
|
|
17
|
-
"kubectl",
|
|
18
|
-
"apply",
|
|
19
|
-
"-f",
|
|
20
|
-
"https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml",
|
|
21
|
-
],
|
|
22
|
-
"Installing gateway CRDs",
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
# Cert Manager
|
|
26
|
-
await driver.run_in_vm(
|
|
27
|
-
[
|
|
28
|
-
"helm",
|
|
29
|
-
"--kubeconfig=/etc/rancher/k3s/k3s.yaml",
|
|
30
|
-
"install",
|
|
31
|
-
"cert-manager",
|
|
32
|
-
"oci://quay.io/jetstack/charts/cert-manager",
|
|
33
|
-
"--version",
|
|
34
|
-
"v1.18.2",
|
|
35
|
-
"--namespace",
|
|
36
|
-
"cert-manager",
|
|
37
|
-
"--create-namespace",
|
|
38
|
-
"--set",
|
|
39
|
-
"crds.enabled=true",
|
|
40
|
-
"--wait",
|
|
41
|
-
],
|
|
42
|
-
"Installing cert-manager",
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
# Istio
|
|
46
|
-
await driver.run_in_vm(
|
|
47
|
-
["helm", "repo", "add", "istio", "https://istio-release.storage.googleapis.com/charts"],
|
|
48
|
-
"Adding Istio repo to Helm",
|
|
49
|
-
)
|
|
50
|
-
await driver.run_in_vm(["helm", "repo", "update"], "Updating Helm repos")
|
|
51
|
-
for component in ["base", "istiod", "cni", "ztunnel"]:
|
|
52
|
-
await driver.run_in_vm(
|
|
53
|
-
[
|
|
54
|
-
"helm",
|
|
55
|
-
"--kubeconfig=/etc/rancher/k3s/k3s.yaml",
|
|
56
|
-
"install",
|
|
57
|
-
f"istio-{component}",
|
|
58
|
-
f"istio/{component}",
|
|
59
|
-
"--namespace",
|
|
60
|
-
"istio-system",
|
|
61
|
-
"--create-namespace",
|
|
62
|
-
"--set=profile=ambient",
|
|
63
|
-
"--set=global.platform=k3s",
|
|
64
|
-
"--wait",
|
|
65
|
-
],
|
|
66
|
-
f"Installing Istio ({component})",
|
|
67
|
-
)
|
|
68
|
-
await driver.run_in_vm(
|
|
69
|
-
["k3s", "kubectl", "label", "namespace", "default", "istio.io/dataplane-mode=ambient"],
|
|
70
|
-
"Labeling the default namespace",
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
# Configuration
|
|
74
|
-
Resource = typing.TypedDict(
|
|
75
|
-
"Resource", {"apiVersion": str, "kind": str, "metadata": dict[str, str], "spec": dict[str, typing.Any]}
|
|
76
|
-
)
|
|
77
|
-
resources: list[Resource] = [
|
|
78
|
-
{
|
|
79
|
-
"apiVersion": "cert-manager.io/v1",
|
|
80
|
-
"kind": "Issuer",
|
|
81
|
-
"metadata": {"name": "default-issuer", "namespace": "default"},
|
|
82
|
-
"spec": {"selfSigned": {}},
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"apiVersion": "cert-manager.io/v1",
|
|
86
|
-
"kind": "Issuer",
|
|
87
|
-
"metadata": {"name": "istio-system-issuer", "namespace": "istio-system"},
|
|
88
|
-
"spec": {"selfSigned": {}},
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"apiVersion": "cert-manager.io/v1",
|
|
92
|
-
"kind": "Certificate",
|
|
93
|
-
"metadata": {"name": "agentstack-tls", "namespace": "istio-system"},
|
|
94
|
-
"spec": {
|
|
95
|
-
"secretName": "agentstack-tls",
|
|
96
|
-
"commonName": "agentstack",
|
|
97
|
-
"dnsNames": ["agentstack", "agentstack.localhost"],
|
|
98
|
-
"issuerRef": {"name": "istio-system-issuer", "kind": "Issuer"},
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"apiVersion": "cert-manager.io/v1",
|
|
103
|
-
"kind": "Certificate",
|
|
104
|
-
"metadata": {"name": "ingestion-svc", "namespace": "default"},
|
|
105
|
-
"spec": {
|
|
106
|
-
"secretName": "ingestion-svc-tls",
|
|
107
|
-
"commonName": "ingestion-svc",
|
|
108
|
-
"dnsNames": [
|
|
109
|
-
"ingestion-svc",
|
|
110
|
-
"ingestion-svc.default",
|
|
111
|
-
"ingestion-svc.default.svc",
|
|
112
|
-
"ingestion-svc.default.svc.cluster.local",
|
|
113
|
-
],
|
|
114
|
-
"issuerRef": {"name": "default-issuer", "kind": "Issuer"},
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
"apiVersion": "gateway.networking.k8s.io/v1",
|
|
119
|
-
"kind": "Gateway",
|
|
120
|
-
"metadata": {"name": "agentstack-gateway", "namespace": "istio-system"},
|
|
121
|
-
"spec": {
|
|
122
|
-
"gatewayClassName": "istio",
|
|
123
|
-
"listeners": [
|
|
124
|
-
{
|
|
125
|
-
"name": "https",
|
|
126
|
-
"hostname": "agentstack.localhost",
|
|
127
|
-
"port": 8336,
|
|
128
|
-
"protocol": "HTTPS",
|
|
129
|
-
"tls": {"mode": "Terminate", "certificateRefs": [{"name": "agentstack-tls"}]},
|
|
130
|
-
"allowedRoutes": {"namespaces": {"from": "All"}},
|
|
131
|
-
}
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
"apiVersion": "gateway.networking.k8s.io/v1",
|
|
137
|
-
"kind": "HTTPRoute",
|
|
138
|
-
"metadata": {"name": "agentstack-ui"},
|
|
139
|
-
"spec": {
|
|
140
|
-
"parentRefs": [{"name": "agentstack-gateway", "namespace": "istio-system"}],
|
|
141
|
-
"hostnames": ["agentstack.testing", "agentstack.localhost"],
|
|
142
|
-
"rules": [
|
|
143
|
-
{
|
|
144
|
-
"matches": [{"path": {"type": "PathPrefix", "value": "/"}}],
|
|
145
|
-
"backendRefs": [{"name": "agentstack-ui-svc", "port": 8334}],
|
|
146
|
-
}
|
|
147
|
-
],
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
]
|
|
151
|
-
for resource in resources:
|
|
152
|
-
await driver.run_in_vm(
|
|
153
|
-
["k3s", "kubectl", "apply", "-f", "-"],
|
|
154
|
-
f"Applying {resource['metadata']['name']} ({resource['kind']})",
|
|
155
|
-
input=yaml.dump(resource, sort_keys=False).encode("utf-8"),
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
# Extra services
|
|
159
|
-
for addon in ["prometheus", "kiali"]:
|
|
160
|
-
await driver.run_in_vm(
|
|
161
|
-
[
|
|
162
|
-
"k3s",
|
|
163
|
-
"kubectl",
|
|
164
|
-
"apply",
|
|
165
|
-
"-f",
|
|
166
|
-
f"https://raw.githubusercontent.com/istio/istio/master/samples/addons/{addon}.yaml",
|
|
167
|
-
],
|
|
168
|
-
f"Installing {addon.capitalize()}",
|
|
169
|
-
)
|
|
170
|
-
await driver.run_in_vm(
|
|
171
|
-
[
|
|
172
|
-
"k3s",
|
|
173
|
-
"kubectl",
|
|
174
|
-
"-n",
|
|
175
|
-
"istio-system",
|
|
176
|
-
"expose",
|
|
177
|
-
"deployment",
|
|
178
|
-
"kiali",
|
|
179
|
-
"--protocol=TCP",
|
|
180
|
-
"--port=20001",
|
|
181
|
-
"--target-port=20001",
|
|
182
|
-
"--type=LoadBalancer",
|
|
183
|
-
"--name=kiali-external",
|
|
184
|
-
],
|
|
185
|
-
"Exposing Kiali service",
|
|
186
|
-
)
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
agentstack_cli/__init__.py,sha256=S5dAiaVo6QG_iir2KqVizsoHLKbNikjgRZ38CrxTM4s,6683
|
|
2
|
-
agentstack_cli/api.py,sha256=3bjdlSp2vaEWv94Aa5z_s0nW1euFP_etQ2bWZ4G7Gdg,5619
|
|
3
|
-
agentstack_cli/async_typer.py,sha256=mTc1B6fMd4EAT60nj5KuJMuIKEfRGRbPHMFo_7fcVt0,4354
|
|
4
|
-
agentstack_cli/auth_manager.py,sha256=NIZPmyxh1iUkTnz60Ds8kJUhXelEC_iVGCCpo4BLKDo,10049
|
|
5
|
-
agentstack_cli/configuration.py,sha256=8HClHaXZFA9Ri7xWvb4Fq6sufjVcBCVjLK2WR3y3lJI,2718
|
|
6
|
-
agentstack_cli/console.py,sha256=x7YdbrUtc6gnFMViI8gEVLjF2ns7EKNybanW3h8Q1qU,710
|
|
7
|
-
agentstack_cli/utils.py,sha256=_ka8dEdj1o7Am-hhKJTQvqfZzNIJSUfnHV7KyLnRefY,12326
|
|
8
|
-
agentstack_cli/commands/__init__.py,sha256=jr8otByt8HFY9OWptuWdpq4WHE7A1srCSRXZV066FrI,94
|
|
9
|
-
agentstack_cli/commands/agent.py,sha256=6wSPKvM3SYpJRkMpjl8iwpGaWCqtbRA8y-YaHVi_QZE,57685
|
|
10
|
-
agentstack_cli/commands/build.py,sha256=7oRwd_2-vpUnKHvwykPHqqMBMyHGCZ99lIRdg1yDPVE,9002
|
|
11
|
-
agentstack_cli/commands/model.py,sha256=9htp0J03u4Pg9qroxWAT-RF5ADPMpm264nzIhg13tso,30695
|
|
12
|
-
agentstack_cli/commands/self.py,sha256=1zX9Qu0ma146LUiHWrNOXXIj32i44Cvi9O030PiGCXg,9363
|
|
13
|
-
agentstack_cli/commands/server.py,sha256=VYGOEJDxFEF1BS2sbFWIDLQNtYVJDJHY287zCEaqrtY,12040
|
|
14
|
-
agentstack_cli/commands/user.py,sha256=9eAwExLlW906BdhVynTGkw4NLpPg-Is9OzZbY38-7_8,3056
|
|
15
|
-
agentstack_cli/commands/platform/__init__.py,sha256=p-jRVPZnsn6b0C521FTkK-fo3YW1189h-vYnoVIxd8I,8281
|
|
16
|
-
agentstack_cli/commands/platform/base_driver.py,sha256=Qe2NV3DOH73DufS89qaHYSJbkO0Qeagaz7wbmAxUOWY,7306
|
|
17
|
-
agentstack_cli/commands/platform/istio.py,sha256=RefHzR4G2Cl6mdHC37TTSXXc0kFFmNjXuKcIPIhFSZ0,6247
|
|
18
|
-
agentstack_cli/commands/platform/lima_driver.py,sha256=a30flm1EEbsCPbzMugGHjShnljqS1kBu9aspJDY3SgY,11020
|
|
19
|
-
agentstack_cli/commands/platform/wsl_driver.py,sha256=PaJju3sz6Go6AJ9Wu7tfiulvO3nX8Wym3csTRSv-vQk,9341
|
|
20
|
-
agentstack_cli/data/.gitignore,sha256=W0urBl_DVtzJyKHGXo53wMVzMPJseiK9RMn46No6i70,14
|
|
21
|
-
agentstack_cli/data/helm-chart.tgz,sha256=Z-ggF7FkG-kWSOwRslw4PRFySQJqrRAkSwmgQposAcI,306640
|
|
22
|
-
agentstack_cli-0.5.2rc3.dist-info/METADATA,sha256=-t-7tlNhqQ-ZTX8UykvgUFQdy6K7BirW9IJvpf36F0s,3766
|
|
23
|
-
agentstack_cli-0.5.2rc3.dist-info/WHEEL,sha256=M6du7VZflc4UPsGphmOXHANdgk8zessdJG0DBUuoA-U,78
|
|
24
|
-
agentstack_cli-0.5.2rc3.dist-info/entry_points.txt,sha256=g3RAenJ90a0mLfx8x-ETzsr3uEdFnvuhTX3Amy1AUbA,87
|
|
25
|
-
agentstack_cli-0.5.2rc3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|