@aws/ml-container-creator 0.9.1 → 0.10.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/config/parameter-schema-v2.json +2065 -0
- package/package.json +4 -4
- package/servers/lib/catalogs/jumpstart-public.json +101 -16
- package/servers/lib/catalogs/models.json +182 -26
- package/src/app.js +1 -389
- package/src/lib/bootstrap-command-handler.js +75 -1078
- package/src/lib/bootstrap-profile-manager.js +634 -0
- package/src/lib/bootstrap-provisioners.js +421 -0
- package/src/lib/config-loader.js +405 -0
- package/src/lib/config-manager.js +59 -1685
- package/src/lib/config-mcp-client.js +118 -0
- package/src/lib/config-validator.js +634 -0
- package/src/lib/cuda-resolver.js +140 -0
- package/src/lib/e2e-catalog-validator.js +251 -3
- package/src/lib/e2e-ci-recorder.js +103 -0
- package/src/lib/generated/cli-options.js +8 -4
- package/src/lib/generated/parameter-matrix.js +671 -0
- package/src/lib/generated/validation-rules.js +2 -2
- package/src/lib/marketplace-flow.js +276 -0
- package/src/lib/mcp-query-runner.js +768 -0
- package/src/lib/parameter-schema-validator.js +62 -18
- package/src/lib/prompt-runner.js +41 -1504
- package/src/lib/prompts/feature-prompts.js +172 -0
- package/src/lib/prompts/index.js +48 -0
- package/src/lib/prompts/infrastructure-prompts.js +690 -0
- package/src/lib/prompts/model-prompts.js +552 -0
- package/src/lib/prompts/project-prompts.js +70 -0
- package/src/lib/prompts.js +2 -1446
- package/src/lib/registry-command-handler.js +135 -3
- package/src/lib/secrets-prompt-runner.js +251 -0
- package/src/lib/template-variable-resolver.js +398 -0
- package/config/parameter-schema.json +0 -88
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Config Loader - Handles loading configuration from all sources.
|
|
6
|
+
* Uses delegation pattern: receives parent ConfigManager reference to access shared state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import BootstrapConfig from './bootstrap-config.js';
|
|
12
|
+
import { parseKeyValue } from './key-value-parser.js';
|
|
13
|
+
import { ConfigurationError } from './config-manager.js';
|
|
14
|
+
|
|
15
|
+
export default class ConfigLoader {
|
|
16
|
+
constructor(manager) {
|
|
17
|
+
this.manager = manager;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load from bootstrap config (~/.ml-container-creator/config.json)
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
async _loadBootstrapConfig() {
|
|
25
|
+
try {
|
|
26
|
+
const bootstrapConfig = new BootstrapConfig();
|
|
27
|
+
const activeProfile = bootstrapConfig.getActiveProfile();
|
|
28
|
+
if (!activeProfile) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const profileConfig = activeProfile.config;
|
|
33
|
+
const mapped = {};
|
|
34
|
+
|
|
35
|
+
if (profileConfig.roleArn) {
|
|
36
|
+
mapped.awsRoleArn = profileConfig.roleArn;
|
|
37
|
+
}
|
|
38
|
+
if (profileConfig.awsRegion) {
|
|
39
|
+
mapped.awsRegion = profileConfig.awsRegion;
|
|
40
|
+
}
|
|
41
|
+
if (profileConfig.awsProfile) {
|
|
42
|
+
mapped.awsProfile = profileConfig.awsProfile;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.manager._mergeConfig(mapped);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// Ignore errors — config file may not exist or may be malformed
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Load from package.json "ml-container-creator" section (filtered by matrix)
|
|
53
|
+
* @private
|
|
54
|
+
*/
|
|
55
|
+
async _loadPackageJsonConfig() {
|
|
56
|
+
try {
|
|
57
|
+
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
58
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
59
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
60
|
+
const generatorConfig = packageJson['ml-container-creator'];
|
|
61
|
+
if (generatorConfig) {
|
|
62
|
+
const filteredConfig = {};
|
|
63
|
+
Object.entries(generatorConfig).forEach(([key, value]) => {
|
|
64
|
+
if (this.manager._isSourceSupported(key, 'packageJson')) {
|
|
65
|
+
filteredConfig[key] = this.manager._parseValue(key, value);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
this.manager._mergeConfig(filteredConfig);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
// Ignore errors - this is optional
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Load from config/mcp.json
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
async _loadCustomConfigFile() {
|
|
81
|
+
try {
|
|
82
|
+
const configPath = path.join(this.manager.GENERATOR_ROOT, 'config', 'mcp.json');
|
|
83
|
+
if (fs.existsSync(configPath)) {
|
|
84
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
85
|
+
this.manager._mergeConfig(config);
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// Ignore errors - this is optional
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Load from CLI --config file or --config-json inline string.
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
async _loadCliConfigFile() {
|
|
97
|
+
let configFile = this.manager.options.config;
|
|
98
|
+
|
|
99
|
+
// Check environment variable if CLI option not provided
|
|
100
|
+
if (!configFile && process.env.ML_CONTAINER_CREATOR_CONFIG) {
|
|
101
|
+
configFile = process.env.ML_CONTAINER_CREATOR_CONFIG;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (configFile) {
|
|
105
|
+
this._loadConfigFromFile(configFile);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// --config-json: inline JSON string or path to a JSON file
|
|
109
|
+
const configJson = this.manager.options['config-json'];
|
|
110
|
+
if (configJson) {
|
|
111
|
+
this._loadConfigFromJson(configJson);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Load configuration from a JSON file path.
|
|
117
|
+
* @param {string} configFile - Path to the JSON config file
|
|
118
|
+
* @private
|
|
119
|
+
*/
|
|
120
|
+
_loadConfigFromFile(configFile) {
|
|
121
|
+
try {
|
|
122
|
+
const configPath = path.resolve(configFile);
|
|
123
|
+
if (!fs.existsSync(configPath)) {
|
|
124
|
+
throw new ConfigurationError(
|
|
125
|
+
`Config file not found: ${configPath}`,
|
|
126
|
+
'configFile',
|
|
127
|
+
'cli'
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
fs.accessSync(configPath, fs.constants.R_OK);
|
|
133
|
+
} catch (accessError) {
|
|
134
|
+
throw new ConfigurationError(
|
|
135
|
+
`Config file is not readable: ${configPath}`,
|
|
136
|
+
'configFile',
|
|
137
|
+
'cli'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
142
|
+
this._applyJsonConfig(config);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error instanceof ConfigurationError) {
|
|
145
|
+
throw error;
|
|
146
|
+
} else {
|
|
147
|
+
throw new ConfigurationError(
|
|
148
|
+
`Failed to load config file ${configFile}: ${error.message}`,
|
|
149
|
+
'configFile',
|
|
150
|
+
'cli'
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Load configuration from an inline JSON string or a JSON file path.
|
|
158
|
+
* @param {string} configJson - Inline JSON string or path to a JSON file
|
|
159
|
+
* @private
|
|
160
|
+
*/
|
|
161
|
+
_loadConfigFromJson(configJson) {
|
|
162
|
+
let config;
|
|
163
|
+
try {
|
|
164
|
+
config = JSON.parse(configJson);
|
|
165
|
+
} catch {
|
|
166
|
+
try {
|
|
167
|
+
const configPath = path.resolve(configJson);
|
|
168
|
+
if (!fs.existsSync(configPath)) {
|
|
169
|
+
throw new ConfigurationError(
|
|
170
|
+
`--config-json value is not valid JSON and file not found: ${configJson}`,
|
|
171
|
+
'configJson',
|
|
172
|
+
'cli'
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error instanceof ConfigurationError) {
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
throw new ConfigurationError(
|
|
181
|
+
`Failed to parse --config-json: ${error.message}`,
|
|
182
|
+
'configJson',
|
|
183
|
+
'cli'
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
this._applyJsonConfig(config);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Apply a parsed JSON config object, filtering to supported parameters.
|
|
192
|
+
* @param {Object} config - Parsed JSON config object
|
|
193
|
+
* @private
|
|
194
|
+
*/
|
|
195
|
+
_applyJsonConfig(config) {
|
|
196
|
+
const filteredConfig = {};
|
|
197
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
198
|
+
// Handle nested endpointConfig object
|
|
199
|
+
if (key === 'endpointConfig' && typeof value === 'object' && value !== null) {
|
|
200
|
+
const endpointMapping = {
|
|
201
|
+
initialInstanceCount: 'endpointInitialInstanceCount',
|
|
202
|
+
dataCapturePercent: 'endpointDataCapturePercent',
|
|
203
|
+
variantName: 'endpointVariantName',
|
|
204
|
+
volumeSize: 'endpointVolumeSize'
|
|
205
|
+
};
|
|
206
|
+
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
|
207
|
+
const flatKey = endpointMapping[nestedKey];
|
|
208
|
+
if (flatKey && this.manager._isSourceSupported(flatKey, 'configFile')) {
|
|
209
|
+
filteredConfig[flatKey] = nestedValue;
|
|
210
|
+
this.manager._recordSource(flatKey, nestedValue, 'config-file');
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Handle nested icConfig object
|
|
217
|
+
if (key === 'icConfig' && typeof value === 'object' && value !== null) {
|
|
218
|
+
const icMapping = {
|
|
219
|
+
cpuCount: 'icCpuCount',
|
|
220
|
+
memorySize: 'icMemorySize',
|
|
221
|
+
gpuCount: 'icGpuCount',
|
|
222
|
+
copyCount: 'icCopyCount',
|
|
223
|
+
modelWeight: 'icModelWeight'
|
|
224
|
+
};
|
|
225
|
+
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
|
226
|
+
const flatKey = icMapping[nestedKey];
|
|
227
|
+
if (flatKey && this.manager._isSourceSupported(flatKey, 'configFile')) {
|
|
228
|
+
filteredConfig[flatKey] = nestedValue;
|
|
229
|
+
this.manager._recordSource(flatKey, nestedValue, 'config-file');
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Handle modelEnvVars object
|
|
236
|
+
if (key === 'modelEnvVars' && typeof value === 'object' && value !== null) {
|
|
237
|
+
if (!this.manager.config.modelEnvVars) {
|
|
238
|
+
this.manager.config.modelEnvVars = {};
|
|
239
|
+
}
|
|
240
|
+
const cliModelEnvVars = (this.manager.explicitConfig && this.manager.explicitConfig.modelEnvVars) || {};
|
|
241
|
+
Object.entries(value).forEach(([envKey, envValue]) => {
|
|
242
|
+
if (!(envKey in cliModelEnvVars)) {
|
|
243
|
+
this.manager.config.modelEnvVars[envKey] = envValue;
|
|
244
|
+
this.manager._recordSource(`modelEnvVars.${envKey}`, envValue, 'config-file');
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Handle serverEnvVars object
|
|
251
|
+
if (key === 'serverEnvVars' && typeof value === 'object' && value !== null) {
|
|
252
|
+
if (!this.manager.config.serverEnvVars) {
|
|
253
|
+
this.manager.config.serverEnvVars = {};
|
|
254
|
+
}
|
|
255
|
+
const cliServerEnvVars = (this.manager.explicitConfig && this.manager.explicitConfig.serverEnvVars) || {};
|
|
256
|
+
Object.entries(value).forEach(([envKey, envValue]) => {
|
|
257
|
+
if (!(envKey in cliServerEnvVars)) {
|
|
258
|
+
this.manager.config.serverEnvVars[envKey] = envValue;
|
|
259
|
+
this.manager._recordSource(`serverEnvVars.${envKey}`, envValue, 'config-file');
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (this.manager._isSourceSupported(key, 'configFile')) {
|
|
266
|
+
filteredConfig[key] = this.manager._parseValue(key, value);
|
|
267
|
+
this.manager._recordSource(key, this.manager._parseValue(key, value), 'config-file');
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
this.manager._mergeConfig(filteredConfig);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Load from environment variables (filtered by matrix)
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
async _loadEnvironmentVariables() {
|
|
278
|
+
const envMapping = {};
|
|
279
|
+
Object.entries(this.manager.parameterMatrix).forEach(([param, config]) => {
|
|
280
|
+
if (config.envVar) {
|
|
281
|
+
envMapping[config.envVar] = { param, ambient: false };
|
|
282
|
+
}
|
|
283
|
+
// Also check ambient env vars (e.g., AWS_REGION, AWS_ROLE, HF_TOKEN)
|
|
284
|
+
if (config.ambientEnvVar) {
|
|
285
|
+
envMapping[config.ambientEnvVar] = { param, ambient: true };
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
Object.entries(envMapping).forEach(([envVar, { param: configKey, ambient }]) => {
|
|
290
|
+
const value = process.env[envVar];
|
|
291
|
+
if (value !== undefined && value !== '' && this.manager._isSourceSupported(configKey, 'envVar')) {
|
|
292
|
+
this.manager.config[configKey] = this.manager._parseValue(configKey, value);
|
|
293
|
+
this.manager._recordSource(configKey, this.manager._parseValue(configKey, value), 'env-var');
|
|
294
|
+
if (!ambient) {
|
|
295
|
+
if (!this.manager.explicitConfig) {
|
|
296
|
+
this.manager.explicitConfig = {};
|
|
297
|
+
}
|
|
298
|
+
this.manager.explicitConfig[configKey] = this.manager._parseValue(configKey, value);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Load from CLI arguments (positional)
|
|
306
|
+
* @private
|
|
307
|
+
*/
|
|
308
|
+
async _loadCliArguments() {
|
|
309
|
+
if (this.manager.args && this.manager.args.length > 0) {
|
|
310
|
+
this.manager.config.projectName = this.manager.args[0];
|
|
311
|
+
if (!this.manager.explicitConfig) {
|
|
312
|
+
this.manager.explicitConfig = {};
|
|
313
|
+
}
|
|
314
|
+
this.manager.explicitConfig.projectName = this.manager.args[0];
|
|
315
|
+
this.manager.projectNameFromArgument = true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Load from CLI options (highest precedence, filtered by matrix)
|
|
321
|
+
* @private
|
|
322
|
+
*/
|
|
323
|
+
async _loadCliOptions() {
|
|
324
|
+
const options = this.manager.options;
|
|
325
|
+
|
|
326
|
+
Object.entries(this.manager.parameterMatrix).forEach(([param, config]) => {
|
|
327
|
+
if (config.cliOption && options[config.cliOption] !== undefined) {
|
|
328
|
+
this.manager.config[param] = this.manager._parseValue(param, options[config.cliOption]);
|
|
329
|
+
this.manager._recordSource(param, this.manager._parseValue(param, options[config.cliOption]), 'cli');
|
|
330
|
+
if (!this.manager.explicitConfig) {
|
|
331
|
+
this.manager.explicitConfig = {};
|
|
332
|
+
}
|
|
333
|
+
this.manager.explicitConfig[param] = this.manager._parseValue(param, options[config.cliOption]);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Parse --model-env KEY=VALUE pairs
|
|
338
|
+
this._parseEnvVarOptions('model-env', 'modelEnvVars');
|
|
339
|
+
|
|
340
|
+
// Parse --server-env KEY=VALUE pairs
|
|
341
|
+
this._parseEnvVarOptions('server-env', 'serverEnvVars');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Normalizes deprecated parameter values to their canonical equivalents.
|
|
346
|
+
* @private
|
|
347
|
+
*/
|
|
348
|
+
_normalizeDeprecatedValues() {
|
|
349
|
+
const DEPRECATED_VALUES = {
|
|
350
|
+
deploymentTarget: {
|
|
351
|
+
'managed-inference': {
|
|
352
|
+
canonical: 'realtime-inference',
|
|
353
|
+
message: '--deployment-target=managed-inference is deprecated, use realtime-inference instead'
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
for (const [param, aliases] of Object.entries(DEPRECATED_VALUES)) {
|
|
359
|
+
const currentValue = this.manager.config[param];
|
|
360
|
+
if (currentValue && aliases[currentValue]) {
|
|
361
|
+
const { canonical, message } = aliases[currentValue];
|
|
362
|
+
console.log(`\n⚠️ Deprecation: ${message}`);
|
|
363
|
+
this.manager.config[param] = canonical;
|
|
364
|
+
if (this.manager.explicitConfig && this.manager.explicitConfig[param] === currentValue) {
|
|
365
|
+
this.manager.explicitConfig[param] = canonical;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Parse --model-env or --server-env CLI options into env var collections.
|
|
373
|
+
* @param {string} optionName - CLI option name
|
|
374
|
+
* @param {string} configKey - Config key to store results
|
|
375
|
+
* @private
|
|
376
|
+
*/
|
|
377
|
+
_parseEnvVarOptions(optionName, configKey) {
|
|
378
|
+
const rawValue = this.manager.options[optionName];
|
|
379
|
+
if (rawValue === undefined || rawValue === null) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const values = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
384
|
+
|
|
385
|
+
if (!this.manager.config[configKey] || typeof this.manager.config[configKey] !== 'object') {
|
|
386
|
+
this.manager.config[configKey] = {};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
for (const entry of values) {
|
|
390
|
+
if (typeof entry !== 'string' || entry.trim() === '') {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
const { key, value } = parseKeyValue(entry);
|
|
394
|
+
this.manager.config[configKey][key] = value;
|
|
395
|
+
this.manager._recordSource(`${configKey}.${key}`, value, 'cli');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (Object.keys(this.manager.config[configKey]).length > 0) {
|
|
399
|
+
if (!this.manager.explicitConfig) {
|
|
400
|
+
this.manager.explicitConfig = {};
|
|
401
|
+
}
|
|
402
|
+
this.manager.explicitConfig[configKey] = { ...this.manager.config[configKey] };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|