aissemble-inference-deploy 1.5.0rc3__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.
- aissemble_inference_deploy/__init__.py +38 -0
- aissemble_inference_deploy/cli.py +278 -0
- aissemble_inference_deploy/config.py +182 -0
- aissemble_inference_deploy/generators/__init__.py +36 -0
- aissemble_inference_deploy/generators/base.py +239 -0
- aissemble_inference_deploy/generators/docker.py +307 -0
- aissemble_inference_deploy/generators/kserve.py +89 -0
- aissemble_inference_deploy/generators/kubernetes.py +119 -0
- aissemble_inference_deploy/generators/local.py +162 -0
- aissemble_inference_deploy/registry.py +158 -0
- aissemble_inference_deploy/templates/docker/.dockerignore.j2 +47 -0
- aissemble_inference_deploy/templates/docker/Dockerfile.j2 +59 -0
- aissemble_inference_deploy/templates/docker/README.md.j2 +163 -0
- aissemble_inference_deploy/templates/docker/docker-compose.yml.j2 +22 -0
- aissemble_inference_deploy/templates/kserve/README.md.j2 +278 -0
- aissemble_inference_deploy/templates/kserve/inference-service.yaml.j2 +14 -0
- aissemble_inference_deploy/templates/kserve/serving-runtime.yaml.j2 +35 -0
- aissemble_inference_deploy/templates/kubernetes/README.md.j2 +164 -0
- aissemble_inference_deploy/templates/kubernetes/deployment.yaml.j2 +50 -0
- aissemble_inference_deploy/templates/kubernetes/kustomization.yaml.j2 +11 -0
- aissemble_inference_deploy/templates/kubernetes/overlays/dev/kustomization.yaml.j2 +52 -0
- aissemble_inference_deploy/templates/kubernetes/overlays/prod/kustomization.yaml.j2 +36 -0
- aissemble_inference_deploy/templates/kubernetes/service.yaml.j2 +19 -0
- aissemble_inference_deploy/templates/local/run-mlserver.sh.j2 +47 -0
- aissemble_inference_deploy-1.5.0rc3.dist-info/METADATA +248 -0
- aissemble_inference_deploy-1.5.0rc3.dist-info/RECORD +29 -0
- aissemble_inference_deploy-1.5.0rc3.dist-info/WHEEL +4 -0
- aissemble_inference_deploy-1.5.0rc3.dist-info/entry_points.txt +8 -0
- aissemble_inference_deploy-1.5.0rc3.dist-info/licenses/LICENSE.txt +201 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
###
|
|
2
|
+
# #%L
|
|
3
|
+
# aiSSEMBLE::Open Inference Protocol::Modules::deploy
|
|
4
|
+
# %%
|
|
5
|
+
# Copyright (C) 2024 Booz Allen Hamilton Inc.
|
|
6
|
+
# %%
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
# #L%
|
|
19
|
+
###
|
|
20
|
+
"""
|
|
21
|
+
aiSSEMBLE OIP Deploy - Deployment tooling for OIP-compatible models.
|
|
22
|
+
|
|
23
|
+
This module provides CLI tooling to generate deployment configurations
|
|
24
|
+
for any OIP-compatible model across multiple deployment targets:
|
|
25
|
+
- Local (MLServer)
|
|
26
|
+
- Docker
|
|
27
|
+
- Kubernetes (vanilla)
|
|
28
|
+
- KServe (serverless)
|
|
29
|
+
|
|
30
|
+
Custom generators can be added via the 'inference.generators' entry point group.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
__version__ = "1.5.0.dev"
|
|
34
|
+
|
|
35
|
+
from .generators.base import Generator, ModelInfo
|
|
36
|
+
from .registry import GeneratorRegistry
|
|
37
|
+
|
|
38
|
+
__all__ = ["Generator", "GeneratorRegistry", "ModelInfo", "__version__"]
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
###
|
|
2
|
+
# #%L
|
|
3
|
+
# aiSSEMBLE::Open Inference Protocol::Deploy
|
|
4
|
+
# %%
|
|
5
|
+
# Copyright (C) 2024 Booz Allen Hamilton Inc.
|
|
6
|
+
# %%
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
# #L%
|
|
19
|
+
###
|
|
20
|
+
"""
|
|
21
|
+
CLI commands for inference-deploy.
|
|
22
|
+
|
|
23
|
+
Provides the `inference deploy` command group for generating deployment configurations.
|
|
24
|
+
Generators are discovered via entry points, allowing custom generators to be
|
|
25
|
+
installed as separate packages.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from datetime import datetime, timezone
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
import click
|
|
32
|
+
|
|
33
|
+
from . import __version__
|
|
34
|
+
from .config import DeployConfig
|
|
35
|
+
from .registry import GeneratorRegistry
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_registry() -> GeneratorRegistry:
|
|
39
|
+
"""Get the generator registry instance."""
|
|
40
|
+
return GeneratorRegistry.instance()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@click.group()
|
|
44
|
+
@click.version_option(version=__version__)
|
|
45
|
+
def main():
|
|
46
|
+
"""Inference deployment tooling - generate deployment configs for OIP-compatible models."""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@main.group()
|
|
51
|
+
def deploy():
|
|
52
|
+
"""Generate and manage deployment configurations."""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@deploy.command("init")
|
|
57
|
+
@click.option(
|
|
58
|
+
"--target",
|
|
59
|
+
"-t",
|
|
60
|
+
multiple=True,
|
|
61
|
+
default=None,
|
|
62
|
+
help="Deployment target(s) to generate configs for. Use 'all' for all targets.",
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--model-dir",
|
|
66
|
+
"-m",
|
|
67
|
+
type=click.Path(exists=True, path_type=Path),
|
|
68
|
+
default=None,
|
|
69
|
+
help="Path to models directory (default: ./models)",
|
|
70
|
+
)
|
|
71
|
+
@click.option(
|
|
72
|
+
"--output-dir",
|
|
73
|
+
"-o",
|
|
74
|
+
type=click.Path(path_type=Path),
|
|
75
|
+
default=None,
|
|
76
|
+
help="Output directory for generated configs (default: ./deploy)",
|
|
77
|
+
)
|
|
78
|
+
@click.option(
|
|
79
|
+
"--project-dir",
|
|
80
|
+
"-p",
|
|
81
|
+
type=click.Path(exists=True, path_type=Path),
|
|
82
|
+
default=None,
|
|
83
|
+
help="Project root directory (default: current directory)",
|
|
84
|
+
)
|
|
85
|
+
def init(
|
|
86
|
+
target: tuple[str, ...] | None,
|
|
87
|
+
model_dir: Path | None,
|
|
88
|
+
output_dir: Path | None,
|
|
89
|
+
project_dir: Path | None,
|
|
90
|
+
):
|
|
91
|
+
"""Initialize deployment configurations for your models.
|
|
92
|
+
|
|
93
|
+
Generates deployment configs in the deploy/ directory for the specified
|
|
94
|
+
target(s). Use --target all to generate for all available targets.
|
|
95
|
+
|
|
96
|
+
Generators are discovered via entry points, so custom generators can be
|
|
97
|
+
installed as separate packages.
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
|
|
101
|
+
inference deploy init --target local
|
|
102
|
+
|
|
103
|
+
inference deploy init --target docker --target kubernetes
|
|
104
|
+
|
|
105
|
+
inference deploy init --target all
|
|
106
|
+
"""
|
|
107
|
+
registry = get_registry()
|
|
108
|
+
available = registry.list_available()
|
|
109
|
+
|
|
110
|
+
if not available:
|
|
111
|
+
click.echo("Error: No generators found. Install a generator package or check")
|
|
112
|
+
click.echo("that aissemble-inference-deploy is installed correctly.")
|
|
113
|
+
raise SystemExit(1)
|
|
114
|
+
|
|
115
|
+
# Default to 'local' if available, otherwise first available
|
|
116
|
+
if target is None or len(target) == 0:
|
|
117
|
+
if "local" in available:
|
|
118
|
+
targets = ["local"]
|
|
119
|
+
else:
|
|
120
|
+
targets = [available[0]]
|
|
121
|
+
else:
|
|
122
|
+
targets = list(target)
|
|
123
|
+
|
|
124
|
+
# Expand 'all' to all available targets
|
|
125
|
+
if "all" in targets:
|
|
126
|
+
targets = available
|
|
127
|
+
|
|
128
|
+
# Kubernetes depends on Docker - auto-include if not present
|
|
129
|
+
if "kubernetes" in targets and "docker" not in targets:
|
|
130
|
+
click.echo("Note: Adding 'docker' target (required by kubernetes)")
|
|
131
|
+
targets.insert(0, "docker")
|
|
132
|
+
|
|
133
|
+
# KServe depends on Docker - auto-include if not present
|
|
134
|
+
if "kserve" in targets and "docker" not in targets:
|
|
135
|
+
click.echo("Note: Adding 'docker' target (required by kserve)")
|
|
136
|
+
targets.insert(0, "docker")
|
|
137
|
+
|
|
138
|
+
# Validate targets
|
|
139
|
+
for t in targets:
|
|
140
|
+
if t not in available and t != "all":
|
|
141
|
+
click.echo(f"Error: Unknown target '{t}'")
|
|
142
|
+
click.echo(f"Available targets: {', '.join(available)}")
|
|
143
|
+
raise SystemExit(1)
|
|
144
|
+
|
|
145
|
+
project_dir = project_dir or Path.cwd()
|
|
146
|
+
output_dir = output_dir or project_dir / "deploy"
|
|
147
|
+
|
|
148
|
+
# Check if running from wrong directory (inside deploy/)
|
|
149
|
+
cwd = Path.cwd()
|
|
150
|
+
if cwd.name == "deploy" and project_dir == cwd:
|
|
151
|
+
click.echo(
|
|
152
|
+
"Error: It looks like you're running from inside a deploy/ directory."
|
|
153
|
+
)
|
|
154
|
+
click.echo(
|
|
155
|
+
"Please run from your project root (where pyproject.toml and models/ are)."
|
|
156
|
+
)
|
|
157
|
+
click.echo()
|
|
158
|
+
click.echo("Example:")
|
|
159
|
+
click.echo(" cd /path/to/your-project")
|
|
160
|
+
click.echo(" inference deploy init --target docker")
|
|
161
|
+
raise SystemExit(1)
|
|
162
|
+
|
|
163
|
+
# Check for project root indicators
|
|
164
|
+
has_pyproject = (project_dir / "pyproject.toml").exists()
|
|
165
|
+
has_models = (project_dir / "models").exists()
|
|
166
|
+
if not has_pyproject and not has_models:
|
|
167
|
+
click.echo(
|
|
168
|
+
f"Warning: No pyproject.toml or models/ directory found in {project_dir}"
|
|
169
|
+
)
|
|
170
|
+
click.echo("Are you running from your project root?")
|
|
171
|
+
click.echo()
|
|
172
|
+
if not click.confirm("Continue anyway?"):
|
|
173
|
+
raise SystemExit(1)
|
|
174
|
+
|
|
175
|
+
# Load or create config
|
|
176
|
+
config_path = output_dir / ".inference-deploy.yaml"
|
|
177
|
+
config = DeployConfig.load(config_path)
|
|
178
|
+
config.generator_version = __version__
|
|
179
|
+
config.generated_at = datetime.now(timezone.utc).isoformat()
|
|
180
|
+
|
|
181
|
+
click.echo(f"Generating deployment configs in {output_dir}")
|
|
182
|
+
click.echo(f"Targets: {', '.join(targets)}")
|
|
183
|
+
click.echo()
|
|
184
|
+
|
|
185
|
+
all_generated_files = []
|
|
186
|
+
|
|
187
|
+
for target_name in targets:
|
|
188
|
+
generator_cls = registry.get(target_name)
|
|
189
|
+
if generator_cls is None:
|
|
190
|
+
click.echo(f" [{target_name}] Error: Generator not found")
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
generator = generator_cls(project_dir, output_dir)
|
|
194
|
+
|
|
195
|
+
# Detect models
|
|
196
|
+
models_path = model_dir or project_dir / "models"
|
|
197
|
+
models = generator.detect_models(models_path)
|
|
198
|
+
|
|
199
|
+
if not models:
|
|
200
|
+
click.echo(f" [{target_name}] Warning: No models found in {models_path}")
|
|
201
|
+
else:
|
|
202
|
+
model_names = ", ".join(m.name for m in models)
|
|
203
|
+
click.echo(f" [{target_name}] Found models: {model_names}")
|
|
204
|
+
|
|
205
|
+
# Generate configs
|
|
206
|
+
generated_files = generator.generate(models)
|
|
207
|
+
all_generated_files.extend(generated_files)
|
|
208
|
+
|
|
209
|
+
for file_path in generated_files:
|
|
210
|
+
config.add_file(file_path, target_name, output_dir)
|
|
211
|
+
rel_path = file_path.relative_to(output_dir)
|
|
212
|
+
click.echo(f" [{target_name}] Generated: {rel_path}")
|
|
213
|
+
|
|
214
|
+
if target_name not in config.targets:
|
|
215
|
+
config.targets.append(target_name)
|
|
216
|
+
|
|
217
|
+
# Save config
|
|
218
|
+
config.save(config_path)
|
|
219
|
+
click.echo()
|
|
220
|
+
click.echo(f"Config saved to {config_path.relative_to(project_dir)}")
|
|
221
|
+
|
|
222
|
+
# Print next steps
|
|
223
|
+
click.echo()
|
|
224
|
+
click.echo("Next steps:")
|
|
225
|
+
if "local" in targets:
|
|
226
|
+
click.echo(" Local: cd deploy/local && ./run-mlserver.sh")
|
|
227
|
+
if "kubernetes" in targets:
|
|
228
|
+
# Kubernetes workflow: build image, then deploy
|
|
229
|
+
click.echo(" K8s: cd deploy/docker && docker-compose build")
|
|
230
|
+
click.echo(" kubectl apply -k deploy/kubernetes/overlays/dev")
|
|
231
|
+
elif "docker" in targets:
|
|
232
|
+
# Docker-only workflow: build and run
|
|
233
|
+
click.echo(" Docker: cd deploy/docker && docker-compose up --build")
|
|
234
|
+
if "kserve" in targets:
|
|
235
|
+
click.echo(" KServe: kubectl apply -f deploy/kserve/serving-runtime.yaml")
|
|
236
|
+
click.echo(" kubectl apply -f deploy/kserve/inference-service.yaml")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@deploy.command("list-targets")
|
|
240
|
+
def list_targets():
|
|
241
|
+
"""List available deployment targets.
|
|
242
|
+
|
|
243
|
+
Generators are discovered via entry points. Install additional generator
|
|
244
|
+
packages to add more targets.
|
|
245
|
+
"""
|
|
246
|
+
registry = get_registry()
|
|
247
|
+
available = registry.list_available()
|
|
248
|
+
|
|
249
|
+
if not available:
|
|
250
|
+
click.echo("No generators found.")
|
|
251
|
+
click.echo()
|
|
252
|
+
click.echo(
|
|
253
|
+
"Install a generator package or check that aissemble-inference-deploy"
|
|
254
|
+
)
|
|
255
|
+
click.echo("is installed correctly.")
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
click.echo("Available deployment targets:")
|
|
259
|
+
click.echo()
|
|
260
|
+
for name in available:
|
|
261
|
+
generator_cls = registry.get(name)
|
|
262
|
+
# Get description from docstring if available
|
|
263
|
+
doc = generator_cls.__doc__ if generator_cls else None
|
|
264
|
+
if doc:
|
|
265
|
+
# Get first line of docstring
|
|
266
|
+
desc = doc.strip().split("\n")[0]
|
|
267
|
+
click.echo(f" {name:15} - {desc}")
|
|
268
|
+
else:
|
|
269
|
+
click.echo(f" {name}")
|
|
270
|
+
|
|
271
|
+
click.echo()
|
|
272
|
+
click.echo(
|
|
273
|
+
"Custom generators can be added via the 'inference.generators' entry point."
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == "__main__":
|
|
278
|
+
main()
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
###
|
|
2
|
+
# #%L
|
|
3
|
+
# aiSSEMBLE::Open Inference Protocol::Deploy
|
|
4
|
+
# %%
|
|
5
|
+
# Copyright (C) 2024 Booz Allen Hamilton Inc.
|
|
6
|
+
# %%
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
# #L%
|
|
19
|
+
###
|
|
20
|
+
"""
|
|
21
|
+
Configuration management for oip-deploy.
|
|
22
|
+
|
|
23
|
+
Manages the .inference-deploy.yaml file that tracks generated configs,
|
|
24
|
+
versions, and checksums for update/merge functionality.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import re
|
|
29
|
+
import sys
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from datetime import datetime, timezone
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
import yaml
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class GeneratedFile:
|
|
40
|
+
"""Tracks a single generated file."""
|
|
41
|
+
|
|
42
|
+
path: str
|
|
43
|
+
checksum: str
|
|
44
|
+
generator: str
|
|
45
|
+
generated_at: str
|
|
46
|
+
|
|
47
|
+
def __post_init__(self):
|
|
48
|
+
"""Validate checksum format."""
|
|
49
|
+
if not re.match(r"^[a-f0-9]{64}$", self.checksum):
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"Invalid checksum format (expected SHA256 hex): {self.checksum}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class DeployConfig:
|
|
57
|
+
"""Configuration stored in .inference-deploy.yaml."""
|
|
58
|
+
|
|
59
|
+
version: str = "1.0"
|
|
60
|
+
generator_version: str = ""
|
|
61
|
+
generated_at: str = ""
|
|
62
|
+
targets: list[str] = field(default_factory=list)
|
|
63
|
+
files: list[GeneratedFile] = field(default_factory=list)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def load(cls, path: Path) -> "DeployConfig":
|
|
67
|
+
"""
|
|
68
|
+
Load config from a YAML file.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
path: Path to .inference-deploy.yaml file
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
DeployConfig instance
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ValueError: If YAML is malformed
|
|
78
|
+
"""
|
|
79
|
+
if not path.exists():
|
|
80
|
+
return cls()
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
content = path.read_text(encoding="utf-8")
|
|
84
|
+
data = yaml.safe_load(content)
|
|
85
|
+
except yaml.YAMLError as e:
|
|
86
|
+
raise ValueError(f"Invalid YAML in {path}: {e}")
|
|
87
|
+
except OSError as e:
|
|
88
|
+
raise ValueError(f"Cannot read {path}: {e}")
|
|
89
|
+
|
|
90
|
+
if data is None:
|
|
91
|
+
return cls()
|
|
92
|
+
|
|
93
|
+
if not isinstance(data, dict):
|
|
94
|
+
raise ValueError(f"Expected dict in {path}, got {type(data).__name__}")
|
|
95
|
+
|
|
96
|
+
# Validate and load files with proper error handling
|
|
97
|
+
files = []
|
|
98
|
+
for file_data in data.get("files", []):
|
|
99
|
+
if not isinstance(file_data, dict):
|
|
100
|
+
print(
|
|
101
|
+
f"Warning: Skipping invalid file entry (not a dict): {file_data}",
|
|
102
|
+
file=sys.stderr,
|
|
103
|
+
)
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
files.append(GeneratedFile(**file_data))
|
|
108
|
+
except TypeError as e:
|
|
109
|
+
print(
|
|
110
|
+
f"Warning: Skipping invalid file entry: {e}",
|
|
111
|
+
file=sys.stderr,
|
|
112
|
+
)
|
|
113
|
+
except ValueError as e:
|
|
114
|
+
print(
|
|
115
|
+
f"Warning: Skipping file entry with invalid checksum: {e}",
|
|
116
|
+
file=sys.stderr,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return cls(
|
|
120
|
+
version=str(data.get("version", "1.0")),
|
|
121
|
+
generator_version=str(data.get("generator_version", "")),
|
|
122
|
+
generated_at=str(data.get("generated_at", "")),
|
|
123
|
+
targets=list(data.get("targets", [])),
|
|
124
|
+
files=files,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def save(self, path: Path) -> None:
|
|
128
|
+
"""Save config to a YAML file."""
|
|
129
|
+
data: dict[str, Any] = {
|
|
130
|
+
"version": self.version,
|
|
131
|
+
"generator_version": self.generator_version,
|
|
132
|
+
"generated_at": self.generated_at,
|
|
133
|
+
"targets": self.targets,
|
|
134
|
+
"files": [
|
|
135
|
+
{
|
|
136
|
+
"path": f.path,
|
|
137
|
+
"checksum": f.checksum,
|
|
138
|
+
"generator": f.generator,
|
|
139
|
+
"generated_at": f.generated_at,
|
|
140
|
+
}
|
|
141
|
+
for f in self.files
|
|
142
|
+
],
|
|
143
|
+
}
|
|
144
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
path.write_text(
|
|
146
|
+
yaml.dump(data, default_flow_style=False, sort_keys=False),
|
|
147
|
+
encoding="utf-8",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def add_file(self, path: Path, generator: str, base_dir: Path) -> None:
|
|
151
|
+
"""Add or update a tracked file."""
|
|
152
|
+
rel_path = str(path.relative_to(base_dir))
|
|
153
|
+
checksum = compute_checksum(path)
|
|
154
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
155
|
+
|
|
156
|
+
# Update existing or add new
|
|
157
|
+
for f in self.files:
|
|
158
|
+
if f.path == rel_path:
|
|
159
|
+
f.checksum = checksum
|
|
160
|
+
f.generated_at = now
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
self.files.append(
|
|
164
|
+
GeneratedFile(
|
|
165
|
+
path=rel_path,
|
|
166
|
+
checksum=checksum,
|
|
167
|
+
generator=generator,
|
|
168
|
+
generated_at=now,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def get_file(self, rel_path: str) -> GeneratedFile | None:
|
|
173
|
+
"""Get a tracked file by its relative path."""
|
|
174
|
+
for f in self.files:
|
|
175
|
+
if f.path == rel_path:
|
|
176
|
+
return f
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def compute_checksum(path: Path) -> str:
|
|
181
|
+
"""Compute SHA256 checksum of a file."""
|
|
182
|
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
###
|
|
2
|
+
# #%L
|
|
3
|
+
# aiSSEMBLE::Open Inference Protocol::Modules::deploy
|
|
4
|
+
# %%
|
|
5
|
+
# Copyright (C) 2024 Booz Allen Hamilton Inc.
|
|
6
|
+
# %%
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
# #L%
|
|
19
|
+
###
|
|
20
|
+
"""
|
|
21
|
+
Deployment config generators for different targets.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .base import Generator
|
|
25
|
+
from .docker import DockerGenerator
|
|
26
|
+
from .kserve import KServeGenerator
|
|
27
|
+
from .kubernetes import KubernetesGenerator
|
|
28
|
+
from .local import LocalGenerator
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"Generator",
|
|
32
|
+
"DockerGenerator",
|
|
33
|
+
"KServeGenerator",
|
|
34
|
+
"KubernetesGenerator",
|
|
35
|
+
"LocalGenerator",
|
|
36
|
+
]
|