@aws/ml-container-creator 0.2.5 → 0.3.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/bin/cli.js +45 -4
- package/config/bootstrap-stack.json +14 -0
- package/infra/ci-harness/package-lock.json +22 -9
- 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 +564 -0
- package/servers/instance-sizer/lib/instance-ranker.js +270 -0
- package/servers/instance-sizer/lib/model-resolver.js +269 -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 +302 -254
- package/servers/lib/catalogs/model-sizes.json +131 -0
- package/servers/lib/catalogs/models.json +632 -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 +6 -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 +4 -4
- 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 +36 -0
- package/src/lib/architecture-sync.js +171 -0
- package/src/lib/arn-detection.js +22 -0
- package/src/lib/bootstrap-command-handler.js +120 -0
- package/src/lib/cli-handler.js +3 -3
- package/src/lib/config-manager.js +47 -1
- package/src/lib/configuration-manager.js +2 -2
- package/src/lib/cross-cutting-checker.js +460 -0
- package/src/lib/deployment-entry-schema.js +1 -2
- 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 +866 -149
- package/src/lib/prompts.js +2 -2
- package/src/lib/registry-command-handler.js +236 -0
- 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/secret-classification.js +56 -0
- package/src/lib/secrets-command-handler.js +550 -0
- package/src/lib/service-model-parser.js +102 -0
- package/src/lib/validate-runner.js +216 -0
- package/src/lib/validation-report.js +140 -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/src/prompt-adapter.js +3 -2
- package/templates/Dockerfile +1 -1
- package/templates/do/build +37 -5
- package/templates/do/config +15 -3
- package/templates/do/deploy +60 -5
- package/templates/do/logs +18 -3
- package/templates/do/run +15 -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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Required field validator.
|
|
3
|
+
* Validates that all required fields in an operation's input shape
|
|
4
|
+
* are present and non-empty in the payload.
|
|
5
|
+
*
|
|
6
|
+
* Requirements: 5.1, 5.2, 5.3
|
|
7
|
+
*/
|
|
8
|
+
import BaseValidator from './base-validator.js';
|
|
9
|
+
|
|
10
|
+
export default class RequiredFieldValidator extends BaseValidator {
|
|
11
|
+
get name() {
|
|
12
|
+
return 'required-field';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get mode() {
|
|
16
|
+
return 'static';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate required field presence for all payload operations.
|
|
21
|
+
* @param {Object} context - ValidationContext from PayloadBuilder
|
|
22
|
+
* @param {Object} options
|
|
23
|
+
* @param {Array} options.serviceModels - Parsed ServiceModelIndex objects
|
|
24
|
+
* @param {Array} options.priorFindings - Findings from earlier validators
|
|
25
|
+
* @returns {Promise<Array>} Array of Finding objects
|
|
26
|
+
*/
|
|
27
|
+
async validate(context, options) {
|
|
28
|
+
const findings = [];
|
|
29
|
+
const serviceModels = options.serviceModels || [];
|
|
30
|
+
|
|
31
|
+
for (const [operationKey, payload] of Object.entries(context.payloads || {})) {
|
|
32
|
+
const [service, operation] = operationKey.split(':');
|
|
33
|
+
|
|
34
|
+
for (const model of serviceModels) {
|
|
35
|
+
const op = model.operations.get(operation);
|
|
36
|
+
if (!op || !op.input) continue;
|
|
37
|
+
|
|
38
|
+
const inputShape = model.shapes.get(op.input);
|
|
39
|
+
if (!inputShape || inputShape.type !== 'structure') continue;
|
|
40
|
+
|
|
41
|
+
this._validateRequiredFields(
|
|
42
|
+
payload, inputShape, model, service, operation, '', findings
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return findings;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Recursively validate required fields in a structure.
|
|
52
|
+
* @param {Object} payload - The payload object to validate
|
|
53
|
+
* @param {Object} shape - The structure shape definition
|
|
54
|
+
* @param {Object} model - The ServiceModelIndex
|
|
55
|
+
* @param {string} service - Service name
|
|
56
|
+
* @param {string} operation - Operation name
|
|
57
|
+
* @param {string} parentPath - Dot-notation path prefix
|
|
58
|
+
* @param {Array} findings - Accumulator for findings
|
|
59
|
+
*/
|
|
60
|
+
_validateRequiredFields(payload, shape, model, service, operation, parentPath, findings) {
|
|
61
|
+
if (!shape || shape.type !== 'structure') return;
|
|
62
|
+
|
|
63
|
+
const requiredFields = shape.required || [];
|
|
64
|
+
|
|
65
|
+
for (const fieldName of requiredFields) {
|
|
66
|
+
const fieldPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
|
|
67
|
+
const memberDef = shape.members.get
|
|
68
|
+
? shape.members.get(fieldName)
|
|
69
|
+
: shape.members[fieldName];
|
|
70
|
+
|
|
71
|
+
const value = payload ? payload[fieldName] : undefined;
|
|
72
|
+
|
|
73
|
+
if (value === undefined || value === null || value === '') {
|
|
74
|
+
const description = memberDef && memberDef.documentation
|
|
75
|
+
? memberDef.documentation
|
|
76
|
+
: `Required field for ${operation}`;
|
|
77
|
+
|
|
78
|
+
findings.push({
|
|
79
|
+
service,
|
|
80
|
+
operation,
|
|
81
|
+
fieldPath,
|
|
82
|
+
invalidValue: value === undefined ? 'undefined' : value === null ? 'null' : '(empty string)',
|
|
83
|
+
constraint: { type: 'required', field: fieldName },
|
|
84
|
+
severity: 'error',
|
|
85
|
+
confidence: 'definitive',
|
|
86
|
+
source: this.name,
|
|
87
|
+
remediationHint: `Required field "${fieldName}" is missing or empty in ${operation}. ${description}`
|
|
88
|
+
});
|
|
89
|
+
} else if (typeof value === 'object' && !Array.isArray(value) && memberDef) {
|
|
90
|
+
// Recursively validate nested structures
|
|
91
|
+
const nestedShape = model.shapes.get(memberDef.shape);
|
|
92
|
+
if (nestedShape && nestedShape.type === 'structure') {
|
|
93
|
+
this._validateRequiredFields(
|
|
94
|
+
value, nestedShape, model, service, operation, fieldPath, findings
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Also recursively validate nested structures that are present (even if optional)
|
|
101
|
+
if (payload && typeof payload === 'object' && shape.members) {
|
|
102
|
+
for (const [fieldName, value] of Object.entries(payload)) {
|
|
103
|
+
if (value === null || value === undefined) continue;
|
|
104
|
+
if (typeof value !== 'object' || Array.isArray(value)) continue;
|
|
105
|
+
|
|
106
|
+
const memberDef = shape.members.get
|
|
107
|
+
? shape.members.get(fieldName)
|
|
108
|
+
: shape.members[fieldName];
|
|
109
|
+
if (!memberDef) continue;
|
|
110
|
+
|
|
111
|
+
const nestedShape = model.shapes.get(memberDef.shape);
|
|
112
|
+
if (!nestedShape || nestedShape.type !== 'structure') continue;
|
|
113
|
+
|
|
114
|
+
// Skip if already validated as a required field above
|
|
115
|
+
if (requiredFields.includes(fieldName)) continue;
|
|
116
|
+
|
|
117
|
+
const fieldPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
|
|
118
|
+
this._validateRequiredFields(
|
|
119
|
+
value, nestedShape, model, service, operation, fieldPath, findings
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Recursively validate list elements that are structures
|
|
125
|
+
if (payload && typeof payload === 'object' && shape.members) {
|
|
126
|
+
for (const [fieldName, value] of Object.entries(payload)) {
|
|
127
|
+
if (!Array.isArray(value)) continue;
|
|
128
|
+
|
|
129
|
+
const memberDef = shape.members.get
|
|
130
|
+
? shape.members.get(fieldName)
|
|
131
|
+
: shape.members[fieldName];
|
|
132
|
+
if (!memberDef) continue;
|
|
133
|
+
|
|
134
|
+
const listShape = model.shapes.get(memberDef.shape);
|
|
135
|
+
if (!listShape || listShape.type !== 'list' || !listShape.member) continue;
|
|
136
|
+
|
|
137
|
+
const elementShape = model.shapes.get(listShape.member.shape);
|
|
138
|
+
if (!elementShape || elementShape.type !== 'structure') continue;
|
|
139
|
+
|
|
140
|
+
const fieldPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
|
|
141
|
+
for (let i = 0; i < value.length; i++) {
|
|
142
|
+
this._validateRequiredFields(
|
|
143
|
+
value[i], elementShape, model, service, operation,
|
|
144
|
+
`${fieldPath}[${i}]`, findings
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/* eslint-disable eqeqeq */
|
|
2
|
+
/**
|
|
3
|
+
* Type constraint validator.
|
|
4
|
+
* Validates that payload field values match their expected types
|
|
5
|
+
* (integer, string, boolean, list) and numeric min/max constraints.
|
|
6
|
+
* Also validates pattern constraints on string fields.
|
|
7
|
+
*
|
|
8
|
+
* Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 4.4
|
|
9
|
+
*/
|
|
10
|
+
import BaseValidator from './base-validator.js';
|
|
11
|
+
|
|
12
|
+
export default class TypeValidator extends BaseValidator {
|
|
13
|
+
get name() {
|
|
14
|
+
return 'type';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get mode() {
|
|
18
|
+
return 'static';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate type constraints for all payload fields.
|
|
23
|
+
* @param {Object} context - ValidationContext from PayloadBuilder
|
|
24
|
+
* @param {Object} options
|
|
25
|
+
* @param {Array} options.serviceModels - Parsed ServiceModelIndex objects
|
|
26
|
+
* @param {Array} options.priorFindings - Findings from earlier validators
|
|
27
|
+
* @returns {Promise<Array>} Array of Finding objects
|
|
28
|
+
*/
|
|
29
|
+
async validate(context, options) {
|
|
30
|
+
const findings = [];
|
|
31
|
+
const serviceModels = options.serviceModels || [];
|
|
32
|
+
|
|
33
|
+
for (const [operationKey, payload] of Object.entries(context.payloads || {})) {
|
|
34
|
+
const [service, operation] = operationKey.split(':');
|
|
35
|
+
|
|
36
|
+
for (const model of serviceModels) {
|
|
37
|
+
const op = model.operations.get(operation);
|
|
38
|
+
if (!op || !op.input) continue;
|
|
39
|
+
|
|
40
|
+
const inputShape = model.shapes.get(op.input);
|
|
41
|
+
if (!inputShape || inputShape.type !== 'structure') continue;
|
|
42
|
+
|
|
43
|
+
this._validateStructure(
|
|
44
|
+
payload, inputShape, model, service, operation, '', findings
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return findings;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Recursively validate type constraints in a structure.
|
|
54
|
+
* @param {Object} payload - The payload object to validate
|
|
55
|
+
* @param {Object} shape - The structure shape definition
|
|
56
|
+
* @param {Object} model - The ServiceModelIndex
|
|
57
|
+
* @param {string} service - Service name
|
|
58
|
+
* @param {string} operation - Operation name
|
|
59
|
+
* @param {string} parentPath - Dot-notation path prefix
|
|
60
|
+
* @param {Array} findings - Accumulator for findings
|
|
61
|
+
*/
|
|
62
|
+
_validateStructure(payload, shape, model, service, operation, parentPath, findings) {
|
|
63
|
+
if (!payload || typeof payload !== 'object' || !shape.members) return;
|
|
64
|
+
|
|
65
|
+
for (const [fieldName, value] of Object.entries(payload)) {
|
|
66
|
+
const memberDef = shape.members.get
|
|
67
|
+
? shape.members.get(fieldName)
|
|
68
|
+
: shape.members[fieldName];
|
|
69
|
+
if (!memberDef) continue;
|
|
70
|
+
|
|
71
|
+
const fieldPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
|
|
72
|
+
const fieldShape = model.shapes.get(memberDef.shape);
|
|
73
|
+
if (!fieldShape) continue;
|
|
74
|
+
|
|
75
|
+
this._validateField(value, fieldShape, model, service, operation, fieldPath, findings);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Validate a single field value against its shape definition.
|
|
81
|
+
* @param {*} value - The field value
|
|
82
|
+
* @param {Object} fieldShape - The shape definition for this field
|
|
83
|
+
* @param {Object} model - The ServiceModelIndex
|
|
84
|
+
* @param {string} service - Service name
|
|
85
|
+
* @param {string} operation - Operation name
|
|
86
|
+
* @param {string} fieldPath - Full dot-notation path
|
|
87
|
+
* @param {Array} findings - Accumulator for findings
|
|
88
|
+
*/
|
|
89
|
+
_validateField(value, fieldShape, model, service, operation, fieldPath, findings) {
|
|
90
|
+
if (value === null || value === undefined) return;
|
|
91
|
+
|
|
92
|
+
switch (fieldShape.type) {
|
|
93
|
+
case 'string':
|
|
94
|
+
this._validateString(value, fieldShape, service, operation, fieldPath, findings);
|
|
95
|
+
break;
|
|
96
|
+
case 'integer':
|
|
97
|
+
case 'long':
|
|
98
|
+
this._validateInteger(value, fieldShape, service, operation, fieldPath, findings);
|
|
99
|
+
break;
|
|
100
|
+
case 'float':
|
|
101
|
+
case 'double':
|
|
102
|
+
this._validateNumeric(value, fieldShape, service, operation, fieldPath, findings);
|
|
103
|
+
break;
|
|
104
|
+
case 'boolean':
|
|
105
|
+
this._validateBoolean(value, service, operation, fieldPath, findings);
|
|
106
|
+
break;
|
|
107
|
+
case 'list':
|
|
108
|
+
this._validateList(value, fieldShape, model, service, operation, fieldPath, findings);
|
|
109
|
+
break;
|
|
110
|
+
case 'structure':
|
|
111
|
+
this._validateStructure(value, fieldShape, model, service, operation, fieldPath, findings);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Validate a string field.
|
|
118
|
+
*/
|
|
119
|
+
_validateString(value, fieldShape, service, operation, fieldPath, findings) {
|
|
120
|
+
if (typeof value !== 'string') {
|
|
121
|
+
findings.push({
|
|
122
|
+
service,
|
|
123
|
+
operation,
|
|
124
|
+
fieldPath,
|
|
125
|
+
invalidValue: value,
|
|
126
|
+
constraint: { type: 'type', expected: 'string', actual: typeof value },
|
|
127
|
+
severity: 'error',
|
|
128
|
+
confidence: 'definitive',
|
|
129
|
+
source: this.name,
|
|
130
|
+
remediationHint: `Expected a string value but got ${typeof value}.`
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Pattern validation (only when no enum — enum validator handles enum shapes)
|
|
136
|
+
if (fieldShape.pattern && !fieldShape.enum) {
|
|
137
|
+
try {
|
|
138
|
+
const regex = new RegExp(fieldShape.pattern);
|
|
139
|
+
if (!regex.test(value)) {
|
|
140
|
+
findings.push({
|
|
141
|
+
service,
|
|
142
|
+
operation,
|
|
143
|
+
fieldPath,
|
|
144
|
+
invalidValue: value,
|
|
145
|
+
constraint: { type: 'pattern', pattern: fieldShape.pattern },
|
|
146
|
+
severity: 'error',
|
|
147
|
+
confidence: 'definitive',
|
|
148
|
+
source: this.name,
|
|
149
|
+
remediationHint: `Value "${value}" does not match required pattern: ${fieldShape.pattern}`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
// Invalid regex in service model — skip pattern validation
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// String length constraints
|
|
158
|
+
if (fieldShape.min != null && value.length < fieldShape.min) {
|
|
159
|
+
findings.push({
|
|
160
|
+
service,
|
|
161
|
+
operation,
|
|
162
|
+
fieldPath,
|
|
163
|
+
invalidValue: value,
|
|
164
|
+
constraint: { type: 'range', min: fieldShape.min, max: fieldShape.max },
|
|
165
|
+
severity: 'error',
|
|
166
|
+
confidence: 'definitive',
|
|
167
|
+
source: this.name,
|
|
168
|
+
remediationHint: `String length ${value.length} is below minimum ${fieldShape.min}.`
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (fieldShape.max != null && value.length > fieldShape.max) {
|
|
172
|
+
findings.push({
|
|
173
|
+
service,
|
|
174
|
+
operation,
|
|
175
|
+
fieldPath,
|
|
176
|
+
invalidValue: value,
|
|
177
|
+
constraint: { type: 'range', min: fieldShape.min, max: fieldShape.max },
|
|
178
|
+
severity: 'error',
|
|
179
|
+
confidence: 'definitive',
|
|
180
|
+
source: this.name,
|
|
181
|
+
remediationHint: `String length ${value.length} exceeds maximum ${fieldShape.max}.`
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Validate an integer field.
|
|
188
|
+
*/
|
|
189
|
+
_validateInteger(value, fieldShape, service, operation, fieldPath, findings) {
|
|
190
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
191
|
+
findings.push({
|
|
192
|
+
service,
|
|
193
|
+
operation,
|
|
194
|
+
fieldPath,
|
|
195
|
+
invalidValue: value,
|
|
196
|
+
constraint: { type: 'type', expected: 'integer', actual: typeof value },
|
|
197
|
+
severity: 'error',
|
|
198
|
+
confidence: 'definitive',
|
|
199
|
+
source: this.name,
|
|
200
|
+
remediationHint: `Expected an integer value but got ${typeof value === 'number' ? 'float' : typeof value}.`
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this._validateRange(value, fieldShape, service, operation, fieldPath, findings);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Validate a numeric (float/double) field.
|
|
210
|
+
*/
|
|
211
|
+
_validateNumeric(value, fieldShape, service, operation, fieldPath, findings) {
|
|
212
|
+
if (typeof value !== 'number') {
|
|
213
|
+
findings.push({
|
|
214
|
+
service,
|
|
215
|
+
operation,
|
|
216
|
+
fieldPath,
|
|
217
|
+
invalidValue: value,
|
|
218
|
+
constraint: { type: 'type', expected: 'number', actual: typeof value },
|
|
219
|
+
severity: 'error',
|
|
220
|
+
confidence: 'definitive',
|
|
221
|
+
source: this.name,
|
|
222
|
+
remediationHint: `Expected a numeric value but got ${typeof value}.`
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this._validateRange(value, fieldShape, service, operation, fieldPath, findings);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Validate a boolean field.
|
|
232
|
+
*/
|
|
233
|
+
_validateBoolean(value, service, operation, fieldPath, findings) {
|
|
234
|
+
if (typeof value !== 'boolean') {
|
|
235
|
+
findings.push({
|
|
236
|
+
service,
|
|
237
|
+
operation,
|
|
238
|
+
fieldPath,
|
|
239
|
+
invalidValue: value,
|
|
240
|
+
constraint: { type: 'type', expected: 'boolean', actual: typeof value },
|
|
241
|
+
severity: 'error',
|
|
242
|
+
confidence: 'definitive',
|
|
243
|
+
source: this.name,
|
|
244
|
+
remediationHint: `Expected a boolean value but got ${typeof value}.`
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Validate a list field.
|
|
251
|
+
*/
|
|
252
|
+
_validateList(value, fieldShape, model, service, operation, fieldPath, findings) {
|
|
253
|
+
if (!Array.isArray(value)) {
|
|
254
|
+
findings.push({
|
|
255
|
+
service,
|
|
256
|
+
operation,
|
|
257
|
+
fieldPath,
|
|
258
|
+
invalidValue: value,
|
|
259
|
+
constraint: { type: 'type', expected: 'list', actual: typeof value },
|
|
260
|
+
severity: 'error',
|
|
261
|
+
confidence: 'definitive',
|
|
262
|
+
source: this.name,
|
|
263
|
+
remediationHint: `Expected an array but got ${typeof value}.`
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Recursively validate each element
|
|
269
|
+
if (fieldShape.member && fieldShape.member.shape) {
|
|
270
|
+
const elementShape = model.shapes.get(fieldShape.member.shape);
|
|
271
|
+
if (elementShape) {
|
|
272
|
+
for (let i = 0; i < value.length; i++) {
|
|
273
|
+
this._validateField(
|
|
274
|
+
value[i], elementShape, model, service, operation,
|
|
275
|
+
`${fieldPath}[${i}]`, findings
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Validate numeric range constraints (min/max).
|
|
284
|
+
*/
|
|
285
|
+
_validateRange(value, fieldShape, service, operation, fieldPath, findings) {
|
|
286
|
+
if (fieldShape.min != null && value < fieldShape.min) {
|
|
287
|
+
findings.push({
|
|
288
|
+
service,
|
|
289
|
+
operation,
|
|
290
|
+
fieldPath,
|
|
291
|
+
invalidValue: value,
|
|
292
|
+
constraint: { type: 'range', min: fieldShape.min, max: fieldShape.max },
|
|
293
|
+
severity: 'error',
|
|
294
|
+
confidence: 'definitive',
|
|
295
|
+
source: this.name,
|
|
296
|
+
remediationHint: `Value ${value} is below minimum ${fieldShape.min}.`
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
if (fieldShape.max != null && value > fieldShape.max) {
|
|
300
|
+
findings.push({
|
|
301
|
+
service,
|
|
302
|
+
operation,
|
|
303
|
+
fieldPath,
|
|
304
|
+
invalidValue: value,
|
|
305
|
+
constraint: { type: 'range', min: fieldShape.min, max: fieldShape.max },
|
|
306
|
+
severity: 'error',
|
|
307
|
+
confidence: 'definitive',
|
|
308
|
+
source: this.name,
|
|
309
|
+
remediationHint: `Value ${value} exceeds maximum ${fieldShape.max}.`
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
package/src/prompt-adapter.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import { select, input, confirm, checkbox, number, Separator } from '@inquirer/prompts';
|
|
4
|
+
import { select, input, confirm, checkbox, number, password, Separator } from '@inquirer/prompts';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Maps Yeoman prompt type names to @inquirer/prompts runner functions.
|
|
8
8
|
*/
|
|
9
|
-
const runners = { list: select, select, input, confirm, checkbox, number };
|
|
9
|
+
const runners = { list: select, select, input, confirm, checkbox, number, password };
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Runs a sequence of Yeoman-style prompt definitions using @inquirer/prompts.
|
|
@@ -55,6 +55,7 @@ export async function runPrompts(prompts, previousAnswers = {}, options = {}) {
|
|
|
55
55
|
if (mappedChoices !== undefined) config.choices = mappedChoices;
|
|
56
56
|
if (defaultVal !== undefined) config.default = defaultVal;
|
|
57
57
|
if (prompt.validate) config.validate = prompt.validate;
|
|
58
|
+
if (prompt.mask !== undefined) config.mask = prompt.mask;
|
|
58
59
|
|
|
59
60
|
answers[prompt.name] = await runner(config);
|
|
60
61
|
}
|
package/templates/Dockerfile
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<% } %>
|
|
11
11
|
|
|
12
12
|
<% if (framework !== 'transformers') { %>
|
|
13
|
-
FROM <%= baseImage || 'python:3.12-slim' %>
|
|
13
|
+
FROM <%= baseImage || 'public.ecr.aws/docker/library/python:3.12-slim' %>
|
|
14
14
|
|
|
15
15
|
# Set a docker label to name this project, postpended with the build time
|
|
16
16
|
LABEL project.name="<%= projectName %>-<%= buildTimestamp %>" \
|
package/templates/do/build
CHANGED
|
@@ -22,6 +22,33 @@ if ! command -v docker &> /dev/null; then
|
|
|
22
22
|
exit 2
|
|
23
23
|
fi
|
|
24
24
|
|
|
25
|
+
# Always build for linux/amd64 — SageMaker runs x86_64 instances.
|
|
26
|
+
# Without this, Apple Silicon Macs produce arm64 images that silently
|
|
27
|
+
# fail on SageMaker with CannotStartContainerError.
|
|
28
|
+
PLATFORM_FLAG="--platform linux/amd64"
|
|
29
|
+
|
|
30
|
+
# --- Secrets Manager resolution (build-time) ---
|
|
31
|
+
if [ -n "${HF_TOKEN_ARN:-}" ]; then
|
|
32
|
+
echo "🔐 Resolving HuggingFace token from Secrets Manager..."
|
|
33
|
+
HF_TOKEN=$(aws secretsmanager get-secret-value --secret-id "${HF_TOKEN_ARN}" --query SecretString --output text) || {
|
|
34
|
+
echo "❌ Failed to resolve HuggingFace token from Secrets Manager"
|
|
35
|
+
exit 3
|
|
36
|
+
}
|
|
37
|
+
export HF_TOKEN
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${NGC_API_KEY_ARN:-}" ]; then
|
|
41
|
+
echo "🔐 Resolving NGC API key from Secrets Manager..."
|
|
42
|
+
NGC_API_KEY=$(aws secretsmanager get-secret-value --secret-id "${NGC_API_KEY_ARN}" --query SecretString --output text) || {
|
|
43
|
+
echo "❌ Failed to resolve NGC API key from Secrets Manager"
|
|
44
|
+
exit 3
|
|
45
|
+
}
|
|
46
|
+
export NGC_API_KEY
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# NOTE: Build-time secrets are passed as --build-arg. The secret value may persist
|
|
50
|
+
# in the image layer. A future improvement will use BuildKit --secret mounts.
|
|
51
|
+
|
|
25
52
|
# Framework-specific build logic
|
|
26
53
|
case "${DEPLOYMENT_CONFIG}" in
|
|
27
54
|
transformers-tensorrt-llm)
|
|
@@ -41,12 +68,12 @@ case "${DEPLOYMENT_CONFIG}" in
|
|
|
41
68
|
echo "${NGC_API_KEY}" | docker login nvcr.io --username '$oauthtoken' --password-stdin
|
|
42
69
|
|
|
43
70
|
echo "🏗️ Building GPU-enabled image with TensorRT-LLM..."
|
|
44
|
-
docker build -t "${PROJECT_NAME}:latest" .
|
|
71
|
+
docker build ${PLATFORM_FLAG} -t "${PROJECT_NAME}:latest" .
|
|
45
72
|
;;
|
|
46
73
|
|
|
47
74
|
transformers-vllm|transformers-sglang)
|
|
48
75
|
echo "🏗️ Building GPU-enabled image..."
|
|
49
|
-
docker build -t "${PROJECT_NAME}:latest" .
|
|
76
|
+
docker build ${PLATFORM_FLAG} -t "${PROJECT_NAME}:latest" .
|
|
50
77
|
;;
|
|
51
78
|
|
|
52
79
|
transformers-lmi|transformers-djl)
|
|
@@ -55,12 +82,17 @@ case "${DEPLOYMENT_CONFIG}" in
|
|
|
55
82
|
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 763104351884.dkr.ecr.us-east-1.amazonaws.com
|
|
56
83
|
|
|
57
84
|
echo "🏗️ Building GPU-enabled image..."
|
|
58
|
-
docker build -t "${PROJECT_NAME}:latest" .
|
|
85
|
+
docker build ${PLATFORM_FLAG} -t "${PROJECT_NAME}:latest" .
|
|
59
86
|
;;
|
|
60
87
|
|
|
61
|
-
sklearn-*|xgboost-*|tensorflow
|
|
88
|
+
sklearn-*|xgboost-*|tensorflow-*|http-flask|http-fastapi)
|
|
62
89
|
echo "🏗️ Building CPU-optimized image..."
|
|
63
|
-
docker build -t "${PROJECT_NAME}:latest" .
|
|
90
|
+
docker build ${PLATFORM_FLAG} -t "${PROJECT_NAME}:latest" .
|
|
91
|
+
;;
|
|
92
|
+
|
|
93
|
+
triton-*)
|
|
94
|
+
echo "🏗️ Building Triton Inference Server image..."
|
|
95
|
+
docker build ${PLATFORM_FLAG} -t "${PROJECT_NAME}:latest" .
|
|
64
96
|
;;
|
|
65
97
|
|
|
66
98
|
*)
|
package/templates/do/config
CHANGED
|
@@ -151,17 +151,29 @@ export <%= key %>=${<%= key %>:-<%= value %>}
|
|
|
151
151
|
# Framework-specific configuration
|
|
152
152
|
<% if (framework === 'transformers') { %>
|
|
153
153
|
export MODEL_NAME="<%= modelName %>"
|
|
154
|
-
|
|
154
|
+
# Secrets Manager integration: when an ARN is configured, do-scripts resolve the
|
|
155
|
+
# secret at the appropriate stage (build-time or runtime). When a plaintext value
|
|
156
|
+
# is configured, it is exported directly. The _ARN suffix signals resolution is needed.
|
|
157
|
+
<% if (typeof hfTokenArn !== 'undefined' && hfTokenArn) { %>
|
|
158
|
+
export HF_TOKEN_ARN="<%= hfTokenArn %>"
|
|
159
|
+
<% } else if (hfToken) { %>
|
|
155
160
|
export HF_TOKEN="<%= hfToken %>"
|
|
156
161
|
<% } %>
|
|
157
|
-
<% if (
|
|
162
|
+
<% if (typeof ngcTokenArn !== 'undefined' && ngcTokenArn) { %>
|
|
163
|
+
export NGC_API_KEY_ARN="<%= ngcTokenArn %>"
|
|
164
|
+
<% } else if (ngcApiKey) { %>
|
|
158
165
|
export NGC_API_KEY="<%= ngcApiKey %>"
|
|
159
166
|
<% } %>
|
|
160
167
|
<% } %>
|
|
161
168
|
|
|
162
169
|
<% if (framework === 'diffusors') { %>
|
|
163
170
|
export MODEL_NAME="<%= modelName %>"
|
|
164
|
-
|
|
171
|
+
# Secrets Manager integration: when an ARN is configured, do-scripts resolve the
|
|
172
|
+
# secret at the appropriate stage (build-time or runtime). When a plaintext value
|
|
173
|
+
# is configured, it is exported directly. The _ARN suffix signals resolution is needed.
|
|
174
|
+
<% if (typeof hfTokenArn !== 'undefined' && hfTokenArn) { %>
|
|
175
|
+
export HF_TOKEN_ARN="<%= hfTokenArn %>"
|
|
176
|
+
<% } else if (hfToken) { %>
|
|
165
177
|
export HF_TOKEN="<%= hfToken %>"
|
|
166
178
|
<% } %>
|
|
167
179
|
<% } %>
|