@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.
Files changed (72) hide show
  1. package/bin/cli.js +45 -4
  2. package/config/bootstrap-stack.json +14 -0
  3. package/infra/ci-harness/package-lock.json +22 -9
  4. package/package.json +7 -8
  5. package/servers/base-image-picker/index.js +3 -3
  6. package/servers/base-image-picker/manifest.json +4 -2
  7. package/servers/instance-sizer/index.js +564 -0
  8. package/servers/instance-sizer/lib/instance-ranker.js +270 -0
  9. package/servers/instance-sizer/lib/model-resolver.js +269 -0
  10. package/servers/instance-sizer/lib/vram-estimator.js +177 -0
  11. package/servers/instance-sizer/manifest.json +17 -0
  12. package/servers/instance-sizer/package.json +15 -0
  13. package/servers/{instance-recommender → lib}/catalogs/instances.json +136 -34
  14. package/servers/{base-image-picker → lib}/catalogs/model-servers.json +302 -254
  15. package/servers/lib/catalogs/model-sizes.json +131 -0
  16. package/servers/lib/catalogs/models.json +632 -0
  17. package/servers/{model-picker → lib}/catalogs/popular-diffusors.json +32 -10
  18. package/servers/{model-picker → lib}/catalogs/popular-transformers.json +59 -26
  19. package/servers/{base-image-picker → lib}/catalogs/python-slim.json +12 -12
  20. package/servers/lib/schemas/image-catalog.schema.json +6 -12
  21. package/servers/lib/schemas/instances.schema.json +29 -0
  22. package/servers/lib/schemas/model-catalog.schema.json +12 -10
  23. package/servers/lib/schemas/unified-model-catalog.schema.json +129 -0
  24. package/servers/model-picker/index.js +4 -4
  25. package/servers/model-picker/manifest.json +2 -3
  26. package/servers/region-picker/index.js +1 -1
  27. package/servers/region-picker/manifest.json +1 -1
  28. package/src/app.js +36 -0
  29. package/src/lib/architecture-sync.js +171 -0
  30. package/src/lib/arn-detection.js +22 -0
  31. package/src/lib/bootstrap-command-handler.js +120 -0
  32. package/src/lib/cli-handler.js +3 -3
  33. package/src/lib/config-manager.js +47 -1
  34. package/src/lib/configuration-manager.js +2 -2
  35. package/src/lib/cross-cutting-checker.js +460 -0
  36. package/src/lib/deployment-entry-schema.js +1 -2
  37. package/src/lib/dry-run-validator.js +78 -0
  38. package/src/lib/generation-validator.js +102 -0
  39. package/src/lib/mcp-validator-config.js +89 -0
  40. package/src/lib/payload-builder.js +153 -0
  41. package/src/lib/prompt-runner.js +866 -149
  42. package/src/lib/prompts.js +2 -2
  43. package/src/lib/registry-command-handler.js +236 -0
  44. package/src/lib/registry-loader.js +5 -5
  45. package/src/lib/schema-sync.js +203 -0
  46. package/src/lib/schema-validation-engine.js +195 -0
  47. package/src/lib/secret-classification.js +56 -0
  48. package/src/lib/secrets-command-handler.js +550 -0
  49. package/src/lib/service-model-parser.js +102 -0
  50. package/src/lib/validate-runner.js +216 -0
  51. package/src/lib/validation-report.js +140 -0
  52. package/src/lib/validators/base-validator.js +36 -0
  53. package/src/lib/validators/catalog-validator.js +177 -0
  54. package/src/lib/validators/enum-validator.js +120 -0
  55. package/src/lib/validators/required-field-validator.js +150 -0
  56. package/src/lib/validators/type-validator.js +313 -0
  57. package/src/prompt-adapter.js +3 -2
  58. package/templates/Dockerfile +1 -1
  59. package/templates/do/build +37 -5
  60. package/templates/do/config +15 -3
  61. package/templates/do/deploy +60 -5
  62. package/templates/do/logs +18 -3
  63. package/templates/do/run +15 -1
  64. package/templates/do/validate +61 -0
  65. package/servers/instance-recommender/LICENSE +0 -202
  66. package/servers/instance-recommender/index.js +0 -284
  67. package/servers/instance-recommender/manifest.json +0 -16
  68. package/servers/instance-recommender/package.json +0 -15
  69. /package/servers/{model-picker → lib}/catalogs/jumpstart-public.json +0 -0
  70. /package/servers/{region-picker → lib}/catalogs/regions.json +0 -0
  71. /package/servers/{base-image-picker → lib}/catalogs/triton-backends.json +0 -0
  72. /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
+ }
@@ -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
  }
@@ -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 %>" \
@@ -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
  *)
@@ -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
- <% if (hfToken) { %>
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 (ngcApiKey) { %>
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
- <% if (hfToken) { %>
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
  <% } %>