@aws/ml-container-creator 0.2.5 → 0.2.6
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/bin/cli.js +7 -2
- package/package.json +7 -8
- package/servers/base-image-picker/index.js +3 -3
- package/servers/base-image-picker/manifest.json +4 -2
- package/servers/instance-sizer/index.js +561 -0
- package/servers/instance-sizer/lib/instance-ranker.js +245 -0
- package/servers/instance-sizer/lib/model-resolver.js +265 -0
- package/servers/instance-sizer/lib/vram-estimator.js +177 -0
- package/servers/instance-sizer/manifest.json +17 -0
- package/servers/instance-sizer/package.json +15 -0
- package/servers/{instance-recommender → lib}/catalogs/instances.json +136 -34
- package/servers/{base-image-picker → lib}/catalogs/model-servers.json +19 -249
- package/servers/lib/catalogs/model-sizes.json +131 -0
- package/servers/lib/catalogs/models.json +602 -0
- package/servers/{model-picker → lib}/catalogs/popular-diffusors.json +32 -10
- package/servers/{model-picker → lib}/catalogs/popular-transformers.json +59 -26
- package/servers/{base-image-picker → lib}/catalogs/python-slim.json +12 -12
- package/servers/lib/schemas/image-catalog.schema.json +0 -12
- package/servers/lib/schemas/instances.schema.json +29 -0
- package/servers/lib/schemas/model-catalog.schema.json +12 -10
- package/servers/lib/schemas/unified-model-catalog.schema.json +129 -0
- package/servers/model-picker/index.js +2 -3
- package/servers/model-picker/manifest.json +2 -3
- package/servers/region-picker/index.js +1 -1
- package/servers/region-picker/manifest.json +1 -1
- package/src/app.js +17 -0
- package/src/lib/bootstrap-command-handler.js +38 -0
- package/src/lib/cli-handler.js +3 -3
- package/src/lib/config-manager.js +4 -1
- package/src/lib/configuration-manager.js +2 -2
- package/src/lib/cross-cutting-checker.js +341 -0
- package/src/lib/dry-run-validator.js +78 -0
- package/src/lib/generation-validator.js +102 -0
- package/src/lib/mcp-validator-config.js +89 -0
- package/src/lib/payload-builder.js +153 -0
- package/src/lib/prompt-runner.js +445 -135
- package/src/lib/prompts.js +1 -1
- package/src/lib/registry-loader.js +5 -5
- package/src/lib/schema-sync.js +203 -0
- package/src/lib/schema-validation-engine.js +195 -0
- package/src/lib/service-model-parser.js +102 -0
- package/src/lib/validate-runner.js +167 -0
- package/src/lib/validation-report.js +133 -0
- package/src/lib/validators/base-validator.js +36 -0
- package/src/lib/validators/catalog-validator.js +177 -0
- package/src/lib/validators/enum-validator.js +120 -0
- package/src/lib/validators/required-field-validator.js +150 -0
- package/src/lib/validators/type-validator.js +313 -0
- package/templates/Dockerfile +1 -1
- package/templates/do/build +15 -5
- package/templates/do/run +5 -1
- package/templates/do/validate +61 -0
- package/servers/instance-recommender/LICENSE +0 -202
- package/servers/instance-recommender/index.js +0 -284
- package/servers/instance-recommender/manifest.json +0 -16
- package/servers/instance-recommender/package.json +0 -15
- /package/servers/{model-picker → lib}/catalogs/jumpstart-public.json +0 -0
- /package/servers/{region-picker → lib}/catalogs/regions.json +0 -0
- /package/servers/{base-image-picker → lib}/catalogs/triton-backends.json +0 -0
- /package/servers/{base-image-picker → lib}/catalogs/triton.json +0 -0
|
@@ -45,6 +45,12 @@ export default class BootstrapCommandHandler {
|
|
|
45
45
|
* @param {object} options - Parsed CLI options
|
|
46
46
|
*/
|
|
47
47
|
async handle(args, options) {
|
|
48
|
+
// Handle legacy --sync-schemas flag for backward compatibility
|
|
49
|
+
if (options['sync-schemas']) {
|
|
50
|
+
await this._handleSyncSchemas();
|
|
51
|
+
if (args.length === 0) return;
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
if (args.length === 0) {
|
|
49
55
|
await this._handleInteractiveSetup(options);
|
|
50
56
|
return;
|
|
@@ -74,6 +80,9 @@ export default class BootstrapCommandHandler {
|
|
|
74
80
|
case 'update':
|
|
75
81
|
await this._handleUpdate(options);
|
|
76
82
|
break;
|
|
83
|
+
case 'sync-schemas':
|
|
84
|
+
await this._handleSyncSchemas();
|
|
85
|
+
break;
|
|
77
86
|
default:
|
|
78
87
|
console.log(`Unknown bootstrap subcommand: ${subcommand}`);
|
|
79
88
|
this._showHelp();
|
|
@@ -927,6 +936,35 @@ export default class BootstrapCommandHandler {
|
|
|
927
936
|
console.log(`\n Done. ${toRemove.length} removed, ${after.length} remaining.`);
|
|
928
937
|
}
|
|
929
938
|
|
|
939
|
+
/**
|
|
940
|
+
* Handle sync-schemas subcommand: download service models and verify AWS CLI.
|
|
941
|
+
*/
|
|
942
|
+
async _handleSyncSchemas() {
|
|
943
|
+
console.log('\n📦 Schema Sync — Downloading AWS service models...\n');
|
|
944
|
+
|
|
945
|
+
// Verify AWS CLI is installed
|
|
946
|
+
try {
|
|
947
|
+
const version = execSync('aws --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
948
|
+
console.log(` AWS CLI: ${version}`);
|
|
949
|
+
} catch {
|
|
950
|
+
console.log(' ⚠️ AWS CLI not found.');
|
|
951
|
+
console.log(' Install: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html');
|
|
952
|
+
console.log(' Continuing without AWS CLI verification...\n');
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Dynamic import to avoid circular dependencies
|
|
956
|
+
const { syncSchemas } = await import('./schema-sync.js');
|
|
957
|
+
const result = await syncSchemas();
|
|
958
|
+
|
|
959
|
+
if (result.success) {
|
|
960
|
+
console.log('\n ✅ Schema sync complete.');
|
|
961
|
+
} else {
|
|
962
|
+
console.log('\n ⚠️ Schema sync completed with errors (some services may be unavailable).');
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
console.log(` Manifest written: lastSynced = ${result.manifest.lastSynced}\n`);
|
|
966
|
+
}
|
|
967
|
+
|
|
930
968
|
/**
|
|
931
969
|
* Re-deploy bootstrap infrastructure using the active profile.
|
|
932
970
|
* No prompts — reads all config from the existing profile and re-applies
|
package/src/lib/cli-handler.js
CHANGED
|
@@ -280,9 +280,9 @@ TRANSFORMER MODEL EXAMPLES:
|
|
|
280
280
|
|
|
281
281
|
REGISTRY CONTRIBUTION:
|
|
282
282
|
To contribute to the catalogs:
|
|
283
|
-
- Framework Catalog: servers/
|
|
284
|
-
- Model Catalog: servers/
|
|
285
|
-
- Instance Catalog: servers/
|
|
283
|
+
- Framework Catalog: servers/lib/catalogs/model-servers.json
|
|
284
|
+
- Model Catalog: servers/lib/catalogs/popular-transformers.json
|
|
285
|
+
- Instance Catalog: servers/lib/catalogs/instances.json
|
|
286
286
|
|
|
287
287
|
See docs/REGISTRY_CONTRIBUTION_GUIDE.md for detailed contribution guidelines.
|
|
288
288
|
|
|
@@ -29,7 +29,7 @@ import ParameterSchemaValidator from './parameter-schema-validator.js';
|
|
|
29
29
|
|
|
30
30
|
const __configMgrFilename = fileURLToPath(import.meta.url);
|
|
31
31
|
const __configMgrDir = dirname(__configMgrFilename);
|
|
32
|
-
const tritonBackendsCatalogPath = resolve(__configMgrDir, '../../servers/
|
|
32
|
+
const tritonBackendsCatalogPath = resolve(__configMgrDir, '../../servers/lib/catalogs/triton-backends.json');
|
|
33
33
|
|
|
34
34
|
function loadTritonBackendsFromCatalog() {
|
|
35
35
|
try {
|
|
@@ -1927,6 +1927,9 @@ export default class ConfigManager {
|
|
|
1927
1927
|
// For required parameters: fill auto-generatable values
|
|
1928
1928
|
if (this.config[param] === undefined || this.config[param] === null) {
|
|
1929
1929
|
if (param === 'instanceType') {
|
|
1930
|
+
// If instance-sizer is configured and model is known, defer to sizer
|
|
1931
|
+
// The sizer query happens in PromptRunner after model is selected
|
|
1932
|
+
// For now, set a heuristic default that may be overridden by the sizer
|
|
1930
1933
|
const arch = architecture || 'http';
|
|
1931
1934
|
this.config[param] = arch === 'http' ? 'ml.m5.large' : 'ml.g5.xlarge';
|
|
1932
1935
|
} else if (param === 'modelFormat') {
|
|
@@ -619,8 +619,8 @@ export default class ConfigurationManager {
|
|
|
619
619
|
*/
|
|
620
620
|
_generateSubmissionInstructions(registryType, config, configEntry) {
|
|
621
621
|
const registryFile = registryType === 'framework'
|
|
622
|
-
? 'servers/
|
|
623
|
-
: 'servers/
|
|
622
|
+
? 'servers/lib/catalogs/model-servers.json'
|
|
623
|
+
: 'servers/lib/catalogs/popular-transformers.json';
|
|
624
624
|
|
|
625
625
|
const registryName = registryType === 'framework'
|
|
626
626
|
? 'Framework_Catalog'
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/* eslint-disable eqeqeq */
|
|
2
|
+
/**
|
|
3
|
+
* Validates consistency rules across multiple payloads and configuration sources.
|
|
4
|
+
* Checks GPU counts, tensor parallelism, model source requirements, role ARN format,
|
|
5
|
+
* CUDA compatibility, and model type / instance alignment.
|
|
6
|
+
*
|
|
7
|
+
* Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7
|
|
8
|
+
*/
|
|
9
|
+
export default class CrossCuttingChecker {
|
|
10
|
+
/**
|
|
11
|
+
* Run all cross-cutting consistency checks.
|
|
12
|
+
* @param {Object} context - ValidationContext from PayloadBuilder
|
|
13
|
+
* @param {Object} instanceCatalog - Instance catalog (from servers/lib/catalogs/instances.json)
|
|
14
|
+
* @returns {Array} Array of Finding objects
|
|
15
|
+
*/
|
|
16
|
+
check(context, instanceCatalog) {
|
|
17
|
+
const findings = [];
|
|
18
|
+
|
|
19
|
+
findings.push(...this.checkGpuConsistency(context, instanceCatalog));
|
|
20
|
+
findings.push(...this.checkTensorParallelism(context, instanceCatalog));
|
|
21
|
+
findings.push(...this.checkModelSourceRequirements(context));
|
|
22
|
+
findings.push(...this.checkRoleArnFormat(context));
|
|
23
|
+
findings.push(...this.checkCudaCompatibility(context, instanceCatalog));
|
|
24
|
+
findings.push(...this.checkModelTypeInstanceAlignment(context, instanceCatalog));
|
|
25
|
+
|
|
26
|
+
return findings;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Verify GPU count consistency: instance type ↔ IC spec.
|
|
31
|
+
* @param {Object} context - ValidationContext
|
|
32
|
+
* @param {Object} instanceCatalog - Instance catalog
|
|
33
|
+
* @returns {Array} Findings
|
|
34
|
+
*/
|
|
35
|
+
checkGpuConsistency(context, instanceCatalog) {
|
|
36
|
+
const findings = [];
|
|
37
|
+
const config = context.config || {};
|
|
38
|
+
const catalog = instanceCatalog?.catalog || instanceCatalog || {};
|
|
39
|
+
|
|
40
|
+
const instanceType = config.INSTANCE_TYPE;
|
|
41
|
+
if (!instanceType) return findings;
|
|
42
|
+
|
|
43
|
+
const instanceInfo = catalog[instanceType];
|
|
44
|
+
if (!instanceInfo) return findings;
|
|
45
|
+
|
|
46
|
+
const instanceGpuCount = instanceInfo.gpus;
|
|
47
|
+
if (instanceGpuCount == null || instanceGpuCount === 0) return findings;
|
|
48
|
+
|
|
49
|
+
const icGpuCount = config.IC_GPU_COUNT;
|
|
50
|
+
if (icGpuCount == null) return findings;
|
|
51
|
+
|
|
52
|
+
if (Number(icGpuCount) !== Number(instanceGpuCount)) {
|
|
53
|
+
findings.push({
|
|
54
|
+
service: 'cross-cutting',
|
|
55
|
+
operation: 'configuration',
|
|
56
|
+
fieldPath: 'NumberOfAcceleratorDevicesRequired',
|
|
57
|
+
invalidValue: icGpuCount,
|
|
58
|
+
constraint: {
|
|
59
|
+
type: 'gpu-consistency',
|
|
60
|
+
expected: instanceGpuCount,
|
|
61
|
+
instanceType
|
|
62
|
+
},
|
|
63
|
+
severity: 'error',
|
|
64
|
+
confidence: 'high',
|
|
65
|
+
source: 'cross-cutting',
|
|
66
|
+
remediationHint: `NumberOfAcceleratorDevicesRequired (${icGpuCount}) does not match GPU count (${instanceGpuCount}) for instance type ${instanceType}. Set IC_GPU_COUNT to ${instanceGpuCount}.`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return findings;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Verify tensor parallelism three-way check:
|
|
75
|
+
* VLLM_TENSOR_PARALLEL_SIZE == NumberOfAcceleratorDevicesRequired == instance GPU count.
|
|
76
|
+
* Only applies when model server is vLLM or SGLang.
|
|
77
|
+
* @param {Object} context - ValidationContext
|
|
78
|
+
* @param {Object} instanceCatalog - Instance catalog
|
|
79
|
+
* @returns {Array} Findings
|
|
80
|
+
*/
|
|
81
|
+
checkTensorParallelism(context, instanceCatalog) {
|
|
82
|
+
const findings = [];
|
|
83
|
+
const config = context.config || {};
|
|
84
|
+
const catalog = instanceCatalog?.catalog || instanceCatalog || {};
|
|
85
|
+
|
|
86
|
+
const modelServer = config.MODEL_SERVER || config.modelServer || '';
|
|
87
|
+
const normalizedServer = modelServer.toLowerCase();
|
|
88
|
+
|
|
89
|
+
if (normalizedServer !== 'vllm' && normalizedServer !== 'sglang') {
|
|
90
|
+
return findings;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const tpSize = config.VLLM_TENSOR_PARALLEL_SIZE;
|
|
94
|
+
if (tpSize == null) return findings;
|
|
95
|
+
|
|
96
|
+
const instanceType = config.INSTANCE_TYPE;
|
|
97
|
+
const instanceInfo = instanceType ? catalog[instanceType] : null;
|
|
98
|
+
const instanceGpuCount = instanceInfo?.gpus;
|
|
99
|
+
|
|
100
|
+
const icGpuCount = config.IC_GPU_COUNT;
|
|
101
|
+
|
|
102
|
+
// Check TP size vs IC GPU count
|
|
103
|
+
if (icGpuCount != null && Number(tpSize) !== Number(icGpuCount)) {
|
|
104
|
+
findings.push({
|
|
105
|
+
service: 'cross-cutting',
|
|
106
|
+
operation: 'configuration',
|
|
107
|
+
fieldPath: 'VLLM_TENSOR_PARALLEL_SIZE',
|
|
108
|
+
invalidValue: tpSize,
|
|
109
|
+
constraint: {
|
|
110
|
+
type: 'tensor-parallelism',
|
|
111
|
+
expected: icGpuCount,
|
|
112
|
+
field: 'NumberOfAcceleratorDevicesRequired'
|
|
113
|
+
},
|
|
114
|
+
severity: 'error',
|
|
115
|
+
confidence: 'high',
|
|
116
|
+
source: 'cross-cutting',
|
|
117
|
+
remediationHint: `VLLM_TENSOR_PARALLEL_SIZE (${tpSize}) must equal NumberOfAcceleratorDevicesRequired (${icGpuCount}) for ${modelServer}.`
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check TP size vs instance GPU count
|
|
122
|
+
if (instanceGpuCount != null && Number(tpSize) !== Number(instanceGpuCount)) {
|
|
123
|
+
findings.push({
|
|
124
|
+
service: 'cross-cutting',
|
|
125
|
+
operation: 'configuration',
|
|
126
|
+
fieldPath: 'VLLM_TENSOR_PARALLEL_SIZE',
|
|
127
|
+
invalidValue: tpSize,
|
|
128
|
+
constraint: {
|
|
129
|
+
type: 'tensor-parallelism',
|
|
130
|
+
expected: instanceGpuCount,
|
|
131
|
+
field: 'instanceGpuCount'
|
|
132
|
+
},
|
|
133
|
+
severity: 'error',
|
|
134
|
+
confidence: 'high',
|
|
135
|
+
source: 'cross-cutting',
|
|
136
|
+
remediationHint: `VLLM_TENSOR_PARALLEL_SIZE (${tpSize}) must equal instance GPU count (${instanceGpuCount}) for ${instanceType}.`
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return findings;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Verify model source requirements (artifact URI, hub access config).
|
|
145
|
+
* @param {Object} context - ValidationContext
|
|
146
|
+
* @returns {Array} Findings
|
|
147
|
+
*/
|
|
148
|
+
checkModelSourceRequirements(context) {
|
|
149
|
+
const findings = [];
|
|
150
|
+
const config = context.config || {};
|
|
151
|
+
|
|
152
|
+
const modelSource = config.modelSource || config.MODEL_SOURCE || '';
|
|
153
|
+
|
|
154
|
+
// When modelSource is 'jumpstart-hub', verify HubAccessConfig.HubContentArn is present
|
|
155
|
+
if (modelSource === 'jumpstart-hub') {
|
|
156
|
+
const payloads = context.payloads || {};
|
|
157
|
+
let hubContentArnFound = false;
|
|
158
|
+
|
|
159
|
+
for (const payload of Object.values(payloads)) {
|
|
160
|
+
if (payload?.HubAccessConfig?.HubContentArn) {
|
|
161
|
+
hubContentArnFound = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!hubContentArnFound && !config.HUB_CONTENT_ARN) {
|
|
167
|
+
findings.push({
|
|
168
|
+
service: 'cross-cutting',
|
|
169
|
+
operation: 'configuration',
|
|
170
|
+
fieldPath: 'HubAccessConfig.HubContentArn',
|
|
171
|
+
invalidValue: null,
|
|
172
|
+
constraint: {
|
|
173
|
+
type: 'conditional-required',
|
|
174
|
+
condition: 'modelSource === jumpstart-hub'
|
|
175
|
+
},
|
|
176
|
+
severity: 'error',
|
|
177
|
+
confidence: 'high',
|
|
178
|
+
source: 'cross-cutting',
|
|
179
|
+
remediationHint: 'When modelSource is "jumpstart-hub", HubAccessConfig.HubContentArn must be present in the payload.'
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// When modelSource in {s3, jumpstart, jumpstart-hub, registry}, verify MODEL_ARTIFACT_URI is non-empty
|
|
185
|
+
const sourcesRequiringArtifact = ['s3', 'jumpstart', 'jumpstart-hub', 'registry'];
|
|
186
|
+
if (sourcesRequiringArtifact.includes(modelSource)) {
|
|
187
|
+
const artifactUri = config.MODEL_ARTIFACT_URI || '';
|
|
188
|
+
if (!artifactUri || artifactUri.trim() === '') {
|
|
189
|
+
findings.push({
|
|
190
|
+
service: 'cross-cutting',
|
|
191
|
+
operation: 'configuration',
|
|
192
|
+
fieldPath: 'MODEL_ARTIFACT_URI',
|
|
193
|
+
invalidValue: artifactUri || null,
|
|
194
|
+
constraint: {
|
|
195
|
+
type: 'conditional-required',
|
|
196
|
+
condition: `modelSource === ${modelSource}`
|
|
197
|
+
},
|
|
198
|
+
severity: 'error',
|
|
199
|
+
confidence: 'high',
|
|
200
|
+
source: 'cross-cutting',
|
|
201
|
+
remediationHint: `When modelSource is "${modelSource}", MODEL_ARTIFACT_URI must be set and non-empty.`
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return findings;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Verify role ARN format for realtime-inference.
|
|
211
|
+
* @param {Object} context - ValidationContext
|
|
212
|
+
* @returns {Array} Findings
|
|
213
|
+
*/
|
|
214
|
+
checkRoleArnFormat(context) {
|
|
215
|
+
const findings = [];
|
|
216
|
+
const config = context.config || {};
|
|
217
|
+
const deploymentTarget = context.deploymentTarget || '';
|
|
218
|
+
|
|
219
|
+
if (deploymentTarget !== 'realtime-inference') return findings;
|
|
220
|
+
|
|
221
|
+
const roleArn = config.ROLE_ARN;
|
|
222
|
+
if (roleArn == null || roleArn === '') return findings;
|
|
223
|
+
|
|
224
|
+
const arnPattern = /^arn:aws:iam::\d{12}:role\/.+$/;
|
|
225
|
+
if (!arnPattern.test(roleArn)) {
|
|
226
|
+
findings.push({
|
|
227
|
+
service: 'cross-cutting',
|
|
228
|
+
operation: 'configuration',
|
|
229
|
+
fieldPath: 'ROLE_ARN',
|
|
230
|
+
invalidValue: roleArn,
|
|
231
|
+
constraint: {
|
|
232
|
+
type: 'pattern',
|
|
233
|
+
pattern: 'arn:aws:iam::\\d{12}:role/.+'
|
|
234
|
+
},
|
|
235
|
+
severity: 'error',
|
|
236
|
+
confidence: 'high',
|
|
237
|
+
source: 'cross-cutting',
|
|
238
|
+
remediationHint: `ROLE_ARN "${roleArn}" does not match IAM role ARN pattern. Expected format: arn:aws:iam::<12-digit-account-id>:role/<role-name>.`
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return findings;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Verify CUDA version compatibility: base image CUDA ∩ instance CUDA versions is non-empty.
|
|
247
|
+
* @param {Object} context - ValidationContext
|
|
248
|
+
* @param {Object} instanceCatalog - Instance catalog
|
|
249
|
+
* @returns {Array} Findings
|
|
250
|
+
*/
|
|
251
|
+
checkCudaCompatibility(context, instanceCatalog) {
|
|
252
|
+
const findings = [];
|
|
253
|
+
const config = context.config || {};
|
|
254
|
+
const catalog = instanceCatalog?.catalog || instanceCatalog || {};
|
|
255
|
+
|
|
256
|
+
const instanceType = config.INSTANCE_TYPE;
|
|
257
|
+
if (!instanceType) return findings;
|
|
258
|
+
|
|
259
|
+
const instanceInfo = catalog[instanceType];
|
|
260
|
+
if (!instanceInfo) return findings;
|
|
261
|
+
|
|
262
|
+
const instanceCudaVersions = instanceInfo.cudaVersions;
|
|
263
|
+
if (!instanceCudaVersions || !Array.isArray(instanceCudaVersions) || instanceCudaVersions.length === 0) {
|
|
264
|
+
return findings;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Extract base image CUDA requirement from config
|
|
268
|
+
const cudaRequirement = config.acceleratorVersion || config.CUDA_VERSION || '';
|
|
269
|
+
if (!cudaRequirement) return findings;
|
|
270
|
+
|
|
271
|
+
// Check if any instance CUDA version matches the base image requirement
|
|
272
|
+
// Compare major version (e.g., "12" matches "12.1", "12.2")
|
|
273
|
+
const requiredMajor = String(cudaRequirement).split('.')[0];
|
|
274
|
+
|
|
275
|
+
const hasCompatible = instanceCudaVersions.some(v => {
|
|
276
|
+
const vMajor = String(v).split('.')[0];
|
|
277
|
+
return vMajor === requiredMajor;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (!hasCompatible) {
|
|
281
|
+
findings.push({
|
|
282
|
+
service: 'cross-cutting',
|
|
283
|
+
operation: 'configuration',
|
|
284
|
+
fieldPath: 'acceleratorVersion',
|
|
285
|
+
invalidValue: cudaRequirement,
|
|
286
|
+
constraint: {
|
|
287
|
+
type: 'cuda-compatibility',
|
|
288
|
+
instanceCudaVersions,
|
|
289
|
+
instanceType
|
|
290
|
+
},
|
|
291
|
+
severity: 'error',
|
|
292
|
+
confidence: 'high',
|
|
293
|
+
source: 'cross-cutting',
|
|
294
|
+
remediationHint: `Base image requires CUDA ${cudaRequirement} but instance ${instanceType} supports CUDA versions [${instanceCudaVersions.join(', ')}]. No compatible CUDA version found.`
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return findings;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Verify predictor models are not assigned GPU instances.
|
|
303
|
+
* @param {Object} context - ValidationContext
|
|
304
|
+
* @param {Object} instanceCatalog - Instance catalog
|
|
305
|
+
* @returns {Array} Findings
|
|
306
|
+
*/
|
|
307
|
+
checkModelTypeInstanceAlignment(context, instanceCatalog) {
|
|
308
|
+
const findings = [];
|
|
309
|
+
const config = context.config || {};
|
|
310
|
+
const catalog = instanceCatalog?.catalog || instanceCatalog || {};
|
|
311
|
+
|
|
312
|
+
const modelType = config.modelType || config.MODEL_TYPE || '';
|
|
313
|
+
if (modelType !== 'predictor') return findings;
|
|
314
|
+
|
|
315
|
+
const instanceType = config.INSTANCE_TYPE;
|
|
316
|
+
if (!instanceType) return findings;
|
|
317
|
+
|
|
318
|
+
const instanceInfo = catalog[instanceType];
|
|
319
|
+
if (!instanceInfo) return findings;
|
|
320
|
+
|
|
321
|
+
if (instanceInfo.gpus > 0 || instanceInfo.category === 'gpu') {
|
|
322
|
+
findings.push({
|
|
323
|
+
service: 'cross-cutting',
|
|
324
|
+
operation: 'configuration',
|
|
325
|
+
fieldPath: 'INSTANCE_TYPE',
|
|
326
|
+
invalidValue: instanceType,
|
|
327
|
+
constraint: {
|
|
328
|
+
type: 'model-type-alignment',
|
|
329
|
+
modelType: 'predictor',
|
|
330
|
+
instanceCategory: instanceInfo.category
|
|
331
|
+
},
|
|
332
|
+
severity: 'warning',
|
|
333
|
+
confidence: 'high',
|
|
334
|
+
source: 'cross-cutting',
|
|
335
|
+
remediationHint: `Model type "predictor" typically does not require GPU acceleration. Consider using a CPU instance (e.g., ml.m5.xlarge) instead of ${instanceType}.`
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return findings;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Dry-run validator — a module that can be called during do/deploy --dry-run.
|
|
6
|
+
* Runs schema validation and blocks deployment if schema errors are found.
|
|
7
|
+
*
|
|
8
|
+
* Returns { passed: boolean, report: ValidationReport }
|
|
9
|
+
*
|
|
10
|
+
* Requirements: 9.1
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import PayloadBuilder from './payload-builder.js';
|
|
16
|
+
import SchemaValidationEngine from './schema-validation-engine.js';
|
|
17
|
+
import ServiceModelParser from './service-model-parser.js';
|
|
18
|
+
import { getRegistryPath } from './schema-sync.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run schema validation for dry-run mode.
|
|
22
|
+
* Blocks deployment if schema errors are found.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} config - Configuration values from do/config
|
|
25
|
+
* @param {string} deploymentTarget - 'realtime-inference' | 'async-inference' | 'batch-transform'
|
|
26
|
+
* @param {Object} [options]
|
|
27
|
+
* @param {boolean} [options.smart] - Enable smart-mode validators
|
|
28
|
+
* @param {string} [options.registryPath] - Override schema registry path
|
|
29
|
+
* @returns {Promise<{ passed: boolean, report: Object|null, skipped: boolean }>}
|
|
30
|
+
*/
|
|
31
|
+
export async function validateDryRun(config, deploymentTarget, options = {}) {
|
|
32
|
+
const smart = options.smart || false;
|
|
33
|
+
const registryPath = options.registryPath || getRegistryPath();
|
|
34
|
+
|
|
35
|
+
// Skip if schema registry is not present
|
|
36
|
+
if (!existsSync(registryPath) || !existsSync(path.join(registryPath, 'manifest.json'))) {
|
|
37
|
+
return { passed: true, report: null, skipped: true };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Construct payloads
|
|
41
|
+
const builder = new PayloadBuilder();
|
|
42
|
+
const context = builder.build(config, deploymentTarget);
|
|
43
|
+
|
|
44
|
+
// Load and parse service models from registry
|
|
45
|
+
const parser = new ServiceModelParser();
|
|
46
|
+
const serviceModels = [];
|
|
47
|
+
try {
|
|
48
|
+
const entries = readdirSync(registryPath, { withFileTypes: true });
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
const modelPath = path.join(registryPath, entry.name, 'service-2.json');
|
|
52
|
+
if (existsSync(modelPath)) {
|
|
53
|
+
const rawModel = JSON.parse(readFileSync(modelPath, 'utf8'));
|
|
54
|
+
serviceModels.push(parser.parse(rawModel));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
return { passed: true, report: null, skipped: true };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Run validation engine
|
|
63
|
+
const engine = new SchemaValidationEngine({
|
|
64
|
+
registryPath,
|
|
65
|
+
smartMode: smart,
|
|
66
|
+
serviceModels
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const report = await engine.validate(context);
|
|
70
|
+
const summary = report.getSummary();
|
|
71
|
+
|
|
72
|
+
// Block deployment if errors found
|
|
73
|
+
const passed = summary.errors === 0;
|
|
74
|
+
|
|
75
|
+
return { passed, report, skipped: false };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default { validateDryRun };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generation-time validation helper.
|
|
6
|
+
* Called after deploy scripts are generated to validate payloads against service models.
|
|
7
|
+
* Prints errors as warnings (non-blocking) with a summary line.
|
|
8
|
+
*
|
|
9
|
+
* - Skips silently if schema registry is not present
|
|
10
|
+
* - Skips entirely if --no-validate flag is passed (check via options parameter)
|
|
11
|
+
*
|
|
12
|
+
* This is a standalone module — does NOT modify the main generator file.
|
|
13
|
+
*
|
|
14
|
+
* Requirements: 8.1, 8.2, 8.3, 8.4, 8.5
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import PayloadBuilder from './payload-builder.js';
|
|
20
|
+
import SchemaValidationEngine from './schema-validation-engine.js';
|
|
21
|
+
import ServiceModelParser from './service-model-parser.js';
|
|
22
|
+
import { getRegistryPath } from './schema-sync.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Run schema validation at generation time (non-blocking).
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} config - Configuration values (from generator answers or do/config)
|
|
28
|
+
* @param {string} deploymentTarget - 'realtime-inference' | 'async-inference' | 'batch-transform'
|
|
29
|
+
* @param {Object} [options]
|
|
30
|
+
* @param {boolean} [options.noValidate] - If true, skip validation entirely
|
|
31
|
+
* @param {string} [options.registryPath] - Override schema registry path
|
|
32
|
+
* @returns {Promise<{ skipped: boolean, report: Object|null }>}
|
|
33
|
+
*/
|
|
34
|
+
export async function runGenerationValidation(config, deploymentTarget, options = {}) {
|
|
35
|
+
// Skip entirely if --no-validate flag is passed
|
|
36
|
+
if (options.noValidate) {
|
|
37
|
+
return { skipped: true, report: null };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const registryPath = options.registryPath || getRegistryPath();
|
|
41
|
+
|
|
42
|
+
// Skip silently if schema registry is not present
|
|
43
|
+
if (!existsSync(registryPath) || !existsSync(path.join(registryPath, 'manifest.json'))) {
|
|
44
|
+
return { skipped: true, report: null };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Construct payloads
|
|
48
|
+
const builder = new PayloadBuilder();
|
|
49
|
+
const context = builder.build(config, deploymentTarget);
|
|
50
|
+
|
|
51
|
+
// Load and parse service models from registry
|
|
52
|
+
const parser = new ServiceModelParser();
|
|
53
|
+
const serviceModels = [];
|
|
54
|
+
try {
|
|
55
|
+
const entries = readdirSync(registryPath, { withFileTypes: true });
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
const modelPath = path.join(registryPath, entry.name, 'service-2.json');
|
|
59
|
+
if (existsSync(modelPath)) {
|
|
60
|
+
const rawModel = JSON.parse(readFileSync(modelPath, 'utf8'));
|
|
61
|
+
serviceModels.push(parser.parse(rawModel));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// If we can't load models, skip validation silently
|
|
67
|
+
return { skipped: true, report: null };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Run validation
|
|
71
|
+
const engine = new SchemaValidationEngine({
|
|
72
|
+
registryPath,
|
|
73
|
+
ignoreStaleness: true,
|
|
74
|
+
serviceModels
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const report = await engine.validate(context);
|
|
78
|
+
const summary = report.getSummary();
|
|
79
|
+
|
|
80
|
+
// Print errors as warnings (non-blocking)
|
|
81
|
+
if (summary.errors > 0) {
|
|
82
|
+
console.log('');
|
|
83
|
+
console.log('\x1b[33m⚠️ Schema validation found issues:\x1b[0m');
|
|
84
|
+
|
|
85
|
+
for (const error of report.schemaErrors) {
|
|
86
|
+
const location = [error.operation, error.fieldPath].filter(Boolean).join(' → ');
|
|
87
|
+
console.log(` \x1b[33m⚠\x1b[0m ${location}: ${error.invalidValue || ''} ${error.remediationHint || ''}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const error of report.crossCuttingErrors) {
|
|
91
|
+
const location = [error.operation, error.fieldPath].filter(Boolean).join(' → ');
|
|
92
|
+
console.log(` \x1b[33m⚠\x1b[0m ${location}: ${error.remediationHint || ''}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log('');
|
|
96
|
+
console.log(` ${summary.errors} issue(s) found. Run \x1b[36mdo/validate\x1b[0m before deployment.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { skipped: false, report };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default { runGenerationValidation };
|