@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,238 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bootstrap Configuration
|
|
6
|
+
*
|
|
7
|
+
* Handles reading, writing, and managing the bootstrap configuration file
|
|
8
|
+
* at ~/.ml-container-creator/config.json. Supports named profiles for
|
|
9
|
+
* multiple AWS environment configurations.
|
|
10
|
+
*
|
|
11
|
+
* Config file format:
|
|
12
|
+
* {
|
|
13
|
+
* "activeProfile": "default",
|
|
14
|
+
* "profiles": {
|
|
15
|
+
* "default": {
|
|
16
|
+
* "awsProfile": "default",
|
|
17
|
+
* "awsRegion": "us-east-1",
|
|
18
|
+
* "accountId": "111111111111",
|
|
19
|
+
* "roleArn": "arn:aws:iam::111111111111:role/mlcc-sagemaker-execution-role",
|
|
20
|
+
* "ecrRepositoryName": "ml-container-creator",
|
|
21
|
+
* "asyncS3Bucket": "...",
|
|
22
|
+
* "batchS3Bucket": "...",
|
|
23
|
+
* "ciInfraProvisioned": false,
|
|
24
|
+
* "ciTableName": "mlcc-ci-table"
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* Optional CI fields (added by bootstrap --ci):
|
|
30
|
+
* - ciInfraProvisioned (boolean): Whether CI harness infrastructure has been deployed. Defaults to false.
|
|
31
|
+
* - ciTableName (string): Name of the DynamoDB CI table. Defaults to "mlcc-ci-table".
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
|
|
35
|
+
import { dirname, join } from 'node:path'
|
|
36
|
+
import { homedir } from 'node:os'
|
|
37
|
+
|
|
38
|
+
export default class BootstrapConfig {
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} [configPath] - Absolute path to the config JSON file.
|
|
41
|
+
* Defaults to ~/.ml-container-creator/config.json
|
|
42
|
+
*/
|
|
43
|
+
constructor(configPath) {
|
|
44
|
+
this.configPath = configPath || join(homedir(), '.ml-container-creator', 'config.json')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Read the config file and return the parsed config object.
|
|
49
|
+
*
|
|
50
|
+
* @returns {{ activeProfile: string, profiles: Object }|null} The config object, or null if file doesn't exist
|
|
51
|
+
*/
|
|
52
|
+
read() {
|
|
53
|
+
if (!existsSync(this.configPath)) {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const raw = readFileSync(this.configPath, 'utf8')
|
|
58
|
+
return JSON.parse(raw)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Write a config object to the config file.
|
|
63
|
+
* Creates the parent directory if it doesn't exist.
|
|
64
|
+
* Uses 2-space indentation and a trailing newline.
|
|
65
|
+
*
|
|
66
|
+
* @param {Object} config - The config object to write
|
|
67
|
+
*/
|
|
68
|
+
write(config) {
|
|
69
|
+
const dir = dirname(this.configPath)
|
|
70
|
+
if (!existsSync(dir)) {
|
|
71
|
+
mkdirSync(dir, { recursive: true })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2) + '\n')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check whether the config file exists.
|
|
79
|
+
*
|
|
80
|
+
* @returns {boolean} true if the config file exists
|
|
81
|
+
*/
|
|
82
|
+
exists() {
|
|
83
|
+
return existsSync(this.configPath)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the active profile name and its config.
|
|
88
|
+
*
|
|
89
|
+
* @returns {{ name: string, config: Object }|null} The active profile, or null if no active profile
|
|
90
|
+
*/
|
|
91
|
+
getActiveProfile() {
|
|
92
|
+
const config = this.read()
|
|
93
|
+
if (!config || !config.activeProfile || !config.profiles) {
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const profileConfig = config.profiles[config.activeProfile]
|
|
98
|
+
if (!profileConfig) {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { name: config.activeProfile, config: profileConfig }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get a specific profile's config by name.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} name - The profile name
|
|
109
|
+
* @returns {Object|null} The profile config object, or null if not found
|
|
110
|
+
*/
|
|
111
|
+
getProfile(name) {
|
|
112
|
+
const config = this.read()
|
|
113
|
+
if (!config || !config.profiles) {
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return config.profiles[name] || null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get a specific profile's config with CI defaults applied.
|
|
122
|
+
* Ensures profiles created before CI integration still work by
|
|
123
|
+
* providing graceful defaults for missing CI fields.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} name - The profile name
|
|
126
|
+
* @returns {Object|null} The profile config with CI defaults, or null if not found
|
|
127
|
+
*/
|
|
128
|
+
getProfileWithDefaults(name) {
|
|
129
|
+
const profile = this.getProfile(name)
|
|
130
|
+
if (!profile) {
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
ciInfraProvisioned: false,
|
|
136
|
+
ciTableName: 'mlcc-ci-table',
|
|
137
|
+
...profile
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get the active profile with CI defaults applied.
|
|
143
|
+
* Ensures profiles created before CI integration still work by
|
|
144
|
+
* providing graceful defaults for missing CI fields.
|
|
145
|
+
*
|
|
146
|
+
* @returns {{ name: string, config: Object }|null} The active profile with CI defaults, or null
|
|
147
|
+
*/
|
|
148
|
+
getActiveProfileWithDefaults() {
|
|
149
|
+
const active = this.getActiveProfile()
|
|
150
|
+
if (!active) {
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
name: active.name,
|
|
156
|
+
config: {
|
|
157
|
+
ciInfraProvisioned: false,
|
|
158
|
+
ciTableName: 'mlcc-ci-table',
|
|
159
|
+
...active.config
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create or update a profile in the config.
|
|
166
|
+
* Sets the given profile as the active profile and writes the config.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} name - The profile name
|
|
169
|
+
* @param {Object} profileData - The profile configuration data
|
|
170
|
+
*/
|
|
171
|
+
setProfile(name, profileData) {
|
|
172
|
+
let config = this.read()
|
|
173
|
+
if (!config) {
|
|
174
|
+
config = { activeProfile: null, profiles: {} }
|
|
175
|
+
}
|
|
176
|
+
if (!config.profiles) {
|
|
177
|
+
config.profiles = {}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
config.profiles[name] = profileData
|
|
181
|
+
config.activeProfile = name
|
|
182
|
+
this.write(config)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Remove a profile from the config.
|
|
187
|
+
* If the removed profile was active, sets activeProfile to the first
|
|
188
|
+
* remaining profile or null if no profiles remain.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} name - The profile name to remove
|
|
191
|
+
* @returns {boolean} true if the profile was removed, false if not found
|
|
192
|
+
*/
|
|
193
|
+
removeProfile(name) {
|
|
194
|
+
const config = this.read()
|
|
195
|
+
if (!config || !config.profiles || !config.profiles[name]) {
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
delete config.profiles[name]
|
|
200
|
+
|
|
201
|
+
if (config.activeProfile === name) {
|
|
202
|
+
const remaining = Object.keys(config.profiles)
|
|
203
|
+
config.activeProfile = remaining.length > 0 ? remaining[0] : null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.write(config)
|
|
207
|
+
return true
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* List all profile names in the config.
|
|
212
|
+
*
|
|
213
|
+
* @returns {string[]} Array of profile name strings
|
|
214
|
+
*/
|
|
215
|
+
listProfiles() {
|
|
216
|
+
const config = this.read()
|
|
217
|
+
if (!config || !config.profiles) {
|
|
218
|
+
return []
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return Object.keys(config.profiles)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Set the active profile by name and write the config.
|
|
226
|
+
*
|
|
227
|
+
* @param {string} name - The profile name to set as active
|
|
228
|
+
*/
|
|
229
|
+
setActiveProfile(name) {
|
|
230
|
+
const config = this.read()
|
|
231
|
+
if (!config) {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
config.activeProfile = name
|
|
236
|
+
this.write(config)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CI Register Helpers
|
|
6
|
+
*
|
|
7
|
+
* Extracted logic from the `do/register` bash template into testable
|
|
8
|
+
* JavaScript functions. These functions mirror the bash implementations
|
|
9
|
+
* for configId hashing, CI record building, and record default handling.
|
|
10
|
+
*
|
|
11
|
+
* Used by unit and property-based tests to validate CI registration
|
|
12
|
+
* behavior without executing the bash template directly.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createHash } from 'node:crypto'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compute a deterministic configId from canonical deployment fields.
|
|
19
|
+
*
|
|
20
|
+
* Mirrors the bash logic:
|
|
21
|
+
* echo -n "${DEPLOYMENT_CONFIG}:${MODEL_NAME:-none}:${INSTANCE_TYPE}:${AWS_REGION}:${DEPLOYMENT_TARGET}" \
|
|
22
|
+
* | sha256sum | cut -c1-16
|
|
23
|
+
*
|
|
24
|
+
* @param {string} deploymentConfig - e.g. "transformers-vllm"
|
|
25
|
+
* @param {string} modelName - e.g. "meta-llama/Llama-2-7b-chat-hf", defaults to "none"
|
|
26
|
+
* @param {string} instanceType - e.g. "ml.g5.xlarge"
|
|
27
|
+
* @param {string} region - e.g. "us-east-1"
|
|
28
|
+
* @param {string} deploymentTarget - e.g. "managed-inference"
|
|
29
|
+
* @returns {string} 16-character lowercase hex string
|
|
30
|
+
*/
|
|
31
|
+
export function computeConfigId(deploymentConfig, modelName, instanceType, region, deploymentTarget) {
|
|
32
|
+
const input = `${deploymentConfig}:${modelName || 'none'}:${instanceType}:${region}:${deploymentTarget}`
|
|
33
|
+
const hash = createHash('sha256').update(input).digest('hex')
|
|
34
|
+
return hash.slice(0, 16)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build a CI DynamoDB record structure from registration inputs.
|
|
39
|
+
*
|
|
40
|
+
* Mirrors the `write_ci_record` function in the bash template.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} configId - The computed configId (16-char hex)
|
|
43
|
+
* @param {string} configJson - Compact JSON string of the full configuration
|
|
44
|
+
* @param {object} promotedAttrs - Promoted top-level attributes
|
|
45
|
+
* @param {string} promotedAttrs.deploymentConfig - e.g. "transformers-vllm"
|
|
46
|
+
* @param {string} promotedAttrs.baseImage - e.g. "vllm/vllm-openai:v0.8.5"
|
|
47
|
+
* @param {string} promotedAttrs.baseImageVersion - e.g. "v0.8.5"
|
|
48
|
+
* @param {string} promotedAttrs.projectName - e.g. "test-vllm"
|
|
49
|
+
* @returns {object} DynamoDB item structure (plain JS object, not DynamoDB JSON)
|
|
50
|
+
*/
|
|
51
|
+
export function buildCiRecord(configId, configJson, promotedAttrs) {
|
|
52
|
+
const createdAt = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z')
|
|
53
|
+
return {
|
|
54
|
+
configId,
|
|
55
|
+
schemaVersion: 1,
|
|
56
|
+
configJson,
|
|
57
|
+
testStatus: 'untested',
|
|
58
|
+
lastTestTimestamp: '1970-01-01T00:00:00Z',
|
|
59
|
+
deploymentConfig: promotedAttrs.deploymentConfig || '',
|
|
60
|
+
baseImage: promotedAttrs.baseImage || '',
|
|
61
|
+
baseImageVersion: promotedAttrs.baseImageVersion || '',
|
|
62
|
+
projectName: promotedAttrs.projectName || '',
|
|
63
|
+
createdAt
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Apply default values for missing attributes on a CI record.
|
|
69
|
+
*
|
|
70
|
+
* Ensures consumers handle records written under older schemaVersions
|
|
71
|
+
* gracefully (Requirement 2.7).
|
|
72
|
+
*
|
|
73
|
+
* @param {object} record - A CI record that may have missing attributes
|
|
74
|
+
* @returns {object} The record with defaults applied (mutates and returns same object)
|
|
75
|
+
*/
|
|
76
|
+
export function applyRecordDefaults(record) {
|
|
77
|
+
if (record.schemaVersion === undefined || record.schemaVersion === null) {
|
|
78
|
+
record.schemaVersion = 1
|
|
79
|
+
}
|
|
80
|
+
if (!record.testStatus) {
|
|
81
|
+
record.testStatus = 'untested'
|
|
82
|
+
}
|
|
83
|
+
if (!record.lastTestTimestamp) {
|
|
84
|
+
record.lastTestTimestamp = '1970-01-01T00:00:00Z'
|
|
85
|
+
}
|
|
86
|
+
if (!record.buildStrategy) {
|
|
87
|
+
record.buildStrategy = 'codebuild-submit'
|
|
88
|
+
}
|
|
89
|
+
if (!record.stageResults) {
|
|
90
|
+
record.stageResults = {}
|
|
91
|
+
}
|
|
92
|
+
if (!record.errorMessage && record.errorMessage !== '') {
|
|
93
|
+
record.errorMessage = ''
|
|
94
|
+
}
|
|
95
|
+
if (!record.deploymentConfig) {
|
|
96
|
+
record.deploymentConfig = ''
|
|
97
|
+
}
|
|
98
|
+
if (!record.baseImage) {
|
|
99
|
+
record.baseImage = ''
|
|
100
|
+
}
|
|
101
|
+
if (!record.baseImageVersion) {
|
|
102
|
+
record.baseImageVersion = ''
|
|
103
|
+
}
|
|
104
|
+
if (!record.projectName) {
|
|
105
|
+
record.projectName = ''
|
|
106
|
+
}
|
|
107
|
+
return record
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Extract the baseImageVersion from a base image string.
|
|
112
|
+
*
|
|
113
|
+
* Mirrors the bash logic:
|
|
114
|
+
* case "${promoted_base_image}" in *:*) promoted_base_image_version="${promoted_base_image##*:}" ;; esac
|
|
115
|
+
*
|
|
116
|
+
* @param {string} baseImage - e.g. "vllm/vllm-openai:v0.8.5"
|
|
117
|
+
* @returns {string} The version tag, or empty string if no tag present
|
|
118
|
+
*/
|
|
119
|
+
export function extractBaseImageVersion(baseImage) {
|
|
120
|
+
if (!baseImage || !baseImage.includes(':')) {
|
|
121
|
+
return ''
|
|
122
|
+
}
|
|
123
|
+
return baseImage.split(':').pop()
|
|
124
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CI Report Helpers
|
|
6
|
+
*
|
|
7
|
+
* Extracted logic from the `do/ci report` bash template into testable
|
|
8
|
+
* JavaScript functions. These functions mirror the bash implementations
|
|
9
|
+
* for coverage report generation, regression detection, and coverage
|
|
10
|
+
* arithmetic.
|
|
11
|
+
*
|
|
12
|
+
* Used by unit and property-based tests to validate CI report behavior
|
|
13
|
+
* without executing the bash template directly.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The 15 known deployment configurations across 4 architectures.
|
|
18
|
+
*/
|
|
19
|
+
export const KNOWN_DEPLOYMENT_CONFIGS = [
|
|
20
|
+
'transformers-vllm',
|
|
21
|
+
'transformers-sglang',
|
|
22
|
+
'transformers-lmi',
|
|
23
|
+
'transformers-djl',
|
|
24
|
+
'transformers-tensorrt-llm',
|
|
25
|
+
'http-flask',
|
|
26
|
+
'http-fastapi',
|
|
27
|
+
'http-nginx',
|
|
28
|
+
'triton-fil',
|
|
29
|
+
'triton-python',
|
|
30
|
+
'triton-onnx',
|
|
31
|
+
'triton-tensorrt',
|
|
32
|
+
'diffusors-vllm',
|
|
33
|
+
'diffusors-sglang',
|
|
34
|
+
'diffusors-comfyui'
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Group an array of CI records by their deploymentConfig field.
|
|
39
|
+
*
|
|
40
|
+
* @param {object[]} records - Array of CI_Record objects
|
|
41
|
+
* @returns {Map<string, object[]>} Map from deploymentConfig to array of records
|
|
42
|
+
*/
|
|
43
|
+
export function groupByDeploymentConfig(records) {
|
|
44
|
+
const groups = new Map()
|
|
45
|
+
for (const record of records) {
|
|
46
|
+
const key = record.deploymentConfig || ''
|
|
47
|
+
if (!groups.has(key)) {
|
|
48
|
+
groups.set(key, [])
|
|
49
|
+
}
|
|
50
|
+
groups.get(key).push(record)
|
|
51
|
+
}
|
|
52
|
+
return groups
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Detect regressions — deployment configs that transitioned from pass to fail-*.
|
|
57
|
+
*
|
|
58
|
+
* A regression is defined as a record whose testStatus starts with 'fail-'
|
|
59
|
+
* AND whose previousTestStatus was 'pass'. Records that were never 'pass'
|
|
60
|
+
* or that transition from one failure to another are NOT regressions.
|
|
61
|
+
*
|
|
62
|
+
* @param {object[]} records - Array of CI_Record objects, each with testStatus and optionally previousTestStatus
|
|
63
|
+
* @returns {object[]} Array of records that are regressions
|
|
64
|
+
*/
|
|
65
|
+
export function detectRegressions(records) {
|
|
66
|
+
return records.filter(record => {
|
|
67
|
+
const current = record.testStatus || ''
|
|
68
|
+
const previous = record.previousTestStatus || ''
|
|
69
|
+
return current.startsWith('fail-') && previous === 'pass'
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Compute a full coverage report from CI records and the known config list.
|
|
75
|
+
*
|
|
76
|
+
* @param {object[]} records - Array of CI_Record objects from the CI_Table
|
|
77
|
+
* @param {string[]} knownConfigs - Array of known deployment configuration names
|
|
78
|
+
* @returns {object} Coverage report with summary statistics
|
|
79
|
+
* @returns {number} return.total - Total number of known configs
|
|
80
|
+
* @returns {number} return.tested - Number of configs with at least one CI_Record
|
|
81
|
+
* @returns {number} return.passing - Number of configs where latest testStatus is 'pass'
|
|
82
|
+
* @returns {number} return.failing - Number of configs where latest testStatus starts with 'fail-'
|
|
83
|
+
* @returns {number} return.untested - Number of known configs with no CI_Record
|
|
84
|
+
* @returns {number} return.coveragePercent - (tested / total) * 100, rounded to 1 decimal
|
|
85
|
+
* @returns {object[]} return.configurations - Per-config status details
|
|
86
|
+
* @returns {object[]} return.regressions - Records flagged as regressions
|
|
87
|
+
* @returns {string[]} return.untestedConfigs - Known configs with no CI_Record
|
|
88
|
+
*/
|
|
89
|
+
export function computeCoverageReport(records, knownConfigs) {
|
|
90
|
+
const grouped = groupByDeploymentConfig(records)
|
|
91
|
+
|
|
92
|
+
// Determine which known configs have been tested
|
|
93
|
+
const testedConfigSet = new Set()
|
|
94
|
+
const passingSet = new Set()
|
|
95
|
+
const failingSet = new Set()
|
|
96
|
+
|
|
97
|
+
const configurations = []
|
|
98
|
+
|
|
99
|
+
for (const config of knownConfigs) {
|
|
100
|
+
const configRecords = grouped.get(config) || []
|
|
101
|
+
if (configRecords.length === 0) {
|
|
102
|
+
configurations.push({
|
|
103
|
+
deploymentConfig: config,
|
|
104
|
+
status: 'untested',
|
|
105
|
+
recordCount: 0
|
|
106
|
+
})
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
testedConfigSet.add(config)
|
|
111
|
+
|
|
112
|
+
// Use the most recent record (by lastTestTimestamp) to determine status
|
|
113
|
+
const sorted = [...configRecords].sort((a, b) => {
|
|
114
|
+
const tsA = a.lastTestTimestamp || '1970-01-01T00:00:00Z'
|
|
115
|
+
const tsB = b.lastTestTimestamp || '1970-01-01T00:00:00Z'
|
|
116
|
+
return tsB.localeCompare(tsA)
|
|
117
|
+
})
|
|
118
|
+
const latest = sorted[0]
|
|
119
|
+
const status = latest.testStatus || 'untested'
|
|
120
|
+
|
|
121
|
+
if (status === 'pass') {
|
|
122
|
+
passingSet.add(config)
|
|
123
|
+
} else if (status.startsWith('fail-')) {
|
|
124
|
+
failingSet.add(config)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
configurations.push({
|
|
128
|
+
deploymentConfig: config,
|
|
129
|
+
status,
|
|
130
|
+
recordCount: configRecords.length,
|
|
131
|
+
latestRecord: latest
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const total = knownConfigs.length
|
|
136
|
+
const tested = testedConfigSet.size
|
|
137
|
+
const untested = total - tested
|
|
138
|
+
const passing = passingSet.size
|
|
139
|
+
const failing = failingSet.size
|
|
140
|
+
const coveragePercent = total > 0
|
|
141
|
+
? Math.round((tested / total) * 1000) / 10
|
|
142
|
+
: 0
|
|
143
|
+
|
|
144
|
+
const untestedConfigs = knownConfigs.filter(c => !testedConfigSet.has(c))
|
|
145
|
+
const regressions = detectRegressions(records)
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
total,
|
|
149
|
+
tested,
|
|
150
|
+
passing,
|
|
151
|
+
failing,
|
|
152
|
+
untested,
|
|
153
|
+
coveragePercent,
|
|
154
|
+
configurations,
|
|
155
|
+
regressions,
|
|
156
|
+
untestedConfigs
|
|
157
|
+
}
|
|
158
|
+
}
|