boatyard 0.6.0.dev170__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.
Files changed (42) hide show
  1. boatyard/__init__.py +0 -0
  2. boatyard/build.py +267 -0
  3. boatyard/cli.py +23 -0
  4. boatyard/compose.py +24 -0
  5. boatyard/config/__init__.py +0 -0
  6. boatyard/config/config.py +787 -0
  7. boatyard/config/lock.py +152 -0
  8. boatyard/config/values.py +188 -0
  9. boatyard/crane.py +23 -0
  10. boatyard/dockerfiles.py +201 -0
  11. boatyard/lock.py +233 -0
  12. boatyard/markers.py +201 -0
  13. boatyard/paths.py +5 -0
  14. boatyard/publish.py +72 -0
  15. boatyard/py.typed +0 -0
  16. boatyard/score.py +267 -0
  17. boatyard/score_compose.py +274 -0
  18. boatyard/score_k8s.py +152 -0
  19. boatyard/score_patch_templates/gpu-cuda.tpl +24 -0
  20. boatyard/score_patch_templates/gpu-pod.tpl +43 -0
  21. boatyard/score_patch_templates/gpu-rocm.tpl +30 -0
  22. boatyard/score_patch_templates/probe-adapter-healthchecks.tpl +55 -0
  23. boatyard/score_patch_templates/restart.tpl +21 -0
  24. boatyard/score_patch_templates/scale-to-zero.tpl +30 -0
  25. boatyard/temp.py +5 -0
  26. boatyard/verbs/__init__.py +0 -0
  27. boatyard/verbs/build.py +54 -0
  28. boatyard/verbs/down.py +39 -0
  29. boatyard/verbs/lock.py +34 -0
  30. boatyard/verbs/matrix.py +57 -0
  31. boatyard/verbs/mirror.py +50 -0
  32. boatyard/verbs/publish.py +138 -0
  33. boatyard/verbs/score.py +161 -0
  34. boatyard/verbs/tree_tags.py +35 -0
  35. boatyard/verbs/up.py +265 -0
  36. boatyard/yaml.py +34 -0
  37. boatyard-0.6.0.dev170.dist-info/METADATA +12 -0
  38. boatyard-0.6.0.dev170.dist-info/RECORD +42 -0
  39. boatyard-0.6.0.dev170.dist-info/WHEEL +4 -0
  40. boatyard-0.6.0.dev170.dist-info/entry_points.txt +2 -0
  41. boatyard-0.6.0.dev170.dist-info/licenses/LICENSE +201 -0
  42. boatyard-0.6.0.dev170.dist-info/licenses/NOTICE +1 -0
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from tempfile import TemporaryDirectory
6
+ from typing import Annotated, NamedTuple
7
+
8
+ import typer
9
+ from bashrun.bash import bash, bash_check, bash_output
10
+
11
+ from boatyard import dockerfiles
12
+ from boatyard.config.config import ConfigFile, Target
13
+ from boatyard.config.lock import LOCK_PATH, LockFile
14
+ from boatyard.yaml import load_yaml_model
15
+ from boatyard.score import (
16
+ SECRETS_FILE,
17
+ SECRETS_OUTPUT,
18
+ SecretsDeclaration,
19
+ write_file,
20
+ )
21
+ from boatyard.score_compose import generate_compose
22
+ from boatyard.paths import DEFAULT_CONFIG_PATH
23
+ from boatyard.score_k8s import generate_k8s
24
+
25
+ app = typer.Typer()
26
+
27
+
28
+ class ToolRequirement(NamedTuple):
29
+ binary: str
30
+ version: str
31
+ probe: str
32
+ archive: str
33
+ extract: str
34
+
35
+
36
+ @app.command()
37
+ def generate_score(
38
+ target: Annotated[Target, typer.Option(help="Which Score artifact to generate.")] = "both",
39
+ local: Annotated[
40
+ bool,
41
+ typer.Option(
42
+ "--local",
43
+ help="Generate for a local cluster: local storage class, written to the configured local output path.",
44
+ ),
45
+ ] = False,
46
+ check: Annotated[
47
+ bool,
48
+ typer.Option("--check", help="Regenerate and exit 1 when the committed artifacts are stale."),
49
+ ] = False,
50
+ ) -> None:
51
+ if check and local:
52
+ raise typer.BadParameter("--check and --local are mutually exclusive")
53
+ root = Path.cwd()
54
+ config_path = root / DEFAULT_CONFIG_PATH
55
+ config = ConfigFile.load(config_path) if config_path.is_file() else None
56
+ if config is None or not (config.local_service_entries() or config.k8s):
57
+ if check:
58
+ print("no local service entries and no k8s section; nothing to check")
59
+ return
60
+ raise ValueError("boatyard.yml declares no local service entries and no k8s section")
61
+ local_entries = config.local_service_entries()
62
+
63
+ if config.k8s is None and target in ("k8s", "both"):
64
+ message = (
65
+ "--target k8s: this config declares no k8s section (compose-only)"
66
+ if target == "k8s"
67
+ else "--target both: this config declares no k8s section; pass --target compose"
68
+ )
69
+ raise typer.BadParameter(message)
70
+ if not local_entries and target in ("compose", "both"):
71
+ message = (
72
+ "--target compose: this config declares no local service entries (k8s-only)"
73
+ if target == "compose"
74
+ else "--target both: this config declares no local service entries; pass --target k8s"
75
+ )
76
+ raise typer.BadParameter(message)
77
+ if config.k8s is None and local:
78
+ raise typer.BadParameter("--local requires a k8s section; this config declares none")
79
+
80
+ # Require the build keys, derive the tree tags, and load the lock.
81
+ if not (config.repository or config.base_images):
82
+ raise ValueError("repository or base_images is required in boatyard.yml")
83
+ service_shas = dockerfiles.compute_service_shas(root, config.service_builds)
84
+ lock_path = root / LOCK_PATH
85
+ if not lock_path.is_file():
86
+ raise ValueError(f"{lock_path.as_posix()}: the image lock is required")
87
+ lock = LockFile.load(lock_path, config)
88
+ if not lock.images and not lock.charts:
89
+ raise ValueError(f"{lock_path.as_posix()}: the image lock carries no entries")
90
+
91
+ # Require the secrets declaration; an empty file parses as the empty declaration.
92
+ secrets_path = root / SECRETS_FILE
93
+ if not secrets_path.is_file():
94
+ raise ValueError(f"{secrets_path.as_posix()}: the secrets declaration is required")
95
+ secrets = load_yaml_model(SecretsDeclaration, secrets_path)
96
+
97
+ ensure_tools(
98
+ [
99
+ ToolRequirement(
100
+ binary=binary,
101
+ version=version,
102
+ probe=f"{binary} {version}",
103
+ archive=(
104
+ f"https://github.com/score-spec/{binary}/releases/download/{version}/"
105
+ f"{binary}_{version}_linux_amd64.tar.gz"
106
+ ),
107
+ extract=binary,
108
+ )
109
+ for binary, version in (
110
+ ("score-compose", config.score_compose_version),
111
+ ("score-k8s", config.score_k8s_version),
112
+ )
113
+ ]
114
+ + [
115
+ ToolRequirement(
116
+ binary="helm",
117
+ version=config.helm_version,
118
+ probe=f'"v{config.helm_version}"',
119
+ archive=f"https://get.helm.sh/helm-v{config.helm_version}-linux-amd64.tar.gz",
120
+ extract="--strip-components=1 linux-amd64/helm",
121
+ )
122
+ ]
123
+ )
124
+
125
+ outputs: list[Path] = []
126
+ if target in ("both", "compose"):
127
+ outputs.extend(generate_compose(config, lock, service_shas, secrets))
128
+
129
+ if target in ("both", "k8s") and config.k8s is not None:
130
+ outputs.append(generate_k8s(config, config.k8s, lock, service_shas, local, secrets))
131
+
132
+ secrets_output = root / SECRETS_OUTPUT
133
+ secrets_output.parent.mkdir(parents=True, exist_ok=True)
134
+ write_file(
135
+ secrets_output,
136
+ "# Generated by generate-score from the secrets declaration\n"
137
+ + "".join(f"{name}\n" for name, key in sorted(secrets.root.items()) if key.mint),
138
+ )
139
+ outputs.append(secrets_output)
140
+
141
+ if check:
142
+ pathspec = " ".join(output.as_posix() for output in outputs)
143
+ if bash_output(f"git status --porcelain -- {pathspec}").strip():
144
+ bash(f"git diff -- {pathspec}")
145
+ raise SystemExit(
146
+ "score artifacts are stale. Run 'generate-score' and commit the result as 'Run generate-score'."
147
+ )
148
+
149
+
150
+ def ensure_tools(requirements: list[ToolRequirement]) -> None:
151
+ for requirement in requirements:
152
+ if bash_check(f"{requirement.binary} version") and requirement.probe in bash_output(
153
+ f"{requirement.binary} version"
154
+ ):
155
+ continue
156
+ print(f"Install {requirement.binary} {requirement.version}")
157
+ sudo = "sudo " if os.geteuid() != 0 else ""
158
+ with TemporaryDirectory() as directory:
159
+ archive = Path(directory) / Path(requirement.archive).name
160
+ bash(f"curl -fsSL {requirement.archive} -o {archive.as_posix()}")
161
+ bash(f"{sudo}tar -xzf {archive.as_posix()} -C /usr/local/bin {requirement.extract}")
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated
5
+
6
+ import typer
7
+
8
+ from boatyard import dockerfiles
9
+ from boatyard.config.config import ConfigFile
10
+ from boatyard.dockerfiles import service_sha_env
11
+ from boatyard.paths import DEFAULT_CONFIG_PATH
12
+
13
+ app = typer.Typer()
14
+
15
+
16
+ @app.command()
17
+ def tree_tags(
18
+ services: Annotated[
19
+ list[str] | None,
20
+ typer.Argument(help="Derived service names; each answers its HEAD tree tag, one per line."),
21
+ ] = None,
22
+ ) -> None:
23
+ root = Path.cwd()
24
+ config = ConfigFile.load(root / DEFAULT_CONFIG_PATH)
25
+ vocabulary = sorted(config.service_builds)
26
+ if not services:
27
+ raise typer.BadParameter(
28
+ f"at least one service name is required; this repo derives {vocabulary or 'no services'}"
29
+ )
30
+ unknown = [name for name in services if name not in config.service_builds]
31
+ if unknown:
32
+ raise typer.BadParameter(f"unknown service(s) {unknown}; this repo derives {vocabulary}")
33
+ shas = dockerfiles.compute_service_shas(config.root, config.service_builds)
34
+ for name in services:
35
+ print(shas[service_sha_env(name)])
boatyard/verbs/up.py ADDED
@@ -0,0 +1,265 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from collections.abc import Generator, Mapping
5
+ from contextlib import ExitStack, contextmanager
6
+ from itertools import groupby
7
+ from pathlib import Path
8
+ from tempfile import NamedTemporaryFile
9
+ from typing import Annotated
10
+
11
+ import typer
12
+ import yaml
13
+ from bashrun.bash import bash_handoff
14
+
15
+ from boatyard.build import run_build
16
+ from boatyard.compose import compose_command
17
+ from boatyard.config.config import (
18
+ CADDY_IMAGE,
19
+ ConfigFile,
20
+ EDGE_SERVICE_NAME,
21
+ OCI_REFERENCE_PREFIX,
22
+ Yaml,
23
+ bundle_lock_key,
24
+ compose_bundle_output_path,
25
+ double_dollars,
26
+ env_lock_slot,
27
+ scan_bundle_texts,
28
+ )
29
+ from boatyard.config.lock import LockFile
30
+ from boatyard.config.values import ValuesFile
31
+ from boatyard.paths import DEFAULT_CONFIG_PATH, LOCK_PATH, VALUES_FILE
32
+
33
+ # Name the edge listener port and its configs key
34
+ EDGE_LISTEN_PORT = 8443
35
+ EDGE_CONFIG_NAME = "devkit-edge-caddyfile"
36
+
37
+ app = typer.Typer()
38
+
39
+
40
+ @app.command()
41
+ def up(
42
+ attached: Annotated[bool, typer.Option("--attached", "-a", help="Run in foreground (not detached)")] = False,
43
+ quiet_pull: Annotated[
44
+ bool,
45
+ typer.Option("--quiet-pull", "-q", help="Suppress per-layer pull progress (still shows pull/push totals)."),
46
+ ] = False,
47
+ build: Annotated[
48
+ bool,
49
+ typer.Option("--build", help="Build all images locally before bringing the stack up"),
50
+ ] = False,
51
+ ) -> None:
52
+ # Load the config and the typed machine values from the working directory
53
+ root = Path.cwd()
54
+ config = ConfigFile.load(root / DEFAULT_CONFIG_PATH)
55
+ if not (root / VALUES_FILE).exists():
56
+ raise ValueError(f"{VALUES_FILE.as_posix()} is required")
57
+ config_values = ValuesFile.load(root / VALUES_FILE, config)
58
+
59
+ # Require the config to declare a compose stack
60
+ if not config.services:
61
+ raise ValueError("boatyard.yml must declare services")
62
+
63
+ # Resolve the chosen options into leaf services
64
+ services = config.resolve_services(config_values.choices)
65
+
66
+ # Project the consumer lock's manifest references and seed the assembly accumulators
67
+ lock = LockFile.load(config.root / LOCK_PATH, config)
68
+ consumer_entries = lock.manifest_references(config_values.platform)
69
+ entries: dict[str, str] = {}
70
+ includes: list[Yaml] = []
71
+ requirements: dict[str, list[str]] = {}
72
+
73
+ # Collect each entry's include, requirements, and env entries in declaration order
74
+ for service in services:
75
+ name = service.name
76
+
77
+ # Include a remote ref as its lock-pinned digest
78
+ if service.entry.ref is not None and name not in config_values.path_overrides:
79
+ key = bundle_lock_key(name)
80
+ pinned = lock.require(key, f"bundle {name!r}")
81
+ requirements[name] = lock.bundles[key].requires
82
+ includes.append(f"{OCI_REFERENCE_PREFIX}{pinned}")
83
+ continue
84
+
85
+ # Include a local entry through its generated output
86
+ if service.entry.ref is None:
87
+ bundle_compose_path = compose_bundle_output_path(config.root, name)
88
+ if not bundle_compose_path.is_file():
89
+ raise ValueError(
90
+ f"selected entry {name!r} has no bundle output {bundle_compose_path.as_posix()}; generate-score writes it"
91
+ )
92
+ lock_references = consumer_entries
93
+ else:
94
+ # Include an overridden ref through its local path
95
+ candidate = config_values.path_overrides[name]
96
+ bundle_compose_path = (candidate if candidate.is_absolute() else config.root / candidate).resolve()
97
+ if not bundle_compose_path.is_file():
98
+ raise ValueError(f"path_overrides.{name}: {bundle_compose_path.as_posix()} does not exist")
99
+
100
+ # Walk up from the override to the boatyard.yml-bearing producer root
101
+ producer_root = next(
102
+ (ancestor for ancestor in bundle_compose_path.parents if (ancestor / DEFAULT_CONFIG_PATH).is_file()),
103
+ None,
104
+ )
105
+ if producer_root is None:
106
+ raise ValueError(
107
+ f"{bundle_compose_path.as_posix()}: no boatyard.yml-bearing repo root above the bundle override"
108
+ )
109
+
110
+ # Project the producer lock's manifest references under the consumer's platform
111
+ lock_references = LockFile.load(
112
+ producer_root / LOCK_PATH, ConfigFile.load(producer_root / DEFAULT_CONFIG_PATH)
113
+ ).manifest_references(config_values.platform)
114
+
115
+ # Scan the bundle file for its requirements and refuse the reserved edge service
116
+ text = bundle_compose_path.read_text(encoding="utf-8")
117
+ scan = scan_bundle_texts(name, service.routes, service.ports, [text])
118
+ if EDGE_SERVICE_NAME in scan.service_names:
119
+ raise ValueError(
120
+ f"{bundle_compose_path.as_posix()}: "
121
+ f"the included bundle defines the reserved service {EDGE_SERVICE_NAME!r}"
122
+ )
123
+
124
+ # Record the entry's requirements, include the file by absolute path, and fan its lock keys under the marker
125
+ requirements[name] = scan.requires
126
+ includes.append(bundle_compose_path.resolve().as_posix())
127
+ entries.update({env_lock_slot(name, key): value for key, value in lock_references.items()})
128
+
129
+ # Refuse bundles whose requirements no environment source fills, naming shell-set slots as ignored
130
+ filled = {variable for variable, value in {**config_values.variables, **entries}.items() if value}
131
+ missing = {
132
+ name: sorted(set(required) - filled) for name, required in requirements.items() if set(required) - filled
133
+ }
134
+ if missing:
135
+ name, absent = min(missing.items())
136
+ ignored = sorted(shell_name for shell_name in absent if os.environ.get(shell_name))
137
+ note = f"; {', '.join(ignored)} set in the shell and ignored" if ignored else ""
138
+ raise ValueError(
139
+ f"bundle {name!r} requires {absent}, which no environment source sets "
140
+ f"(boatyard.values.yaml or the lock env file){note}"
141
+ )
142
+
143
+ # Group the ports under their namespaced owners into per-service stanzas
144
+ document_services: dict[str, Yaml] = {
145
+ f"{service.name}-{owner}": {
146
+ "ports": [port.model_dump(exclude={"service"}, exclude_none=True) for port in owner_ports]
147
+ }
148
+ for service in services
149
+ for owner, owner_ports in groupby(
150
+ sorted(service.ports, key=lambda port: port.service), key=lambda port: port.service
151
+ )
152
+ }
153
+
154
+ # Assemble the root document, sections omitted when empty
155
+ document: dict[str, Yaml] = {"name": config.project_name}
156
+
157
+ # Render the edge and its Caddyfile when a gateway is declared
158
+ if config.gateway is not None:
159
+ edge: dict[str, Yaml] = {
160
+ "image": consumer_entries[CADDY_IMAGE],
161
+ "configs": [{"source": EDGE_CONFIG_NAME, "target": "/etc/caddy/Caddyfile"}],
162
+ "ports": [{"target": str(EDGE_LISTEN_PORT), "published": config.gateway, "protocol": "tcp"}],
163
+ # Attach the edge to every selected bundle's network
164
+ "networks": dict.fromkeys(f"{service.name}-net" for service in services),
165
+ }
166
+ if any(service.auth for service in services):
167
+ if config_values.public_url is None:
168
+ raise ValueError("an auth option is selected; public_url is required in boatyard.values.yaml")
169
+ edge["environment"] = config_values.public_url.forwarded
170
+ document_services[EDGE_SERVICE_NAME] = edge
171
+
172
+ # Render one handle block per route in declaration order
173
+ lines = [f":{EDGE_LISTEN_PORT} {{"]
174
+ for service, index, route in (
175
+ (service, index, route) for service in services for index, route in enumerate(service.routes)
176
+ ):
177
+ upstream = f"{service.name}-{route.upstream}"
178
+ opener = (
179
+ ("\thandle_path" if route.strip_prefix else "\thandle")
180
+ + ("" if route.path == "/*" else f" {route.path}")
181
+ + " {"
182
+ )
183
+ if route.raw is not None:
184
+ lines.extend((opener, *["\t\t" + line for line in route.raw.splitlines()], "\t}"))
185
+ continue
186
+
187
+ if service.auth and index == 0:
188
+ lines.extend((
189
+ opener,
190
+ *[
191
+ f"\t\treverse_proxy {upstream} {{",
192
+ "\t\t\theader_up X-Forwarded-Proto {$FORWARDED_PROTO}",
193
+ "\t\t\theader_up X-Forwarded-Port {$FORWARDED_PORT}",
194
+ "\t\t}",
195
+ ],
196
+ "\t}",
197
+ ))
198
+ continue
199
+
200
+ lines.extend((opener, *[f"\t\treverse_proxy {upstream}"], "\t}"))
201
+ lines.append("}")
202
+ document["configs"] = {EDGE_CONFIG_NAME: {"content": double_dollars("\n".join(lines) + "\n")}}
203
+
204
+ if includes:
205
+ document["include"] = includes
206
+ if document_services:
207
+ document["services"] = document_services
208
+
209
+ with ExitStack() as stack:
210
+ # Materialize the env file
211
+ with NamedTemporaryFile("w", suffix=".env", prefix="devkit-env-", delete=False, encoding="utf-8") as handle:
212
+ handle.write(
213
+ "".join(
214
+ f"# {header}\n{render_env_entries(section)}"
215
+ for header, section in ((VALUES_FILE.name, config_values.variables), (LOCK_PATH.name, entries))
216
+ if section
217
+ )
218
+ )
219
+ materialized_env_file = Path(handle.name)
220
+ stack.enter_context(lock_env_unwind(materialized_env_file))
221
+
222
+ # Materialize the compose file
223
+ with NamedTemporaryFile(
224
+ "w", suffix=".yaml", prefix="devkit-assembly-", delete=False, encoding="utf-8"
225
+ ) as handle:
226
+ handle.write(yaml.safe_dump(document, sort_keys=False, default_flow_style=False))
227
+ materialized_compose_file = Path(handle.name)
228
+ stack.callback(materialized_compose_file.unlink, missing_ok=True)
229
+
230
+ # Build local images
231
+ if build:
232
+ run_build(config=config)
233
+
234
+ # Hand off to docker compose
235
+ bash_handoff(
236
+ compose_command(
237
+ root,
238
+ materialized_compose_file,
239
+ "up",
240
+ *["--pull", "missing", *(["--quiet-pull"] if quiet_pull else []), *(["-d"] if not attached else [])],
241
+ env_file=materialized_env_file,
242
+ slot_names=[
243
+ *config_values.variables,
244
+ *entries,
245
+ *(name for required in requirements.values() for name in required),
246
+ ],
247
+ )
248
+ )
249
+
250
+
251
+ def render_env_entries(entries: Mapping[str, str]) -> str:
252
+ # Render the entries as sorted dotenv lines
253
+ return "".join(f"{name}={reference}\n" for name, reference in sorted(entries.items()))
254
+
255
+
256
+ @contextmanager
257
+ def lock_env_unwind(path: Path) -> Generator[None]:
258
+ # Keep the env file on failure echoing only its path, since contents may carry secrets, and unlink it on success
259
+ try:
260
+ yield
261
+ except BaseException:
262
+ print(f"env file kept for inspection: {path}")
263
+ raise
264
+
265
+ path.unlink(missing_ok=True)
boatyard/yaml.py ADDED
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import yaml
6
+ from pydantic import BaseModel
7
+
8
+
9
+ def load_yaml_model[ModelT: BaseModel](
10
+ model: type[ModelT], path: Path, context: dict[str, object] | None = None
11
+ ) -> ModelT:
12
+ source = path.as_posix()
13
+ text = path.read_text(encoding="utf-8")
14
+ document_node = yaml.SafeLoader(text).get_single_node()
15
+ if document_node is not None:
16
+ _reject_duplicate_keys(document_node, source)
17
+ loaded: object = yaml.safe_load(text)
18
+ if loaded is None:
19
+ loaded = {}
20
+ return model.model_validate(loaded, context=context)
21
+
22
+
23
+ def _reject_duplicate_keys(node: yaml.Node, source: str) -> None:
24
+ if isinstance(node, yaml.MappingNode):
25
+ seen: set[object] = set()
26
+ for key_node, value_node in node.value:
27
+ key = key_node.value if isinstance(key_node, yaml.ScalarNode) else repr(key_node.value)
28
+ if key in seen:
29
+ raise ValueError(f"{source}: line {key_node.start_mark.line + 1}: duplicate key {key!r}")
30
+ seen.add(key)
31
+ _reject_duplicate_keys(value_node, source)
32
+ elif isinstance(node, yaml.SequenceNode):
33
+ for item in node.value:
34
+ _reject_duplicate_keys(item, source)
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.5
2
+ Name: boatyard
3
+ Version: 0.6.0.dev170
4
+ Summary: Deploy-files definition language and compiler, plus a runtime for the compose target
5
+ License-File: LICENSE
6
+ License-File: NOTICE
7
+ Requires-Python: >=3.13
8
+ Requires-Dist: bashrun>=0.1.0
9
+ Requires-Dist: packaging>=24.1
10
+ Requires-Dist: pydantic>=2.11.7
11
+ Requires-Dist: pyyaml>=6.0
12
+ Requires-Dist: typer>=0.17.4
@@ -0,0 +1,42 @@
1
+ boatyard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ boatyard/build.py,sha256=Ys8UmebsLzfOnUpfpuvCSeIBX1A1DOcnNxkf6QyntE8,10666
3
+ boatyard/cli.py,sha256=i6cyJpHXIb3_9O1i2yobhS1LaE1byeSPs91ZWbC-ZGE,720
4
+ boatyard/compose.py,sha256=XL29GyVqMVsfNcgirVzwqUCyVwoiPx59aGBu6fUSgx4,939
5
+ boatyard/crane.py,sha256=hcIMCE5CLQKFgpyySVJubuuJuFJ6MST30ws3LWuSEfo,627
6
+ boatyard/dockerfiles.py,sha256=PIRmxSCkgWBRYz7wHTE2XnzsXLa9mgSd-c2jTiQU63w,8706
7
+ boatyard/lock.py,sha256=rCbiGlZv8gsD3nRqqapfzQB7Lemff-pjaXnRGiyBMDY,9464
8
+ boatyard/markers.py,sha256=fTTEx33phwxZxZecLhkQkWTcJgLJ0h6ZZQl6DSqjtvk,7393
9
+ boatyard/paths.py,sha256=gpR3ZhyciIFQQQGlvCPCxS8SFfVbSWrSw7gMiKiXUqs,150
10
+ boatyard/publish.py,sha256=XwQubwpTPDcAMgCxLjg5RCrIsWIWxmrEmzyBZZOuOpQ,2667
11
+ boatyard/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ boatyard/score.py,sha256=NC1Oi4NMGU-85iHt7h8PYdze4DZTZyMex6MG7sANdus,9641
13
+ boatyard/score_compose.py,sha256=K4QD6VlEIXqGqmRZq8VpME0aYRR2RxQszVsQBSlJvkM,10011
14
+ boatyard/score_k8s.py,sha256=ZsSC5qukYY0FcLfjqhiVGCr1bFbUqQYGaPuIYCQeqCo,5040
15
+ boatyard/temp.py,sha256=fV1RYL7oamnoAsmDEx0yx4i4hIjLykT7hCuiiNL_7XA,131
16
+ boatyard/yaml.py,sha256=xoGfpaHPHxjSaMEkoy3BYxX2sRhhmVvM6SPh2zIP5cg,1236
17
+ boatyard/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ boatyard/config/config.py,sha256=pexCK7WNXL6JXn5Tc_WEZ-y6AqaB7FLVwkRon_7mATg,30937
19
+ boatyard/config/lock.py,sha256=qyjMvb6QgM5PSlYVg44oDNt195p52oY7GsPGllbOksk,6805
20
+ boatyard/config/values.py,sha256=FWHeXFENgJaO565qzffZw4k9USAgRc8ditYPeveBNNg,7668
21
+ boatyard/score_patch_templates/gpu-cuda.tpl,sha256=kJFpV0mzeW48cAr3hw97n6EmpW9w7SETAyTdhPO5YDQ,1032
22
+ boatyard/score_patch_templates/gpu-pod.tpl,sha256=c3plZTU6MrYKZXViFc0orXrFXS4QyQ7BuKesMkgWHic,1942
23
+ boatyard/score_patch_templates/gpu-rocm.tpl,sha256=7RdEapTwFPzZ-cuOLrXeTR50sHOZyI-_BAph5Mtag94,1148
24
+ boatyard/score_patch_templates/probe-adapter-healthchecks.tpl,sha256=B3dDUS1wWezwunxPpyfHlPwW2hkCBM-xyIOfqEVeXzM,2172
25
+ boatyard/score_patch_templates/restart.tpl,sha256=d_tAuZgw6Imwf8gpRWMqM4gjsZZ786VMAUZ9mQ-lvW8,909
26
+ boatyard/score_patch_templates/scale-to-zero.tpl,sha256=CFm08MWJJUw0BeSx_HkQKon3qJcz8DN0LZCMTIyA-X8,1120
27
+ boatyard/verbs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ boatyard/verbs/build.py,sha256=uFUXzV8wzOWUdH7YFHzdwpcjlqNDRyAyRMpcxW81jq0,2015
29
+ boatyard/verbs/down.py,sha256=qp-BthIEskhB1nvE_zFY5tF_He1-BOx1ts0BSY9FrpA,1267
30
+ boatyard/verbs/lock.py,sha256=zDb_vKaZr0WjYVMZbjwvKHUyuG3kUJ0TjyMyYL-Rdi8,1089
31
+ boatyard/verbs/matrix.py,sha256=Mli1TUAGkF5Y7vCD-Nla6Sq6kF91c-FcW-3O5RGCVx0,1956
32
+ boatyard/verbs/mirror.py,sha256=xCXM3ic9AuNRSkOwLTVCbvgfGT59KwPEXK4BQMeRcSY,1741
33
+ boatyard/verbs/publish.py,sha256=9m4jsD8KT_uliyLlotLOz3YWyYNq5OmUH-kgnlQaqro,5496
34
+ boatyard/verbs/score.py,sha256=APqmm9A7sut3ZqJ9AtjWGtLD33eItbiH5PM2Fose7fw,6294
35
+ boatyard/verbs/tree_tags.py,sha256=BN57sA42Z5g7aeAxVEsmGXegRt0idj9RUi96v1Dv7SA,1145
36
+ boatyard/verbs/up.py,sha256=17wUfxkL4ewZXxAIojnZavo4h4UraEh7Kbdv18WfBZE,11239
37
+ boatyard-0.6.0.dev170.dist-info/METADATA,sha256=RryIMz3tdmNnQJ2s9BuoqZBz4khkBUP1wmgs1G0aXkw,369
38
+ boatyard-0.6.0.dev170.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
39
+ boatyard-0.6.0.dev170.dist-info/entry_points.txt,sha256=vuiOwN-ufRd_fxnl3DIG_2V3kcv05l0kRXlVhFIPgNs,46
40
+ boatyard-0.6.0.dev170.dist-info/licenses/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
41
+ boatyard-0.6.0.dev170.dist-info/licenses/NOTICE,sha256=jvglZza3GCdN7v4_TLuXUqX9C_uABlwAKUkcIeKwk_8,26
42
+ boatyard-0.6.0.dev170.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ boatyard = boatyard.cli:app