@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,198 @@
1
+ /**
2
+ * Registry Loader
3
+ *
4
+ * Loads configuration data from catalog JSON files and transforms them
5
+ * into the shapes expected by consumer modules (Configuration_Manager,
6
+ * Configuration_Matcher, Prompt_Runner, Validation_Engine, Template_Engine).
7
+ *
8
+ * This is the adapter layer between catalog JSON (single source of truth)
9
+ * and the generator's internal data shapes. No MCP runtime dependency.
10
+ */
11
+
12
+ import { readFileSync } from 'node:fs'
13
+ import { resolve, dirname } from 'node:path'
14
+ import { fileURLToPath } from 'node:url'
15
+
16
+ const __filename = fileURLToPath(import.meta.url)
17
+ const __dirname = dirname(__filename)
18
+
19
+ // Catalog file paths relative to this module
20
+ const CATALOG_PATHS = {
21
+ modelServers: resolve(__dirname, '../../servers/base-image-picker/catalogs/model-servers.json'),
22
+ tritonBackends: resolve(__dirname, '../../servers/base-image-picker/catalogs/triton-backends.json'),
23
+ instances: resolve(__dirname, '../../servers/instance-recommender/catalogs/instances.json'),
24
+ popularTransformers: resolve(__dirname, '../../servers/model-picker/catalogs/popular-transformers.json'),
25
+ popularDiffusors: resolve(__dirname, '../../servers/model-picker/catalogs/popular-diffusors.json'),
26
+ }
27
+
28
+ class RegistryLoader {
29
+ constructor() {
30
+ this._catalogCache = {}
31
+ }
32
+
33
+ /**
34
+ * Load and parse a JSON catalog file with caching.
35
+ * Returns null on failure (missing file, invalid JSON).
36
+ */
37
+ _loadCatalog(catalogPath) {
38
+ if (this._catalogCache[catalogPath] !== undefined) {
39
+ return this._catalogCache[catalogPath]
40
+ }
41
+ try {
42
+ const raw = readFileSync(catalogPath, 'utf8')
43
+ const data = JSON.parse(raw)
44
+ this._catalogCache[catalogPath] = data
45
+ return data
46
+ } catch (error) {
47
+ console.warn(`Failed to load catalog ${catalogPath}: ${error.message}`)
48
+ this._catalogCache[catalogPath] = null
49
+ return null
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Load framework registry from model-servers.json catalog.
55
+ *
56
+ * Transforms catalog Image_Entry arrays into the shape:
57
+ * { frameworkName: { version: FrameworkConfig } }
58
+ *
59
+ * Where FrameworkConfig = {
60
+ * baseImage, accelerator, envVars, inferenceAmiVersion,
61
+ * recommendedInstanceTypes, validationLevel, profiles, notes
62
+ * }
63
+ *
64
+ * @returns {Object} Framework registry or empty object on failure
65
+ */
66
+ async loadFrameworkRegistry() {
67
+ try {
68
+ const catalog = this._loadCatalog(CATALOG_PATHS.modelServers)
69
+ if (!catalog) return {}
70
+
71
+ const registry = {}
72
+ for (const [frameworkName, entries] of Object.entries(catalog)) {
73
+ if (!Array.isArray(entries)) continue
74
+ registry[frameworkName] = {}
75
+
76
+ for (const entry of entries) {
77
+ const version = entry.labels?.framework_version
78
+ if (!version) continue
79
+
80
+ registry[frameworkName][version] = {
81
+ baseImage: entry.image,
82
+ accelerator: entry.accelerator || { type: 'cpu', version: null, versionRange: { min: null, max: null } },
83
+ envVars: entry.defaults?.envVars || {},
84
+ inferenceAmiVersion: entry.defaults?.inferenceAmiVersion || '',
85
+ recommendedInstanceTypes: entry.defaults?.recommendedInstanceTypes || [],
86
+ validationLevel: entry.validationLevel || 'untested',
87
+ profiles: entry.profiles || {},
88
+ notes: entry.notes || '',
89
+ }
90
+ }
91
+ }
92
+ return registry
93
+ } catch (error) {
94
+ console.warn(`Failed to load framework registry: ${error.message}`)
95
+ return {}
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Load model registry from popular-transformers.json and popular-diffusors.json.
101
+ *
102
+ * Merges both catalogs into a single object with the shape:
103
+ * { modelIdOrPattern: ModelConfig }
104
+ *
105
+ * Where ModelConfig = {
106
+ * family, chatTemplate, requiresTemplate, validationLevel,
107
+ * frameworkCompatibility, profiles, notes
108
+ * }
109
+ *
110
+ * Maps catalog snake_case keys to camelCase:
111
+ * chat_template → chatTemplate
112
+ * framework_compatibility → frameworkCompatibility
113
+ * validation_level → validationLevel
114
+ *
115
+ * @returns {Object} Model registry or empty object on failure
116
+ */
117
+ async loadModelRegistry() {
118
+ try {
119
+ const transformers = this._loadCatalog(CATALOG_PATHS.popularTransformers) || {}
120
+ const diffusors = this._loadCatalog(CATALOG_PATHS.popularDiffusors) || {}
121
+
122
+ const registry = {}
123
+ const allModels = { ...transformers, ...diffusors }
124
+
125
+ for (const [modelId, entry] of Object.entries(allModels)) {
126
+ registry[modelId] = {
127
+ family: entry.family || '',
128
+ chatTemplate: entry.chat_template ?? null,
129
+ requiresTemplate: entry.chat_template != null && entry.chat_template !== '',
130
+ validationLevel: entry.validation_level || 'experimental',
131
+ frameworkCompatibility: entry.framework_compatibility || {},
132
+ profiles: entry.profiles || {},
133
+ notes: entry.notes || '',
134
+ }
135
+ }
136
+ return registry
137
+ } catch (error) {
138
+ console.warn(`Failed to load model registry: ${error.message}`)
139
+ return {}
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Load instance accelerator mapping from instances.json catalog.
145
+ *
146
+ * Transforms catalog entries into the shape:
147
+ * { instanceType: { family, accelerator: { type, hardware, architecture, versions, default }, memory, vcpus, notes } }
148
+ *
149
+ * @returns {Object} Instance accelerator mapping or empty object on failure
150
+ */
151
+ async loadInstanceAcceleratorMapping() {
152
+ try {
153
+ const catalog = this._loadCatalog(CATALOG_PATHS.instances)
154
+ if (!catalog || !catalog.catalog) return {}
155
+
156
+ const mapping = {}
157
+ for (const [instanceType, entry] of Object.entries(catalog.catalog)) {
158
+ mapping[instanceType] = {
159
+ family: entry.family || '',
160
+ accelerator: {
161
+ type: entry.acceleratorType || 'cpu',
162
+ hardware: entry.hardware || 'None',
163
+ architecture: entry.gpuArchitecture || 'None',
164
+ versions: entry.cudaVersions || null,
165
+ default: entry.defaultCudaVersion || null,
166
+ },
167
+ memory: entry.memGb ? `${entry.memGb} GB` : '0 GB',
168
+ vcpus: entry.vcpus || 0,
169
+ notes: entry.notes || '',
170
+ }
171
+ }
172
+ return mapping
173
+ } catch (error) {
174
+ console.warn(`Failed to load instance accelerator mapping: ${error.message}`)
175
+ return {}
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Load Triton backend metadata from triton-backends.json catalog.
181
+ *
182
+ * Returns the catalog data directly since its shape already matches
183
+ * what consumers expect.
184
+ *
185
+ * @returns {Object} Triton backends or empty object on failure
186
+ */
187
+ async loadTritonBackends() {
188
+ try {
189
+ const catalog = this._loadCatalog(CATALOG_PATHS.tritonBackends)
190
+ return catalog || {}
191
+ } catch (error) {
192
+ console.warn(`Failed to load triton backends: ${error.message}`)
193
+ return {}
194
+ }
195
+ }
196
+ }
197
+
198
+ export default RegistryLoader
@@ -0,0 +1,80 @@
1
+ import AcceleratorValidator from './accelerator-validator.js';
2
+
3
+ /**
4
+ * ROCm accelerator validator for AMD GPUs.
5
+ * Implements ROCm semantic versioning.
6
+ *
7
+ * Requirements: 4.9, 4.22
8
+ */
9
+ export default class RocmValidator extends AcceleratorValidator {
10
+ /**
11
+ * Validate ROCm version compatibility.
12
+ * ROCm uses semantic versioning (e.g., 5.4.0, 5.5.0).
13
+ * Major version must match, minor version must be >= required.
14
+ *
15
+ * @param {Object} frameworkConfig - Framework accelerator requirements
16
+ * @param {Object} instanceConfig - Instance accelerator capabilities
17
+ * @returns {Object} ValidationResult
18
+ */
19
+ validate(frameworkConfig, instanceConfig) {
20
+ const required = frameworkConfig.accelerator;
21
+ const provided = instanceConfig.accelerator;
22
+
23
+ // Parse required ROCm version
24
+ const requiredVersion = this.parseVersion(required.version);
25
+
26
+ // Check if instance supports required ROCm version
27
+ const compatibleVersions = provided.versions.filter(v => {
28
+ const providedVersion = this.parseVersion(v);
29
+ return this.isCompatible(requiredVersion, providedVersion);
30
+ });
31
+
32
+ if (compatibleVersions.length === 0) {
33
+ return {
34
+ compatible: false,
35
+ error: this.getVersionMismatchMessage(required.version, provided.versions)
36
+ };
37
+ }
38
+
39
+ return {
40
+ compatible: true,
41
+ info: `Using ROCm ${compatibleVersions[0]} (compatible with required ${required.version})`
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Parse semantic version string into components.
47
+ *
48
+ * @param {string} versionString - Version string (e.g., "5.4.0")
49
+ * @returns {Object} Parsed version with major, minor, patch
50
+ */
51
+ parseVersion(versionString) {
52
+ const [major, minor, patch] = versionString.split('.').map(Number);
53
+ return { major, minor, patch };
54
+ }
55
+
56
+ /**
57
+ * Check if provided version is compatible with required version.
58
+ * ROCm: major must match, minor must be >= required.
59
+ *
60
+ * @param {Object} required - Required version
61
+ * @param {Object} provided - Provided version
62
+ * @returns {boolean} True if compatible
63
+ */
64
+ isCompatible(required, provided) {
65
+ return provided.major === required.major &&
66
+ provided.minor >= required.minor;
67
+ }
68
+
69
+ /**
70
+ * Get user-friendly error message for ROCm version mismatch.
71
+ *
72
+ * @param {string} required - Required ROCm version
73
+ * @param {Array<string>} provided - Provided ROCm versions
74
+ * @returns {string} User-friendly error message
75
+ */
76
+ getVersionMismatchMessage(required, provided) {
77
+ return `Framework requires ROCm ${required}, but instance only supports ${provided.join(', ')}. ` +
78
+ 'AMD GPU instances with ROCm support may be limited in SageMaker.';
79
+ }
80
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Schema Validator
3
+ *
4
+ * Validates registry data against JSON schemas without external dependencies.
5
+ * Uses a simple validation approach suitable for the registry structure.
6
+ */
7
+
8
+ export default class SchemaValidator {
9
+ /**
10
+ * Validate data against a schema
11
+ * @param {Object} data - Data to validate
12
+ * @param {Object} schema - JSON schema
13
+ * @returns {Object} - { valid: boolean, errors: string[] }
14
+ */
15
+ validate(data, schema) {
16
+ const errors = [];
17
+
18
+ try {
19
+ this._validateType(data, schema, '', errors);
20
+ } catch (error) {
21
+ errors.push(`Validation error: ${error.message}`);
22
+ }
23
+
24
+ return {
25
+ valid: errors.length === 0,
26
+ errors
27
+ };
28
+ }
29
+
30
+ _validateType(data, schema, path, errors) {
31
+ // Handle type validation
32
+ if (schema.type) {
33
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
34
+ const dataType = this._getType(data);
35
+
36
+ if (!types.includes(dataType)) {
37
+ errors.push(`${path || 'root'}: Expected type ${types.join(' or ')}, got ${dataType}`);
38
+ return;
39
+ }
40
+ }
41
+
42
+ // Handle enum validation
43
+ if (schema.enum && !schema.enum.includes(data)) {
44
+ errors.push(`${path || 'root'}: Value must be one of ${schema.enum.join(', ')}`);
45
+ return;
46
+ }
47
+
48
+ // Handle object validation
49
+ if (this._getType(data) === 'object' && schema.type === 'object') {
50
+ this._validateObject(data, schema, path, errors);
51
+ }
52
+
53
+ // Handle array validation
54
+ if (this._getType(data) === 'array' && schema.type === 'array') {
55
+ this._validateArray(data, schema, path, errors);
56
+ }
57
+
58
+ // Handle string validation
59
+ if (this._getType(data) === 'string' && schema.type === 'string') {
60
+ this._validateString(data, schema, path, errors);
61
+ }
62
+
63
+ // Handle number validation
64
+ if (this._getType(data) === 'number' && schema.type === 'number') {
65
+ this._validateNumber(data, schema, path, errors);
66
+ }
67
+ }
68
+
69
+ _validateObject(data, schema, path, errors) {
70
+ // Check required properties
71
+ if (schema.required) {
72
+ for (const requiredProp of schema.required) {
73
+ if (!(requiredProp in data)) {
74
+ errors.push(`${path || 'root'}: Missing required property '${requiredProp}'`);
75
+ }
76
+ }
77
+ }
78
+
79
+ // Validate properties
80
+ if (schema.properties) {
81
+ for (const [key, value] of Object.entries(data)) {
82
+ if (schema.properties[key]) {
83
+ this._validateType(value, schema.properties[key], `${path}.${key}`, errors);
84
+ }
85
+ }
86
+ }
87
+
88
+ // Validate patternProperties
89
+ if (schema.patternProperties) {
90
+ for (const [key, value] of Object.entries(data)) {
91
+ for (const [pattern, propSchema] of Object.entries(schema.patternProperties)) {
92
+ const regex = new RegExp(pattern);
93
+ if (regex.test(key)) {
94
+ this._validateType(value, propSchema, `${path}.${key}`, errors);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ _validateArray(data, schema, path, errors) {
102
+ // Check minItems
103
+ if (schema.minItems !== undefined && data.length < schema.minItems) {
104
+ errors.push(`${path || 'root'}: Array must have at least ${schema.minItems} items`);
105
+ }
106
+
107
+ // Check maxItems
108
+ if (schema.maxItems !== undefined && data.length > schema.maxItems) {
109
+ errors.push(`${path || 'root'}: Array must have at most ${schema.maxItems} items`);
110
+ }
111
+
112
+ // Validate items
113
+ if (schema.items) {
114
+ data.forEach((item, index) => {
115
+ this._validateType(item, schema.items, `${path}[${index}]`, errors);
116
+ });
117
+ }
118
+ }
119
+
120
+ _validateString(data, schema, path, errors) {
121
+ // Check minLength
122
+ if (schema.minLength !== undefined && data.length < schema.minLength) {
123
+ errors.push(`${path || 'root'}: String must be at least ${schema.minLength} characters`);
124
+ }
125
+
126
+ // Check maxLength
127
+ if (schema.maxLength !== undefined && data.length > schema.maxLength) {
128
+ errors.push(`${path || 'root'}: String must be at most ${schema.maxLength} characters`);
129
+ }
130
+
131
+ // Check pattern
132
+ if (schema.pattern) {
133
+ const regex = new RegExp(schema.pattern);
134
+ if (!regex.test(data)) {
135
+ errors.push(`${path || 'root'}: String does not match pattern ${schema.pattern}`);
136
+ }
137
+ }
138
+ }
139
+
140
+ _validateNumber(data, schema, path, errors) {
141
+ // Check minimum
142
+ if (schema.minimum !== undefined && data < schema.minimum) {
143
+ errors.push(`${path || 'root'}: Number must be at least ${schema.minimum}`);
144
+ }
145
+
146
+ // Check maximum
147
+ if (schema.maximum !== undefined && data > schema.maximum) {
148
+ errors.push(`${path || 'root'}: Number must be at most ${schema.maximum}`);
149
+ }
150
+ }
151
+
152
+ _getType(value) {
153
+ if (value === null) return 'null';
154
+ if (Array.isArray(value)) return 'array';
155
+ return typeof value;
156
+ }
157
+ }
@@ -0,0 +1,59 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Sensitive Value Redactor
6
+ *
7
+ * Detects keys matching sensitive patterns and replaces their values
8
+ * with a redaction marker. Used by the do/register template to sanitize
9
+ * parameters before writing to DynamoDB.
10
+ *
11
+ * Requirements: 6.5
12
+ */
13
+
14
+ /**
15
+ * Redaction marker used to replace sensitive values.
16
+ */
17
+ export const REDACTION_MARKER = '***REDACTED***'
18
+
19
+ /**
20
+ * Exact key names that are always considered sensitive.
21
+ */
22
+ export const SENSITIVE_EXACT_KEYS = ['HF_TOKEN', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN']
23
+
24
+ /**
25
+ * Substrings that, when found in a key (case-insensitive), mark it as sensitive.
26
+ */
27
+ export const SENSITIVE_SUBSTRINGS = ['SECRET', 'TOKEN']
28
+
29
+ /**
30
+ * Determine whether a given key matches sensitive patterns.
31
+ *
32
+ * A key is sensitive if:
33
+ * - It exactly matches one of SENSITIVE_EXACT_KEYS
34
+ * - Its uppercase form contains any of SENSITIVE_SUBSTRINGS
35
+ *
36
+ * @param {string} key - The environment variable key to check
37
+ * @returns {boolean} True if the key is sensitive
38
+ */
39
+ export function isSensitiveKey(key) {
40
+ if (SENSITIVE_EXACT_KEYS.includes(key)) return true
41
+ const upper = key.toUpperCase()
42
+ return SENSITIVE_SUBSTRINGS.some(sub => upper.includes(sub))
43
+ }
44
+
45
+ /**
46
+ * Redact sensitive values in a parameters object.
47
+ * Returns a new object with sensitive values replaced by the redaction marker.
48
+ * Non-sensitive values are preserved unchanged.
49
+ *
50
+ * @param {Object<string, string>} params - Key-value map of parameters
51
+ * @returns {Object<string, string>} New object with sensitive values redacted
52
+ */
53
+ export function redactSensitiveValues(params) {
54
+ const result = {}
55
+ for (const [key, value] of Object.entries(params)) {
56
+ result[key] = isSensitiveKey(key) ? REDACTION_MARKER : value
57
+ }
58
+ return result
59
+ }
@@ -0,0 +1,156 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import ejs from 'ejs';
7
+ import CommentGenerator from './comment-generator.js';
8
+
9
+ /**
10
+ * TemplateEngine - Generates files with injected configurations
11
+ *
12
+ * Responsible for generating Dockerfiles and deployment scripts with
13
+ * configuration data from catalogs, HuggingFace API, and user input.
14
+ * Integrates CommentGenerator for comprehensive documentation.
15
+ */
16
+ export default class TemplateEngine {
17
+ /**
18
+ * @param {Object} options - Configuration options
19
+ * @param {string} options.templateDir - Root directory containing template files
20
+ * @param {string} options.destDir - Root directory for generated output
21
+ */
22
+ constructor({ templateDir, destDir }) {
23
+ this.templateDir = templateDir;
24
+ this.destDir = destDir;
25
+ this.commentGenerator = new CommentGenerator();
26
+ }
27
+
28
+ /**
29
+ * Generate Dockerfile with configuration injection
30
+ * @param {Object} config - Configuration profile
31
+ * @returns {void}
32
+ */
33
+ generateDockerfile(config) {
34
+ // Generate comments for documentation
35
+ const comments = this.commentGenerator.generateDockerfileComments(config);
36
+
37
+ // Prepare template variables with configuration and comments
38
+ const templateVars = {
39
+ ...config,
40
+ comments,
41
+ // Preserve environment variable ordering
42
+ orderedEnvVars: this._getOrderedEnvVars(config.envVars || {})
43
+ };
44
+
45
+ // Render and write Dockerfile template
46
+ this._renderTemplate('Dockerfile', 'Dockerfile', templateVars);
47
+ }
48
+
49
+ /**
50
+ * Generate deployment script with configuration injection
51
+ * @param {Object} config - Configuration profile
52
+ * @returns {void}
53
+ */
54
+ generateDeploymentScript(config) {
55
+ // No-op: legacy deploy/ scripts have been removed.
56
+ // Deployment is handled by do/deploy in the do-framework.
57
+ }
58
+
59
+ /**
60
+ * Render a single EJS template file to the destination directory.
61
+ * @private
62
+ * @param {string} templateRelPath - Relative path within templateDir
63
+ * @param {string} destRelPath - Relative path within destDir
64
+ * @param {Object} vars - Template variables for EJS rendering
65
+ */
66
+ _renderTemplate(templateRelPath, destRelPath, vars) {
67
+ const src = path.resolve(this.templateDir, templateRelPath);
68
+ const dest = path.resolve(this.destDir, destRelPath);
69
+
70
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
71
+
72
+ const content = fs.readFileSync(src, 'utf8');
73
+ const rendered = ejs.render(content, vars, { filename: src });
74
+ fs.writeFileSync(dest, rendered);
75
+ }
76
+
77
+ /**
78
+ * Get environment variables in correct order
79
+ * Preserves dependency order (e.g., CUDA paths before framework variables)
80
+ * @private
81
+ * @param {Object} envVars - Environment variables object
82
+ * @returns {Array<{key: string, value: string}>} Ordered array of env vars
83
+ */
84
+ _getOrderedEnvVars(envVars) {
85
+ const entries = Object.entries(envVars);
86
+
87
+ // Define priority order for environment variable categories
88
+ const priorities = {
89
+ // System paths (highest priority)
90
+ 'LD_LIBRARY_PATH': 1,
91
+ 'PATH': 1,
92
+ 'CUDA_HOME': 1,
93
+ 'CUDA_PATH': 1,
94
+
95
+ // CUDA configuration
96
+ 'CUDA_VISIBLE_DEVICES': 2,
97
+ 'NVIDIA_VISIBLE_DEVICES': 2,
98
+ 'NVIDIA_DRIVER_CAPABILITIES': 2,
99
+
100
+ // Framework-specific (medium priority)
101
+ 'VLLM': 3,
102
+ 'TENSORRT': 3,
103
+ 'SGLANG': 3,
104
+ 'TRANSFORMERS': 3,
105
+
106
+ // Application configuration (lower priority)
107
+ 'MAX': 4,
108
+ 'BATCH': 4,
109
+ 'WORKER': 4,
110
+ 'THREAD': 4,
111
+
112
+ // Other variables (lowest priority)
113
+ 'default': 5
114
+ };
115
+
116
+ // Sort entries by priority
117
+ const sorted = entries.sort(([keyA], [keyB]) => {
118
+ const priorityA = this._getEnvVarPriority(keyA, priorities);
119
+ const priorityB = this._getEnvVarPriority(keyB, priorities);
120
+
121
+ if (priorityA !== priorityB) {
122
+ return priorityA - priorityB;
123
+ }
124
+
125
+ // If same priority, sort alphabetically
126
+ return keyA.localeCompare(keyB);
127
+ });
128
+
129
+ // Convert to array of objects for template
130
+ return sorted.map(([key, value]) => ({ key, value }));
131
+ }
132
+
133
+ /**
134
+ * Get priority for an environment variable
135
+ * @private
136
+ * @param {string} key - Environment variable name
137
+ * @param {Object} priorities - Priority mapping
138
+ * @returns {number} Priority value (lower = higher priority)
139
+ */
140
+ _getEnvVarPriority(key, priorities) {
141
+ // Check for exact match first
142
+ if (priorities[key]) {
143
+ return priorities[key];
144
+ }
145
+
146
+ // Check for partial matches
147
+ for (const [pattern, priority] of Object.entries(priorities)) {
148
+ if (pattern !== 'default' && key.includes(pattern)) {
149
+ return priority;
150
+ }
151
+ }
152
+
153
+ // Default priority
154
+ return priorities.default;
155
+ }
156
+ }