agentstack-cli 0.4.0__py3-none-macosx_12_0_arm64.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 +77 -0
- agentstack_cli/api.py +125 -0
- agentstack_cli/async_typer.py +104 -0
- agentstack_cli/auth_manager.py +115 -0
- agentstack_cli/commands/__init__.py +3 -0
- agentstack_cli/commands/agent.py +1077 -0
- agentstack_cli/commands/build.py +184 -0
- agentstack_cli/commands/mcp.py +141 -0
- agentstack_cli/commands/model.py +624 -0
- agentstack_cli/commands/platform/__init__.py +181 -0
- agentstack_cli/commands/platform/base_driver.py +222 -0
- agentstack_cli/commands/platform/istio.py +186 -0
- agentstack_cli/commands/platform/lima_driver.py +210 -0
- agentstack_cli/commands/platform/wsl_driver.py +226 -0
- agentstack_cli/commands/self.py +206 -0
- agentstack_cli/commands/server.py +237 -0
- agentstack_cli/configuration.py +76 -0
- agentstack_cli/console.py +25 -0
- agentstack_cli/data/.gitignore +2 -0
- agentstack_cli/data/helm-chart.tgz +0 -0
- agentstack_cli/data/lima-guestagent.Linux-aarch64.gz +0 -0
- agentstack_cli/data/limactl +0 -0
- agentstack_cli/utils.py +281 -0
- agentstack_cli-0.4.0.dist-info/METADATA +104 -0
- agentstack_cli-0.4.0.dist-info/RECORD +27 -0
- agentstack_cli-0.4.0.dist-info/WHEEL +4 -0
- agentstack_cli-0.4.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import datetime
|
|
5
|
+
import functools
|
|
6
|
+
import importlib.resources
|
|
7
|
+
import os
|
|
8
|
+
import pathlib
|
|
9
|
+
import platform
|
|
10
|
+
import shutil
|
|
11
|
+
import sys
|
|
12
|
+
import textwrap
|
|
13
|
+
import typing
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
import typer
|
|
17
|
+
from agentstack_sdk.platform import Provider
|
|
18
|
+
from tenacity import AsyncRetrying, retry_if_exception_type, stop_after_delay, wait_fixed
|
|
19
|
+
|
|
20
|
+
from agentstack_cli.async_typer import AsyncTyper
|
|
21
|
+
from agentstack_cli.commands.platform.base_driver import BaseDriver
|
|
22
|
+
from agentstack_cli.commands.platform.lima_driver import LimaDriver
|
|
23
|
+
from agentstack_cli.commands.platform.wsl_driver import WSLDriver
|
|
24
|
+
from agentstack_cli.console import console
|
|
25
|
+
from agentstack_cli.utils import verbosity
|
|
26
|
+
|
|
27
|
+
app = AsyncTyper()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@functools.cache
|
|
31
|
+
def get_driver(vm_name: str = "agentstack") -> BaseDriver:
|
|
32
|
+
has_lima = (importlib.resources.files("agentstack_cli") / "data" / "limactl").is_file() or shutil.which("limactl")
|
|
33
|
+
has_vz = os.path.exists("/System/Library/Frameworks/Virtualization.framework")
|
|
34
|
+
arch = "aarch64" if platform.machine().lower() == "arm64" else platform.machine().lower()
|
|
35
|
+
has_qemu = bool(shutil.which(f"qemu-system-{arch}"))
|
|
36
|
+
|
|
37
|
+
if platform.system() == "Windows" or shutil.which("wsl.exe"):
|
|
38
|
+
return WSLDriver(vm_name=vm_name)
|
|
39
|
+
elif has_lima and (has_vz or has_qemu):
|
|
40
|
+
return LimaDriver(vm_name=vm_name)
|
|
41
|
+
else:
|
|
42
|
+
console.error("Could not find a compatible VM runtime.")
|
|
43
|
+
if platform.system() == "Darwin":
|
|
44
|
+
console.hint("This version of macOS is unsupported, please update the system.")
|
|
45
|
+
elif platform.system() == "Linux":
|
|
46
|
+
if not has_lima:
|
|
47
|
+
console.hint(
|
|
48
|
+
"This Linux distribution is not suppored by Lima VM binary releases (required: glibc>=2.34). Manually install Lima VM >=1.2.1 through either:\n"
|
|
49
|
+
+ " - Your distribution's package manager, if available (https://repology.org/project/lima/versions)\n"
|
|
50
|
+
+ " - Homebrew, which uses its own separate glibc on Linux (https://brew.sh)\n"
|
|
51
|
+
+ " - Building it yourself, and ensuring that limactl is in PATH (https://lima-vm.io/docs/installation/source/)"
|
|
52
|
+
)
|
|
53
|
+
if not has_qemu:
|
|
54
|
+
console.hint(
|
|
55
|
+
f"QEMU is needed on Linux, please install it and ensure that qemu-system-{arch} is in PATH. Refer to https://www.qemu.org/download/ for instructions."
|
|
56
|
+
)
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command("start")
|
|
61
|
+
async def start(
|
|
62
|
+
set_values_list: typing.Annotated[
|
|
63
|
+
list[str], typer.Option("--set", help="Set Helm chart values using <key>=<value> syntax", default_factory=list)
|
|
64
|
+
],
|
|
65
|
+
import_images: typing.Annotated[
|
|
66
|
+
list[str],
|
|
67
|
+
typer.Option(
|
|
68
|
+
"--import", help="Import an image from a local Docker CLI into Agent Stack platform", default_factory=list
|
|
69
|
+
),
|
|
70
|
+
],
|
|
71
|
+
values_file: typing.Annotated[
|
|
72
|
+
pathlib.Path | None, typer.Option("-f", help="Set Helm chart values using yaml values file")
|
|
73
|
+
] = None,
|
|
74
|
+
vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
|
|
75
|
+
verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
|
|
76
|
+
):
|
|
77
|
+
"""Start Agent Stack platform."""
|
|
78
|
+
import agentstack_cli.commands.server
|
|
79
|
+
|
|
80
|
+
values_file_path = None
|
|
81
|
+
if values_file:
|
|
82
|
+
values_file_path = pathlib.Path(values_file)
|
|
83
|
+
if not values_file_path.is_file():
|
|
84
|
+
raise FileNotFoundError(f"Values file {values_file} not found.")
|
|
85
|
+
|
|
86
|
+
with verbosity(verbose):
|
|
87
|
+
driver = get_driver(vm_name=vm_name)
|
|
88
|
+
await driver.create_vm()
|
|
89
|
+
await driver.install_tools()
|
|
90
|
+
await driver.deploy(set_values_list=set_values_list, values_file=values_file_path, import_images=import_images)
|
|
91
|
+
|
|
92
|
+
with console.status("Waiting for Agent Stack platform to be ready...", spinner="dots"):
|
|
93
|
+
timeout = datetime.timedelta(minutes=20)
|
|
94
|
+
try:
|
|
95
|
+
async for attempt in AsyncRetrying(
|
|
96
|
+
stop=stop_after_delay(timeout),
|
|
97
|
+
wait=wait_fixed(datetime.timedelta(seconds=1)),
|
|
98
|
+
retry=retry_if_exception_type((httpx.HTTPError, ConnectionError)),
|
|
99
|
+
reraise=True,
|
|
100
|
+
):
|
|
101
|
+
with attempt:
|
|
102
|
+
await Provider.list()
|
|
103
|
+
except Exception as ex:
|
|
104
|
+
raise ConnectionError(
|
|
105
|
+
f"Server did not start in {timeout}. Please check your internet connection."
|
|
106
|
+
) from ex
|
|
107
|
+
|
|
108
|
+
console.success("Agent Stack platform started successfully!")
|
|
109
|
+
|
|
110
|
+
if any("phoenix.enabled=true" in value.lower() for value in set_values_list):
|
|
111
|
+
console.print(
|
|
112
|
+
textwrap.dedent("""\
|
|
113
|
+
|
|
114
|
+
License Notice:
|
|
115
|
+
When you enable Phoenix, be aware that Arize Phoenix is licensed under the Elastic License v2 (ELv2),
|
|
116
|
+
which has specific terms regarding commercial use and distribution. By enabling Phoenix, you acknowledge
|
|
117
|
+
that you are responsible for ensuring compliance with the ELv2 license terms for your specific use case.
|
|
118
|
+
Please review the Phoenix license (https://github.com/Arize-ai/phoenix/blob/main/LICENSE) before enabling
|
|
119
|
+
this feature in production environments.
|
|
120
|
+
"""),
|
|
121
|
+
style="dim",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
await agentstack_cli.commands.server.server_login("http://localhost:8333")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.command("stop")
|
|
128
|
+
async def stop(
|
|
129
|
+
vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
|
|
130
|
+
verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
|
|
131
|
+
):
|
|
132
|
+
"""Stop Agent Stack platform."""
|
|
133
|
+
with verbosity(verbose):
|
|
134
|
+
driver = get_driver(vm_name=vm_name)
|
|
135
|
+
if not await driver.status():
|
|
136
|
+
console.info("Agent Stack platform not found. Nothing to stop.")
|
|
137
|
+
return
|
|
138
|
+
await driver.stop()
|
|
139
|
+
console.success("Agent Stack platform stopped successfully.")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.command("delete")
|
|
143
|
+
async def delete(
|
|
144
|
+
vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
|
|
145
|
+
verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
|
|
146
|
+
):
|
|
147
|
+
"""Delete Agent Stack platform."""
|
|
148
|
+
with verbosity(verbose):
|
|
149
|
+
driver = get_driver(vm_name=vm_name)
|
|
150
|
+
await driver.delete()
|
|
151
|
+
console.success("Agent Stack platform deleted successfully.")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@app.command("import")
|
|
155
|
+
async def import_image_cmd(
|
|
156
|
+
tag: typing.Annotated[str, typer.Argument(help="Docker image tag to import")],
|
|
157
|
+
vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
|
|
158
|
+
verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
|
|
159
|
+
):
|
|
160
|
+
"""Import a local docker image into the Agent Stack platform."""
|
|
161
|
+
with verbosity(verbose):
|
|
162
|
+
driver = get_driver(vm_name=vm_name)
|
|
163
|
+
if (await driver.status()) != "running":
|
|
164
|
+
console.error("Agent Stack platform is not running.")
|
|
165
|
+
sys.exit(1)
|
|
166
|
+
await driver.import_image(tag)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@app.command("exec")
|
|
170
|
+
async def exec_cmd(
|
|
171
|
+
command: typing.Annotated[list[str] | None, typer.Argument()] = None,
|
|
172
|
+
vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "agentstack",
|
|
173
|
+
verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
|
|
174
|
+
):
|
|
175
|
+
"""For debugging -- execute a command inside the Agent Stack platform VM."""
|
|
176
|
+
with verbosity(verbose, show_success_status=False):
|
|
177
|
+
driver = get_driver(vm_name=vm_name)
|
|
178
|
+
if (await driver.status()) != "running":
|
|
179
|
+
console.error("Agent Stack platform is not running.")
|
|
180
|
+
sys.exit(1)
|
|
181
|
+
await driver.exec(command or ["/bin/bash"])
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import abc
|
|
5
|
+
import importlib.resources
|
|
6
|
+
import pathlib
|
|
7
|
+
import shlex
|
|
8
|
+
import typing
|
|
9
|
+
from subprocess import CompletedProcess
|
|
10
|
+
from textwrap import dedent
|
|
11
|
+
|
|
12
|
+
import anyio
|
|
13
|
+
import pydantic
|
|
14
|
+
import yaml
|
|
15
|
+
from tenacity import AsyncRetrying, stop_after_attempt
|
|
16
|
+
|
|
17
|
+
import agentstack_cli.commands.platform.istio
|
|
18
|
+
from agentstack_cli.configuration import Configuration
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseDriver(abc.ABC):
|
|
22
|
+
vm_name: str
|
|
23
|
+
|
|
24
|
+
def __init__(self, vm_name: str = "agentstack"):
|
|
25
|
+
self.vm_name = vm_name
|
|
26
|
+
|
|
27
|
+
@abc.abstractmethod
|
|
28
|
+
async def run_in_vm(
|
|
29
|
+
self,
|
|
30
|
+
command: list[str],
|
|
31
|
+
message: str,
|
|
32
|
+
env: dict[str, str] | None = None,
|
|
33
|
+
input: bytes | None = None,
|
|
34
|
+
) -> CompletedProcess[bytes]: ...
|
|
35
|
+
|
|
36
|
+
@abc.abstractmethod
|
|
37
|
+
async def status(self) -> typing.Literal["running"] | str | None: ...
|
|
38
|
+
|
|
39
|
+
@abc.abstractmethod
|
|
40
|
+
async def create_vm(self) -> None: ...
|
|
41
|
+
|
|
42
|
+
@abc.abstractmethod
|
|
43
|
+
async def stop(self) -> None: ...
|
|
44
|
+
|
|
45
|
+
@abc.abstractmethod
|
|
46
|
+
async def delete(self) -> None: ...
|
|
47
|
+
|
|
48
|
+
@abc.abstractmethod
|
|
49
|
+
async def import_image(self, tag: str) -> None: ...
|
|
50
|
+
|
|
51
|
+
@abc.abstractmethod
|
|
52
|
+
async def exec(self, command: list[str]) -> None: ...
|
|
53
|
+
|
|
54
|
+
async def install_tools(self) -> None:
|
|
55
|
+
# Configure k3s registry for local registry access
|
|
56
|
+
registry_config = dedent(
|
|
57
|
+
"""\
|
|
58
|
+
mirrors:
|
|
59
|
+
"agentstack-registry-svc.default:5001":
|
|
60
|
+
endpoint:
|
|
61
|
+
- "http://localhost:30501"
|
|
62
|
+
configs:
|
|
63
|
+
"agentstack-registry-svc.default:5001":
|
|
64
|
+
tls:
|
|
65
|
+
insecure_skip_verify: true
|
|
66
|
+
"""
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
await self.run_in_vm(
|
|
70
|
+
[
|
|
71
|
+
"sh",
|
|
72
|
+
"-c",
|
|
73
|
+
(
|
|
74
|
+
f"sudo mkdir -p /etc/rancher/k3s /registry-data && "
|
|
75
|
+
f"echo '{registry_config}' | "
|
|
76
|
+
"sudo tee /etc/rancher/k3s/registries.yaml > /dev/null"
|
|
77
|
+
),
|
|
78
|
+
],
|
|
79
|
+
"Configuring k3s registry",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
await self.run_in_vm(
|
|
83
|
+
[
|
|
84
|
+
"sh",
|
|
85
|
+
"-c",
|
|
86
|
+
"which k3s || curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644 --https-listen-port=16443",
|
|
87
|
+
],
|
|
88
|
+
"Installing k3s",
|
|
89
|
+
)
|
|
90
|
+
await self.run_in_vm(
|
|
91
|
+
[
|
|
92
|
+
"sh",
|
|
93
|
+
"-c",
|
|
94
|
+
"which helm || curl -sfL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash",
|
|
95
|
+
],
|
|
96
|
+
"Installing Helm",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
async def deploy(
|
|
100
|
+
self,
|
|
101
|
+
set_values_list: list[str],
|
|
102
|
+
values_file: pathlib.Path | None = None,
|
|
103
|
+
import_images: list[str] | None = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
await self.run_in_vm(
|
|
106
|
+
["sh", "-c", "mkdir -p /tmp/agentstack && cat >/tmp/agentstack/chart.tgz"],
|
|
107
|
+
"Preparing Helm chart",
|
|
108
|
+
input=(importlib.resources.files("agentstack_cli") / "data" / "helm-chart.tgz").read_bytes(),
|
|
109
|
+
)
|
|
110
|
+
values = {
|
|
111
|
+
**{svc: {"service": {"type": "LoadBalancer"}} for svc in ["collector", "docling", "ui", "phoenix"]},
|
|
112
|
+
"hostNetwork": True,
|
|
113
|
+
"externalRegistries": {"public_github": str(Configuration().agent_registry)},
|
|
114
|
+
"encryptionKey": "Ovx8qImylfooq4-HNwOzKKDcXLZCB3c_m0JlB9eJBxc=",
|
|
115
|
+
"features": {
|
|
116
|
+
"uiNavigation": True,
|
|
117
|
+
"selfRegistration": True,
|
|
118
|
+
"localSetup": True,
|
|
119
|
+
"generateConversationTitle": False, # TODO: enable when UI implementation is ready
|
|
120
|
+
},
|
|
121
|
+
"auth": {"enabled": False},
|
|
122
|
+
}
|
|
123
|
+
if values_file:
|
|
124
|
+
values.update(yaml.safe_load(values_file.read_text()))
|
|
125
|
+
await self.run_in_vm(
|
|
126
|
+
["sh", "-c", "cat >/tmp/agentstack/values.yaml"],
|
|
127
|
+
"Preparing Helm values",
|
|
128
|
+
input=yaml.dump(values).encode("utf-8"),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
images_str = (
|
|
132
|
+
await self.run_in_vm(
|
|
133
|
+
[
|
|
134
|
+
"/bin/bash",
|
|
135
|
+
"-c",
|
|
136
|
+
"helm template agentstack /tmp/agentstack/chart.tgz --values=/tmp/agentstack/values.yaml "
|
|
137
|
+
+ " ".join(shlex.quote(f"--set={value}") for value in set_values_list)
|
|
138
|
+
+ " | sed -n '/^\\s*image:/{ /{{/!{ s/.*image:\\s*//p } }'",
|
|
139
|
+
],
|
|
140
|
+
"Listing necessary images",
|
|
141
|
+
)
|
|
142
|
+
).stdout.decode()
|
|
143
|
+
for image in import_images or []:
|
|
144
|
+
await self.import_image(image)
|
|
145
|
+
for image in {typing.cast(str, yaml.safe_load(line)) for line in images_str.splitlines()} - set(
|
|
146
|
+
import_images or []
|
|
147
|
+
):
|
|
148
|
+
async for attempt in AsyncRetrying(stop=stop_after_attempt(5)):
|
|
149
|
+
with attempt:
|
|
150
|
+
attempt_num = attempt.retry_state.attempt_number
|
|
151
|
+
await self.run_in_vm(
|
|
152
|
+
[
|
|
153
|
+
"k3s",
|
|
154
|
+
"ctr",
|
|
155
|
+
"image",
|
|
156
|
+
"pull",
|
|
157
|
+
image if "." in image.split("/")[0] else f"docker.io/{image}",
|
|
158
|
+
],
|
|
159
|
+
f"Pulling image {image}" + (f" (attempt {attempt_num})" if attempt_num > 1 else ""),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if any("auth.oidc.enabled=true" in value.lower() for value in set_values_list):
|
|
163
|
+
await agentstack_cli.commands.platform.istio.install(driver=self)
|
|
164
|
+
|
|
165
|
+
kubeconfig_path = anyio.Path(Configuration().lima_home) / self.vm_name / "copied-from-guest" / "kubeconfig.yaml"
|
|
166
|
+
await kubeconfig_path.parent.mkdir(parents=True, exist_ok=True)
|
|
167
|
+
await kubeconfig_path.write_text(
|
|
168
|
+
(
|
|
169
|
+
await self.run_in_vm(
|
|
170
|
+
["/bin/cat", "/etc/rancher/k3s/k3s.yaml"],
|
|
171
|
+
"Copying kubeconfig from Agent Stack platform",
|
|
172
|
+
)
|
|
173
|
+
).stdout.decode()
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
await self.run_in_vm(
|
|
177
|
+
[
|
|
178
|
+
"helm",
|
|
179
|
+
"upgrade",
|
|
180
|
+
"--install",
|
|
181
|
+
"agentstack",
|
|
182
|
+
"/tmp/agentstack/chart.tgz",
|
|
183
|
+
"--namespace=default",
|
|
184
|
+
"--create-namespace",
|
|
185
|
+
"--values=/tmp/agentstack/values.yaml",
|
|
186
|
+
"--timeout=20m",
|
|
187
|
+
"--wait",
|
|
188
|
+
"--kubeconfig=/etc/rancher/k3s/k3s.yaml",
|
|
189
|
+
*(f"--set={value}" for value in set_values_list),
|
|
190
|
+
],
|
|
191
|
+
"Deploying Agent Stack platform with Helm",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if import_images:
|
|
195
|
+
await self.run_in_vm(
|
|
196
|
+
["k3s", "kubectl", "rollout", "restart", "deployment"],
|
|
197
|
+
"Restarting deployments to load imported images",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
async def version(self) -> str | None:
|
|
201
|
+
if (await self.status()) != "running":
|
|
202
|
+
return None
|
|
203
|
+
HelmStatus = typing.TypedDict("HelmStatus", {"status": str, "app_version": str})
|
|
204
|
+
helm_status = pydantic.TypeAdapter(list[HelmStatus]).validate_json(
|
|
205
|
+
(
|
|
206
|
+
await self.run_in_vm(
|
|
207
|
+
[
|
|
208
|
+
"/usr/local/bin/helm",
|
|
209
|
+
"--kubeconfig=/etc/rancher/k3s/k3s.yaml",
|
|
210
|
+
"ls",
|
|
211
|
+
"--namespace=default",
|
|
212
|
+
"--filter=^agentstack$",
|
|
213
|
+
"-o",
|
|
214
|
+
"json",
|
|
215
|
+
],
|
|
216
|
+
"Getting Agent Stack platform version",
|
|
217
|
+
)
|
|
218
|
+
).stdout
|
|
219
|
+
)
|
|
220
|
+
if helm_status[0]["status"] != "deployed":
|
|
221
|
+
return None
|
|
222
|
+
return helm_status[0]["app_version"]
|
|
@@ -0,0 +1,186 @@
|
|
|
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
|
+
)
|