@aws/ml-container-creator 0.2.0
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.
- package/LICENSE +202 -0
- package/LICENSE-THIRD-PARTY +68620 -0
- package/NOTICE +2 -0
- package/README.md +106 -0
- package/bin/cli.js +365 -0
- package/config/defaults.json +32 -0
- package/config/presets/transformers-djl.json +26 -0
- package/config/presets/transformers-gpu.json +24 -0
- package/config/presets/transformers-lmi.json +27 -0
- package/package.json +129 -0
- package/servers/README.md +419 -0
- package/servers/base-image-picker/catalogs/model-servers.json +1191 -0
- package/servers/base-image-picker/catalogs/python-slim.json +38 -0
- package/servers/base-image-picker/catalogs/triton-backends.json +51 -0
- package/servers/base-image-picker/catalogs/triton.json +38 -0
- package/servers/base-image-picker/index.js +495 -0
- package/servers/base-image-picker/manifest.json +17 -0
- package/servers/base-image-picker/package.json +15 -0
- package/servers/hyperpod-cluster-picker/LICENSE +202 -0
- package/servers/hyperpod-cluster-picker/index.js +424 -0
- package/servers/hyperpod-cluster-picker/manifest.json +14 -0
- package/servers/hyperpod-cluster-picker/package.json +17 -0
- package/servers/instance-recommender/LICENSE +202 -0
- package/servers/instance-recommender/catalogs/instances.json +852 -0
- package/servers/instance-recommender/index.js +284 -0
- package/servers/instance-recommender/manifest.json +16 -0
- package/servers/instance-recommender/package.json +15 -0
- package/servers/lib/LICENSE +202 -0
- package/servers/lib/bedrock-client.js +160 -0
- package/servers/lib/custom-validators.js +46 -0
- package/servers/lib/dynamic-resolver.js +36 -0
- package/servers/lib/package.json +11 -0
- package/servers/lib/schemas/image-catalog.schema.json +185 -0
- package/servers/lib/schemas/instances.schema.json +124 -0
- package/servers/lib/schemas/manifest.schema.json +64 -0
- package/servers/lib/schemas/model-catalog.schema.json +91 -0
- package/servers/lib/schemas/regions.schema.json +26 -0
- package/servers/lib/schemas/triton-backends.schema.json +51 -0
- package/servers/model-picker/catalogs/jumpstart-public.json +66 -0
- package/servers/model-picker/catalogs/popular-diffusors.json +88 -0
- package/servers/model-picker/catalogs/popular-transformers.json +226 -0
- package/servers/model-picker/index.js +1693 -0
- package/servers/model-picker/manifest.json +18 -0
- package/servers/model-picker/package.json +20 -0
- package/servers/region-picker/LICENSE +202 -0
- package/servers/region-picker/catalogs/regions.json +263 -0
- package/servers/region-picker/index.js +230 -0
- package/servers/region-picker/manifest.json +16 -0
- package/servers/region-picker/package.json +15 -0
- package/src/app.js +1007 -0
- package/src/copy-tpl.js +77 -0
- package/src/lib/accelerator-validator.js +39 -0
- package/src/lib/asset-manager.js +385 -0
- package/src/lib/aws-profile-parser.js +181 -0
- package/src/lib/bootstrap-command-handler.js +1647 -0
- package/src/lib/bootstrap-config.js +238 -0
- package/src/lib/ci-register-helpers.js +124 -0
- package/src/lib/ci-report-helpers.js +158 -0
- package/src/lib/ci-stage-helpers.js +268 -0
- package/src/lib/cli-handler.js +529 -0
- package/src/lib/comment-generator.js +544 -0
- package/src/lib/community-reports-validator.js +91 -0
- package/src/lib/config-manager.js +2106 -0
- package/src/lib/configuration-exporter.js +204 -0
- package/src/lib/configuration-manager.js +695 -0
- package/src/lib/configuration-matcher.js +221 -0
- package/src/lib/cpu-validator.js +36 -0
- package/src/lib/cuda-validator.js +57 -0
- package/src/lib/deployment-config-resolver.js +103 -0
- package/src/lib/deployment-entry-schema.js +125 -0
- package/src/lib/deployment-registry.js +598 -0
- package/src/lib/docker-introspection-validator.js +51 -0
- package/src/lib/engine-prefix-resolver.js +60 -0
- package/src/lib/huggingface-client.js +172 -0
- package/src/lib/key-value-parser.js +37 -0
- package/src/lib/known-flags-validator.js +200 -0
- package/src/lib/manifest-cli.js +280 -0
- package/src/lib/mcp-client.js +303 -0
- package/src/lib/mcp-command-handler.js +532 -0
- package/src/lib/neuron-validator.js +80 -0
- package/src/lib/parameter-schema-validator.js +284 -0
- package/src/lib/prompt-runner.js +1349 -0
- package/src/lib/prompts.js +1138 -0
- package/src/lib/registry-command-handler.js +519 -0
- package/src/lib/registry-loader.js +198 -0
- package/src/lib/rocm-validator.js +80 -0
- package/src/lib/schema-validator.js +157 -0
- package/src/lib/sensitive-redactor.js +59 -0
- package/src/lib/template-engine.js +156 -0
- package/src/lib/template-manager.js +341 -0
- package/src/lib/validation-engine.js +314 -0
- package/src/prompt-adapter.js +63 -0
- package/templates/Dockerfile +300 -0
- package/templates/IAM_PERMISSIONS.md +84 -0
- package/templates/MIGRATION.md +488 -0
- package/templates/PROJECT_README.md +439 -0
- package/templates/TEMPLATE_SYSTEM.md +243 -0
- package/templates/buildspec.yml +64 -0
- package/templates/code/chat_template.jinja +1 -0
- package/templates/code/flask/gunicorn_config.py +35 -0
- package/templates/code/flask/wsgi.py +10 -0
- package/templates/code/model_handler.py +387 -0
- package/templates/code/serve +300 -0
- package/templates/code/serve.py +175 -0
- package/templates/code/serving.properties +105 -0
- package/templates/code/start_server.py +39 -0
- package/templates/code/start_server.sh +39 -0
- package/templates/diffusors/Dockerfile +72 -0
- package/templates/diffusors/patch_image_api.py +35 -0
- package/templates/diffusors/serve +115 -0
- package/templates/diffusors/start_server.sh +114 -0
- package/templates/do/.gitkeep +1 -0
- package/templates/do/README.md +541 -0
- package/templates/do/build +83 -0
- package/templates/do/ci +681 -0
- package/templates/do/clean +811 -0
- package/templates/do/config +260 -0
- package/templates/do/deploy +1560 -0
- package/templates/do/export +306 -0
- package/templates/do/logs +319 -0
- package/templates/do/manifest +12 -0
- package/templates/do/push +119 -0
- package/templates/do/register +580 -0
- package/templates/do/run +113 -0
- package/templates/do/submit +417 -0
- package/templates/do/test +1147 -0
- package/templates/hyperpod/configmap.yaml +24 -0
- package/templates/hyperpod/deployment.yaml +71 -0
- package/templates/hyperpod/pvc.yaml +42 -0
- package/templates/hyperpod/service.yaml +17 -0
- package/templates/nginx-diffusors.conf +74 -0
- package/templates/nginx-predictors.conf +47 -0
- package/templates/nginx-tensorrt.conf +74 -0
- package/templates/requirements.txt +61 -0
- package/templates/sample_model/test_inference.py +123 -0
- package/templates/sample_model/train_abalone.py +252 -0
- package/templates/test/test_endpoint.sh +79 -0
- package/templates/test/test_local_image.sh +80 -0
- package/templates/test/test_model_handler.py +180 -0
- package/templates/triton/Dockerfile +128 -0
- package/templates/triton/config.pbtxt +163 -0
- package/templates/triton/model.py +130 -0
- package/templates/triton/requirements.txt +11 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Registry Command Handler
|
|
6
|
+
*
|
|
7
|
+
* Handles the `registry` CLI subcommand tree for managing
|
|
8
|
+
* deployment entries in the deployment registry.
|
|
9
|
+
*
|
|
10
|
+
* Subcommands:
|
|
11
|
+
* log Internal: called by do/register
|
|
12
|
+
* list [--backend, --architecture, --model, --instance-type, --status]
|
|
13
|
+
* get <id> Show full entry details
|
|
14
|
+
* remove <id> Remove an entry
|
|
15
|
+
* replay <id> [overrides] Replay a deployment
|
|
16
|
+
* export [id] [--status] Export entries as JSON
|
|
17
|
+
* import <file> [--merge|--replace] Import entries from JSON
|
|
18
|
+
* search [--model, --architecture, --backend, --instance-type]
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import os from 'node:os'
|
|
22
|
+
import path from 'node:path'
|
|
23
|
+
import { readFileSync } from 'node:fs'
|
|
24
|
+
import { execSync } from 'node:child_process'
|
|
25
|
+
import { fileURLToPath } from 'node:url'
|
|
26
|
+
import DeploymentRegistry, { reconstructReplayFlags } from './deployment-registry.js'
|
|
27
|
+
|
|
28
|
+
const PERSONAL_REGISTRY_PATH = path.join(os.homedir(), '.ml-container-creator', 'registry.json')
|
|
29
|
+
const PROJECT_REGISTRY_PATH = path.join(process.cwd(), '.ml-container-creator', 'registry.json')
|
|
30
|
+
|
|
31
|
+
export default class RegistryCommandHandler {
|
|
32
|
+
constructor() {
|
|
33
|
+
// No external dependencies required
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Dispatch registry subcommands.
|
|
38
|
+
* @param {string[]} args - Remaining positional args after 'registry'
|
|
39
|
+
* @param {object} options - Parsed CLI options
|
|
40
|
+
*/
|
|
41
|
+
async handle(args, options) {
|
|
42
|
+
if (args.length === 0) {
|
|
43
|
+
this._showRegistryHelp()
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const subcommand = args[0].toLowerCase()
|
|
48
|
+
|
|
49
|
+
switch (subcommand) {
|
|
50
|
+
case 'log':
|
|
51
|
+
await this._handleLog(options)
|
|
52
|
+
break
|
|
53
|
+
case 'list':
|
|
54
|
+
this._handleList(options)
|
|
55
|
+
break
|
|
56
|
+
case 'get':
|
|
57
|
+
this._handleGet(args[1])
|
|
58
|
+
break
|
|
59
|
+
case 'remove':
|
|
60
|
+
this._handleRemove(args[1])
|
|
61
|
+
break
|
|
62
|
+
case 'replay':
|
|
63
|
+
await this._handleReplay(args[1], options)
|
|
64
|
+
break
|
|
65
|
+
case 'export':
|
|
66
|
+
this._handleExport(args[1], options)
|
|
67
|
+
break
|
|
68
|
+
case 'import':
|
|
69
|
+
await this._handleImport(args[1], options)
|
|
70
|
+
break
|
|
71
|
+
case 'search':
|
|
72
|
+
this._handleSearch(options)
|
|
73
|
+
break
|
|
74
|
+
default:
|
|
75
|
+
console.log(`Unknown registry subcommand: ${subcommand}`)
|
|
76
|
+
this._showRegistryHelp()
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Internal: log a deployment entry (called by do/register).
|
|
83
|
+
*
|
|
84
|
+
* Parses CLI flags into a Deployment_Entry structure and adds it
|
|
85
|
+
* to the appropriate registry (personal or project-level).
|
|
86
|
+
*
|
|
87
|
+
* @param {object} options - Parsed CLI options from do/register
|
|
88
|
+
*/
|
|
89
|
+
async _handleLog(options) {
|
|
90
|
+
const registryPath = options.project ? PROJECT_REGISTRY_PATH : PERSONAL_REGISTRY_PATH
|
|
91
|
+
const registry = new DeploymentRegistry(registryPath)
|
|
92
|
+
|
|
93
|
+
const deploymentConfig = options.deploymentConfig || options['deployment-config'] || ''
|
|
94
|
+
const architecture = options.architecture || ''
|
|
95
|
+
const backend = options.backend || ''
|
|
96
|
+
|
|
97
|
+
const entry = {
|
|
98
|
+
timestamp: new Date().toISOString(),
|
|
99
|
+
status: options.status || 'success',
|
|
100
|
+
deployment: {
|
|
101
|
+
deploymentConfig,
|
|
102
|
+
architecture,
|
|
103
|
+
backend,
|
|
104
|
+
baseImage: options.baseImage || options['base-image'] || null,
|
|
105
|
+
deploymentTarget: options.deploymentTarget || options['deployment-target'] || null,
|
|
106
|
+
buildTarget: options.buildTarget || options['build-target'] || null
|
|
107
|
+
},
|
|
108
|
+
model: {
|
|
109
|
+
modelName: options.modelName || options['model-name'] || null,
|
|
110
|
+
modelFormat: options.modelFormat || options['model-format'] || null
|
|
111
|
+
},
|
|
112
|
+
infrastructure: {
|
|
113
|
+
instanceType: options.instanceType || options['instance-type'] || null,
|
|
114
|
+
region: options.region || null,
|
|
115
|
+
roleArn: options.roleArn || options['role-arn'] || null
|
|
116
|
+
},
|
|
117
|
+
configuration: {
|
|
118
|
+
parameters: {}
|
|
119
|
+
},
|
|
120
|
+
outcome: {
|
|
121
|
+
notes: options.notes || null
|
|
122
|
+
},
|
|
123
|
+
metadata: {
|
|
124
|
+
generatorVersion: options.generatorVersion || options['generator-version'] || 'unknown',
|
|
125
|
+
source: 'local',
|
|
126
|
+
importedFrom: null
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Parse parameters from JSON string if provided
|
|
131
|
+
if (options.parameters) {
|
|
132
|
+
try {
|
|
133
|
+
entry.configuration.parameters = typeof options.parameters === 'string'
|
|
134
|
+
? JSON.parse(options.parameters)
|
|
135
|
+
: options.parameters
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.log(`Warning: Could not parse parameters JSON: ${err.message}`)
|
|
138
|
+
entry.configuration.parameters = {}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const id = registry.add(entry)
|
|
144
|
+
console.log(`✅ Deployment entry logged successfully.`)
|
|
145
|
+
console.log(` Entry ID: ${id}`)
|
|
146
|
+
console.log(` View details: ml-container-creator registry get ${id}`)
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.log(`Error logging deployment entry: ${err.message}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* registry list [--backend, --architecture, --model, --instance-type, --status]
|
|
154
|
+
*
|
|
155
|
+
* Displays entries from both personal and project-level registries.
|
|
156
|
+
* Supports filtering by backend, architecture, model, instance-type, and status.
|
|
157
|
+
*
|
|
158
|
+
* @param {object} options - Parsed CLI options
|
|
159
|
+
*/
|
|
160
|
+
_handleList(options) {
|
|
161
|
+
const filters = this._extractFilters(options)
|
|
162
|
+
|
|
163
|
+
const personalRegistry = new DeploymentRegistry(PERSONAL_REGISTRY_PATH)
|
|
164
|
+
const projectRegistry = new DeploymentRegistry(PROJECT_REGISTRY_PATH)
|
|
165
|
+
|
|
166
|
+
const personalEntries = personalRegistry.list(filters).map(e => ({ ...e, _source: 'personal' }))
|
|
167
|
+
const projectEntries = projectRegistry.list(filters).map(e => ({ ...e, _source: 'project' }))
|
|
168
|
+
|
|
169
|
+
const allEntries = [...personalEntries, ...projectEntries]
|
|
170
|
+
|
|
171
|
+
if (allEntries.length === 0) {
|
|
172
|
+
console.log('No deployment entries found.')
|
|
173
|
+
console.log('Use "./do/register" after a successful deployment to add an entry.')
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log('\nDeployment Registry Entries:\n')
|
|
178
|
+
for (const entry of allEntries) {
|
|
179
|
+
const id = entry.id || '(no id)'
|
|
180
|
+
const ts = entry.timestamp ? entry.timestamp.slice(0, 19) : '(no timestamp)'
|
|
181
|
+
const dc = entry.deployment?.deploymentConfig || '(none)'
|
|
182
|
+
const mn = entry.model?.modelName || '(none)'
|
|
183
|
+
const it = entry.infrastructure?.instanceType || '(none)'
|
|
184
|
+
const st = entry.status || '(none)'
|
|
185
|
+
const src = entry._source === 'project' ? ' [project]' : ''
|
|
186
|
+
console.log(` ${id} ${ts} ${dc} ${mn} ${it} ${st}${src}`)
|
|
187
|
+
}
|
|
188
|
+
console.log('')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* registry get <id>
|
|
193
|
+
*
|
|
194
|
+
* Displays the full entry as formatted JSON.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} id - Entry ID
|
|
197
|
+
*/
|
|
198
|
+
_handleGet(id) {
|
|
199
|
+
if (!id) {
|
|
200
|
+
console.log('Usage: ml-container-creator registry get <id>')
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const entry = this._findEntry(id)
|
|
205
|
+
|
|
206
|
+
if (!entry) {
|
|
207
|
+
console.log(`Error: Entry "${id}" not found.`)
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(`\nDeployment Entry: ${id}\n`)
|
|
212
|
+
console.log(JSON.stringify(entry, null, 2))
|
|
213
|
+
console.log('')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* registry remove <id>
|
|
218
|
+
*
|
|
219
|
+
* Removes an entry from the registry.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} id - Entry ID
|
|
222
|
+
*/
|
|
223
|
+
_handleRemove(id) {
|
|
224
|
+
if (!id) {
|
|
225
|
+
console.log('Usage: ml-container-creator registry remove <id>')
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const personalRegistry = new DeploymentRegistry(PERSONAL_REGISTRY_PATH)
|
|
230
|
+
if (personalRegistry.remove(id)) {
|
|
231
|
+
console.log(`✅ Entry "${id}" removed from personal registry.`)
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const projectRegistry = new DeploymentRegistry(PROJECT_REGISTRY_PATH)
|
|
236
|
+
if (projectRegistry.remove(id)) {
|
|
237
|
+
console.log(`✅ Entry "${id}" removed from project registry.`)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log(`Error: Entry "${id}" not found.`)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* registry replay <id> [overrides]
|
|
246
|
+
*
|
|
247
|
+
* Looks up an entry, reconstructs CLI flags, applies overrides,
|
|
248
|
+
* and invokes the generator with the reconstructed flags.
|
|
249
|
+
*
|
|
250
|
+
* @param {string} id - Entry ID
|
|
251
|
+
* @param {object} options - Parsed CLI options (overrides)
|
|
252
|
+
*/
|
|
253
|
+
async _handleReplay(id, options) {
|
|
254
|
+
if (!id) {
|
|
255
|
+
console.log('Usage: ml-container-creator registry replay <id> [--model-name <name>] [--instance-type <type>] ...')
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const entry = this._findEntry(id)
|
|
260
|
+
|
|
261
|
+
if (!entry) {
|
|
262
|
+
console.log(`Error: Entry "${id}" not found.`)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Build overrides from user-provided CLI options
|
|
267
|
+
const overrides = {}
|
|
268
|
+
const overrideMap = {
|
|
269
|
+
'deployment-config': '--deployment-config',
|
|
270
|
+
'deploymentConfig': '--deployment-config',
|
|
271
|
+
'model-name': '--model-name',
|
|
272
|
+
'modelName': '--model-name',
|
|
273
|
+
'instance-type': '--instance-type',
|
|
274
|
+
'instanceType': '--instance-type',
|
|
275
|
+
'region': '--region',
|
|
276
|
+
'model-format': '--model-format',
|
|
277
|
+
'modelFormat': '--model-format'
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const [optKey, flagKey] of Object.entries(overrideMap)) {
|
|
281
|
+
if (options[optKey] != null) {
|
|
282
|
+
overrides[flagKey] = options[optKey]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const flags = reconstructReplayFlags(entry, overrides)
|
|
287
|
+
|
|
288
|
+
console.log(`\nReplaying deployment entry: ${id}`)
|
|
289
|
+
console.log(` Deployment config: ${flags['--deployment-config'] || '(will prompt)'}`)
|
|
290
|
+
console.log(` Model name: ${flags['--model-name'] || '(will prompt)'}`)
|
|
291
|
+
console.log(` Instance type: ${flags['--instance-type'] || '(will prompt)'}`)
|
|
292
|
+
console.log(` Region: ${flags['--region'] || '(will prompt)'}`)
|
|
293
|
+
console.log('')
|
|
294
|
+
|
|
295
|
+
const flagArgs = []
|
|
296
|
+
for (const [flag, value] of Object.entries(flags)) {
|
|
297
|
+
flagArgs.push(flag, value)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Resolve the CLI script path relative to this module
|
|
301
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
302
|
+
const cliPath = path.resolve(path.dirname(__filename), '../../bin/cli.js')
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
execSync(`ml-container-creator ${flagArgs.join(' ')}`, { stdio: 'inherit' })
|
|
306
|
+
} catch {
|
|
307
|
+
// Fallback: invoke via node + script path if binary is not on PATH
|
|
308
|
+
execSync(`${process.execPath} ${cliPath} ${flagArgs.join(' ')}`, { stdio: 'inherit' })
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* registry export [id] [--status]
|
|
314
|
+
*
|
|
315
|
+
* Exports entries as JSON to stdout.
|
|
316
|
+
*
|
|
317
|
+
* @param {string} [id] - Optional entry ID to export a single entry
|
|
318
|
+
* @param {object} options - Parsed CLI options
|
|
319
|
+
*/
|
|
320
|
+
_handleExport(id, options) {
|
|
321
|
+
const registryPath = options.project ? PROJECT_REGISTRY_PATH : PERSONAL_REGISTRY_PATH
|
|
322
|
+
const registry = new DeploymentRegistry(registryPath)
|
|
323
|
+
|
|
324
|
+
const exportOptions = {}
|
|
325
|
+
if (options.status) {
|
|
326
|
+
exportOptions.status = options.status
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const result = registry.exportEntries(id || null, exportOptions)
|
|
330
|
+
|
|
331
|
+
if (result.entries.length === 0) {
|
|
332
|
+
console.log('No entries to export.')
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
console.log(JSON.stringify(result, null, 2))
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* registry import <file> [--merge|--replace]
|
|
341
|
+
*
|
|
342
|
+
* Reads a JSON file, validates it, and imports entries into the registry.
|
|
343
|
+
*
|
|
344
|
+
* @param {string} filePath - Path to the import file
|
|
345
|
+
* @param {object} options - Parsed CLI options
|
|
346
|
+
*/
|
|
347
|
+
async _handleImport(filePath, options) {
|
|
348
|
+
if (!filePath) {
|
|
349
|
+
console.log('Usage: ml-container-creator registry import <file> [--merge|--replace]')
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let raw
|
|
354
|
+
try {
|
|
355
|
+
raw = readFileSync(filePath, 'utf8')
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.log(`Error: File not found: ${filePath}`)
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let json
|
|
362
|
+
try {
|
|
363
|
+
json = JSON.parse(raw)
|
|
364
|
+
} catch (err) {
|
|
365
|
+
console.log(`Error: Invalid JSON in ${filePath}: ${err.message}`)
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (!json.version || !Array.isArray(json.entries)) {
|
|
370
|
+
console.log('Error: Invalid export format — missing required "version" or "entries" fields.')
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let strategy = 'skip'
|
|
375
|
+
if (options.merge) {
|
|
376
|
+
strategy = 'merge'
|
|
377
|
+
} else if (options.replace) {
|
|
378
|
+
strategy = 'replace'
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const registryPath = options.project ? PROJECT_REGISTRY_PATH : PERSONAL_REGISTRY_PATH
|
|
382
|
+
const registry = new DeploymentRegistry(registryPath)
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
const result = registry.importEntries(json, strategy, path.basename(filePath))
|
|
386
|
+
console.log(`\nImport complete:`)
|
|
387
|
+
console.log(` Added: ${result.added}`)
|
|
388
|
+
console.log(` Skipped: ${result.skipped}`)
|
|
389
|
+
console.log(` Conflicts: ${result.conflicts}`)
|
|
390
|
+
console.log('')
|
|
391
|
+
} catch (err) {
|
|
392
|
+
console.log(`Error importing entries: ${err.message}`)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* registry search [--model, --architecture, --backend, --instance-type, --status]
|
|
398
|
+
*
|
|
399
|
+
* Searches across both personal and project-level registries.
|
|
400
|
+
* Uses glob matching for model names.
|
|
401
|
+
*
|
|
402
|
+
* @param {object} options - Parsed CLI options
|
|
403
|
+
*/
|
|
404
|
+
_handleSearch(options) {
|
|
405
|
+
const query = this._extractFilters(options)
|
|
406
|
+
|
|
407
|
+
const personalRegistry = new DeploymentRegistry(PERSONAL_REGISTRY_PATH)
|
|
408
|
+
const projectRegistry = new DeploymentRegistry(PROJECT_REGISTRY_PATH)
|
|
409
|
+
|
|
410
|
+
const personalResults = personalRegistry.search(query).map(e => ({ ...e, _source: 'personal' }))
|
|
411
|
+
const projectResults = projectRegistry.search(query).map(e => ({ ...e, _source: 'project' }))
|
|
412
|
+
|
|
413
|
+
const allResults = [...personalResults, ...projectResults]
|
|
414
|
+
|
|
415
|
+
if (allResults.length === 0) {
|
|
416
|
+
console.log('No matching entries found.')
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
console.log(`\nSearch Results (${allResults.length} match${allResults.length === 1 ? '' : 'es'}):\n`)
|
|
421
|
+
for (const entry of allResults) {
|
|
422
|
+
const id = entry.id || '(no id)'
|
|
423
|
+
const ts = entry.timestamp ? entry.timestamp.slice(0, 19) : '(no timestamp)'
|
|
424
|
+
const dc = entry.deployment?.deploymentConfig || '(none)'
|
|
425
|
+
const mn = entry.model?.modelName || '(none)'
|
|
426
|
+
const it = entry.infrastructure?.instanceType || '(none)'
|
|
427
|
+
const st = entry.status || '(none)'
|
|
428
|
+
const src = entry._source === 'project' ? ' [project]' : ''
|
|
429
|
+
console.log(` ${id} ${ts} ${dc} ${mn} ${it} ${st}${src}`)
|
|
430
|
+
}
|
|
431
|
+
console.log('')
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Show registry usage help.
|
|
436
|
+
*/
|
|
437
|
+
_showRegistryHelp() {
|
|
438
|
+
console.log(`
|
|
439
|
+
Deployment Registry Management
|
|
440
|
+
|
|
441
|
+
USAGE:
|
|
442
|
+
ml-container-creator registry <subcommand> [options]
|
|
443
|
+
|
|
444
|
+
SUBCOMMANDS:
|
|
445
|
+
list List deployment entries
|
|
446
|
+
get <id> Show full entry details
|
|
447
|
+
remove <id> Remove an entry
|
|
448
|
+
replay <id> [overrides] Replay a deployment configuration
|
|
449
|
+
export [id] [--status <status>] Export entries as JSON
|
|
450
|
+
import <file> [--merge|--replace] Import entries from JSON
|
|
451
|
+
search [filters] Search entries with glob matching
|
|
452
|
+
|
|
453
|
+
FILTER OPTIONS (for list and search):
|
|
454
|
+
--backend <backend> Filter by backend (e.g., vllm, flask)
|
|
455
|
+
--architecture <arch> Filter by architecture (e.g., transformers, http)
|
|
456
|
+
--model <name> Filter by model name (search supports glob patterns)
|
|
457
|
+
--instance-type <type> Filter by instance type
|
|
458
|
+
--status <status> Filter by status (success, partial, failed)
|
|
459
|
+
|
|
460
|
+
REPLAY OPTIONS:
|
|
461
|
+
--deployment-config <config> Override deployment config
|
|
462
|
+
--model-name <name> Override model name
|
|
463
|
+
--instance-type <type> Override instance type
|
|
464
|
+
--region <region> Override region
|
|
465
|
+
|
|
466
|
+
IMPORT OPTIONS:
|
|
467
|
+
--merge Keep both existing and imported on conflict
|
|
468
|
+
--replace Overwrite existing with imported on conflict
|
|
469
|
+
|
|
470
|
+
OTHER OPTIONS:
|
|
471
|
+
--project Use project-level registry instead of personal
|
|
472
|
+
|
|
473
|
+
EXAMPLES:
|
|
474
|
+
ml-container-creator registry list
|
|
475
|
+
ml-container-creator registry list --backend vllm --status success
|
|
476
|
+
ml-container-creator registry get a1b2c3d4
|
|
477
|
+
ml-container-creator registry remove a1b2c3d4
|
|
478
|
+
ml-container-creator registry replay a1b2c3d4
|
|
479
|
+
ml-container-creator registry replay a1b2c3d4 --instance-type ml.g5.2xlarge
|
|
480
|
+
ml-container-creator registry export > my-deployments.json
|
|
481
|
+
ml-container-creator registry export a1b2c3d4
|
|
482
|
+
ml-container-creator registry import team-deployments.json --merge
|
|
483
|
+
ml-container-creator registry search --model "meta-llama/*" --backend vllm
|
|
484
|
+
`)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ── Helper methods ──────────────────────────────────────────────
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Extract filter options from CLI options into a filters object.
|
|
491
|
+
* @param {object} options - Parsed CLI options
|
|
492
|
+
* @returns {object} Filter key-value pairs
|
|
493
|
+
*/
|
|
494
|
+
_extractFilters(options) {
|
|
495
|
+
const filters = {}
|
|
496
|
+
if (options.backend) filters.backend = options.backend
|
|
497
|
+
if (options.architecture) filters.architecture = options.architecture
|
|
498
|
+
if (options.model) filters.model = options.model
|
|
499
|
+
if (options['instance-type'] || options.instanceType) {
|
|
500
|
+
filters['instance-type'] = options['instance-type'] || options.instanceType
|
|
501
|
+
}
|
|
502
|
+
if (options.status) filters.status = options.status
|
|
503
|
+
return filters
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Find an entry by ID across both personal and project registries.
|
|
508
|
+
* @param {string} id - Entry ID
|
|
509
|
+
* @returns {object|null} The matching entry, or null
|
|
510
|
+
*/
|
|
511
|
+
_findEntry(id) {
|
|
512
|
+
const personalRegistry = new DeploymentRegistry(PERSONAL_REGISTRY_PATH)
|
|
513
|
+
const entry = personalRegistry.get(id)
|
|
514
|
+
if (entry) return entry
|
|
515
|
+
|
|
516
|
+
const projectRegistry = new DeploymentRegistry(PROJECT_REGISTRY_PATH)
|
|
517
|
+
return projectRegistry.get(id)
|
|
518
|
+
}
|
|
519
|
+
}
|