@aws/ml-container-creator 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +202 -0
  2. package/LICENSE-THIRD-PARTY +68620 -0
  3. package/NOTICE +2 -0
  4. package/README.md +106 -0
  5. package/bin/cli.js +365 -0
  6. package/config/defaults.json +32 -0
  7. package/config/presets/transformers-djl.json +26 -0
  8. package/config/presets/transformers-gpu.json +24 -0
  9. package/config/presets/transformers-lmi.json +27 -0
  10. package/package.json +129 -0
  11. package/servers/README.md +419 -0
  12. package/servers/base-image-picker/catalogs/model-servers.json +1191 -0
  13. package/servers/base-image-picker/catalogs/python-slim.json +38 -0
  14. package/servers/base-image-picker/catalogs/triton-backends.json +51 -0
  15. package/servers/base-image-picker/catalogs/triton.json +38 -0
  16. package/servers/base-image-picker/index.js +495 -0
  17. package/servers/base-image-picker/manifest.json +17 -0
  18. package/servers/base-image-picker/package.json +15 -0
  19. package/servers/hyperpod-cluster-picker/LICENSE +202 -0
  20. package/servers/hyperpod-cluster-picker/index.js +424 -0
  21. package/servers/hyperpod-cluster-picker/manifest.json +14 -0
  22. package/servers/hyperpod-cluster-picker/package.json +17 -0
  23. package/servers/instance-recommender/LICENSE +202 -0
  24. package/servers/instance-recommender/catalogs/instances.json +852 -0
  25. package/servers/instance-recommender/index.js +284 -0
  26. package/servers/instance-recommender/manifest.json +16 -0
  27. package/servers/instance-recommender/package.json +15 -0
  28. package/servers/lib/LICENSE +202 -0
  29. package/servers/lib/bedrock-client.js +160 -0
  30. package/servers/lib/custom-validators.js +46 -0
  31. package/servers/lib/dynamic-resolver.js +36 -0
  32. package/servers/lib/package.json +11 -0
  33. package/servers/lib/schemas/image-catalog.schema.json +185 -0
  34. package/servers/lib/schemas/instances.schema.json +124 -0
  35. package/servers/lib/schemas/manifest.schema.json +64 -0
  36. package/servers/lib/schemas/model-catalog.schema.json +91 -0
  37. package/servers/lib/schemas/regions.schema.json +26 -0
  38. package/servers/lib/schemas/triton-backends.schema.json +51 -0
  39. package/servers/model-picker/catalogs/jumpstart-public.json +66 -0
  40. package/servers/model-picker/catalogs/popular-diffusors.json +88 -0
  41. package/servers/model-picker/catalogs/popular-transformers.json +226 -0
  42. package/servers/model-picker/index.js +1693 -0
  43. package/servers/model-picker/manifest.json +18 -0
  44. package/servers/model-picker/package.json +20 -0
  45. package/servers/region-picker/LICENSE +202 -0
  46. package/servers/region-picker/catalogs/regions.json +263 -0
  47. package/servers/region-picker/index.js +230 -0
  48. package/servers/region-picker/manifest.json +16 -0
  49. package/servers/region-picker/package.json +15 -0
  50. package/src/app.js +1007 -0
  51. package/src/copy-tpl.js +77 -0
  52. package/src/lib/accelerator-validator.js +39 -0
  53. package/src/lib/asset-manager.js +385 -0
  54. package/src/lib/aws-profile-parser.js +181 -0
  55. package/src/lib/bootstrap-command-handler.js +1647 -0
  56. package/src/lib/bootstrap-config.js +238 -0
  57. package/src/lib/ci-register-helpers.js +124 -0
  58. package/src/lib/ci-report-helpers.js +158 -0
  59. package/src/lib/ci-stage-helpers.js +268 -0
  60. package/src/lib/cli-handler.js +529 -0
  61. package/src/lib/comment-generator.js +544 -0
  62. package/src/lib/community-reports-validator.js +91 -0
  63. package/src/lib/config-manager.js +2106 -0
  64. package/src/lib/configuration-exporter.js +204 -0
  65. package/src/lib/configuration-manager.js +695 -0
  66. package/src/lib/configuration-matcher.js +221 -0
  67. package/src/lib/cpu-validator.js +36 -0
  68. package/src/lib/cuda-validator.js +57 -0
  69. package/src/lib/deployment-config-resolver.js +103 -0
  70. package/src/lib/deployment-entry-schema.js +125 -0
  71. package/src/lib/deployment-registry.js +598 -0
  72. package/src/lib/docker-introspection-validator.js +51 -0
  73. package/src/lib/engine-prefix-resolver.js +60 -0
  74. package/src/lib/huggingface-client.js +172 -0
  75. package/src/lib/key-value-parser.js +37 -0
  76. package/src/lib/known-flags-validator.js +200 -0
  77. package/src/lib/manifest-cli.js +280 -0
  78. package/src/lib/mcp-client.js +303 -0
  79. package/src/lib/mcp-command-handler.js +532 -0
  80. package/src/lib/neuron-validator.js +80 -0
  81. package/src/lib/parameter-schema-validator.js +284 -0
  82. package/src/lib/prompt-runner.js +1349 -0
  83. package/src/lib/prompts.js +1138 -0
  84. package/src/lib/registry-command-handler.js +519 -0
  85. package/src/lib/registry-loader.js +198 -0
  86. package/src/lib/rocm-validator.js +80 -0
  87. package/src/lib/schema-validator.js +157 -0
  88. package/src/lib/sensitive-redactor.js +59 -0
  89. package/src/lib/template-engine.js +156 -0
  90. package/src/lib/template-manager.js +341 -0
  91. package/src/lib/validation-engine.js +314 -0
  92. package/src/prompt-adapter.js +63 -0
  93. package/templates/Dockerfile +300 -0
  94. package/templates/IAM_PERMISSIONS.md +84 -0
  95. package/templates/MIGRATION.md +488 -0
  96. package/templates/PROJECT_README.md +439 -0
  97. package/templates/TEMPLATE_SYSTEM.md +243 -0
  98. package/templates/buildspec.yml +64 -0
  99. package/templates/code/chat_template.jinja +1 -0
  100. package/templates/code/flask/gunicorn_config.py +35 -0
  101. package/templates/code/flask/wsgi.py +10 -0
  102. package/templates/code/model_handler.py +387 -0
  103. package/templates/code/serve +300 -0
  104. package/templates/code/serve.py +175 -0
  105. package/templates/code/serving.properties +105 -0
  106. package/templates/code/start_server.py +39 -0
  107. package/templates/code/start_server.sh +39 -0
  108. package/templates/diffusors/Dockerfile +72 -0
  109. package/templates/diffusors/patch_image_api.py +35 -0
  110. package/templates/diffusors/serve +115 -0
  111. package/templates/diffusors/start_server.sh +114 -0
  112. package/templates/do/.gitkeep +1 -0
  113. package/templates/do/README.md +541 -0
  114. package/templates/do/build +83 -0
  115. package/templates/do/ci +681 -0
  116. package/templates/do/clean +811 -0
  117. package/templates/do/config +260 -0
  118. package/templates/do/deploy +1560 -0
  119. package/templates/do/export +306 -0
  120. package/templates/do/logs +319 -0
  121. package/templates/do/manifest +12 -0
  122. package/templates/do/push +119 -0
  123. package/templates/do/register +580 -0
  124. package/templates/do/run +113 -0
  125. package/templates/do/submit +417 -0
  126. package/templates/do/test +1147 -0
  127. package/templates/hyperpod/configmap.yaml +24 -0
  128. package/templates/hyperpod/deployment.yaml +71 -0
  129. package/templates/hyperpod/pvc.yaml +42 -0
  130. package/templates/hyperpod/service.yaml +17 -0
  131. package/templates/nginx-diffusors.conf +74 -0
  132. package/templates/nginx-predictors.conf +47 -0
  133. package/templates/nginx-tensorrt.conf +74 -0
  134. package/templates/requirements.txt +61 -0
  135. package/templates/sample_model/test_inference.py +123 -0
  136. package/templates/sample_model/train_abalone.py +252 -0
  137. package/templates/test/test_endpoint.sh +79 -0
  138. package/templates/test/test_local_image.sh +80 -0
  139. package/templates/test/test_model_handler.py +180 -0
  140. package/templates/triton/Dockerfile +128 -0
  141. package/templates/triton/config.pbtxt +163 -0
  142. package/templates/triton/model.py +130 -0
  143. package/templates/triton/requirements.txt +11 -0
@@ -0,0 +1,580 @@
1
+ #!/bin/bash
2
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ set -e
6
+ set -u
7
+ set -o pipefail
8
+
9
+ # Source configuration
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ source "${SCRIPT_DIR}/config"
12
+
13
+ # ============================================================
14
+ # Register deployment to the deployment registry
15
+ # ============================================================
16
+
17
+ # Parse command line arguments
18
+ NOTES=""
19
+ STATUS="success"
20
+ PROJECT_FLAG=""
21
+ JSON_OUTPUT=false
22
+ CI_MODE=false
23
+ CI_TABLE_NAME="${CI_TABLE_NAME:-mlcc-ci-table}"
24
+ CI_BUILD_STRATEGY="codebuild-submit"
25
+
26
+ while [[ $# -gt 0 ]]; do
27
+ case "$1" in
28
+ --notes)
29
+ NOTES="$2"
30
+ shift 2
31
+ ;;
32
+ --notes=*)
33
+ NOTES="${1#*=}"
34
+ shift
35
+ ;;
36
+ --status)
37
+ STATUS="$2"
38
+ shift 2
39
+ ;;
40
+ --status=*)
41
+ STATUS="${1#*=}"
42
+ shift
43
+ ;;
44
+ --project)
45
+ PROJECT_FLAG="--project"
46
+ shift
47
+ ;;
48
+ --json)
49
+ JSON_OUTPUT=true
50
+ shift
51
+ ;;
52
+ --ci)
53
+ CI_MODE=true
54
+ JSON_OUTPUT=true
55
+ shift
56
+ ;;
57
+ --ci-table)
58
+ CI_TABLE_NAME="$2"
59
+ shift 2
60
+ ;;
61
+ --ci-table=*)
62
+ CI_TABLE_NAME="${1#*=}"
63
+ shift
64
+ ;;
65
+ --build-strategy)
66
+ CI_BUILD_STRATEGY="$2"
67
+ shift 2
68
+ ;;
69
+ --build-strategy=*)
70
+ CI_BUILD_STRATEGY="${1#*=}"
71
+ shift
72
+ ;;
73
+ *)
74
+ echo "⚠️ Unknown option: $1"
75
+ echo ""
76
+ echo "Usage: ./do/register [--notes \"text\"] [--status success|partial|failed] [--project] [--json] [--ci] [--ci-table <name>] [--build-strategy <strategy>]"
77
+ exit 1
78
+ ;;
79
+ esac
80
+ done
81
+
82
+ # Validate status
83
+ case "${STATUS}" in
84
+ success|partial|failed) ;;
85
+ *)
86
+ echo "❌ Invalid status: ${STATUS}"
87
+ echo " Valid values: success, partial, failed"
88
+ exit 1
89
+ ;;
90
+ esac
91
+
92
+ # ============================================================
93
+ # Derive architecture and backend from DEPLOYMENT_CONFIG
94
+ # ============================================================
95
+
96
+ # DEPLOYMENT_CONFIG format: <architecture>-<backend> (e.g., transformers-vllm, http-flask, triton-fil)
97
+ ARCHITECTURE="${DEPLOYMENT_CONFIG%%-*}"
98
+ BACKEND="${DEPLOYMENT_CONFIG#*-}"
99
+
100
+ echo "📋 Registering deployment to registry"
101
+ echo " Project: ${PROJECT_NAME}"
102
+ echo " Deployment config: ${DEPLOYMENT_CONFIG}"
103
+ echo " Architecture: ${ARCHITECTURE}"
104
+ echo " Backend: ${BACKEND}"
105
+ echo ""
106
+
107
+ # ============================================================
108
+ # Engine prefix mapping for transformer env var filtering
109
+ # ============================================================
110
+
111
+ get_engine_prefix() {
112
+ local backend="$1"
113
+ case "${backend}" in
114
+ vllm) echo "VLLM_" ;;
115
+ vllm-omni) echo "VLLM_" ;;
116
+ sglang) echo "SGLANG_" ;;
117
+ tensorrt-llm) echo "TRTLLM_" ;;
118
+ lmi) echo "LMI_" ;;
119
+ djl) echo "DJL_" ;;
120
+ *) echo "" ;;
121
+ esac
122
+ }
123
+
124
+ # ============================================================
125
+ # Build PARAMETERS JSON from shell environment (sourced by do/config)
126
+ # ============================================================
127
+
128
+ PARAMETERS="{}"
129
+
130
+ <%
131
+ // Build a helper to determine sensitive keys for redaction
132
+ const sensitivePatterns = ['HF_TOKEN', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN'];
133
+ const sensitiveSubstrings = ['SECRET', 'TOKEN'];
134
+
135
+ function isSensitiveKey(key) {
136
+ if (sensitivePatterns.includes(key)) return true;
137
+ const upper = key.toUpperCase();
138
+ return sensitiveSubstrings.some(sub => upper.includes(sub));
139
+ }
140
+
141
+ function redactValue(key, value) {
142
+ return isSensitiveKey(key) ? '***REDACTED***' : value;
143
+ }
144
+ %>
145
+
146
+ case "${ARCHITECTURE}" in
147
+ transformers|diffusors)
148
+ # Extract env vars matching engine prefix + HF_TOKEN + HF_MODEL_ID
149
+ ENGINE_PREFIX=$(get_engine_prefix "${BACKEND}")
150
+
151
+ PARAMETERS=$(python3 -c "
152
+ import os, json
153
+ prefix = '${ENGINE_PREFIX}'
154
+ result = {}
155
+ for key, value in os.environ.items():
156
+ if prefix and key.startswith(prefix):
157
+ result[key] = value
158
+ elif key == 'HF_TOKEN':
159
+ result[key] = '***REDACTED***'
160
+ elif key == 'HF_MODEL_ID':
161
+ result[key] = value
162
+ <% if (modelEnvVars && Object.keys(modelEnvVars).length > 0) { %>
163
+ # Include model env vars
164
+ <% Object.entries(modelEnvVars).forEach(([key, value]) => { %>
165
+ result['<%= key %>'] = '<%= redactValue(key, value) %>'
166
+ <% }); %>
167
+ <% } %>
168
+ <% if (serverEnvVars && Object.keys(serverEnvVars).length > 0) { %>
169
+ # Include server env vars (with engine prefix applied at generation time)
170
+ <% Object.entries(serverEnvVars).forEach(([key, value]) => { %>
171
+ result['<%= key %>'] = '<%= redactValue(key, value) %>'
172
+ <% }); %>
173
+ <% } %>
174
+ print(json.dumps(result))
175
+ " 2>/dev/null || echo "{}")
176
+ ;;
177
+
178
+ http)
179
+ # Extract all env vars from do/config except system vars, redact secrets
180
+ PARAMETERS=$(python3 -c "
181
+ import os, json
182
+ exclude = {
183
+ 'PATH', 'PYTHONPATH', 'SAGEMAKER_BIND_TO_PORT', 'LANG', 'GPG_KEY',
184
+ 'PYTHON_VERSION', 'PYTHON_PIP_VERSION', 'PYTHON_SETUPTOOLS_VERSION',
185
+ 'PYTHON_GET_PIP_URL', 'PYTHON_GET_PIP_SHA256'
186
+ }
187
+ redact = {'HF_TOKEN', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN'}
188
+ sensitive_substrings = ['SECRET', 'TOKEN']
189
+ result = {}
190
+ for key, value in os.environ.items():
191
+ if key not in exclude:
192
+ is_sensitive = key in redact or any(sub in key.upper() for sub in sensitive_substrings)
193
+ result[key] = '***REDACTED***' if is_sensitive else value
194
+ <% if (modelEnvVars && Object.keys(modelEnvVars).length > 0) { %>
195
+ # Include model env vars
196
+ <% Object.entries(modelEnvVars).forEach(([key, value]) => { %>
197
+ result['<%= key %>'] = '<%= redactValue(key, value) %>'
198
+ <% }); %>
199
+ <% } %>
200
+ <% if (serverEnvVars && Object.keys(serverEnvVars).length > 0) { %>
201
+ # Include server env vars
202
+ <% Object.entries(serverEnvVars).forEach(([key, value]) => { %>
203
+ result['<%= key %>'] = '<%= redactValue(key, value) %>'
204
+ <% }); %>
205
+ <% } %>
206
+ print(json.dumps(result))
207
+ " 2>/dev/null || echo "{}")
208
+ ;;
209
+
210
+ triton)
211
+ # Read config.pbtxt from model repository
212
+ TRITON_MODEL_REPO="${TRITON_MODEL_REPOSITORY:-/opt/ml/model/model_repository}"
213
+ CONFIG_PBTXT=""
214
+
215
+ # Try to find config.pbtxt in the project directory
216
+ PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
217
+ if [ -f "${PROJECT_DIR}/model_repository/model/config.pbtxt" ]; then
218
+ CONFIG_PBTXT=$(cat "${PROJECT_DIR}/model_repository/model/config.pbtxt" 2>/dev/null || echo "")
219
+ elif [ -f "${PROJECT_DIR}/model_repository/config.pbtxt" ]; then
220
+ CONFIG_PBTXT=$(cat "${PROJECT_DIR}/model_repository/config.pbtxt" 2>/dev/null || echo "")
221
+ fi
222
+
223
+ # Read TRITON_MODEL_REPOSITORY from shell env (exported by do/config)
224
+ TRITON_REPO_VAR="${TRITON_MODEL_REPOSITORY:-}"
225
+
226
+ # Build parameters JSON (pipe config.pbtxt via stdin to avoid quoting issues)
227
+ PARAMETERS=$(printf '%s' "${CONFIG_PBTXT}" | python3 -c "
228
+ import sys, json
229
+ config_pbtxt = sys.stdin.read()
230
+ result = {}
231
+ if config_pbtxt.strip():
232
+ result['config.pbtxt'] = config_pbtxt.strip()
233
+ triton_repo = '${TRITON_REPO_VAR}'
234
+ if triton_repo:
235
+ result['TRITON_MODEL_REPOSITORY'] = triton_repo
236
+ <% if (modelEnvVars && Object.keys(modelEnvVars).length > 0) { %>
237
+ # Include model env vars
238
+ <% Object.entries(modelEnvVars).forEach(([key, value]) => { %>
239
+ result['<%= key %>'] = '<%= redactValue(key, value) %>'
240
+ <% }); %>
241
+ <% } %>
242
+ <% if (serverEnvVars && Object.keys(serverEnvVars).length > 0) { %>
243
+ # Include server env vars
244
+ <% Object.entries(serverEnvVars).forEach(([key, value]) => { %>
245
+ result['<%= key %>'] = '<%= redactValue(key, value) %>'
246
+ <% }); %>
247
+ <% } %>
248
+ print(json.dumps(result))
249
+ " 2>/dev/null || echo "{}")
250
+ ;;
251
+ esac
252
+
253
+ # Count captured parameters
254
+ PARAM_COUNT=$(echo "${PARAMETERS}" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
255
+
256
+ # ============================================================
257
+ # Display summary before writing
258
+ # ============================================================
259
+
260
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
261
+ echo "📋 Registration Summary"
262
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
263
+ echo " Deployment config: ${DEPLOYMENT_CONFIG}"
264
+ echo " Architecture: ${ARCHITECTURE}"
265
+ echo " Backend: ${BACKEND}"
266
+ <% if (framework === 'transformers') { %>
267
+ echo " Model name: ${MODEL_NAME:-N/A}"
268
+ <% } %>
269
+ <% if (deploymentTarget === 'managed-inference') { %>
270
+ echo " Instance type: ${INSTANCE_TYPE}"
271
+ <% } else if (deploymentTarget === 'batch-transform') { %>
272
+ echo " Instance: ${INSTANCE_TYPE} x ${BATCH_INSTANCE_COUNT}"
273
+ echo " S3 input: ${BATCH_INPUT_PATH}"
274
+ echo " S3 output: ${BATCH_OUTPUT_PATH}"
275
+ echo " Split type: ${BATCH_SPLIT_TYPE}"
276
+ echo " Strategy: ${BATCH_STRATEGY}"
277
+ <% } %>
278
+ echo " Region: ${AWS_REGION}"
279
+ echo " Status: ${STATUS}"
280
+ echo " Env var source: do/config"
281
+ echo " Parameters: ${PARAM_COUNT} captured"
282
+ if [ -n "${BASE_IMAGE:-}" ]; then
283
+ echo " Base image: ${BASE_IMAGE}"
284
+ fi
285
+ if [ -n "${NOTES}" ]; then
286
+ echo " Notes: ${NOTES}"
287
+ fi
288
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
289
+ echo ""
290
+
291
+ # ============================================================
292
+ # Compute configId (deterministic hash of canonical fields)
293
+ # ============================================================
294
+
295
+ compute_config_id() {
296
+ local input="${DEPLOYMENT_CONFIG}:${MODEL_NAME:-none}:${INSTANCE_TYPE}:${AWS_REGION}:${DEPLOYMENT_TARGET}"
297
+ # Use sha256sum (Linux) with fallback to shasum (macOS)
298
+ if command -v sha256sum &> /dev/null; then
299
+ echo -n "$input" | sha256sum | cut -c1-16
300
+ else
301
+ echo -n "$input" | shasum -a 256 | cut -c1-16
302
+ fi
303
+ }
304
+
305
+ # ============================================================
306
+ # CI Table write logic (--ci flag)
307
+ # ============================================================
308
+
309
+ write_ci_record() {
310
+ local config_id="$1"
311
+
312
+ # Extract promoted attributes
313
+ local promoted_deployment_config="${DEPLOYMENT_CONFIG}"
314
+ local promoted_base_image="${BASE_IMAGE:-}"
315
+ local promoted_project_name="${PROJECT_NAME}"
316
+
317
+ # Extract baseImageVersion from BASE_IMAGE tag (e.g., "vllm/vllm-openai:v0.8.5" -> "v0.8.5")
318
+ local promoted_base_image_version=""
319
+ if [ -n "${promoted_base_image}" ]; then
320
+ case "${promoted_base_image}" in
321
+ *:*)
322
+ promoted_base_image_version="${promoted_base_image##*:}"
323
+ ;;
324
+ esac
325
+ fi
326
+
327
+ # Build the configJson object from current config variables
328
+ local config_json
329
+ config_json=$(cat <<CJEOF
330
+ {
331
+ "projectName": "${PROJECT_NAME}",
332
+ "deploymentConfig": "${DEPLOYMENT_CONFIG}",
333
+ "architecture": "${ARCHITECTURE}",
334
+ "backend": "${BACKEND}",
335
+ "modelName": "${MODEL_NAME:-}",
336
+ "instanceType": "${INSTANCE_TYPE}",
337
+ "awsRegion": "${AWS_REGION}",
338
+ "deploymentTarget": "${DEPLOYMENT_TARGET}",
339
+ "buildTarget": "${BUILD_TARGET}",
340
+ "baseImage": "${BASE_IMAGE:-}",
341
+ "buildStrategy": "${CI_BUILD_STRATEGY}",
342
+ <% if (endpointInitialInstanceCount != null || endpointDataCapturePercent != null || endpointVariantName != null || endpointVolumeSize != null) { %>
343
+ "endpointConfig": {
344
+ <% if (endpointInitialInstanceCount != null) { %>
345
+ "initialInstanceCount": ${ENDPOINT_INITIAL_INSTANCE_COUNT}<%= (endpointDataCapturePercent != null || endpointVariantName != null || endpointVolumeSize != null) ? ',' : '' %>
346
+ <% } %>
347
+ <% if (endpointDataCapturePercent != null) { %>
348
+ "dataCapturePercent": ${ENDPOINT_DATA_CAPTURE_PERCENT}<%= (endpointVariantName != null || endpointVolumeSize != null) ? ',' : '' %>
349
+ <% } %>
350
+ <% if (endpointVariantName != null) { %>
351
+ "variantName": "${ENDPOINT_VARIANT_NAME}"<%= (endpointVolumeSize != null) ? ',' : '' %>
352
+ <% } %>
353
+ <% if (endpointVolumeSize != null) { %>
354
+ "volumeSize": ${ENDPOINT_VOLUME_SIZE}
355
+ <% } %>
356
+ },
357
+ <% } %>
358
+ <% if (icCpuCount != null || icMemorySize != null || icGpuCount != null || icCopyCount != null || icModelWeight != null) { %>
359
+ "icConfig": {
360
+ <% if (icCpuCount != null) { %>
361
+ "cpuCount": ${IC_CPU_COUNT}<%= (icMemorySize != null || icGpuCount != null || icCopyCount != null || icModelWeight != null) ? ',' : '' %>
362
+ <% } %>
363
+ <% if (icMemorySize != null) { %>
364
+ "memorySize": ${IC_MEMORY_SIZE}<%= (icGpuCount != null || icCopyCount != null || icModelWeight != null) ? ',' : '' %>
365
+ <% } %>
366
+ <% if (icGpuCount != null) { %>
367
+ "gpuCount": ${IC_GPU_COUNT}<%= (icCopyCount != null || icModelWeight != null) ? ',' : '' %>
368
+ <% } %>
369
+ <% if (icCopyCount != null) { %>
370
+ "copyCount": ${IC_COPY_COUNT}<%= (icModelWeight != null) ? ',' : '' %>
371
+ <% } %>
372
+ <% if (icModelWeight != null) { %>
373
+ "modelWeight": ${IC_MODEL_WEIGHT}
374
+ <% } %>
375
+ },
376
+ <% } %>
377
+ "parameters": ${PARAMETERS}
378
+ }
379
+ CJEOF
380
+ )
381
+
382
+ # Compact the JSON (remove whitespace)
383
+ config_json=$(echo "$config_json" | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), separators=(',',':')))" 2>/dev/null || echo "$config_json")
384
+
385
+ local created_at
386
+ created_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
387
+
388
+ # Escape configJson for DynamoDB JSON attribute (double-escape quotes)
389
+ local escaped_config_json
390
+ escaped_config_json=$(echo "$config_json" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read().strip()))" 2>/dev/null)
391
+ # Remove the outer quotes added by json.dumps for string
392
+ escaped_config_json="${escaped_config_json:1:${#escaped_config_json}-2}"
393
+
394
+ # Try put-item with condition (new record)
395
+ if aws dynamodb put-item \
396
+ --table-name "${CI_TABLE_NAME}" \
397
+ --item "{
398
+ \"configId\": {\"S\": \"${config_id}\"},
399
+ \"schemaVersion\": {\"N\": \"1\"},
400
+ \"configJson\": {\"S\": \"${escaped_config_json}\"},
401
+ \"testStatus\": {\"S\": \"untested\"},
402
+ \"lastTestTimestamp\": {\"S\": \"1970-01-01T00:00:00Z\"},
403
+ \"deploymentConfig\": {\"S\": \"${promoted_deployment_config}\"},
404
+ \"baseImage\": {\"S\": \"${promoted_base_image}\"},
405
+ \"baseImageVersion\": {\"S\": \"${promoted_base_image_version}\"},
406
+ \"buildStrategy\": {\"S\": \"${CI_BUILD_STRATEGY}\"},
407
+ \"projectName\": {\"S\": \"${promoted_project_name}\"},
408
+ \"createdAt\": {\"S\": \"${created_at}\"}
409
+ }" \
410
+ --condition-expression "attribute_not_exists(configId)" 2>/dev/null; then
411
+ echo "✅ New CI record created: ${config_id}"
412
+ else
413
+ # Record already exists — update it (reset testStatus, update configJson, preserve createdAt)
414
+ if aws dynamodb update-item \
415
+ --table-name "${CI_TABLE_NAME}" \
416
+ --key "{\"configId\": {\"S\": \"${config_id}\"}}" \
417
+ --update-expression "SET configJson = :cj, testStatus = :ts, deploymentConfig = :dc, baseImage = :bi, baseImageVersion = :bv, buildStrategy = :bs, projectName = :pn, schemaVersion = :sv" \
418
+ --expression-attribute-values "{
419
+ \":cj\": {\"S\": \"${escaped_config_json}\"},
420
+ \":ts\": {\"S\": \"untested\"},
421
+ \":dc\": {\"S\": \"${promoted_deployment_config}\"},
422
+ \":bi\": {\"S\": \"${promoted_base_image}\"},
423
+ \":bv\": {\"S\": \"${promoted_base_image_version}\"},
424
+ \":bs\": {\"S\": \"${CI_BUILD_STRATEGY}\"},
425
+ \":pn\": {\"S\": \"${promoted_project_name}\"},
426
+ \":sv\": {\"N\": \"1\"}
427
+ }" 2>/dev/null; then
428
+ echo "✅ Existing CI record updated (testStatus reset to untested): ${config_id}"
429
+ else
430
+ echo "❌ Failed to update CI record: ${config_id}"
431
+ return 1
432
+ fi
433
+ fi
434
+ }
435
+
436
+ # ============================================================
437
+ # Handle --json and --ci output modes
438
+ # ============================================================
439
+
440
+ if [ "${JSON_OUTPUT}" = true ] || [ "${CI_MODE}" = true ]; then
441
+ # Build JSON deployment entry
442
+ DEPLOYMENT_JSON=$(cat <<DJEOF
443
+ {
444
+ "projectName": "${PROJECT_NAME}",
445
+ "deploymentConfig": "${DEPLOYMENT_CONFIG}",
446
+ "architecture": "${ARCHITECTURE}",
447
+ "backend": "${BACKEND}",
448
+ "modelName": "${MODEL_NAME:-}",
449
+ "instanceType": "${INSTANCE_TYPE}",
450
+ "awsRegion": "${AWS_REGION}",
451
+ "deploymentTarget": "${DEPLOYMENT_TARGET}",
452
+ "buildTarget": "${BUILD_TARGET}",
453
+ "baseImage": "${BASE_IMAGE:-}",
454
+ "status": "${STATUS}",
455
+ <% if (endpointInitialInstanceCount != null || endpointDataCapturePercent != null || endpointVariantName != null || endpointVolumeSize != null) { %>
456
+ "endpointConfig": {
457
+ <% if (endpointInitialInstanceCount != null) { %>
458
+ "initialInstanceCount": ${ENDPOINT_INITIAL_INSTANCE_COUNT}<%= (endpointDataCapturePercent != null || endpointVariantName != null || endpointVolumeSize != null) ? ',' : '' %>
459
+ <% } %>
460
+ <% if (endpointDataCapturePercent != null) { %>
461
+ "dataCapturePercent": ${ENDPOINT_DATA_CAPTURE_PERCENT}<%= (endpointVariantName != null || endpointVolumeSize != null) ? ',' : '' %>
462
+ <% } %>
463
+ <% if (endpointVariantName != null) { %>
464
+ "variantName": "${ENDPOINT_VARIANT_NAME}"<%= (endpointVolumeSize != null) ? ',' : '' %>
465
+ <% } %>
466
+ <% if (endpointVolumeSize != null) { %>
467
+ "volumeSize": ${ENDPOINT_VOLUME_SIZE}
468
+ <% } %>
469
+ },
470
+ <% } %>
471
+ <% if (icCpuCount != null || icMemorySize != null || icGpuCount != null || icCopyCount != null || icModelWeight != null) { %>
472
+ "icConfig": {
473
+ <% if (icCpuCount != null) { %>
474
+ "cpuCount": ${IC_CPU_COUNT}<%= (icMemorySize != null || icGpuCount != null || icCopyCount != null || icModelWeight != null) ? ',' : '' %>
475
+ <% } %>
476
+ <% if (icMemorySize != null) { %>
477
+ "memorySize": ${IC_MEMORY_SIZE}<%= (icGpuCount != null || icCopyCount != null || icModelWeight != null) ? ',' : '' %>
478
+ <% } %>
479
+ <% if (icGpuCount != null) { %>
480
+ "gpuCount": ${IC_GPU_COUNT}<%= (icCopyCount != null || icModelWeight != null) ? ',' : '' %>
481
+ <% } %>
482
+ <% if (icCopyCount != null) { %>
483
+ "copyCount": ${IC_COPY_COUNT}<%= (icModelWeight != null) ? ',' : '' %>
484
+ <% } %>
485
+ <% if (icModelWeight != null) { %>
486
+ "modelWeight": ${IC_MODEL_WEIGHT}
487
+ <% } %>
488
+ },
489
+ <% } %>
490
+ "parameters": ${PARAMETERS}
491
+ }
492
+ DJEOF
493
+ )
494
+
495
+ # Output JSON to stdout
496
+ echo "${DEPLOYMENT_JSON}" | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))" 2>/dev/null || echo "${DEPLOYMENT_JSON}"
497
+
498
+ if [ "${CI_MODE}" = true ]; then
499
+ echo ""
500
+ echo "⚠️ CI Integration is experimental and currently only tested for"
501
+ echo " SageMaker Managed Inference — Real Time endpoints."
502
+ echo ""
503
+
504
+ # Compute configId
505
+ CONFIG_ID=$(compute_config_id)
506
+ echo ""
507
+ echo "🔑 configId: ${CONFIG_ID}"
508
+
509
+ # Check if CI_Table exists before writing
510
+ if ! aws dynamodb describe-table --table-name "${CI_TABLE_NAME}" &>/dev/null; then
511
+ echo ""
512
+ echo "⚠️ CI infrastructure not provisioned. Run 'ml-container-creator bootstrap' with CI enabled."
513
+ echo " Skipping CI table write."
514
+ else
515
+ echo "📝 Writing to CI table (${CI_TABLE_NAME})..."
516
+ write_ci_record "${CONFIG_ID}"
517
+ fi
518
+ fi
519
+
520
+ exit 0
521
+ fi
522
+
523
+ # ============================================================
524
+ # Call ml-container-creator registry log
525
+ # ============================================================
526
+
527
+ # Build the registry log command
528
+ CMD_ARGS=(
529
+ "registry" "log"
530
+ "--deployment-config" "${DEPLOYMENT_CONFIG}"
531
+ "--architecture" "${ARCHITECTURE}"
532
+ "--backend" "${BACKEND}"
533
+ "--region" "${AWS_REGION}"
534
+ "--status" "${STATUS}"
535
+ "--deployment-target" "${DEPLOYMENT_TARGET}"
536
+ "--build-target" "${BUILD_TARGET}"
537
+ )
538
+
539
+ <% if (framework === 'transformers' || framework === 'diffusors') { %>
540
+ if [ -n "${MODEL_NAME:-}" ]; then
541
+ CMD_ARGS+=("--model-name" "${MODEL_NAME}")
542
+ fi
543
+ <% } %>
544
+
545
+ <% if (deploymentTarget === 'managed-inference' || deploymentTarget === 'async-inference' || deploymentTarget === 'batch-transform') { %>
546
+ CMD_ARGS+=("--instance-type" "${INSTANCE_TYPE}")
547
+ <% } %>
548
+
549
+ <% if (modelFormat) { %>
550
+ if [ -n "${MODEL_FORMAT:-}" ]; then
551
+ CMD_ARGS+=("--model-format" "${MODEL_FORMAT}")
552
+ fi
553
+ <% } %>
554
+
555
+ if [ -n "${BASE_IMAGE:-}" ]; then
556
+ CMD_ARGS+=("--base-image" "${BASE_IMAGE}")
557
+ fi
558
+
559
+ if [ -n "${NOTES}" ]; then
560
+ CMD_ARGS+=("--notes" "${NOTES}")
561
+ fi
562
+
563
+ if [ -n "${PROJECT_FLAG}" ]; then
564
+ CMD_ARGS+=("${PROJECT_FLAG}")
565
+ fi
566
+
567
+ # Pass parameters as JSON string
568
+ CMD_ARGS+=("--parameters" "${PARAMETERS}")
569
+
570
+ # Pass generator version from package.json if available
571
+ GENERATOR_VERSION=""
572
+ if command -v node &> /dev/null; then
573
+ GENERATOR_VERSION=$(node -e "try{console.log(require('$(npm root -g)/@aws/ml-container-creator/package.json').version)}catch(e){console.log('')}" 2>/dev/null || echo "")
574
+ fi
575
+ if [ -n "${GENERATOR_VERSION}" ]; then
576
+ CMD_ARGS+=("--generator-version" "${GENERATOR_VERSION}")
577
+ fi
578
+
579
+ echo "📝 Writing to registry..."
580
+ ml-container-creator "${CMD_ARGS[@]}"
@@ -0,0 +1,113 @@
1
+ #!/bin/bash
2
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ set -e
6
+ set -u
7
+ set -o pipefail
8
+
9
+ # Source configuration
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ source "${SCRIPT_DIR}/config"
12
+
13
+ echo "🚀 Running Docker container locally for ${PROJECT_NAME}"
14
+ echo " Deployment config: ${DEPLOYMENT_CONFIG}"
15
+ echo " Framework: ${FRAMEWORK}"
16
+ echo " Model server: ${MODEL_SERVER}"
17
+
18
+ # Validate prerequisites
19
+ if ! command -v docker &> /dev/null; then
20
+ echo "❌ Docker is not installed"
21
+ echo " Install from: https://docs.docker.com/get-docker/"
22
+ exit 2
23
+ fi
24
+
25
+ # Check if image exists
26
+ if ! docker image inspect "${PROJECT_NAME}:latest" &> /dev/null; then
27
+ echo "❌ Docker image not found: ${PROJECT_NAME}:latest"
28
+ echo " Build the image first: ./do/build"
29
+ exit 5
30
+ fi
31
+
32
+ # Determine GPU support based on deployment configuration
33
+ GPU_FLAG=""
34
+ case "${DEPLOYMENT_CONFIG}" in
35
+ transformers-*)
36
+ echo "🎮 GPU support enabled for transformers deployment"
37
+ GPU_FLAG="--gpus all"
38
+
39
+ # Check if nvidia-docker is available
40
+ if ! docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi &> /dev/null; then
41
+ echo "⚠️ Warning: GPU support requested but nvidia-docker may not be available"
42
+ echo " The container will start but may fail if it requires GPU"
43
+ echo " Install nvidia-docker: https://github.com/NVIDIA/nvidia-docker"
44
+ fi
45
+ ;;
46
+ sklearn-*|xgboost-*|tensorflow-*)
47
+ echo "💻 CPU-only mode for traditional ML deployment"
48
+ ;;
49
+ *)
50
+ echo "❌ Unknown deployment configuration: ${DEPLOYMENT_CONFIG}"
51
+ exit 3
52
+ ;;
53
+ esac
54
+
55
+ # Prepare model directory mount if specified
56
+ MODEL_MOUNT=""
57
+ if [ -n "${MODEL_DIR:-}" ]; then
58
+ if [ -d "${MODEL_DIR}" ]; then
59
+ echo "📁 Mounting model directory: ${MODEL_DIR}"
60
+ MODEL_MOUNT="-v ${MODEL_DIR}:/opt/ml/model"
61
+ else
62
+ echo "⚠️ Warning: MODEL_DIR specified but directory not found: ${MODEL_DIR}"
63
+ echo " Container will start without model directory mount"
64
+ fi
65
+ fi
66
+
67
+ # Prepare environment variables
68
+ ENV_VARS=""
69
+ <% if (framework === 'transformers') { %>
70
+ if [ -n "${HF_TOKEN:-}" ]; then
71
+ echo "🔑 Using HuggingFace token from environment"
72
+ ENV_VARS="${ENV_VARS} -e HF_TOKEN=${HF_TOKEN}"
73
+ fi
74
+
75
+ if [ -n "${MODEL_NAME:-}" ]; then
76
+ ENV_VARS="${ENV_VARS} -e MODEL_NAME=${MODEL_NAME}"
77
+ fi
78
+ <% } %>
79
+
80
+ echo ""
81
+ echo "🏃 Starting container..."
82
+ echo " Port: 8080 (mapped to localhost:8080)"
83
+ echo " Image: ${PROJECT_NAME}:latest"
84
+ if [ -n "${GPU_FLAG}" ]; then
85
+ echo " GPU: Enabled"
86
+ fi
87
+ if [ -n "${MODEL_MOUNT}" ]; then
88
+ echo " Model mount: ${MODEL_DIR} -> /opt/ml/model"
89
+ fi
90
+ echo ""
91
+ echo "📝 Container logs will stream below"
92
+ echo " Press Ctrl+C to stop the container"
93
+ echo ""
94
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
95
+ echo ""
96
+
97
+ # Run container with cleanup on exit
98
+ docker run -it --rm \
99
+ -p 8080:8080 \
100
+ ${GPU_FLAG} \
101
+ ${MODEL_MOUNT} \
102
+ ${ENV_VARS} \
103
+ "${PROJECT_NAME}:latest"
104
+
105
+ echo ""
106
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
107
+ echo ""
108
+ echo "✅ Container stopped and cleaned up"
109
+ echo ""
110
+ echo "Next steps:"
111
+ echo " • Test endpoints: curl http://localhost:8080/ping"
112
+ echo " • Push to ECR: ./do/push"
113
+ echo " • Deploy to SageMaker: ./do/deploy"