@aws/ml-container-creator 0.2.1 → 0.2.2
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/bin/cli.js +88 -86
- package/config/bootstrap-stack.json +211 -0
- package/config/parameter-schema.json +88 -0
- package/infra/ci-harness/bin/ci-harness.ts +26 -0
- package/infra/ci-harness/buildspec.yml +352 -0
- package/infra/ci-harness/cdk.json +27 -0
- package/infra/ci-harness/lambda/scanner/index.ts +199 -0
- package/infra/ci-harness/lib/ci-harness-stack.ts +609 -0
- package/infra/ci-harness/package-lock.json +3979 -0
- package/infra/ci-harness/package.json +32 -0
- package/infra/ci-harness/tsconfig.json +38 -0
- package/package.json +13 -3
- package/src/app.js +318 -318
- package/src/copy-tpl.js +19 -19
- package/src/lib/asset-manager.js +74 -74
- package/src/lib/aws-profile-parser.js +45 -45
- package/src/lib/bootstrap-command-handler.js +560 -547
- package/src/lib/bootstrap-config.js +45 -45
- package/src/lib/ci-register-helpers.js +19 -19
- package/src/lib/ci-report-helpers.js +37 -37
- package/src/lib/ci-stage-helpers.js +49 -49
- package/src/lib/comment-generator.js +4 -4
- package/src/lib/config-manager.js +105 -105
- package/src/lib/deployment-config-resolver.js +10 -10
- package/src/lib/deployment-registry.js +153 -153
- package/src/lib/engine-prefix-resolver.js +8 -8
- package/src/lib/key-value-parser.js +6 -6
- package/src/lib/manifest-cli.js +108 -108
- package/src/lib/prompt-runner.js +224 -224
- package/src/lib/prompts.js +121 -121
- package/src/lib/registry-command-handler.js +174 -174
- package/src/lib/registry-loader.js +52 -52
- package/src/lib/sensitive-redactor.js +9 -9
- package/src/lib/template-engine.js +1 -1
- package/src/lib/template-manager.js +62 -62
- package/src/prompt-adapter.js +18 -18
package/bin/cli.js
CHANGED
|
@@ -2,35 +2,35 @@
|
|
|
2
2
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
import { createRequire } from 'module'
|
|
6
|
-
import path from 'path'
|
|
7
|
-
import { program, Option, Help } from 'commander'
|
|
8
|
-
import { run } from '../src/app.js'
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { program, Option, Help } from 'commander';
|
|
8
|
+
import { run } from '../src/app.js';
|
|
9
9
|
|
|
10
|
-
const require = createRequire(import.meta.url)
|
|
11
|
-
const { version } = require('../package.json')
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version } = require('../package.json');
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Collect repeatable options into an array.
|
|
15
15
|
* Used for --model-env and --server-env which can be specified multiple times.
|
|
16
16
|
*/
|
|
17
17
|
function collect(value, previous) {
|
|
18
|
-
return previous.concat([value])
|
|
18
|
+
return previous.concat([value]);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
program
|
|
22
22
|
.name('ml-container-creator')
|
|
23
23
|
.version(version)
|
|
24
24
|
.enablePositionalOptions()
|
|
25
|
-
.passThroughOptions()
|
|
26
25
|
.helpCommand('help [command]', 'Display help for command')
|
|
27
|
-
.argument('[project-name]', 'Name for the generated project')
|
|
26
|
+
.argument('[project-name...]', 'Name for the generated project')
|
|
28
27
|
|
|
29
28
|
// --- General ---
|
|
30
29
|
.addOption(new Option('--skip-prompts', 'Skip interactive prompts and use configuration from other sources'))
|
|
31
30
|
.addOption(new Option('--config <path>', 'Path to configuration file'))
|
|
32
31
|
.addOption(new Option('--project-name <name>', 'Project name'))
|
|
33
32
|
.addOption(new Option('--project-dir <dir>', 'Output directory path'))
|
|
33
|
+
.addOption(new Option('--force', 'Overwrite existing output directory without prompting'))
|
|
34
34
|
|
|
35
35
|
// --- Model & Framework ---
|
|
36
36
|
.addOption(new Option('--deployment-config <config>', 'Deployment configuration (e.g. http-flask, transformers-vllm, triton-fil)'))
|
|
@@ -104,7 +104,7 @@ program
|
|
|
104
104
|
.addOption(new Option('--validate-with-docker', 'Enable Docker introspection validation (opt-in)'))
|
|
105
105
|
.addOption(new Option('--offline', 'Disable HuggingFace API lookups'))
|
|
106
106
|
|
|
107
|
-
.action(run)
|
|
107
|
+
.action((projectNameArgs, options) => run(projectNameArgs?.[0] || null, options));
|
|
108
108
|
|
|
109
109
|
// Custom help formatting — group options into logical sections (root command only)
|
|
110
110
|
program.configureHelp({
|
|
@@ -112,28 +112,28 @@ program.configureHelp({
|
|
|
112
112
|
// Only apply custom grouping to the root command
|
|
113
113
|
if (cmd !== program) {
|
|
114
114
|
// Fall back to default Commander formatting for subcommands
|
|
115
|
-
return Help.prototype.formatHelp.call(this, cmd, helper)
|
|
115
|
+
return Help.prototype.formatHelp.call(this, cmd, helper);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
const termWidth = helper.padWidth(cmd, helper)
|
|
118
|
+
const termWidth = helper.padWidth(cmd, helper);
|
|
119
119
|
|
|
120
120
|
function callFormatItem(term, description) {
|
|
121
|
-
return helper.formatItem(term, termWidth, description, helper)
|
|
121
|
+
return helper.formatItem(term, termWidth, description, helper);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
function formatSection(title, options) {
|
|
125
|
-
if (options.length === 0) return []
|
|
125
|
+
if (options.length === 0) return [];
|
|
126
126
|
const lines = options.map(opt => {
|
|
127
127
|
return callFormatItem(
|
|
128
128
|
helper.styleOptionTerm(helper.optionTerm(opt)),
|
|
129
129
|
helper.styleOptionDescription(helper.optionDescription(opt))
|
|
130
|
-
)
|
|
131
|
-
})
|
|
132
|
-
return [helper.styleTitle(`${title}:`), ...lines, '']
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
return [helper.styleTitle(`${title}:`), ...lines, ''];
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// Collect all visible options
|
|
136
|
-
const allOptions = helper.visibleOptions(cmd)
|
|
136
|
+
const allOptions = helper.visibleOptions(cmd);
|
|
137
137
|
|
|
138
138
|
// Partition options into groups by flag prefix/purpose
|
|
139
139
|
const groups = {
|
|
@@ -150,38 +150,38 @@ program.configureHelp({
|
|
|
150
150
|
features: [],
|
|
151
151
|
mcp: [],
|
|
152
152
|
validation: []
|
|
153
|
-
}
|
|
153
|
+
};
|
|
154
154
|
|
|
155
155
|
for (const opt of allOptions) {
|
|
156
|
-
const long = opt.long || ''
|
|
157
|
-
if (['--skip-prompts', '--config', '--project-name', '--project-dir', '--version', '--help'].includes(long)) {
|
|
158
|
-
groups.general.push(opt)
|
|
156
|
+
const long = opt.long || '';
|
|
157
|
+
if (['--skip-prompts', '--config', '--project-name', '--project-dir', '--force', '--version', '--help'].includes(long)) {
|
|
158
|
+
groups.general.push(opt);
|
|
159
159
|
} else if (['--deployment-config', '--framework', '--model-format', '--model-name', '--model-server', '--base-image'].includes(long)) {
|
|
160
|
-
groups.model.push(opt)
|
|
160
|
+
groups.model.push(opt);
|
|
161
161
|
} else if (['--deployment-target', '--instance-type', '--region', '--role-arn', '--build-target', '--codebuild-compute-type'].includes(long)) {
|
|
162
|
-
groups.infra.push(opt)
|
|
162
|
+
groups.infra.push(opt);
|
|
163
163
|
} else if (long.startsWith('--endpoint-')) {
|
|
164
|
-
groups.endpoint.push(opt)
|
|
164
|
+
groups.endpoint.push(opt);
|
|
165
165
|
} else if (long.startsWith('--ic-')) {
|
|
166
|
-
groups.ic.push(opt)
|
|
166
|
+
groups.ic.push(opt);
|
|
167
167
|
} else if (long.startsWith('--async-')) {
|
|
168
|
-
groups.async.push(opt)
|
|
168
|
+
groups.async.push(opt);
|
|
169
169
|
} else if (long.startsWith('--batch-')) {
|
|
170
|
-
groups.batch.push(opt)
|
|
170
|
+
groups.batch.push(opt);
|
|
171
171
|
} else if (long.startsWith('--hyperpod-') || long === '--fsx-volume-handle') {
|
|
172
|
-
groups.hyperpod.push(opt)
|
|
172
|
+
groups.hyperpod.push(opt);
|
|
173
173
|
} else if (['--model-env', '--server-env'].includes(long)) {
|
|
174
|
-
groups.env.push(opt)
|
|
174
|
+
groups.env.push(opt);
|
|
175
175
|
} else if (['--hf-token'].includes(long)) {
|
|
176
|
-
groups.auth.push(opt)
|
|
176
|
+
groups.auth.push(opt);
|
|
177
177
|
} else if (['--include-sample', '--include-testing', '--test-types'].includes(long)) {
|
|
178
|
-
groups.features.push(opt)
|
|
178
|
+
groups.features.push(opt);
|
|
179
179
|
} else if (['--smart', '--discover'].includes(long)) {
|
|
180
|
-
groups.mcp.push(opt)
|
|
180
|
+
groups.mcp.push(opt);
|
|
181
181
|
} else if (['--validate-env-vars', '--validate-with-docker', '--offline'].includes(long)) {
|
|
182
|
-
groups.validation.push(opt)
|
|
182
|
+
groups.validation.push(opt);
|
|
183
183
|
} else {
|
|
184
|
-
groups.general.push(opt)
|
|
184
|
+
groups.general.push(opt);
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -189,56 +189,57 @@ program.configureHelp({
|
|
|
189
189
|
let output = [
|
|
190
190
|
`${helper.styleTitle('Usage:')} ${helper.styleUsage(helper.commandUsage(cmd))}`,
|
|
191
191
|
''
|
|
192
|
-
]
|
|
192
|
+
];
|
|
193
193
|
|
|
194
194
|
// Arguments
|
|
195
|
-
const args = helper.visibleArguments(cmd)
|
|
195
|
+
const args = helper.visibleArguments(cmd);
|
|
196
196
|
if (args.length > 0) {
|
|
197
197
|
const argList = args.map(arg => {
|
|
198
198
|
return callFormatItem(
|
|
199
199
|
helper.styleArgumentTerm(helper.argumentTerm(arg)),
|
|
200
200
|
helper.styleArgumentDescription(helper.argumentDescription(arg))
|
|
201
|
-
)
|
|
202
|
-
})
|
|
203
|
-
output = output.concat([helper.styleTitle('Arguments:'), ...argList, ''])
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
output = output.concat([helper.styleTitle('Arguments:'), ...argList, '']);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
// Option sections
|
|
207
|
-
output = output.concat(formatSection('General', groups.general))
|
|
208
|
-
output = output.concat(formatSection('Model & Framework', groups.model))
|
|
209
|
-
output = output.concat(formatSection('Build & Infrastructure', groups.infra))
|
|
210
|
-
output = output.concat(formatSection('Endpoint (Real-Time Inference)', groups.endpoint))
|
|
211
|
-
output = output.concat(formatSection('Inference Component', groups.ic))
|
|
212
|
-
output = output.concat(formatSection('Async Inference', groups.async))
|
|
213
|
-
output = output.concat(formatSection('Batch Transform', groups.batch))
|
|
214
|
-
output = output.concat(formatSection('HyperPod (EKS)', groups.hyperpod))
|
|
215
|
-
output = output.concat(formatSection('Environment Variables', groups.env))
|
|
216
|
-
output = output.concat(formatSection('Authentication', groups.auth))
|
|
217
|
-
output = output.concat(formatSection('Optional Features', groups.features))
|
|
218
|
-
output = output.concat(formatSection('MCP & Discovery', groups.mcp))
|
|
219
|
-
output = output.concat(formatSection('Validation', groups.validation))
|
|
207
|
+
output = output.concat(formatSection('General', groups.general));
|
|
208
|
+
output = output.concat(formatSection('Model & Framework', groups.model));
|
|
209
|
+
output = output.concat(formatSection('Build & Infrastructure', groups.infra));
|
|
210
|
+
output = output.concat(formatSection('Endpoint (Real-Time Inference)', groups.endpoint));
|
|
211
|
+
output = output.concat(formatSection('Inference Component', groups.ic));
|
|
212
|
+
output = output.concat(formatSection('Async Inference', groups.async));
|
|
213
|
+
output = output.concat(formatSection('Batch Transform', groups.batch));
|
|
214
|
+
output = output.concat(formatSection('HyperPod (EKS)', groups.hyperpod));
|
|
215
|
+
output = output.concat(formatSection('Environment Variables', groups.env));
|
|
216
|
+
output = output.concat(formatSection('Authentication', groups.auth));
|
|
217
|
+
output = output.concat(formatSection('Optional Features', groups.features));
|
|
218
|
+
output = output.concat(formatSection('MCP & Discovery', groups.mcp));
|
|
219
|
+
output = output.concat(formatSection('Validation', groups.validation));
|
|
220
220
|
|
|
221
221
|
// Commands
|
|
222
|
-
const cmds = helper.visibleCommands(cmd)
|
|
222
|
+
const cmds = helper.visibleCommands(cmd);
|
|
223
223
|
if (cmds.length > 0) {
|
|
224
224
|
const cmdList = cmds.map(sub => {
|
|
225
225
|
return callFormatItem(
|
|
226
226
|
helper.styleSubcommandTerm(helper.subcommandTerm(sub)),
|
|
227
227
|
helper.styleSubcommandDescription(helper.subcommandDescription(sub))
|
|
228
|
-
)
|
|
229
|
-
})
|
|
230
|
-
output = output.concat([helper.styleTitle('Commands:'), ...cmdList, ''])
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
output = output.concat([helper.styleTitle('Commands:'), ...cmdList, '']);
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
return output.join('\n')
|
|
233
|
+
return output.join('\n');
|
|
234
234
|
}
|
|
235
|
-
})
|
|
235
|
+
});
|
|
236
236
|
|
|
237
237
|
// Sub-commands — wired to actual handlers
|
|
238
238
|
|
|
239
239
|
program
|
|
240
240
|
.command('bootstrap')
|
|
241
241
|
.description('Set up AWS infrastructure (IAM role, ECR repo, S3 buckets)')
|
|
242
|
+
.passThroughOptions()
|
|
242
243
|
.argument('[action]', 'Bootstrap action (status, use, list, remove, scan, prune, update)')
|
|
243
244
|
.argument('[args...]', 'Additional arguments')
|
|
244
245
|
.option('--profile <profile>', 'AWS profile name')
|
|
@@ -249,15 +250,16 @@ program
|
|
|
249
250
|
.option('--verify', 'Verify resources exist (for status)')
|
|
250
251
|
.option('--delete-stack', 'Delete CloudFormation stack on remove')
|
|
251
252
|
.action(async (action, args, options) => {
|
|
252
|
-
const { default: BootstrapCommandHandler } = await import('../src/lib/bootstrap-command-handler.js')
|
|
253
|
-
const handler = new BootstrapCommandHandler()
|
|
254
|
-
const allArgs = action ? [action, ...args] : []
|
|
255
|
-
await handler.handle(allArgs, options)
|
|
256
|
-
})
|
|
253
|
+
const { default: BootstrapCommandHandler } = await import('../src/lib/bootstrap-command-handler.js');
|
|
254
|
+
const handler = new BootstrapCommandHandler();
|
|
255
|
+
const allArgs = action ? [action, ...args] : [];
|
|
256
|
+
await handler.handle(allArgs, options);
|
|
257
|
+
});
|
|
257
258
|
|
|
258
259
|
program
|
|
259
260
|
.command('mcp')
|
|
260
261
|
.description('Manage MCP servers (add, list, get, remove, init)')
|
|
262
|
+
.passThroughOptions()
|
|
261
263
|
.argument('<action>', 'MCP action (add, list, get, remove, init)')
|
|
262
264
|
.argument('[args...]', 'Additional arguments')
|
|
263
265
|
.option('-e <env>', 'Environment variable in KEY=VALUE format (for add)')
|
|
@@ -265,25 +267,26 @@ program
|
|
|
265
267
|
.option('--limit <n>', 'Result limit for MCP server (for add)')
|
|
266
268
|
.option('--bundled', 'Use a bundled server from servers/ directory')
|
|
267
269
|
.action(async (action, args, options) => {
|
|
268
|
-
const { default: McpCommandHandler } = await import('../src/lib/mcp-command-handler.js')
|
|
269
|
-
const { runPrompts } = await import('../src/prompt-adapter.js')
|
|
270
|
+
const { default: McpCommandHandler } = await import('../src/lib/mcp-command-handler.js');
|
|
271
|
+
const { runPrompts } = await import('../src/prompt-adapter.js');
|
|
270
272
|
// McpCommandHandler expects a generator-like object with destinationPath() and prompt()
|
|
271
273
|
const generatorAdapter = {
|
|
272
274
|
destinationPath(...segments) {
|
|
273
|
-
if (segments.length === 0) return process.cwd()
|
|
274
|
-
return path.join(process.cwd(), ...segments)
|
|
275
|
+
if (segments.length === 0) return process.cwd();
|
|
276
|
+
return path.join(process.cwd(), ...segments);
|
|
275
277
|
},
|
|
276
278
|
async prompt(prompts) {
|
|
277
|
-
return runPrompts(prompts)
|
|
279
|
+
return runPrompts(prompts);
|
|
278
280
|
}
|
|
279
|
-
}
|
|
280
|
-
const handler = new McpCommandHandler(generatorAdapter)
|
|
281
|
-
await handler.handle([action, ...args], options)
|
|
282
|
-
})
|
|
281
|
+
};
|
|
282
|
+
const handler = new McpCommandHandler(generatorAdapter);
|
|
283
|
+
await handler.handle([action, ...args], options);
|
|
284
|
+
});
|
|
283
285
|
|
|
284
286
|
program
|
|
285
287
|
.command('registry')
|
|
286
288
|
.description('Registry operations (list, get, remove, replay, export, import, search) — experimental, may be reconciled with do/register')
|
|
289
|
+
.passThroughOptions()
|
|
287
290
|
.argument('<action>', 'Registry action (log, list, get, remove, replay, export, import, search)')
|
|
288
291
|
.argument('[args...]', 'Additional arguments')
|
|
289
292
|
.option('--backend <backend>', 'Filter by backend')
|
|
@@ -306,20 +309,19 @@ program
|
|
|
306
309
|
.option('--parameters <json>', 'Parameters JSON string')
|
|
307
310
|
.option('--generator-version <version>', 'Generator version')
|
|
308
311
|
.action(async (action, args, options) => {
|
|
309
|
-
const { default: RegistryCommandHandler } = await import('../src/lib/registry-command-handler.js')
|
|
310
|
-
const handler = new RegistryCommandHandler()
|
|
311
|
-
await handler.handle([action, ...args], options)
|
|
312
|
-
})
|
|
312
|
+
const { default: RegistryCommandHandler } = await import('../src/lib/registry-command-handler.js');
|
|
313
|
+
const handler = new RegistryCommandHandler();
|
|
314
|
+
await handler.handle([action, ...args], options);
|
|
315
|
+
});
|
|
313
316
|
|
|
314
317
|
program
|
|
315
318
|
.command('configure')
|
|
316
319
|
.description('Interactive configuration setup (experimental)')
|
|
317
320
|
.action(async () => {
|
|
318
|
-
const { runPrompts } = await import('../src/prompt-adapter.js')
|
|
319
|
-
const { default: ConfigurationExporter } = await import('../src/lib/configuration-exporter.js')
|
|
321
|
+
const { runPrompts } = await import('../src/prompt-adapter.js');
|
|
320
322
|
|
|
321
|
-
console.log('\n🔧 ML Container Creator Configuration (experimental)')
|
|
322
|
-
console.log('\nThis will help you set up configuration files for your project.\n')
|
|
323
|
+
console.log('\n🔧 ML Container Creator Configuration (experimental)');
|
|
324
|
+
console.log('\nThis will help you set up configuration files for your project.\n');
|
|
323
325
|
|
|
324
326
|
const answers = await runPrompts([
|
|
325
327
|
{
|
|
@@ -331,7 +333,7 @@ program
|
|
|
331
333
|
{ name: 'Show environment variable examples', value: 'env' }
|
|
332
334
|
]
|
|
333
335
|
}
|
|
334
|
-
])
|
|
336
|
+
]);
|
|
335
337
|
|
|
336
338
|
if (answers.configType === 'cli') {
|
|
337
339
|
console.log(`
|
|
@@ -347,7 +349,7 @@ program
|
|
|
347
349
|
|
|
348
350
|
# Using a config file
|
|
349
351
|
ml-container-creator --config=my-config.json --skip-prompts
|
|
350
|
-
`)
|
|
352
|
+
`);
|
|
351
353
|
} else if (answers.configType === 'env') {
|
|
352
354
|
console.log(`
|
|
353
355
|
🌍 Environment Variables:
|
|
@@ -358,8 +360,8 @@ program
|
|
|
358
360
|
export HF_TOKEN="hf_..."
|
|
359
361
|
|
|
360
362
|
Then run: ml-container-creator --deployment-config=http-flask --skip-prompts
|
|
361
|
-
`)
|
|
363
|
+
`);
|
|
362
364
|
}
|
|
363
|
-
})
|
|
365
|
+
});
|
|
364
366
|
|
|
365
|
-
program.parse()
|
|
367
|
+
program.parse();
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
{
|
|
2
|
+
"AWSTemplateFormatVersion": "2010-09-09",
|
|
3
|
+
"Description": "ML Container Creator — shared bootstrap infrastructure (IAM role, ECR repository, optional S3 buckets). Re-run bootstrap to apply updates from new versions.",
|
|
4
|
+
|
|
5
|
+
"Parameters": {
|
|
6
|
+
"CreateS3Buckets": {
|
|
7
|
+
"Type": "String",
|
|
8
|
+
"Default": "false",
|
|
9
|
+
"AllowedValues": ["true", "false"],
|
|
10
|
+
"Description": "Whether to create S3 buckets for async inference and batch transform"
|
|
11
|
+
},
|
|
12
|
+
"UseExistingRoleArn": {
|
|
13
|
+
"Type": "String",
|
|
14
|
+
"Default": "",
|
|
15
|
+
"Description": "ARN of an existing IAM role to use instead of creating one. Leave empty to create a new role."
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
"Conditions": {
|
|
20
|
+
"ShouldCreateS3Buckets": { "Fn::Equals": [{ "Ref": "CreateS3Buckets" }, "true"] },
|
|
21
|
+
"ShouldCreateRole": { "Fn::Equals": [{ "Ref": "UseExistingRoleArn" }, ""] }
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
"Resources": {
|
|
25
|
+
"SageMakerExecutionRole": {
|
|
26
|
+
"Type": "AWS::IAM::Role",
|
|
27
|
+
"Condition": "ShouldCreateRole",
|
|
28
|
+
"Properties": {
|
|
29
|
+
"RoleName": "mlcc-sagemaker-execution-role",
|
|
30
|
+
"AssumeRolePolicyDocument": {
|
|
31
|
+
"Version": "2012-10-17",
|
|
32
|
+
"Statement": [
|
|
33
|
+
{
|
|
34
|
+
"Effect": "Allow",
|
|
35
|
+
"Principal": { "Service": "sagemaker.amazonaws.com" },
|
|
36
|
+
"Action": "sts:AssumeRole"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"Policies": [
|
|
41
|
+
{
|
|
42
|
+
"PolicyName": "mlcc-execution-policy",
|
|
43
|
+
"PolicyDocument": {
|
|
44
|
+
"Version": "2012-10-17",
|
|
45
|
+
"Statement": [
|
|
46
|
+
{
|
|
47
|
+
"Sid": "SageMakerEndpoints",
|
|
48
|
+
"Effect": "Allow",
|
|
49
|
+
"Action": [
|
|
50
|
+
"sagemaker:CreateEndpoint",
|
|
51
|
+
"sagemaker:CreateEndpointConfig",
|
|
52
|
+
"sagemaker:CreateModel",
|
|
53
|
+
"sagemaker:CreateInferenceComponent",
|
|
54
|
+
"sagemaker:UpdateEndpoint",
|
|
55
|
+
"sagemaker:UpdateEndpointWeightsAndCapacities",
|
|
56
|
+
"sagemaker:UpdateInferenceComponent",
|
|
57
|
+
"sagemaker:DeleteEndpoint",
|
|
58
|
+
"sagemaker:DeleteEndpointConfig",
|
|
59
|
+
"sagemaker:DeleteModel",
|
|
60
|
+
"sagemaker:DeleteInferenceComponent",
|
|
61
|
+
"sagemaker:DescribeEndpoint",
|
|
62
|
+
"sagemaker:DescribeEndpointConfig",
|
|
63
|
+
"sagemaker:DescribeModel",
|
|
64
|
+
"sagemaker:DescribeInferenceComponent",
|
|
65
|
+
"sagemaker:InvokeEndpoint",
|
|
66
|
+
"sagemaker:InvokeEndpointAsync"
|
|
67
|
+
],
|
|
68
|
+
"Resource": "*"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"Sid": "ECRPull",
|
|
72
|
+
"Effect": "Allow",
|
|
73
|
+
"Action": [
|
|
74
|
+
"ecr:GetAuthorizationToken",
|
|
75
|
+
"ecr:BatchCheckLayerAvailability",
|
|
76
|
+
"ecr:GetDownloadUrlForLayer",
|
|
77
|
+
"ecr:BatchGetImage"
|
|
78
|
+
],
|
|
79
|
+
"Resource": { "Fn::Sub": "arn:aws:ecr:*:${AWS::AccountId}:repository/ml-container-creator" }
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"Sid": "ECRAuth",
|
|
83
|
+
"Effect": "Allow",
|
|
84
|
+
"Action": "ecr:GetAuthorizationToken",
|
|
85
|
+
"Resource": "*"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"Sid": "CloudWatchLogs",
|
|
89
|
+
"Effect": "Allow",
|
|
90
|
+
"Action": [
|
|
91
|
+
"logs:CreateLogGroup",
|
|
92
|
+
"logs:CreateLogStream",
|
|
93
|
+
"logs:PutLogEvents"
|
|
94
|
+
],
|
|
95
|
+
"Resource": "arn:aws:logs:*:*:*"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"Sid": "S3ModelRead",
|
|
99
|
+
"Effect": "Allow",
|
|
100
|
+
"Action": [
|
|
101
|
+
"s3:GetObject",
|
|
102
|
+
"s3:ListBucket"
|
|
103
|
+
],
|
|
104
|
+
"Resource": [
|
|
105
|
+
"arn:aws:s3:::ml-container-creator-*",
|
|
106
|
+
"arn:aws:s3:::ml-container-creator-*/*"
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
"Tags": [
|
|
114
|
+
{ "Key": "mlcc:managed-by", "Value": "ml-container-creator" },
|
|
115
|
+
{ "Key": "mlcc:created-by", "Value": "bootstrap" }
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
"EcrRepository": {
|
|
121
|
+
"Type": "AWS::ECR::Repository",
|
|
122
|
+
"Properties": {
|
|
123
|
+
"RepositoryName": "ml-container-creator",
|
|
124
|
+
"ImageScanningConfiguration": { "ScanOnPush": true },
|
|
125
|
+
"EncryptionConfiguration": { "EncryptionType": "AES256" },
|
|
126
|
+
"LifecyclePolicy": {
|
|
127
|
+
"LifecyclePolicyText": "{\"rules\":[{\"rulePriority\":1,\"description\":\"Expire untagged images after 30 days\",\"selection\":{\"tagStatus\":\"untagged\",\"countType\":\"sinceImagePushed\",\"countUnit\":\"days\",\"countNumber\":30},\"action\":{\"type\":\"expire\"}}]}"
|
|
128
|
+
},
|
|
129
|
+
"Tags": [
|
|
130
|
+
{ "Key": "mlcc:managed-by", "Value": "ml-container-creator" },
|
|
131
|
+
{ "Key": "mlcc:created-by", "Value": "bootstrap" }
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
"AsyncS3Bucket": {
|
|
137
|
+
"Type": "AWS::S3::Bucket",
|
|
138
|
+
"Condition": "ShouldCreateS3Buckets",
|
|
139
|
+
"DeletionPolicy": "Retain",
|
|
140
|
+
"UpdateReplacePolicy": "Retain",
|
|
141
|
+
"Properties": {
|
|
142
|
+
"BucketName": { "Fn::Sub": "${AWS::AccountId}-${AWS::Region}-ml-container-creator-async" },
|
|
143
|
+
"VersioningConfiguration": { "Status": "Enabled" },
|
|
144
|
+
"BucketEncryption": {
|
|
145
|
+
"ServerSideEncryptionConfiguration": [
|
|
146
|
+
{ "ServerSideEncryptionByDefault": { "SSEAlgorithm": "AES256" } }
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
"Tags": [
|
|
150
|
+
{ "Key": "mlcc:managed-by", "Value": "ml-container-creator" },
|
|
151
|
+
{ "Key": "mlcc:created-by", "Value": "bootstrap" }
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
"BatchS3Bucket": {
|
|
157
|
+
"Type": "AWS::S3::Bucket",
|
|
158
|
+
"Condition": "ShouldCreateS3Buckets",
|
|
159
|
+
"DeletionPolicy": "Retain",
|
|
160
|
+
"UpdateReplacePolicy": "Retain",
|
|
161
|
+
"Properties": {
|
|
162
|
+
"BucketName": { "Fn::Sub": "${AWS::AccountId}-${AWS::Region}-ml-container-creator-batch" },
|
|
163
|
+
"VersioningConfiguration": { "Status": "Enabled" },
|
|
164
|
+
"BucketEncryption": {
|
|
165
|
+
"ServerSideEncryptionConfiguration": [
|
|
166
|
+
{ "ServerSideEncryptionByDefault": { "SSEAlgorithm": "AES256" } }
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
"Tags": [
|
|
170
|
+
{ "Key": "mlcc:managed-by", "Value": "ml-container-creator" },
|
|
171
|
+
{ "Key": "mlcc:created-by", "Value": "bootstrap" }
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
"Outputs": {
|
|
178
|
+
"RoleArn": {
|
|
179
|
+
"Description": "SageMaker execution role ARN",
|
|
180
|
+
"Value": {
|
|
181
|
+
"Fn::If": [
|
|
182
|
+
"ShouldCreateRole",
|
|
183
|
+
{ "Fn::GetAtt": ["SageMakerExecutionRole", "Arn"] },
|
|
184
|
+
{ "Ref": "UseExistingRoleArn" }
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"EcrRepositoryName": {
|
|
189
|
+
"Description": "ECR repository name",
|
|
190
|
+
"Value": { "Ref": "EcrRepository" }
|
|
191
|
+
},
|
|
192
|
+
"EcrRepositoryUri": {
|
|
193
|
+
"Description": "ECR repository URI",
|
|
194
|
+
"Value": { "Fn::GetAtt": ["EcrRepository", "RepositoryUri"] }
|
|
195
|
+
},
|
|
196
|
+
"AsyncS3BucketName": {
|
|
197
|
+
"Condition": "ShouldCreateS3Buckets",
|
|
198
|
+
"Description": "S3 bucket for async inference output",
|
|
199
|
+
"Value": { "Ref": "AsyncS3Bucket" }
|
|
200
|
+
},
|
|
201
|
+
"BatchS3BucketName": {
|
|
202
|
+
"Condition": "ShouldCreateS3Buckets",
|
|
203
|
+
"Description": "S3 bucket for batch transform I/O",
|
|
204
|
+
"Value": { "Ref": "BatchS3Bucket" }
|
|
205
|
+
},
|
|
206
|
+
"StackVersion": {
|
|
207
|
+
"Description": "Bootstrap stack template version for forward compatibility tracking",
|
|
208
|
+
"Value": "2026-05-04"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"deploymentTargets": {
|
|
4
|
+
"managed-inference": {
|
|
5
|
+
"endpoint": {
|
|
6
|
+
"initialInstanceCount": {
|
|
7
|
+
"type": "integer",
|
|
8
|
+
"min": 1,
|
|
9
|
+
"max": 100,
|
|
10
|
+
"default": 1,
|
|
11
|
+
"description": "Number of instances for the endpoint",
|
|
12
|
+
"apiReference": "CreateEndpointConfig.ProductionVariants.InitialInstanceCount"
|
|
13
|
+
},
|
|
14
|
+
"dataCapturePercent": {
|
|
15
|
+
"type": "integer",
|
|
16
|
+
"min": 0,
|
|
17
|
+
"max": 100,
|
|
18
|
+
"default": 0,
|
|
19
|
+
"description": "Percentage of requests to capture",
|
|
20
|
+
"apiReference": "CreateEndpointConfig.DataCaptureConfig.InitialSamplingPercentage"
|
|
21
|
+
},
|
|
22
|
+
"variantName": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"pattern": "^[a-zA-Z0-9]([\\w-]{0,62}[a-zA-Z0-9])?$",
|
|
25
|
+
"default": "AllTraffic",
|
|
26
|
+
"description": "Name of the production variant",
|
|
27
|
+
"apiReference": "CreateEndpointConfig.ProductionVariants.VariantName"
|
|
28
|
+
},
|
|
29
|
+
"volumeSize": {
|
|
30
|
+
"type": "integer",
|
|
31
|
+
"min": 1,
|
|
32
|
+
"max": 16384,
|
|
33
|
+
"default": null,
|
|
34
|
+
"description": "Size of the ML storage volume in GB",
|
|
35
|
+
"apiReference": "CreateEndpointConfig.ProductionVariants.VolumeSizeInGB"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"inferenceComponent": {
|
|
39
|
+
"cpuCount": {
|
|
40
|
+
"type": "number",
|
|
41
|
+
"min": 0.25,
|
|
42
|
+
"max": 768,
|
|
43
|
+
"default": null,
|
|
44
|
+
"description": "Number of vCPUs allocated",
|
|
45
|
+
"apiReference": "CreateInferenceComponent.Specification.ComputeResourceRequirements.NumberOfCpuCoresRequired"
|
|
46
|
+
},
|
|
47
|
+
"memorySize": {
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"min": 128,
|
|
50
|
+
"max": 3145728,
|
|
51
|
+
"default": null,
|
|
52
|
+
"description": "Memory allocation in MB",
|
|
53
|
+
"apiReference": "CreateInferenceComponent.Specification.ComputeResourceRequirements.MinMemoryRequiredInMb"
|
|
54
|
+
},
|
|
55
|
+
"gpuCount": {
|
|
56
|
+
"type": "integer",
|
|
57
|
+
"min": 0,
|
|
58
|
+
"max": 8,
|
|
59
|
+
"default": null,
|
|
60
|
+
"description": "Number of GPUs allocated",
|
|
61
|
+
"apiReference": "CreateInferenceComponent.Specification.ComputeResourceRequirements.NumberOfAcceleratorDevicesRequired"
|
|
62
|
+
},
|
|
63
|
+
"copyCount": {
|
|
64
|
+
"type": "integer",
|
|
65
|
+
"min": 0,
|
|
66
|
+
"max": 100,
|
|
67
|
+
"default": 1,
|
|
68
|
+
"description": "Number of inference component copies",
|
|
69
|
+
"apiReference": "CreateInferenceComponent.RuntimeConfig.CopyCount"
|
|
70
|
+
},
|
|
71
|
+
"modelWeight": {
|
|
72
|
+
"type": "number",
|
|
73
|
+
"min": 0,
|
|
74
|
+
"max": 1,
|
|
75
|
+
"default": 1.0,
|
|
76
|
+
"description": "Traffic routing weight for the model",
|
|
77
|
+
"apiReference": "UpdateEndpointWeightsAndCapacities.DesiredWeightsAndCapacities.DesiredWeight"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"eks": {},
|
|
82
|
+
"async": {},
|
|
83
|
+
"batch": {}
|
|
84
|
+
},
|
|
85
|
+
"extensionPoints": {
|
|
86
|
+
"engines": {}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'source-map-support/register';
|
|
3
|
+
import * as cdk from 'aws-cdk-lib';
|
|
4
|
+
import { MlccCiHarnessStack } from '../lib/ci-harness-stack';
|
|
5
|
+
|
|
6
|
+
const app = new cdk.App();
|
|
7
|
+
|
|
8
|
+
// Region and account can be configured via:
|
|
9
|
+
// 1. CDK context: -c region=us-east-1 -c account=123456789012
|
|
10
|
+
// 2. Environment variables: CDK_DEFAULT_REGION, CDK_DEFAULT_ACCOUNT
|
|
11
|
+
// 3. AWS CLI profile (automatic via CDK)
|
|
12
|
+
const region = app.node.tryGetContext('region')
|
|
13
|
+
|| process.env.CDK_DEFAULT_REGION
|
|
14
|
+
|| process.env.AWS_REGION;
|
|
15
|
+
|
|
16
|
+
const account = app.node.tryGetContext('account')
|
|
17
|
+
|| process.env.CDK_DEFAULT_ACCOUNT
|
|
18
|
+
|| process.env.AWS_ACCOUNT_ID;
|
|
19
|
+
|
|
20
|
+
new MlccCiHarnessStack(app, 'MlccCiHarnessStack', {
|
|
21
|
+
env: {
|
|
22
|
+
region,
|
|
23
|
+
account,
|
|
24
|
+
},
|
|
25
|
+
description: 'ML Container Creator CI Integration Harness - automated lifecycle testing infrastructure',
|
|
26
|
+
});
|