@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.
- package/LICENSE +202 -0
- package/LICENSE-THIRD-PARTY +68620 -0
- package/NOTICE +2 -0
- package/README.md +106 -0
- package/bin/cli.js +365 -0
- package/config/defaults.json +32 -0
- package/config/presets/transformers-djl.json +26 -0
- package/config/presets/transformers-gpu.json +24 -0
- package/config/presets/transformers-lmi.json +27 -0
- package/package.json +129 -0
- package/servers/README.md +419 -0
- package/servers/base-image-picker/catalogs/model-servers.json +1191 -0
- package/servers/base-image-picker/catalogs/python-slim.json +38 -0
- package/servers/base-image-picker/catalogs/triton-backends.json +51 -0
- package/servers/base-image-picker/catalogs/triton.json +38 -0
- package/servers/base-image-picker/index.js +495 -0
- package/servers/base-image-picker/manifest.json +17 -0
- package/servers/base-image-picker/package.json +15 -0
- package/servers/hyperpod-cluster-picker/LICENSE +202 -0
- package/servers/hyperpod-cluster-picker/index.js +424 -0
- package/servers/hyperpod-cluster-picker/manifest.json +14 -0
- package/servers/hyperpod-cluster-picker/package.json +17 -0
- package/servers/instance-recommender/LICENSE +202 -0
- package/servers/instance-recommender/catalogs/instances.json +852 -0
- package/servers/instance-recommender/index.js +284 -0
- package/servers/instance-recommender/manifest.json +16 -0
- package/servers/instance-recommender/package.json +15 -0
- package/servers/lib/LICENSE +202 -0
- package/servers/lib/bedrock-client.js +160 -0
- package/servers/lib/custom-validators.js +46 -0
- package/servers/lib/dynamic-resolver.js +36 -0
- package/servers/lib/package.json +11 -0
- package/servers/lib/schemas/image-catalog.schema.json +185 -0
- package/servers/lib/schemas/instances.schema.json +124 -0
- package/servers/lib/schemas/manifest.schema.json +64 -0
- package/servers/lib/schemas/model-catalog.schema.json +91 -0
- package/servers/lib/schemas/regions.schema.json +26 -0
- package/servers/lib/schemas/triton-backends.schema.json +51 -0
- package/servers/model-picker/catalogs/jumpstart-public.json +66 -0
- package/servers/model-picker/catalogs/popular-diffusors.json +88 -0
- package/servers/model-picker/catalogs/popular-transformers.json +226 -0
- package/servers/model-picker/index.js +1693 -0
- package/servers/model-picker/manifest.json +18 -0
- package/servers/model-picker/package.json +20 -0
- package/servers/region-picker/LICENSE +202 -0
- package/servers/region-picker/catalogs/regions.json +263 -0
- package/servers/region-picker/index.js +230 -0
- package/servers/region-picker/manifest.json +16 -0
- package/servers/region-picker/package.json +15 -0
- package/src/app.js +1007 -0
- package/src/copy-tpl.js +77 -0
- package/src/lib/accelerator-validator.js +39 -0
- package/src/lib/asset-manager.js +385 -0
- package/src/lib/aws-profile-parser.js +181 -0
- package/src/lib/bootstrap-command-handler.js +1647 -0
- package/src/lib/bootstrap-config.js +238 -0
- package/src/lib/ci-register-helpers.js +124 -0
- package/src/lib/ci-report-helpers.js +158 -0
- package/src/lib/ci-stage-helpers.js +268 -0
- package/src/lib/cli-handler.js +529 -0
- package/src/lib/comment-generator.js +544 -0
- package/src/lib/community-reports-validator.js +91 -0
- package/src/lib/config-manager.js +2106 -0
- package/src/lib/configuration-exporter.js +204 -0
- package/src/lib/configuration-manager.js +695 -0
- package/src/lib/configuration-matcher.js +221 -0
- package/src/lib/cpu-validator.js +36 -0
- package/src/lib/cuda-validator.js +57 -0
- package/src/lib/deployment-config-resolver.js +103 -0
- package/src/lib/deployment-entry-schema.js +125 -0
- package/src/lib/deployment-registry.js +598 -0
- package/src/lib/docker-introspection-validator.js +51 -0
- package/src/lib/engine-prefix-resolver.js +60 -0
- package/src/lib/huggingface-client.js +172 -0
- package/src/lib/key-value-parser.js +37 -0
- package/src/lib/known-flags-validator.js +200 -0
- package/src/lib/manifest-cli.js +280 -0
- package/src/lib/mcp-client.js +303 -0
- package/src/lib/mcp-command-handler.js +532 -0
- package/src/lib/neuron-validator.js +80 -0
- package/src/lib/parameter-schema-validator.js +284 -0
- package/src/lib/prompt-runner.js +1349 -0
- package/src/lib/prompts.js +1138 -0
- package/src/lib/registry-command-handler.js +519 -0
- package/src/lib/registry-loader.js +198 -0
- package/src/lib/rocm-validator.js +80 -0
- package/src/lib/schema-validator.js +157 -0
- package/src/lib/sensitive-redactor.js +59 -0
- package/src/lib/template-engine.js +156 -0
- package/src/lib/template-manager.js +341 -0
- package/src/lib/validation-engine.js +314 -0
- package/src/prompt-adapter.js +63 -0
- package/templates/Dockerfile +300 -0
- package/templates/IAM_PERMISSIONS.md +84 -0
- package/templates/MIGRATION.md +488 -0
- package/templates/PROJECT_README.md +439 -0
- package/templates/TEMPLATE_SYSTEM.md +243 -0
- package/templates/buildspec.yml +64 -0
- package/templates/code/chat_template.jinja +1 -0
- package/templates/code/flask/gunicorn_config.py +35 -0
- package/templates/code/flask/wsgi.py +10 -0
- package/templates/code/model_handler.py +387 -0
- package/templates/code/serve +300 -0
- package/templates/code/serve.py +175 -0
- package/templates/code/serving.properties +105 -0
- package/templates/code/start_server.py +39 -0
- package/templates/code/start_server.sh +39 -0
- package/templates/diffusors/Dockerfile +72 -0
- package/templates/diffusors/patch_image_api.py +35 -0
- package/templates/diffusors/serve +115 -0
- package/templates/diffusors/start_server.sh +114 -0
- package/templates/do/.gitkeep +1 -0
- package/templates/do/README.md +541 -0
- package/templates/do/build +83 -0
- package/templates/do/ci +681 -0
- package/templates/do/clean +811 -0
- package/templates/do/config +260 -0
- package/templates/do/deploy +1560 -0
- package/templates/do/export +306 -0
- package/templates/do/logs +319 -0
- package/templates/do/manifest +12 -0
- package/templates/do/push +119 -0
- package/templates/do/register +580 -0
- package/templates/do/run +113 -0
- package/templates/do/submit +417 -0
- package/templates/do/test +1147 -0
- package/templates/hyperpod/configmap.yaml +24 -0
- package/templates/hyperpod/deployment.yaml +71 -0
- package/templates/hyperpod/pvc.yaml +42 -0
- package/templates/hyperpod/service.yaml +17 -0
- package/templates/nginx-diffusors.conf +74 -0
- package/templates/nginx-predictors.conf +47 -0
- package/templates/nginx-tensorrt.conf +74 -0
- package/templates/requirements.txt +61 -0
- package/templates/sample_model/test_inference.py +123 -0
- package/templates/sample_model/train_abalone.py +252 -0
- package/templates/test/test_endpoint.sh +79 -0
- package/templates/test/test_local_image.sh +80 -0
- package/templates/test/test_model_handler.py +180 -0
- package/templates/triton/Dockerfile +128 -0
- package/templates/triton/config.pbtxt +163 -0
- package/templates/triton/model.py +130 -0
- 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
|
+
}
|