@aws/ml-container-creator 0.9.1 → 0.10.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/config/parameter-schema-v2.json +2065 -0
- package/package.json +4 -4
- package/servers/lib/catalogs/jumpstart-public.json +101 -16
- package/servers/lib/catalogs/models.json +182 -26
- package/src/app.js +1 -389
- package/src/lib/bootstrap-command-handler.js +75 -1078
- package/src/lib/bootstrap-profile-manager.js +634 -0
- package/src/lib/bootstrap-provisioners.js +421 -0
- package/src/lib/config-loader.js +405 -0
- package/src/lib/config-manager.js +59 -1685
- package/src/lib/config-mcp-client.js +118 -0
- package/src/lib/config-validator.js +634 -0
- package/src/lib/cuda-resolver.js +140 -0
- package/src/lib/e2e-catalog-validator.js +251 -3
- package/src/lib/e2e-ci-recorder.js +103 -0
- package/src/lib/generated/cli-options.js +8 -4
- package/src/lib/generated/parameter-matrix.js +671 -0
- package/src/lib/generated/validation-rules.js +2 -2
- package/src/lib/marketplace-flow.js +276 -0
- package/src/lib/mcp-query-runner.js +768 -0
- package/src/lib/parameter-schema-validator.js +62 -18
- package/src/lib/prompt-runner.js +41 -1504
- package/src/lib/prompts/feature-prompts.js +172 -0
- package/src/lib/prompts/index.js +48 -0
- package/src/lib/prompts/infrastructure-prompts.js +690 -0
- package/src/lib/prompts/model-prompts.js +552 -0
- package/src/lib/prompts/project-prompts.js +70 -0
- package/src/lib/prompts.js +2 -1446
- package/src/lib/registry-command-handler.js +135 -3
- package/src/lib/secrets-prompt-runner.js +251 -0
- package/src/lib/template-variable-resolver.js +398 -0
- package/config/parameter-schema.json +0 -88
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handles AWS resource provisioning for bootstrap (IAM role, ECR, S3 buckets).
|
|
14
|
+
* Delegates back to the BootstrapCommandHandler instance for shared helpers.
|
|
15
|
+
*/
|
|
16
|
+
export default class BootstrapProvisioners {
|
|
17
|
+
constructor(handler) {
|
|
18
|
+
this.handler = handler;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create or reuse the SageMaker execution IAM role.
|
|
23
|
+
* @param {object} options - Parsed CLI options
|
|
24
|
+
* @returns {Promise<string>} Role ARN
|
|
25
|
+
*/
|
|
26
|
+
async _setupIamRole(_options) {
|
|
27
|
+
const roleName = 'mlcc-sagemaker-execution-role';
|
|
28
|
+
|
|
29
|
+
// Define trust policy for SageMaker
|
|
30
|
+
const trustPolicy = {
|
|
31
|
+
Version: '2012-10-17',
|
|
32
|
+
Statement: [
|
|
33
|
+
{
|
|
34
|
+
Effect: 'Allow',
|
|
35
|
+
Principal: {
|
|
36
|
+
Service: 'sagemaker.amazonaws.com'
|
|
37
|
+
},
|
|
38
|
+
Action: 'sts:AssumeRole'
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Define execution policy with least-privilege permissions
|
|
44
|
+
const executionPolicy = {
|
|
45
|
+
Version: '2012-10-17',
|
|
46
|
+
Statement: [
|
|
47
|
+
{
|
|
48
|
+
Sid: 'SageMakerEndpoints',
|
|
49
|
+
Effect: 'Allow',
|
|
50
|
+
Action: [
|
|
51
|
+
'sagemaker:CreateEndpoint',
|
|
52
|
+
'sagemaker:CreateEndpointConfig',
|
|
53
|
+
'sagemaker:CreateModel',
|
|
54
|
+
'sagemaker:CreateInferenceComponent',
|
|
55
|
+
'sagemaker:UpdateEndpoint',
|
|
56
|
+
'sagemaker:UpdateEndpointWeightsAndCapacities',
|
|
57
|
+
'sagemaker:UpdateInferenceComponent',
|
|
58
|
+
'sagemaker:DeleteEndpoint',
|
|
59
|
+
'sagemaker:DeleteEndpointConfig',
|
|
60
|
+
'sagemaker:DeleteModel',
|
|
61
|
+
'sagemaker:DeleteInferenceComponent',
|
|
62
|
+
'sagemaker:DescribeEndpoint',
|
|
63
|
+
'sagemaker:DescribeEndpointConfig',
|
|
64
|
+
'sagemaker:DescribeModel',
|
|
65
|
+
'sagemaker:DescribeInferenceComponent',
|
|
66
|
+
'sagemaker:ListInferenceComponents',
|
|
67
|
+
'sagemaker:InvokeEndpoint',
|
|
68
|
+
'sagemaker:InvokeEndpointAsync'
|
|
69
|
+
],
|
|
70
|
+
Resource: '*'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
Sid: 'SageMakerBenchmarking',
|
|
74
|
+
Effect: 'Allow',
|
|
75
|
+
Action: [
|
|
76
|
+
'sagemaker:CreateAIBenchmarkJob',
|
|
77
|
+
'sagemaker:DescribeAIBenchmarkJob',
|
|
78
|
+
'sagemaker:ListAIBenchmarkJobs',
|
|
79
|
+
'sagemaker:StopAIBenchmarkJob',
|
|
80
|
+
'sagemaker:DeleteAIBenchmarkJob',
|
|
81
|
+
'sagemaker:CreateAIWorkloadConfig',
|
|
82
|
+
'sagemaker:DescribeAIWorkloadConfig',
|
|
83
|
+
'sagemaker:ListAIWorkloadConfigs',
|
|
84
|
+
'sagemaker:DeleteAIWorkloadConfig'
|
|
85
|
+
],
|
|
86
|
+
Resource: '*'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
Sid: 'ECRPull',
|
|
90
|
+
Effect: 'Allow',
|
|
91
|
+
Action: [
|
|
92
|
+
'ecr:GetAuthorizationToken',
|
|
93
|
+
'ecr:BatchCheckLayerAvailability',
|
|
94
|
+
'ecr:GetDownloadUrlForLayer',
|
|
95
|
+
'ecr:BatchGetImage'
|
|
96
|
+
],
|
|
97
|
+
Resource: 'arn:aws:ecr:*:*:repository/ml-container-creator'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
Sid: 'ECRAuth',
|
|
101
|
+
Effect: 'Allow',
|
|
102
|
+
Action: 'ecr:GetAuthorizationToken',
|
|
103
|
+
Resource: '*'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
Sid: 'CloudWatchLogs',
|
|
107
|
+
Effect: 'Allow',
|
|
108
|
+
Action: [
|
|
109
|
+
'logs:CreateLogGroup',
|
|
110
|
+
'logs:CreateLogStream',
|
|
111
|
+
'logs:PutLogEvents'
|
|
112
|
+
],
|
|
113
|
+
Resource: 'arn:aws:logs:*:*:*'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
Sid: 'S3ModelRead',
|
|
117
|
+
Effect: 'Allow',
|
|
118
|
+
Action: [
|
|
119
|
+
's3:GetObject',
|
|
120
|
+
's3:PutObject',
|
|
121
|
+
's3:AbortMultipartUpload',
|
|
122
|
+
's3:ListBucket'
|
|
123
|
+
],
|
|
124
|
+
Resource: [
|
|
125
|
+
'arn:aws:s3:::ml-container-creator-*',
|
|
126
|
+
'arn:aws:s3:::ml-container-creator-*/*'
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
Sid: 'SNSPublish',
|
|
131
|
+
Effect: 'Allow',
|
|
132
|
+
Action: 'sns:Publish',
|
|
133
|
+
Resource: 'arn:aws:sns:*:*:ml-container-creator-*'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
Sid: 'SecretsManagerBenchmark',
|
|
137
|
+
Effect: 'Allow',
|
|
138
|
+
Action: [
|
|
139
|
+
'secretsmanager:CreateSecret',
|
|
140
|
+
'secretsmanager:PutSecretValue',
|
|
141
|
+
'secretsmanager:GetSecretValue',
|
|
142
|
+
'secretsmanager:DescribeSecret'
|
|
143
|
+
],
|
|
144
|
+
Resource: 'arn:aws:secretsmanager:*:*:secret:ml-container-creator/*'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
Sid: 'QuotaAndAvailability',
|
|
148
|
+
Effect: 'Allow',
|
|
149
|
+
Action: [
|
|
150
|
+
'service-quotas:GetServiceQuota',
|
|
151
|
+
'service-quotas:ListServiceQuotas',
|
|
152
|
+
'sagemaker:ListTrainingPlans',
|
|
153
|
+
'sagemaker:DescribeTrainingPlan',
|
|
154
|
+
'sagemaker:ListEndpoints'
|
|
155
|
+
],
|
|
156
|
+
Resource: '*'
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Check if role already exists
|
|
162
|
+
const roleExists = this.handler._resourceExists(
|
|
163
|
+
`iam get-role --role-name ${roleName}`,
|
|
164
|
+
this.handler._currentProfile
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (roleExists) {
|
|
168
|
+
const existingRole = this.handler._execAws(
|
|
169
|
+
`iam get-role --role-name ${roleName}`,
|
|
170
|
+
this.handler._currentProfile
|
|
171
|
+
);
|
|
172
|
+
const roleArn = existingRole.Role.Arn;
|
|
173
|
+
console.log(` ✅ IAM role "${roleName}" already exists — reused`);
|
|
174
|
+
|
|
175
|
+
// Always update the inline policy and tags to ensure they're current
|
|
176
|
+
try {
|
|
177
|
+
const execPolicyFile = this.handler._writeJsonTempFile(executionPolicy, 'exec-policy');
|
|
178
|
+
this.handler._execAws(
|
|
179
|
+
`iam put-role-policy --role-name ${roleName} --policy-name mlcc-execution-policy --policy-document ${execPolicyFile}`,
|
|
180
|
+
this.handler._currentProfile
|
|
181
|
+
);
|
|
182
|
+
console.log(' ✅ IAM policy "mlcc-execution-policy" — updated');
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.log(` ⚠️ Could not update inline policy: ${err.message}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const tags = this._buildResourceTags();
|
|
189
|
+
this.handler._execAws(
|
|
190
|
+
`iam tag-role --role-name ${roleName} --tags ${this.handler._formatTagsForCli(tags)}`,
|
|
191
|
+
this.handler._currentProfile
|
|
192
|
+
);
|
|
193
|
+
console.log(' ✅ IAM role tags — updated');
|
|
194
|
+
} catch (err) {
|
|
195
|
+
console.log(` ⚠️ Could not update role tags: ${err.message}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return roleArn;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Display policies to user before creation
|
|
202
|
+
console.log('\n Trust Policy:');
|
|
203
|
+
console.log(JSON.stringify(trustPolicy, null, 2));
|
|
204
|
+
console.log('\n Execution Policy:');
|
|
205
|
+
console.log(JSON.stringify(executionPolicy, null, 2));
|
|
206
|
+
console.log('');
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
// Create the IAM role — write policy to temp file to avoid shell escaping issues
|
|
210
|
+
const trustPolicyFile = this.handler._writeJsonTempFile(trustPolicy, 'trust-policy');
|
|
211
|
+
const createRoleResult = this.handler._execAws(
|
|
212
|
+
`iam create-role --role-name ${roleName} --assume-role-policy-document ${trustPolicyFile}`,
|
|
213
|
+
this.handler._currentProfile
|
|
214
|
+
);
|
|
215
|
+
const roleArn = createRoleResult.Role.Arn;
|
|
216
|
+
|
|
217
|
+
// Attach inline execution policy
|
|
218
|
+
const execPolicyFile = this.handler._writeJsonTempFile(executionPolicy, 'exec-policy');
|
|
219
|
+
this.handler._execAws(
|
|
220
|
+
`iam put-role-policy --role-name ${roleName} --policy-name mlcc-execution-policy --policy-document ${execPolicyFile}`,
|
|
221
|
+
this.handler._currentProfile
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Apply resource tags
|
|
225
|
+
const tags = this._buildResourceTags();
|
|
226
|
+
this.handler._execAws(
|
|
227
|
+
`iam tag-role --role-name ${roleName} --tags ${this.handler._formatTagsForCli(tags)}`,
|
|
228
|
+
this.handler._currentProfile
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
console.log(` ✅ IAM role "${roleName}" — created`);
|
|
232
|
+
return roleArn;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const errorMessage = error.message || '';
|
|
235
|
+
if (errorMessage.includes('AccessDenied') || errorMessage.includes('UnauthorizedAccess')) {
|
|
236
|
+
console.log(' ⚠️ Permission denied for iam:CreateRole. Please provide an existing role ARN.');
|
|
237
|
+
const { roleArn } = await this.handler._promptFn([{
|
|
238
|
+
type: 'input',
|
|
239
|
+
name: 'roleArn',
|
|
240
|
+
message: 'Enter an existing IAM role ARN for SageMaker execution:'
|
|
241
|
+
}]);
|
|
242
|
+
return roleArn;
|
|
243
|
+
}
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Create or reuse the ECR repository.
|
|
250
|
+
* @returns {Promise<string>} ECR repository name
|
|
251
|
+
*/
|
|
252
|
+
async _setupEcrRepository() {
|
|
253
|
+
const repoName = 'ml-container-creator';
|
|
254
|
+
|
|
255
|
+
// Check if repository already exists
|
|
256
|
+
const repoExists = this.handler._resourceExists(
|
|
257
|
+
`ecr describe-repositories --repository-names ${repoName} --region ${this.handler._currentRegion}`,
|
|
258
|
+
this.handler._currentProfile
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (repoExists) {
|
|
262
|
+
console.log(` ✅ ECR repository "${repoName}" already exists — reused`);
|
|
263
|
+
return repoName;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Build resource tags
|
|
267
|
+
const tags = this._buildResourceTags();
|
|
268
|
+
|
|
269
|
+
// Create the ECR repository with image scanning and AES256 encryption
|
|
270
|
+
this.handler._execAws(
|
|
271
|
+
`ecr create-repository --repository-name ${repoName} --image-scanning-configuration scanOnPush=true --encryption-configuration encryptionType=AES256 --region ${this.handler._currentRegion} --tags ${this.handler._formatTagsForCli(tags)}`,
|
|
272
|
+
this.handler._currentProfile
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// Apply lifecycle policy to expire untagged images after 30 days
|
|
276
|
+
const lifecyclePolicy = {
|
|
277
|
+
rules: [
|
|
278
|
+
{
|
|
279
|
+
rulePriority: 1,
|
|
280
|
+
description: 'Expire untagged images after 30 days',
|
|
281
|
+
selection: {
|
|
282
|
+
tagStatus: 'untagged',
|
|
283
|
+
countType: 'sinceImagePushed',
|
|
284
|
+
countUnit: 'days',
|
|
285
|
+
countNumber: 30
|
|
286
|
+
},
|
|
287
|
+
action: {
|
|
288
|
+
type: 'expire'
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
]
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const lifecyclePolicyFile = this.handler._writeJsonTempFile(lifecyclePolicy, 'ecr-lifecycle');
|
|
295
|
+
this.handler._execAws(
|
|
296
|
+
`ecr put-lifecycle-policy --repository-name ${repoName} --lifecycle-policy-text ${lifecyclePolicyFile} --region ${this.handler._currentRegion}`,
|
|
297
|
+
this.handler._currentProfile
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
console.log(` ✅ ECR repository "${repoName}" — created`);
|
|
301
|
+
return repoName;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Optionally create S3 buckets for async/batch deployments.
|
|
306
|
+
* Always creates the benchmark S3 bucket (unconditional).
|
|
307
|
+
* @returns {Promise<object|null>} Bucket names or null if skipped
|
|
308
|
+
*/
|
|
309
|
+
async _setupS3Buckets() {
|
|
310
|
+
// Always create benchmark bucket (unconditional — avoids re-bootstrap when benchmarking is enabled later)
|
|
311
|
+
const benchmarkBucketName = `ml-container-creator-benchmark-${this.handler._currentRegion}-${this.handler._currentAccountId}`;
|
|
312
|
+
const tags = this._buildResourceTags();
|
|
313
|
+
const benchmarkS3Bucket = await this._createS3Bucket(benchmarkBucketName, tags);
|
|
314
|
+
|
|
315
|
+
const { useS3 } = await this.handler._promptFn([{
|
|
316
|
+
type: 'confirm',
|
|
317
|
+
name: 'useS3',
|
|
318
|
+
message: 'Will you use async inference or batch transform?',
|
|
319
|
+
default: false
|
|
320
|
+
}]);
|
|
321
|
+
|
|
322
|
+
if (!useS3) {
|
|
323
|
+
return { benchmarkS3Bucket };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const asyncBucketName = `ml-container-creator-async-${this.handler._currentRegion}-${this.handler._currentAccountId}`;
|
|
327
|
+
const batchBucketName = `ml-container-creator-batch-${this.handler._currentRegion}-${this.handler._currentAccountId}`;
|
|
328
|
+
|
|
329
|
+
const asyncS3Bucket = await this._createS3Bucket(asyncBucketName, tags);
|
|
330
|
+
const batchS3Bucket = await this._createS3Bucket(batchBucketName, tags);
|
|
331
|
+
|
|
332
|
+
return { asyncS3Bucket, batchS3Bucket, benchmarkS3Bucket };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Create or reuse a single S3 bucket with versioning, encryption, and tags.
|
|
337
|
+
* @param {string} bucketName - S3 bucket name
|
|
338
|
+
* @param {Array<{Key: string, Value: string}>} tags - Resource tags
|
|
339
|
+
* @returns {Promise<string>} Bucket name
|
|
340
|
+
*/
|
|
341
|
+
async _createS3Bucket(bucketName, tags) {
|
|
342
|
+
// Check if bucket already exists
|
|
343
|
+
const bucketExists = this.handler._resourceExists(
|
|
344
|
+
`s3api head-bucket --bucket ${bucketName}`,
|
|
345
|
+
this.handler._currentProfile
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
if (bucketExists) {
|
|
349
|
+
console.log(` ✅ S3 bucket "${bucketName}" already exists — reused`);
|
|
350
|
+
return bucketName;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Build create-bucket command with region-appropriate configuration
|
|
354
|
+
let createCommand = `s3api create-bucket --bucket ${bucketName} --region ${this.handler._currentRegion}`;
|
|
355
|
+
if (this.handler._currentRegion !== 'us-east-1') {
|
|
356
|
+
createCommand += ` --create-bucket-configuration LocationConstraint=${this.handler._currentRegion}`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.handler._execAws(createCommand, this.handler._currentProfile);
|
|
360
|
+
|
|
361
|
+
// Enable versioning
|
|
362
|
+
this.handler._execAws(
|
|
363
|
+
`s3api put-bucket-versioning --bucket ${bucketName} --versioning-configuration Status=Enabled`,
|
|
364
|
+
this.handler._currentProfile
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Enable AES256 server-side encryption
|
|
368
|
+
const encryptionConfig = { Rules: [{ ApplyServerSideEncryptionByDefault: { SSEAlgorithm: 'AES256' } }] };
|
|
369
|
+
const encryptionFile = this.handler._writeJsonTempFile(encryptionConfig, 's3-encryption');
|
|
370
|
+
this.handler._execAws(
|
|
371
|
+
`s3api put-bucket-encryption --bucket ${bucketName} --server-side-encryption-configuration ${encryptionFile}`,
|
|
372
|
+
this.handler._currentProfile
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Apply resource tags
|
|
376
|
+
const tagging = { TagSet: tags };
|
|
377
|
+
const taggingFile = this.handler._writeJsonTempFile(tagging, 's3-tagging');
|
|
378
|
+
this.handler._execAws(
|
|
379
|
+
`s3api put-bucket-tagging --bucket ${bucketName} --tagging ${taggingFile}`,
|
|
380
|
+
this.handler._currentProfile
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
console.log(` ✅ S3 bucket "${bucketName}" — created`);
|
|
384
|
+
return bucketName;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Verify AWS CLI v2 is installed. Returns true if v2 is detected, false otherwise.
|
|
389
|
+
* @returns {boolean}
|
|
390
|
+
*/
|
|
391
|
+
_verifyCliV2() {
|
|
392
|
+
try {
|
|
393
|
+
const versionOutput = execSync('aws --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
394
|
+
if (!versionOutput.includes('aws-cli/2')) {
|
|
395
|
+
console.log(` ❌ AWS CLI v2 is required. Detected: ${versionOutput.split(' ')[0]}`);
|
|
396
|
+
console.log(' Install: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html');
|
|
397
|
+
console.log(' Some features (benchmarking, newer SageMaker APIs) require CLI v2.\n');
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
} catch {
|
|
402
|
+
console.log(' ❌ AWS CLI not found.');
|
|
403
|
+
console.log(' Install: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html\n');
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Build the standard resource tag set.
|
|
410
|
+
* @returns {Array<{Key: string, Value: string}>} Tag array
|
|
411
|
+
*/
|
|
412
|
+
_buildResourceTags() {
|
|
413
|
+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
|
414
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
415
|
+
return [
|
|
416
|
+
{ Key: 'mlcc:managed-by', Value: 'ml-container-creator' },
|
|
417
|
+
{ Key: 'mlcc:created-by', Value: 'bootstrap' },
|
|
418
|
+
{ Key: 'mlcc:version', Value: packageJson.version }
|
|
419
|
+
];
|
|
420
|
+
}
|
|
421
|
+
}
|