@aws/ml-container-creator 0.9.0 → 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/bin/cli.js +31 -137
- package/config/parameter-schema-v2.json +2065 -0
- package/package.json +6 -3
- package/servers/lib/catalogs/jumpstart-public.json +101 -16
- package/servers/lib/catalogs/models.json +182 -26
- package/src/app.js +6 -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 -1668
- 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 +471 -0
- package/src/lib/generated/parameter-matrix.js +671 -0
- package/src/lib/generated/validation-rules.js +202 -0
- 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/templates/code/serve +5 -134
- package/templates/code/serve.d/lmi.ejs +19 -0
- package/templates/code/serve.d/sglang.ejs +47 -0
- package/templates/code/serve.d/tensorrt-llm.ejs +53 -0
- package/templates/code/serve.d/vllm.ejs +48 -0
- package/templates/do/clean +1 -1387
- package/templates/do/clean.d/async-inference.ejs +508 -0
- package/templates/do/clean.d/batch-transform.ejs +512 -0
- package/templates/do/clean.d/hyperpod-eks.ejs +481 -0
- package/templates/do/clean.d/managed-inference.ejs +1043 -0
- package/templates/do/deploy +1 -1766
- package/templates/do/deploy.d/async-inference.ejs +501 -0
- package/templates/do/deploy.d/batch-transform.ejs +529 -0
- package/templates/do/deploy.d/hyperpod-eks.ejs +339 -0
- package/templates/do/deploy.d/managed-inference.ejs +726 -0
- package/config/parameter-schema.json +0 -88
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
* 9. Prompting (fallback)
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import fs from 'fs';
|
|
20
19
|
import path from 'path';
|
|
21
20
|
import { readFileSync } from 'node:fs';
|
|
22
21
|
import { resolve, dirname } from 'node:path';
|
|
23
22
|
import { fileURLToPath } from 'node:url';
|
|
24
|
-
import { McpClient } from './mcp-client.js';
|
|
25
23
|
import DeploymentConfigResolver from './deployment-config-resolver.js';
|
|
26
|
-
import BootstrapConfig from './bootstrap-config.js';
|
|
27
|
-
import { parseKeyValue } from './key-value-parser.js';
|
|
28
24
|
import ParameterSchemaValidator from './parameter-schema-validator.js';
|
|
25
|
+
import ConfigLoader from './config-loader.js';
|
|
26
|
+
import ConfigMcpClient from './config-mcp-client.js';
|
|
27
|
+
import ConfigValidator from './config-validator.js';
|
|
28
|
+
import { parameterMatrix } from './generated/parameter-matrix.js';
|
|
29
29
|
|
|
30
30
|
const __configMgrFilename = fileURLToPath(import.meta.url);
|
|
31
31
|
const __configMgrDir = dirname(__configMgrFilename);
|
|
@@ -85,6 +85,17 @@ export default class ConfigManager {
|
|
|
85
85
|
this.mcpSources = {};
|
|
86
86
|
this.mcpChoices = {};
|
|
87
87
|
this._sourceManifest = [];
|
|
88
|
+
this.GENERATOR_ROOT = GENERATOR_ROOT;
|
|
89
|
+
|
|
90
|
+
// Delegate modules
|
|
91
|
+
this._loader = new ConfigLoader(this);
|
|
92
|
+
this._mcpClient = new ConfigMcpClient(this);
|
|
93
|
+
this._validator = new ConfigValidator(this);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Delegate to config-loader for backward compatibility with tests */
|
|
97
|
+
_applyJsonConfig(config) {
|
|
98
|
+
return this._loader._applyJsonConfig(config);
|
|
88
99
|
}
|
|
89
100
|
|
|
90
101
|
/**
|
|
@@ -191,7 +202,16 @@ export default class ConfigManager {
|
|
|
191
202
|
// For http architecture, engine comes from the --engine CLI option or prompt
|
|
192
203
|
if (parts.architecture === 'http') {
|
|
193
204
|
if (!finalConfig.engine) {
|
|
194
|
-
|
|
205
|
+
// Infer engine from model format if possible
|
|
206
|
+
const formatToEngine = {
|
|
207
|
+
'pkl': 'sklearn',
|
|
208
|
+
'joblib': 'sklearn',
|
|
209
|
+
'json': 'xgboost',
|
|
210
|
+
'keras': 'tensorflow',
|
|
211
|
+
'h5': 'tensorflow',
|
|
212
|
+
'savedmodel': 'tensorflow'
|
|
213
|
+
};
|
|
214
|
+
finalConfig.engine = (finalConfig.modelFormat && formatToEngine[finalConfig.modelFormat]) || 'sklearn';
|
|
195
215
|
}
|
|
196
216
|
} else {
|
|
197
217
|
finalConfig.engine = parts.engine;
|
|
@@ -277,7 +297,7 @@ export default class ConfigManager {
|
|
|
277
297
|
|
|
278
298
|
if (projectNameFromArgument &&
|
|
279
299
|
!explicitDestination &&
|
|
280
|
-
finalConfig.destinationDir === '.') {
|
|
300
|
+
(!finalConfig.destinationDir || finalConfig.destinationDir === '.')) {
|
|
281
301
|
finalConfig.destinationDir = `./${finalConfig.projectName}`;
|
|
282
302
|
}
|
|
283
303
|
|
|
@@ -437,671 +457,11 @@ export default class ConfigManager {
|
|
|
437
457
|
}
|
|
438
458
|
|
|
439
459
|
/**
|
|
440
|
-
* Gets the parameter matrix configuration
|
|
460
|
+
* Gets the parameter matrix configuration (generated from schema)
|
|
441
461
|
* @private
|
|
442
462
|
*/
|
|
443
463
|
_getParameterMatrix() {
|
|
444
|
-
return
|
|
445
|
-
deploymentConfig: {
|
|
446
|
-
cliOption: 'deployment-config',
|
|
447
|
-
envVar: null,
|
|
448
|
-
configFile: true,
|
|
449
|
-
packageJson: false,
|
|
450
|
-
mcp: false,
|
|
451
|
-
promptable: true,
|
|
452
|
-
required: true,
|
|
453
|
-
default: null,
|
|
454
|
-
valueSpace: 'bounded'
|
|
455
|
-
},
|
|
456
|
-
architecture: {
|
|
457
|
-
cliOption: null,
|
|
458
|
-
envVar: null,
|
|
459
|
-
configFile: false,
|
|
460
|
-
packageJson: false,
|
|
461
|
-
mcp: false,
|
|
462
|
-
promptable: false,
|
|
463
|
-
required: false,
|
|
464
|
-
default: null,
|
|
465
|
-
valueSpace: 'bounded'
|
|
466
|
-
},
|
|
467
|
-
backend: {
|
|
468
|
-
cliOption: null,
|
|
469
|
-
envVar: null,
|
|
470
|
-
configFile: false,
|
|
471
|
-
packageJson: false,
|
|
472
|
-
mcp: false,
|
|
473
|
-
promptable: false,
|
|
474
|
-
required: false,
|
|
475
|
-
default: null,
|
|
476
|
-
valueSpace: 'bounded'
|
|
477
|
-
},
|
|
478
|
-
engine: {
|
|
479
|
-
cliOption: 'engine',
|
|
480
|
-
envVar: null,
|
|
481
|
-
configFile: true,
|
|
482
|
-
packageJson: false,
|
|
483
|
-
mcp: false,
|
|
484
|
-
promptable: true,
|
|
485
|
-
required: false,
|
|
486
|
-
default: null,
|
|
487
|
-
valueSpace: 'bounded'
|
|
488
|
-
},
|
|
489
|
-
modelFormat: {
|
|
490
|
-
cliOption: 'model-format',
|
|
491
|
-
envVar: null,
|
|
492
|
-
configFile: true,
|
|
493
|
-
packageJson: false,
|
|
494
|
-
mcp: false,
|
|
495
|
-
promptable: true,
|
|
496
|
-
required: true,
|
|
497
|
-
default: null,
|
|
498
|
-
valueSpace: 'bounded'
|
|
499
|
-
},
|
|
500
|
-
modelName: {
|
|
501
|
-
cliOption: 'model-name',
|
|
502
|
-
envVar: null,
|
|
503
|
-
configFile: true,
|
|
504
|
-
packageJson: false,
|
|
505
|
-
mcp: false,
|
|
506
|
-
promptable: true,
|
|
507
|
-
required: false,
|
|
508
|
-
default: 'openai/gpt-oss-20b',
|
|
509
|
-
valueSpace: 'bounded'
|
|
510
|
-
},
|
|
511
|
-
includeSampleModel: {
|
|
512
|
-
cliOption: 'include-sample',
|
|
513
|
-
envVar: null,
|
|
514
|
-
configFile: true,
|
|
515
|
-
packageJson: false,
|
|
516
|
-
mcp: false,
|
|
517
|
-
promptable: true,
|
|
518
|
-
required: true,
|
|
519
|
-
default: false,
|
|
520
|
-
valueSpace: 'bounded'
|
|
521
|
-
},
|
|
522
|
-
includeTesting: {
|
|
523
|
-
cliOption: 'include-testing',
|
|
524
|
-
envVar: null,
|
|
525
|
-
configFile: true,
|
|
526
|
-
packageJson: false,
|
|
527
|
-
mcp: false,
|
|
528
|
-
promptable: false,
|
|
529
|
-
required: false,
|
|
530
|
-
default: true,
|
|
531
|
-
valueSpace: 'bounded'
|
|
532
|
-
},
|
|
533
|
-
instanceType: {
|
|
534
|
-
cliOption: 'instance-type',
|
|
535
|
-
envVar: 'ML_INSTANCE_TYPE',
|
|
536
|
-
configFile: true,
|
|
537
|
-
packageJson: false,
|
|
538
|
-
mcp: true,
|
|
539
|
-
promptable: true,
|
|
540
|
-
required: true,
|
|
541
|
-
default: null,
|
|
542
|
-
valueSpace: 'unbounded'
|
|
543
|
-
},
|
|
544
|
-
awsRegion: {
|
|
545
|
-
cliOption: 'region',
|
|
546
|
-
envVar: 'AWS_REGION',
|
|
547
|
-
ambientEnvVar: true, // AWS_REGION is commonly set in shells; treat as default, not explicit override
|
|
548
|
-
configFile: true,
|
|
549
|
-
packageJson: true,
|
|
550
|
-
mcp: true,
|
|
551
|
-
promptable: true,
|
|
552
|
-
required: false,
|
|
553
|
-
default: 'us-east-1',
|
|
554
|
-
valueSpace: 'unbounded'
|
|
555
|
-
},
|
|
556
|
-
awsRoleArn: {
|
|
557
|
-
cliOption: 'role-arn',
|
|
558
|
-
envVar: 'AWS_ROLE',
|
|
559
|
-
configFile: true,
|
|
560
|
-
packageJson: true,
|
|
561
|
-
mcp: true,
|
|
562
|
-
promptable: true,
|
|
563
|
-
required: false,
|
|
564
|
-
default: null,
|
|
565
|
-
valueSpace: 'unbounded'
|
|
566
|
-
},
|
|
567
|
-
configFile: {
|
|
568
|
-
cliOption: 'config',
|
|
569
|
-
envVar: 'ML_CONTAINER_CREATOR_CONFIG',
|
|
570
|
-
configFile: false,
|
|
571
|
-
packageJson: true,
|
|
572
|
-
mcp: false,
|
|
573
|
-
promptable: true,
|
|
574
|
-
required: false,
|
|
575
|
-
default: null,
|
|
576
|
-
valueSpace: 'bounded'
|
|
577
|
-
},
|
|
578
|
-
skipPrompts: {
|
|
579
|
-
cliOption: 'skip-prompts',
|
|
580
|
-
envVar: null,
|
|
581
|
-
configFile: false,
|
|
582
|
-
packageJson: false,
|
|
583
|
-
mcp: false,
|
|
584
|
-
promptable: false,
|
|
585
|
-
required: false,
|
|
586
|
-
default: false,
|
|
587
|
-
valueSpace: 'bounded'
|
|
588
|
-
},
|
|
589
|
-
projectName: {
|
|
590
|
-
cliOption: 'project-name',
|
|
591
|
-
envVar: null,
|
|
592
|
-
configFile: true,
|
|
593
|
-
packageJson: true,
|
|
594
|
-
mcp: false,
|
|
595
|
-
promptable: false,
|
|
596
|
-
required: true,
|
|
597
|
-
default: null,
|
|
598
|
-
valueSpace: 'bounded'
|
|
599
|
-
},
|
|
600
|
-
destinationDir: {
|
|
601
|
-
cliOption: 'project-dir',
|
|
602
|
-
envVar: null,
|
|
603
|
-
configFile: true,
|
|
604
|
-
packageJson: true,
|
|
605
|
-
mcp: false,
|
|
606
|
-
promptable: false,
|
|
607
|
-
required: true,
|
|
608
|
-
default: '.',
|
|
609
|
-
valueSpace: 'bounded'
|
|
610
|
-
},
|
|
611
|
-
buildTarget: {
|
|
612
|
-
cliOption: 'build-target',
|
|
613
|
-
envVar: 'ML_BUILD_TARGET',
|
|
614
|
-
configFile: true,
|
|
615
|
-
packageJson: false,
|
|
616
|
-
mcp: false,
|
|
617
|
-
promptable: true,
|
|
618
|
-
required: true,
|
|
619
|
-
default: 'codebuild',
|
|
620
|
-
valueSpace: 'bounded'
|
|
621
|
-
},
|
|
622
|
-
codebuildComputeType: {
|
|
623
|
-
cliOption: 'codebuild-compute-type',
|
|
624
|
-
envVar: 'ML_CODEBUILD_COMPUTE_TYPE',
|
|
625
|
-
configFile: true,
|
|
626
|
-
packageJson: false,
|
|
627
|
-
mcp: false,
|
|
628
|
-
promptable: true,
|
|
629
|
-
required: false,
|
|
630
|
-
default: 'BUILD_GENERAL1_MEDIUM',
|
|
631
|
-
valueSpace: 'bounded'
|
|
632
|
-
},
|
|
633
|
-
codebuildProjectName: {
|
|
634
|
-
cliOption: null,
|
|
635
|
-
envVar: null,
|
|
636
|
-
configFile: true,
|
|
637
|
-
packageJson: false,
|
|
638
|
-
mcp: false,
|
|
639
|
-
promptable: false,
|
|
640
|
-
required: false,
|
|
641
|
-
default: null,
|
|
642
|
-
valueSpace: 'bounded'
|
|
643
|
-
},
|
|
644
|
-
hfToken: {
|
|
645
|
-
cliOption: 'hf-token',
|
|
646
|
-
envVar: null,
|
|
647
|
-
configFile: true,
|
|
648
|
-
packageJson: false,
|
|
649
|
-
mcp: false,
|
|
650
|
-
promptable: true,
|
|
651
|
-
required: false,
|
|
652
|
-
default: null,
|
|
653
|
-
valueSpace: 'bounded'
|
|
654
|
-
},
|
|
655
|
-
hfTokenArn: {
|
|
656
|
-
cliOption: 'hf-token-arn',
|
|
657
|
-
envVar: null,
|
|
658
|
-
configFile: true,
|
|
659
|
-
packageJson: false,
|
|
660
|
-
mcp: false,
|
|
661
|
-
promptable: false,
|
|
662
|
-
required: false,
|
|
663
|
-
default: null,
|
|
664
|
-
valueSpace: 'bounded'
|
|
665
|
-
},
|
|
666
|
-
ngcTokenArn: {
|
|
667
|
-
cliOption: 'ngc-token-arn',
|
|
668
|
-
envVar: null,
|
|
669
|
-
configFile: true,
|
|
670
|
-
packageJson: false,
|
|
671
|
-
mcp: false,
|
|
672
|
-
promptable: false,
|
|
673
|
-
required: false,
|
|
674
|
-
default: null,
|
|
675
|
-
valueSpace: 'bounded'
|
|
676
|
-
},
|
|
677
|
-
deploymentTarget: {
|
|
678
|
-
cliOption: 'deployment-target',
|
|
679
|
-
envVar: 'ML_DEPLOYMENT_TARGET',
|
|
680
|
-
configFile: true,
|
|
681
|
-
packageJson: false,
|
|
682
|
-
mcp: false,
|
|
683
|
-
promptable: true,
|
|
684
|
-
required: true,
|
|
685
|
-
default: 'realtime-inference',
|
|
686
|
-
valueSpace: 'bounded'
|
|
687
|
-
},
|
|
688
|
-
hyperPodCluster: {
|
|
689
|
-
cliOption: 'hyperpod-cluster',
|
|
690
|
-
envVar: null,
|
|
691
|
-
configFile: true,
|
|
692
|
-
packageJson: false,
|
|
693
|
-
mcp: true,
|
|
694
|
-
promptable: true,
|
|
695
|
-
required: false,
|
|
696
|
-
default: null,
|
|
697
|
-
valueSpace: 'unbounded'
|
|
698
|
-
},
|
|
699
|
-
hyperPodNamespace: {
|
|
700
|
-
cliOption: 'hyperpod-namespace',
|
|
701
|
-
envVar: null,
|
|
702
|
-
configFile: true,
|
|
703
|
-
packageJson: false,
|
|
704
|
-
mcp: false,
|
|
705
|
-
promptable: true,
|
|
706
|
-
required: false,
|
|
707
|
-
default: 'default',
|
|
708
|
-
valueSpace: 'bounded'
|
|
709
|
-
},
|
|
710
|
-
hyperPodReplicas: {
|
|
711
|
-
cliOption: 'hyperpod-replicas',
|
|
712
|
-
envVar: null,
|
|
713
|
-
configFile: true,
|
|
714
|
-
packageJson: false,
|
|
715
|
-
mcp: false,
|
|
716
|
-
promptable: true,
|
|
717
|
-
required: false,
|
|
718
|
-
default: 1,
|
|
719
|
-
valueSpace: 'bounded'
|
|
720
|
-
},
|
|
721
|
-
fsxVolumeHandle: {
|
|
722
|
-
cliOption: 'fsx-volume-handle',
|
|
723
|
-
envVar: null,
|
|
724
|
-
configFile: true,
|
|
725
|
-
packageJson: false,
|
|
726
|
-
mcp: false,
|
|
727
|
-
promptable: true,
|
|
728
|
-
required: false,
|
|
729
|
-
default: null,
|
|
730
|
-
valueSpace: 'bounded'
|
|
731
|
-
},
|
|
732
|
-
baseImage: {
|
|
733
|
-
cliOption: 'base-image',
|
|
734
|
-
envVar: null,
|
|
735
|
-
configFile: true,
|
|
736
|
-
packageJson: false,
|
|
737
|
-
mcp: true,
|
|
738
|
-
promptable: true,
|
|
739
|
-
required: false,
|
|
740
|
-
default: null,
|
|
741
|
-
valueSpace: 'unbounded'
|
|
742
|
-
},
|
|
743
|
-
asyncS3OutputPath: {
|
|
744
|
-
cliOption: 'async-s3-output-path',
|
|
745
|
-
envVar: 'ML_ASYNC_S3_OUTPUT_PATH',
|
|
746
|
-
configFile: true,
|
|
747
|
-
packageJson: false,
|
|
748
|
-
mcp: true,
|
|
749
|
-
promptable: true,
|
|
750
|
-
required: false,
|
|
751
|
-
default: null,
|
|
752
|
-
valueSpace: 'unbounded'
|
|
753
|
-
},
|
|
754
|
-
asyncSnsSuccessTopic: {
|
|
755
|
-
cliOption: 'async-sns-success-topic',
|
|
756
|
-
envVar: null,
|
|
757
|
-
configFile: true,
|
|
758
|
-
packageJson: false,
|
|
759
|
-
mcp: true,
|
|
760
|
-
promptable: true,
|
|
761
|
-
required: false,
|
|
762
|
-
default: null,
|
|
763
|
-
valueSpace: 'unbounded'
|
|
764
|
-
},
|
|
765
|
-
asyncSnsErrorTopic: {
|
|
766
|
-
cliOption: 'async-sns-error-topic',
|
|
767
|
-
envVar: null,
|
|
768
|
-
configFile: true,
|
|
769
|
-
packageJson: false,
|
|
770
|
-
mcp: true,
|
|
771
|
-
promptable: true,
|
|
772
|
-
required: false,
|
|
773
|
-
default: null,
|
|
774
|
-
valueSpace: 'unbounded'
|
|
775
|
-
},
|
|
776
|
-
asyncMaxConcurrentInvocations: {
|
|
777
|
-
cliOption: 'async-max-concurrent',
|
|
778
|
-
envVar: null,
|
|
779
|
-
configFile: true,
|
|
780
|
-
packageJson: false,
|
|
781
|
-
mcp: false,
|
|
782
|
-
promptable: true,
|
|
783
|
-
required: false,
|
|
784
|
-
default: 1,
|
|
785
|
-
valueSpace: 'bounded'
|
|
786
|
-
},
|
|
787
|
-
batchInputPath: {
|
|
788
|
-
cliOption: 'batch-input-path',
|
|
789
|
-
envVar: 'ML_BATCH_INPUT_PATH',
|
|
790
|
-
configFile: true,
|
|
791
|
-
packageJson: false,
|
|
792
|
-
mcp: true,
|
|
793
|
-
promptable: true,
|
|
794
|
-
required: false,
|
|
795
|
-
default: null,
|
|
796
|
-
valueSpace: 'unbounded'
|
|
797
|
-
},
|
|
798
|
-
batchOutputPath: {
|
|
799
|
-
cliOption: 'batch-output-path',
|
|
800
|
-
envVar: 'ML_BATCH_OUTPUT_PATH',
|
|
801
|
-
configFile: true,
|
|
802
|
-
packageJson: false,
|
|
803
|
-
mcp: true,
|
|
804
|
-
promptable: true,
|
|
805
|
-
required: false,
|
|
806
|
-
default: null,
|
|
807
|
-
valueSpace: 'unbounded'
|
|
808
|
-
},
|
|
809
|
-
batchInstanceCount: {
|
|
810
|
-
cliOption: 'batch-instance-count',
|
|
811
|
-
envVar: null,
|
|
812
|
-
configFile: true,
|
|
813
|
-
packageJson: false,
|
|
814
|
-
mcp: false,
|
|
815
|
-
promptable: true,
|
|
816
|
-
required: false,
|
|
817
|
-
default: 1,
|
|
818
|
-
valueSpace: 'bounded'
|
|
819
|
-
},
|
|
820
|
-
batchSplitType: {
|
|
821
|
-
cliOption: 'batch-split-type',
|
|
822
|
-
envVar: null,
|
|
823
|
-
configFile: true,
|
|
824
|
-
packageJson: false,
|
|
825
|
-
mcp: false,
|
|
826
|
-
promptable: true,
|
|
827
|
-
required: false,
|
|
828
|
-
default: 'Line',
|
|
829
|
-
valueSpace: 'bounded'
|
|
830
|
-
},
|
|
831
|
-
batchStrategy: {
|
|
832
|
-
cliOption: 'batch-strategy',
|
|
833
|
-
envVar: null,
|
|
834
|
-
configFile: true,
|
|
835
|
-
packageJson: false,
|
|
836
|
-
mcp: false,
|
|
837
|
-
promptable: true,
|
|
838
|
-
required: false,
|
|
839
|
-
default: 'MultiRecord',
|
|
840
|
-
valueSpace: 'bounded'
|
|
841
|
-
},
|
|
842
|
-
batchJoinSource: {
|
|
843
|
-
cliOption: 'batch-join-source',
|
|
844
|
-
envVar: null,
|
|
845
|
-
configFile: true,
|
|
846
|
-
packageJson: false,
|
|
847
|
-
mcp: false,
|
|
848
|
-
promptable: true,
|
|
849
|
-
required: false,
|
|
850
|
-
default: 'None',
|
|
851
|
-
valueSpace: 'bounded'
|
|
852
|
-
},
|
|
853
|
-
batchMaxConcurrentTransforms: {
|
|
854
|
-
cliOption: 'batch-max-concurrent',
|
|
855
|
-
envVar: null,
|
|
856
|
-
configFile: true,
|
|
857
|
-
packageJson: false,
|
|
858
|
-
mcp: false,
|
|
859
|
-
promptable: true,
|
|
860
|
-
required: false,
|
|
861
|
-
default: 1,
|
|
862
|
-
valueSpace: 'bounded'
|
|
863
|
-
},
|
|
864
|
-
batchMaxPayloadInMB: {
|
|
865
|
-
cliOption: 'batch-max-payload',
|
|
866
|
-
envVar: null,
|
|
867
|
-
configFile: true,
|
|
868
|
-
packageJson: false,
|
|
869
|
-
mcp: false,
|
|
870
|
-
promptable: true,
|
|
871
|
-
required: false,
|
|
872
|
-
default: 6,
|
|
873
|
-
valueSpace: 'bounded'
|
|
874
|
-
},
|
|
875
|
-
endpointInitialInstanceCount: {
|
|
876
|
-
cliOption: 'endpoint-initial-instance-count',
|
|
877
|
-
envVar: null,
|
|
878
|
-
configFile: true,
|
|
879
|
-
packageJson: false,
|
|
880
|
-
mcp: false,
|
|
881
|
-
promptable: false,
|
|
882
|
-
required: false,
|
|
883
|
-
default: 1,
|
|
884
|
-
valueSpace: 'bounded',
|
|
885
|
-
schemaValidated: true
|
|
886
|
-
},
|
|
887
|
-
endpointDataCapturePercent: {
|
|
888
|
-
cliOption: 'endpoint-data-capture-percent',
|
|
889
|
-
envVar: null,
|
|
890
|
-
configFile: true,
|
|
891
|
-
packageJson: false,
|
|
892
|
-
mcp: false,
|
|
893
|
-
promptable: false,
|
|
894
|
-
required: false,
|
|
895
|
-
default: 0,
|
|
896
|
-
valueSpace: 'bounded',
|
|
897
|
-
schemaValidated: true
|
|
898
|
-
},
|
|
899
|
-
endpointVariantName: {
|
|
900
|
-
cliOption: 'endpoint-variant-name',
|
|
901
|
-
envVar: null,
|
|
902
|
-
configFile: true,
|
|
903
|
-
packageJson: false,
|
|
904
|
-
mcp: false,
|
|
905
|
-
promptable: false,
|
|
906
|
-
required: false,
|
|
907
|
-
default: 'AllTraffic',
|
|
908
|
-
valueSpace: 'bounded',
|
|
909
|
-
schemaValidated: true
|
|
910
|
-
},
|
|
911
|
-
endpointVolumeSize: {
|
|
912
|
-
cliOption: 'endpoint-volume-size',
|
|
913
|
-
envVar: null,
|
|
914
|
-
configFile: true,
|
|
915
|
-
packageJson: false,
|
|
916
|
-
mcp: false,
|
|
917
|
-
promptable: false,
|
|
918
|
-
required: false,
|
|
919
|
-
default: null,
|
|
920
|
-
valueSpace: 'bounded',
|
|
921
|
-
schemaValidated: true
|
|
922
|
-
},
|
|
923
|
-
icCpuCount: {
|
|
924
|
-
cliOption: 'ic-cpu-count',
|
|
925
|
-
envVar: null,
|
|
926
|
-
configFile: true,
|
|
927
|
-
packageJson: false,
|
|
928
|
-
mcp: false,
|
|
929
|
-
promptable: false,
|
|
930
|
-
required: false,
|
|
931
|
-
default: null,
|
|
932
|
-
valueSpace: 'bounded',
|
|
933
|
-
schemaValidated: true
|
|
934
|
-
},
|
|
935
|
-
icMemorySize: {
|
|
936
|
-
cliOption: 'ic-memory-size',
|
|
937
|
-
envVar: null,
|
|
938
|
-
configFile: true,
|
|
939
|
-
packageJson: false,
|
|
940
|
-
mcp: false,
|
|
941
|
-
promptable: false,
|
|
942
|
-
required: false,
|
|
943
|
-
default: null,
|
|
944
|
-
valueSpace: 'bounded',
|
|
945
|
-
schemaValidated: true
|
|
946
|
-
},
|
|
947
|
-
icGpuCount: {
|
|
948
|
-
cliOption: 'ic-gpu-count',
|
|
949
|
-
envVar: null,
|
|
950
|
-
configFile: true,
|
|
951
|
-
packageJson: false,
|
|
952
|
-
mcp: false,
|
|
953
|
-
promptable: false,
|
|
954
|
-
required: false,
|
|
955
|
-
default: null,
|
|
956
|
-
valueSpace: 'bounded',
|
|
957
|
-
schemaValidated: true
|
|
958
|
-
},
|
|
959
|
-
icCopyCount: {
|
|
960
|
-
cliOption: 'ic-copy-count',
|
|
961
|
-
envVar: null,
|
|
962
|
-
configFile: true,
|
|
963
|
-
packageJson: false,
|
|
964
|
-
mcp: false,
|
|
965
|
-
promptable: false,
|
|
966
|
-
required: false,
|
|
967
|
-
default: 1,
|
|
968
|
-
valueSpace: 'bounded',
|
|
969
|
-
schemaValidated: true
|
|
970
|
-
},
|
|
971
|
-
icModelWeight: {
|
|
972
|
-
cliOption: 'ic-model-weight',
|
|
973
|
-
envVar: null,
|
|
974
|
-
configFile: true,
|
|
975
|
-
packageJson: false,
|
|
976
|
-
mcp: false,
|
|
977
|
-
promptable: false,
|
|
978
|
-
required: false,
|
|
979
|
-
default: 1.0,
|
|
980
|
-
valueSpace: 'bounded',
|
|
981
|
-
schemaValidated: true
|
|
982
|
-
},
|
|
983
|
-
includeBenchmark: {
|
|
984
|
-
cliOption: 'include-benchmark',
|
|
985
|
-
envVar: 'ML_INCLUDE_BENCHMARK',
|
|
986
|
-
configFile: true,
|
|
987
|
-
packageJson: false,
|
|
988
|
-
mcp: false,
|
|
989
|
-
promptable: true,
|
|
990
|
-
required: false,
|
|
991
|
-
default: false,
|
|
992
|
-
valueSpace: 'bounded'
|
|
993
|
-
},
|
|
994
|
-
benchmarkConcurrency: {
|
|
995
|
-
cliOption: 'benchmark-concurrency',
|
|
996
|
-
envVar: null,
|
|
997
|
-
configFile: true,
|
|
998
|
-
packageJson: false,
|
|
999
|
-
mcp: false,
|
|
1000
|
-
promptable: true,
|
|
1001
|
-
required: false,
|
|
1002
|
-
default: 10,
|
|
1003
|
-
valueSpace: 'bounded'
|
|
1004
|
-
},
|
|
1005
|
-
benchmarkInputTokensMean: {
|
|
1006
|
-
cliOption: 'benchmark-input-tokens',
|
|
1007
|
-
envVar: null,
|
|
1008
|
-
configFile: true,
|
|
1009
|
-
packageJson: false,
|
|
1010
|
-
mcp: false,
|
|
1011
|
-
promptable: true,
|
|
1012
|
-
required: false,
|
|
1013
|
-
default: 550,
|
|
1014
|
-
valueSpace: 'bounded'
|
|
1015
|
-
},
|
|
1016
|
-
benchmarkOutputTokensMean: {
|
|
1017
|
-
cliOption: 'benchmark-output-tokens',
|
|
1018
|
-
envVar: null,
|
|
1019
|
-
configFile: true,
|
|
1020
|
-
packageJson: false,
|
|
1021
|
-
mcp: false,
|
|
1022
|
-
promptable: true,
|
|
1023
|
-
required: false,
|
|
1024
|
-
default: 150,
|
|
1025
|
-
valueSpace: 'bounded'
|
|
1026
|
-
},
|
|
1027
|
-
benchmarkStreaming: {
|
|
1028
|
-
cliOption: 'benchmark-streaming',
|
|
1029
|
-
envVar: null,
|
|
1030
|
-
configFile: true,
|
|
1031
|
-
packageJson: false,
|
|
1032
|
-
mcp: false,
|
|
1033
|
-
promptable: true,
|
|
1034
|
-
required: false,
|
|
1035
|
-
default: true,
|
|
1036
|
-
valueSpace: 'bounded'
|
|
1037
|
-
},
|
|
1038
|
-
benchmarkRequestCount: {
|
|
1039
|
-
cliOption: 'benchmark-request-count',
|
|
1040
|
-
envVar: null,
|
|
1041
|
-
configFile: true,
|
|
1042
|
-
packageJson: false,
|
|
1043
|
-
mcp: false,
|
|
1044
|
-
promptable: true,
|
|
1045
|
-
required: false,
|
|
1046
|
-
default: null,
|
|
1047
|
-
valueSpace: 'bounded'
|
|
1048
|
-
},
|
|
1049
|
-
benchmarkS3OutputPath: {
|
|
1050
|
-
cliOption: 'benchmark-s3-output-path',
|
|
1051
|
-
envVar: 'ML_BENCHMARK_S3_OUTPUT_PATH',
|
|
1052
|
-
configFile: true,
|
|
1053
|
-
packageJson: false,
|
|
1054
|
-
mcp: false,
|
|
1055
|
-
promptable: true,
|
|
1056
|
-
required: false,
|
|
1057
|
-
default: null,
|
|
1058
|
-
valueSpace: 'bounded'
|
|
1059
|
-
},
|
|
1060
|
-
enableLora: {
|
|
1061
|
-
cliOption: 'enable-lora',
|
|
1062
|
-
envVar: null,
|
|
1063
|
-
configFile: true,
|
|
1064
|
-
packageJson: false,
|
|
1065
|
-
mcp: false,
|
|
1066
|
-
promptable: true,
|
|
1067
|
-
required: false,
|
|
1068
|
-
default: false,
|
|
1069
|
-
valueSpace: 'bounded'
|
|
1070
|
-
},
|
|
1071
|
-
maxLoras: {
|
|
1072
|
-
cliOption: 'max-loras',
|
|
1073
|
-
envVar: null,
|
|
1074
|
-
configFile: true,
|
|
1075
|
-
packageJson: false,
|
|
1076
|
-
mcp: false,
|
|
1077
|
-
promptable: true,
|
|
1078
|
-
required: false,
|
|
1079
|
-
default: 30,
|
|
1080
|
-
valueSpace: 'bounded'
|
|
1081
|
-
},
|
|
1082
|
-
maxLoraRank: {
|
|
1083
|
-
cliOption: 'max-lora-rank',
|
|
1084
|
-
envVar: null,
|
|
1085
|
-
configFile: true,
|
|
1086
|
-
packageJson: false,
|
|
1087
|
-
mcp: false,
|
|
1088
|
-
promptable: true,
|
|
1089
|
-
required: false,
|
|
1090
|
-
default: 64,
|
|
1091
|
-
valueSpace: 'bounded'
|
|
1092
|
-
},
|
|
1093
|
-
modelPackageArn: {
|
|
1094
|
-
cliOption: 'model-package-arn',
|
|
1095
|
-
envVar: null,
|
|
1096
|
-
configFile: true,
|
|
1097
|
-
packageJson: false,
|
|
1098
|
-
mcp: true,
|
|
1099
|
-
promptable: true,
|
|
1100
|
-
required: false,
|
|
1101
|
-
default: null,
|
|
1102
|
-
valueSpace: 'unbounded'
|
|
1103
|
-
}
|
|
1104
|
-
};
|
|
464
|
+
return parameterMatrix;
|
|
1105
465
|
}
|
|
1106
466
|
|
|
1107
467
|
/**
|
|
@@ -1198,30 +558,7 @@ export default class ConfigManager {
|
|
|
1198
558
|
* @private
|
|
1199
559
|
*/
|
|
1200
560
|
async _loadBootstrapConfig() {
|
|
1201
|
-
|
|
1202
|
-
const bootstrapConfig = new BootstrapConfig();
|
|
1203
|
-
const activeProfile = bootstrapConfig.getActiveProfile();
|
|
1204
|
-
if (!activeProfile) {
|
|
1205
|
-
return;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
const profileConfig = activeProfile.config;
|
|
1209
|
-
const mapped = {};
|
|
1210
|
-
|
|
1211
|
-
if (profileConfig.roleArn) {
|
|
1212
|
-
mapped.awsRoleArn = profileConfig.roleArn;
|
|
1213
|
-
}
|
|
1214
|
-
if (profileConfig.awsRegion) {
|
|
1215
|
-
mapped.awsRegion = profileConfig.awsRegion;
|
|
1216
|
-
}
|
|
1217
|
-
if (profileConfig.awsProfile) {
|
|
1218
|
-
mapped.awsProfile = profileConfig.awsProfile;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
this._mergeConfig(mapped);
|
|
1222
|
-
} catch (error) {
|
|
1223
|
-
// Ignore errors — config file may not exist or may be malformed
|
|
1224
|
-
}
|
|
561
|
+
return this._loader._loadBootstrapConfig();
|
|
1225
562
|
}
|
|
1226
563
|
|
|
1227
564
|
/**
|
|
@@ -1229,25 +566,7 @@ export default class ConfigManager {
|
|
|
1229
566
|
* @private
|
|
1230
567
|
*/
|
|
1231
568
|
async _loadPackageJsonConfig() {
|
|
1232
|
-
|
|
1233
|
-
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
1234
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
1235
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
1236
|
-
const generatorConfig = packageJson['ml-container-creator'];
|
|
1237
|
-
if (generatorConfig) {
|
|
1238
|
-
// Filter config to only include parameters supported in package.json
|
|
1239
|
-
const filteredConfig = {};
|
|
1240
|
-
Object.entries(generatorConfig).forEach(([key, value]) => {
|
|
1241
|
-
if (this._isSourceSupported(key, 'packageJson')) {
|
|
1242
|
-
filteredConfig[key] = this._parseValue(key, value);
|
|
1243
|
-
}
|
|
1244
|
-
});
|
|
1245
|
-
this._mergeConfig(filteredConfig);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
} catch (error) {
|
|
1249
|
-
// Ignore errors - this is optional
|
|
1250
|
-
}
|
|
569
|
+
return this._loader._loadPackageJsonConfig();
|
|
1251
570
|
}
|
|
1252
571
|
|
|
1253
572
|
/**
|
|
@@ -1255,215 +574,15 @@ export default class ConfigManager {
|
|
|
1255
574
|
* @private
|
|
1256
575
|
*/
|
|
1257
576
|
async _loadCustomConfigFile() {
|
|
1258
|
-
|
|
1259
|
-
const configPath = path.join(GENERATOR_ROOT, 'config', 'mcp.json');
|
|
1260
|
-
if (fs.existsSync(configPath)) {
|
|
1261
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1262
|
-
this._mergeConfig(config);
|
|
1263
|
-
}
|
|
1264
|
-
} catch (error) {
|
|
1265
|
-
// Ignore errors - this is optional
|
|
1266
|
-
}
|
|
577
|
+
return this._loader._loadCustomConfigFile();
|
|
1267
578
|
}
|
|
1268
579
|
|
|
1269
580
|
/**
|
|
1270
581
|
* Load from CLI --config file or --config-json inline string.
|
|
1271
|
-
*
|
|
1272
|
-
* --config-json accepts either:
|
|
1273
|
-
* 1. An inline JSON string: --config-json='{"deploymentConfig":"transformers-vllm"}'
|
|
1274
|
-
* 2. A path to a JSON file: --config-json=config.json
|
|
1275
|
-
*
|
|
1276
|
-
* When both --config and --config-json are provided, --config-json wins
|
|
1277
|
-
* (it is applied second, so its values override --config values).
|
|
1278
|
-
*
|
|
1279
|
-
* Also checks the ML_CONTAINER_CREATOR_CONFIG environment variable as a
|
|
1280
|
-
* fallback for --config.
|
|
1281
|
-
*
|
|
1282
582
|
* @private
|
|
1283
583
|
*/
|
|
1284
584
|
async _loadCliConfigFile() {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
// Check environment variable if CLI option not provided
|
|
1288
|
-
if (!configFile && process.env.ML_CONTAINER_CREATOR_CONFIG) {
|
|
1289
|
-
configFile = process.env.ML_CONTAINER_CREATOR_CONFIG;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
if (configFile) {
|
|
1293
|
-
this._loadConfigFromFile(configFile);
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
// --config-json: inline JSON string or path to a JSON file
|
|
1297
|
-
const configJson = this.options['config-json'];
|
|
1298
|
-
if (configJson) {
|
|
1299
|
-
this._loadConfigFromJson(configJson);
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
/**
|
|
1304
|
-
* Load configuration from a JSON file path.
|
|
1305
|
-
* @param {string} configFile - Path to the JSON config file
|
|
1306
|
-
* @private
|
|
1307
|
-
*/
|
|
1308
|
-
_loadConfigFromFile(configFile) {
|
|
1309
|
-
try {
|
|
1310
|
-
const configPath = path.resolve(configFile);
|
|
1311
|
-
if (!fs.existsSync(configPath)) {
|
|
1312
|
-
throw new ConfigurationError(
|
|
1313
|
-
`Config file not found: ${configPath}`,
|
|
1314
|
-
'configFile',
|
|
1315
|
-
'cli'
|
|
1316
|
-
);
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
// Check if file is readable
|
|
1320
|
-
try {
|
|
1321
|
-
fs.accessSync(configPath, fs.constants.R_OK);
|
|
1322
|
-
} catch (accessError) {
|
|
1323
|
-
throw new ConfigurationError(
|
|
1324
|
-
`Config file is not readable: ${configPath}`,
|
|
1325
|
-
'configFile',
|
|
1326
|
-
'cli'
|
|
1327
|
-
);
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1331
|
-
this._applyJsonConfig(config);
|
|
1332
|
-
} catch (error) {
|
|
1333
|
-
if (error instanceof ConfigurationError) {
|
|
1334
|
-
throw error;
|
|
1335
|
-
} else {
|
|
1336
|
-
throw new ConfigurationError(
|
|
1337
|
-
`Failed to load config file ${configFile}: ${error.message}`,
|
|
1338
|
-
'configFile',
|
|
1339
|
-
'cli'
|
|
1340
|
-
);
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
/**
|
|
1346
|
-
* Load configuration from an inline JSON string or a JSON file path.
|
|
1347
|
-
* Tries to parse as JSON first; if that fails and the value looks like
|
|
1348
|
-
* a file path, reads and parses the file instead.
|
|
1349
|
-
*
|
|
1350
|
-
* @param {string} configJson - Inline JSON string or path to a JSON file
|
|
1351
|
-
* @private
|
|
1352
|
-
*/
|
|
1353
|
-
_loadConfigFromJson(configJson) {
|
|
1354
|
-
let config;
|
|
1355
|
-
try {
|
|
1356
|
-
config = JSON.parse(configJson);
|
|
1357
|
-
} catch {
|
|
1358
|
-
// Not valid JSON — try as a file path
|
|
1359
|
-
try {
|
|
1360
|
-
const configPath = path.resolve(configJson);
|
|
1361
|
-
if (!fs.existsSync(configPath)) {
|
|
1362
|
-
throw new ConfigurationError(
|
|
1363
|
-
`--config-json value is not valid JSON and file not found: ${configJson}`,
|
|
1364
|
-
'configJson',
|
|
1365
|
-
'cli'
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1369
|
-
} catch (error) {
|
|
1370
|
-
if (error instanceof ConfigurationError) {
|
|
1371
|
-
throw error;
|
|
1372
|
-
}
|
|
1373
|
-
throw new ConfigurationError(
|
|
1374
|
-
`Failed to parse --config-json: ${error.message}`,
|
|
1375
|
-
'configJson',
|
|
1376
|
-
'cli'
|
|
1377
|
-
);
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
this._applyJsonConfig(config);
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
/**
|
|
1384
|
-
* Apply a parsed JSON config object, filtering to supported parameters.
|
|
1385
|
-
* Handles nested objects for endpoint, iC, and env var configuration.
|
|
1386
|
-
* @param {Object} config - Parsed JSON config object
|
|
1387
|
-
* @private
|
|
1388
|
-
*/
|
|
1389
|
-
_applyJsonConfig(config) {
|
|
1390
|
-
const filteredConfig = {};
|
|
1391
|
-
Object.entries(config).forEach(([key, value]) => {
|
|
1392
|
-
// Handle nested endpointConfig object
|
|
1393
|
-
if (key === 'endpointConfig' && typeof value === 'object' && value !== null) {
|
|
1394
|
-
const endpointMapping = {
|
|
1395
|
-
initialInstanceCount: 'endpointInitialInstanceCount',
|
|
1396
|
-
dataCapturePercent: 'endpointDataCapturePercent',
|
|
1397
|
-
variantName: 'endpointVariantName',
|
|
1398
|
-
volumeSize: 'endpointVolumeSize'
|
|
1399
|
-
};
|
|
1400
|
-
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
|
1401
|
-
const flatKey = endpointMapping[nestedKey];
|
|
1402
|
-
if (flatKey && this._isSourceSupported(flatKey, 'configFile')) {
|
|
1403
|
-
filteredConfig[flatKey] = nestedValue;
|
|
1404
|
-
this._recordSource(flatKey, nestedValue, 'config-file');
|
|
1405
|
-
}
|
|
1406
|
-
});
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
// Handle nested icConfig object
|
|
1411
|
-
if (key === 'icConfig' && typeof value === 'object' && value !== null) {
|
|
1412
|
-
const icMapping = {
|
|
1413
|
-
cpuCount: 'icCpuCount',
|
|
1414
|
-
memorySize: 'icMemorySize',
|
|
1415
|
-
gpuCount: 'icGpuCount',
|
|
1416
|
-
copyCount: 'icCopyCount',
|
|
1417
|
-
modelWeight: 'icModelWeight'
|
|
1418
|
-
};
|
|
1419
|
-
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
|
1420
|
-
const flatKey = icMapping[nestedKey];
|
|
1421
|
-
if (flatKey && this._isSourceSupported(flatKey, 'configFile')) {
|
|
1422
|
-
filteredConfig[flatKey] = nestedValue;
|
|
1423
|
-
this._recordSource(flatKey, nestedValue, 'config-file');
|
|
1424
|
-
}
|
|
1425
|
-
});
|
|
1426
|
-
return;
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
// Handle modelEnvVars object (merge with CLI, CLI takes precedence)
|
|
1430
|
-
if (key === 'modelEnvVars' && typeof value === 'object' && value !== null) {
|
|
1431
|
-
if (!this.config.modelEnvVars) {
|
|
1432
|
-
this.config.modelEnvVars = {};
|
|
1433
|
-
}
|
|
1434
|
-
// Only set keys not already provided by CLI (CLI has higher precedence)
|
|
1435
|
-
const cliModelEnvVars = (this.explicitConfig && this.explicitConfig.modelEnvVars) || {};
|
|
1436
|
-
Object.entries(value).forEach(([envKey, envValue]) => {
|
|
1437
|
-
if (!(envKey in cliModelEnvVars)) {
|
|
1438
|
-
this.config.modelEnvVars[envKey] = envValue;
|
|
1439
|
-
this._recordSource(`modelEnvVars.${envKey}`, envValue, 'config-file');
|
|
1440
|
-
}
|
|
1441
|
-
});
|
|
1442
|
-
return;
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
// Handle serverEnvVars object (merge with CLI, CLI takes precedence)
|
|
1446
|
-
if (key === 'serverEnvVars' && typeof value === 'object' && value !== null) {
|
|
1447
|
-
if (!this.config.serverEnvVars) {
|
|
1448
|
-
this.config.serverEnvVars = {};
|
|
1449
|
-
}
|
|
1450
|
-
// Only set keys not already provided by CLI (CLI has higher precedence)
|
|
1451
|
-
const cliServerEnvVars = (this.explicitConfig && this.explicitConfig.serverEnvVars) || {};
|
|
1452
|
-
Object.entries(value).forEach(([envKey, envValue]) => {
|
|
1453
|
-
if (!(envKey in cliServerEnvVars)) {
|
|
1454
|
-
this.config.serverEnvVars[envKey] = envValue;
|
|
1455
|
-
this._recordSource(`serverEnvVars.${envKey}`, envValue, 'config-file');
|
|
1456
|
-
}
|
|
1457
|
-
});
|
|
1458
|
-
return;
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
if (this._isSourceSupported(key, 'configFile')) {
|
|
1462
|
-
filteredConfig[key] = this._parseValue(key, value);
|
|
1463
|
-
this._recordSource(key, this._parseValue(key, value), 'config-file');
|
|
1464
|
-
}
|
|
1465
|
-
});
|
|
1466
|
-
this._mergeConfig(filteredConfig);
|
|
585
|
+
return this._loader._loadCliConfigFile();
|
|
1467
586
|
}
|
|
1468
587
|
|
|
1469
588
|
/**
|
|
@@ -1471,29 +590,7 @@ export default class ConfigManager {
|
|
|
1471
590
|
* @private
|
|
1472
591
|
*/
|
|
1473
592
|
async _loadEnvironmentVariables() {
|
|
1474
|
-
|
|
1475
|
-
const envMapping = {};
|
|
1476
|
-
Object.entries(this.parameterMatrix).forEach(([param, config]) => {
|
|
1477
|
-
if (config.envVar) {
|
|
1478
|
-
envMapping[config.envVar] = { param, ambient: config.ambientEnvVar === true };
|
|
1479
|
-
}
|
|
1480
|
-
});
|
|
1481
|
-
|
|
1482
|
-
Object.entries(envMapping).forEach(([envVar, { param: configKey, ambient }]) => {
|
|
1483
|
-
const value = process.env[envVar];
|
|
1484
|
-
if (value !== undefined && value !== '' && this._isSourceSupported(configKey, 'envVar')) {
|
|
1485
|
-
this.config[configKey] = this._parseValue(configKey, value);
|
|
1486
|
-
this._recordSource(configKey, this._parseValue(configKey, value), 'env-var');
|
|
1487
|
-
// Track as explicit configuration — unless the env var is ambient
|
|
1488
|
-
// (e.g. AWS_REGION is commonly set in shells as a default, not an override)
|
|
1489
|
-
if (!ambient) {
|
|
1490
|
-
if (!this.explicitConfig) {
|
|
1491
|
-
this.explicitConfig = {};
|
|
1492
|
-
}
|
|
1493
|
-
this.explicitConfig[configKey] = this._parseValue(configKey, value);
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
});
|
|
593
|
+
return this._loader._loadEnvironmentVariables();
|
|
1497
594
|
}
|
|
1498
595
|
|
|
1499
596
|
/**
|
|
@@ -1501,17 +598,7 @@ export default class ConfigManager {
|
|
|
1501
598
|
* @private
|
|
1502
599
|
*/
|
|
1503
600
|
async _loadCliArguments() {
|
|
1504
|
-
|
|
1505
|
-
if (this.args && this.args.length > 0) {
|
|
1506
|
-
this.config.projectName = this.args[0];
|
|
1507
|
-
// Track as explicit configuration
|
|
1508
|
-
if (!this.explicitConfig) {
|
|
1509
|
-
this.explicitConfig = {};
|
|
1510
|
-
}
|
|
1511
|
-
this.explicitConfig.projectName = this.args[0];
|
|
1512
|
-
// Track that project name came from positional argument (for subdirectory creation)
|
|
1513
|
-
this.projectNameFromArgument = true;
|
|
1514
|
-
}
|
|
601
|
+
return this._loader._loadCliArguments();
|
|
1515
602
|
}
|
|
1516
603
|
|
|
1517
604
|
/**
|
|
@@ -1519,95 +606,25 @@ export default class ConfigManager {
|
|
|
1519
606
|
* @private
|
|
1520
607
|
*/
|
|
1521
608
|
async _loadCliOptions() {
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
// Build CLI option mapping from parameter matrix
|
|
1525
|
-
Object.entries(this.parameterMatrix).forEach(([param, config]) => {
|
|
1526
|
-
if (config.cliOption && options[config.cliOption] !== undefined) {
|
|
1527
|
-
this.config[param] = this._parseValue(param, options[config.cliOption]);
|
|
1528
|
-
this._recordSource(param, this._parseValue(param, options[config.cliOption]), 'cli');
|
|
1529
|
-
// Track as explicit configuration
|
|
1530
|
-
if (!this.explicitConfig) {
|
|
1531
|
-
this.explicitConfig = {};
|
|
1532
|
-
}
|
|
1533
|
-
this.explicitConfig[param] = this._parseValue(param, options[config.cliOption]);
|
|
1534
|
-
}
|
|
1535
|
-
});
|
|
1536
|
-
|
|
1537
|
-
// Parse --model-env KEY=VALUE pairs
|
|
1538
|
-
this._parseEnvVarOptions('model-env', 'modelEnvVars');
|
|
1539
|
-
|
|
1540
|
-
// Parse --server-env KEY=VALUE pairs
|
|
1541
|
-
this._parseEnvVarOptions('server-env', 'serverEnvVars');
|
|
609
|
+
return this._loader._loadCliOptions();
|
|
1542
610
|
}
|
|
1543
611
|
|
|
1544
612
|
/**
|
|
1545
613
|
* Normalizes deprecated parameter values to their canonical equivalents.
|
|
1546
|
-
* Prints a deprecation warning when a deprecated value is encountered.
|
|
1547
614
|
* @private
|
|
1548
615
|
*/
|
|
1549
616
|
_normalizeDeprecatedValues() {
|
|
1550
|
-
|
|
1551
|
-
deploymentTarget: {
|
|
1552
|
-
'managed-inference': {
|
|
1553
|
-
canonical: 'realtime-inference',
|
|
1554
|
-
message: '--deployment-target=managed-inference is deprecated, use realtime-inference instead'
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
};
|
|
1558
|
-
|
|
1559
|
-
for (const [param, aliases] of Object.entries(DEPRECATED_VALUES)) {
|
|
1560
|
-
const currentValue = this.config[param];
|
|
1561
|
-
if (currentValue && aliases[currentValue]) {
|
|
1562
|
-
const { canonical, message } = aliases[currentValue];
|
|
1563
|
-
console.log(`\n⚠️ Deprecation: ${message}`);
|
|
1564
|
-
this.config[param] = canonical;
|
|
1565
|
-
// Also update explicit config if it was set there
|
|
1566
|
-
if (this.explicitConfig && this.explicitConfig[param] === currentValue) {
|
|
1567
|
-
this.explicitConfig[param] = canonical;
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
617
|
+
return this._loader._normalizeDeprecatedValues();
|
|
1571
618
|
}
|
|
1572
619
|
|
|
1573
620
|
/**
|
|
1574
621
|
* Parse --model-env or --server-env CLI options into env var collections.
|
|
1575
|
-
*
|
|
1576
|
-
*
|
|
1577
|
-
* @param {string} optionName - CLI option name (e.g., 'model-env')
|
|
1578
|
-
* @param {string} configKey - Config key to store results (e.g., 'modelEnvVars')
|
|
622
|
+
* @param {string} optionName - CLI option name
|
|
623
|
+
* @param {string} configKey - Config key to store results
|
|
1579
624
|
* @private
|
|
1580
625
|
*/
|
|
1581
626
|
_parseEnvVarOptions(optionName, configKey) {
|
|
1582
|
-
|
|
1583
|
-
if (rawValue === undefined || rawValue === null) {
|
|
1584
|
-
return;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
// Normalize to array (may receive a single string or an array)
|
|
1588
|
-
const values = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
1589
|
-
|
|
1590
|
-
// Initialize collection if not already present
|
|
1591
|
-
if (!this.config[configKey] || typeof this.config[configKey] !== 'object') {
|
|
1592
|
-
this.config[configKey] = {};
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
for (const entry of values) {
|
|
1596
|
-
if (typeof entry !== 'string' || entry.trim() === '') {
|
|
1597
|
-
continue;
|
|
1598
|
-
}
|
|
1599
|
-
const { key, value } = parseKeyValue(entry);
|
|
1600
|
-
this.config[configKey][key] = value;
|
|
1601
|
-
this._recordSource(`${configKey}.${key}`, value, 'cli');
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
// Track as explicit configuration
|
|
1605
|
-
if (Object.keys(this.config[configKey]).length > 0) {
|
|
1606
|
-
if (!this.explicitConfig) {
|
|
1607
|
-
this.explicitConfig = {};
|
|
1608
|
-
}
|
|
1609
|
-
this.explicitConfig[configKey] = { ...this.config[configKey] };
|
|
1610
|
-
}
|
|
627
|
+
return this._loader._parseEnvVarOptions(optionName, configKey);
|
|
1611
628
|
}
|
|
1612
629
|
|
|
1613
630
|
/**
|
|
@@ -1617,8 +634,7 @@ export default class ConfigManager {
|
|
|
1617
634
|
* @private
|
|
1618
635
|
*/
|
|
1619
636
|
async _queryMcpServers() {
|
|
1620
|
-
|
|
1621
|
-
// via queryMcpServer(). This method is kept for backward compatibility.
|
|
637
|
+
return this._mcpClient._queryMcpServers();
|
|
1622
638
|
}
|
|
1623
639
|
|
|
1624
640
|
/**
|
|
@@ -1629,70 +645,7 @@ export default class ConfigManager {
|
|
|
1629
645
|
* @returns {Promise<{ values: object, choices: object } | null>}
|
|
1630
646
|
*/
|
|
1631
647
|
async queryMcpServer(serverName, context = {}) {
|
|
1632
|
-
|
|
1633
|
-
try {
|
|
1634
|
-
const configPath = path.join(GENERATOR_ROOT, 'config', 'mcp.json');
|
|
1635
|
-
if (!fs.existsSync(configPath)) return null;
|
|
1636
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1637
|
-
mcpServerConfigs = config.mcpServers;
|
|
1638
|
-
} catch {
|
|
1639
|
-
return null;
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
if (!mcpServerConfigs || !mcpServerConfigs[serverName]) return null;
|
|
1643
|
-
|
|
1644
|
-
const smart = this.options.smart === true;
|
|
1645
|
-
const discover = this.options.discover !== false;
|
|
1646
|
-
const serverConfig = mcpServerConfigs[serverName];
|
|
1647
|
-
|
|
1648
|
-
// Build a custom McpClient that passes context through
|
|
1649
|
-
const client = new McpClient(serverConfig, {
|
|
1650
|
-
timeout: 15000,
|
|
1651
|
-
parameterMatrix: this.parameterMatrix,
|
|
1652
|
-
smart,
|
|
1653
|
-
discover
|
|
1654
|
-
});
|
|
1655
|
-
|
|
1656
|
-
// Override the _buildContext to merge our search context
|
|
1657
|
-
const origBuildContext = client._buildContext.bind(client);
|
|
1658
|
-
client._buildContext = () => ({ ...origBuildContext(), ...context });
|
|
1659
|
-
|
|
1660
|
-
try {
|
|
1661
|
-
const result = await client.query();
|
|
1662
|
-
await client.close();
|
|
1663
|
-
|
|
1664
|
-
if (!result) {
|
|
1665
|
-
const diag = client.getDiagnosticMessage();
|
|
1666
|
-
if (diag) console.log(` ⚠️ ${serverName}: ${diag}`);
|
|
1667
|
-
return null;
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
// Store values
|
|
1671
|
-
for (const [param, value] of Object.entries(result.values || {})) {
|
|
1672
|
-
const paramConfig = this.parameterMatrix[param];
|
|
1673
|
-
if (paramConfig && paramConfig.valueSpace === 'unbounded' && paramConfig.mcp === true) {
|
|
1674
|
-
this.mcpSources[param] = {
|
|
1675
|
-
server: serverName,
|
|
1676
|
-
value,
|
|
1677
|
-
timestamp: new Date().toISOString()
|
|
1678
|
-
};
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
// Store choices
|
|
1683
|
-
for (const [param, choices] of Object.entries(result.choices || {})) {
|
|
1684
|
-
const paramConfig = this.parameterMatrix[param];
|
|
1685
|
-
if (paramConfig && paramConfig.valueSpace === 'unbounded' && paramConfig.mcp === true && Array.isArray(choices)) {
|
|
1686
|
-
this.mcpChoices[param] = choices;
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
return result;
|
|
1691
|
-
} catch (err) {
|
|
1692
|
-
await client.close().catch(() => {});
|
|
1693
|
-
console.log(` ⚠️ ${serverName}: ${err.message}`);
|
|
1694
|
-
return null;
|
|
1695
|
-
}
|
|
648
|
+
return this._mcpClient.queryMcpServer(serverName, context);
|
|
1696
649
|
}
|
|
1697
650
|
|
|
1698
651
|
/**
|
|
@@ -1700,14 +653,7 @@ export default class ConfigManager {
|
|
|
1700
653
|
* @returns {string[]}
|
|
1701
654
|
*/
|
|
1702
655
|
getMcpServerNames() {
|
|
1703
|
-
|
|
1704
|
-
const configPath = path.join(GENERATOR_ROOT, 'config', 'mcp.json');
|
|
1705
|
-
if (!fs.existsSync(configPath)) return [];
|
|
1706
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1707
|
-
return Object.keys(config.mcpServers || {});
|
|
1708
|
-
} catch {
|
|
1709
|
-
return [];
|
|
1710
|
-
}
|
|
656
|
+
return this._mcpClient.getMcpServerNames();
|
|
1711
657
|
}
|
|
1712
658
|
|
|
1713
659
|
/**
|
|
@@ -1765,377 +711,52 @@ export default class ConfigManager {
|
|
|
1765
711
|
}
|
|
1766
712
|
return true;
|
|
1767
713
|
});
|
|
714
|
+
|
|
715
|
+
// Also require key parameters that are needed for generation
|
|
716
|
+
const essentialParams = ['projectName', 'instanceType'];
|
|
717
|
+
const allRequired = [...new Set([...requiredForConfig, ...essentialParams])];
|
|
1768
718
|
|
|
1769
|
-
return
|
|
719
|
+
return allRequired.every(key =>
|
|
1770
720
|
this.config[key] !== undefined && this.config[key] !== null
|
|
1771
721
|
);
|
|
1772
722
|
}
|
|
1773
723
|
|
|
1774
724
|
/**
|
|
1775
725
|
* Validates the current configuration against the parameter matrix
|
|
1776
|
-
* Only reports errors for parameters that cannot be resolved through prompting or auto-generation
|
|
1777
726
|
* @returns {Array} Array of validation errors
|
|
1778
727
|
*/
|
|
1779
728
|
validateConfiguration() {
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
// Old-format deployment-config migration messages
|
|
1783
|
-
const oldFormatMigration = {
|
|
1784
|
-
'sklearn-flask': 'Use --deployment-config=http-flask --engine=sklearn instead',
|
|
1785
|
-
'sklearn-fastapi': 'Use --deployment-config=http-fastapi --engine=sklearn instead',
|
|
1786
|
-
'xgboost-flask': 'Use --deployment-config=http-flask --engine=xgboost instead',
|
|
1787
|
-
'xgboost-fastapi': 'Use --deployment-config=http-fastapi --engine=xgboost instead',
|
|
1788
|
-
'tensorflow-flask': 'Use --deployment-config=http-flask --engine=tensorflow instead',
|
|
1789
|
-
'tensorflow-fastapi': 'Use --deployment-config=http-fastapi --engine=tensorflow instead'
|
|
1790
|
-
};
|
|
1791
|
-
|
|
1792
|
-
// Validate deployment-config
|
|
1793
|
-
if (this.config.deploymentConfig) {
|
|
1794
|
-
const migrationMsg = oldFormatMigration[this.config.deploymentConfig];
|
|
1795
|
-
if (migrationMsg) {
|
|
1796
|
-
errors.push(`Unsupported deployment-config: ${this.config.deploymentConfig}. This value has been replaced. ${migrationMsg}`);
|
|
1797
|
-
} else if (!this.deploymentConfigResolver.isValid(this.config.deploymentConfig)) {
|
|
1798
|
-
const valid = this.deploymentConfigResolver.getAllConfigs().join(', ');
|
|
1799
|
-
errors.push(`Unsupported deployment-config: ${this.config.deploymentConfig}. Valid configs: ${valid}`);
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
// Validate engine (only valid for http architecture)
|
|
1804
|
-
if (this.config.engine) {
|
|
1805
|
-
const validEngines = ['sklearn', 'xgboost', 'tensorflow'];
|
|
1806
|
-
if (!validEngines.includes(this.config.engine)) {
|
|
1807
|
-
errors.push(`Unsupported engine: ${this.config.engine}. Supported: ${validEngines.join(', ')}`);
|
|
1808
|
-
}
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
// Validate model format based on architecture/engine
|
|
1812
|
-
if (this.config.modelFormat && this.config.deploymentConfig) {
|
|
1813
|
-
try {
|
|
1814
|
-
const parts = this.deploymentConfigResolver.decompose(this.config.deploymentConfig);
|
|
1815
|
-
if (parts.architecture === 'http') {
|
|
1816
|
-
const engine = this.config.engine || parts.engine;
|
|
1817
|
-
if (engine) {
|
|
1818
|
-
const supportedOptions = this._getSupportedOptions();
|
|
1819
|
-
const validFormats = supportedOptions.modelFormats[engine] || [];
|
|
1820
|
-
if (validFormats.length > 0 && !validFormats.includes(this.config.modelFormat)) {
|
|
1821
|
-
errors.push(`Unsupported model format '${this.config.modelFormat}' for engine '${engine}'. Supported: ${validFormats.join(', ')}`);
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
} catch {
|
|
1826
|
-
// deploymentConfig already flagged as invalid above
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
// Validate mutual exclusion: plaintext token and ARN cannot both be set
|
|
1831
|
-
if (this.config.hfToken && this.config.hfTokenArn) {
|
|
1832
|
-
errors.push('Cannot specify both --hf-token and --hf-token-arn. Use one or the other.');
|
|
1833
|
-
}
|
|
1834
|
-
if (this.config.ngcTokenArn) {
|
|
1835
|
-
// Check ngcToken from CLI options (Commander converts --ngc-token to ngcToken)
|
|
1836
|
-
const ngcTokenFromCli = this.options['ngc-token'];
|
|
1837
|
-
if (ngcTokenFromCli) {
|
|
1838
|
-
errors.push('Cannot specify both --ngc-token and --ngc-token-arn. Use one or the other.');
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
// Validate AWS Role ARN format if provided
|
|
1843
|
-
if (this.config.awsRoleArn) {
|
|
1844
|
-
try {
|
|
1845
|
-
this._isValidArn(this.config.awsRoleArn);
|
|
1846
|
-
} catch (error) {
|
|
1847
|
-
if (error instanceof ValidationError) {
|
|
1848
|
-
errors.push(error.message);
|
|
1849
|
-
} else {
|
|
1850
|
-
errors.push(`Invalid AWS Role ARN format: ${this.config.awsRoleArn}. Expected format: arn:aws:iam::123456789012:role/RoleName`);
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
// Validate build target (renamed from deployTarget)
|
|
1856
|
-
const buildTarget = this.config.buildTarget || this.config.deployTarget;
|
|
1857
|
-
if (buildTarget && !this._getSupportedOptions().buildTargets.includes(buildTarget)) {
|
|
1858
|
-
errors.push(`Unsupported build target: ${buildTarget}. Supported targets: ${this._getSupportedOptions().buildTargets.join(', ')}`);
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
// Validate CodeBuild compute type
|
|
1862
|
-
if (this.config.codebuildComputeType && !this._getSupportedOptions().codebuildComputeTypes.includes(this.config.codebuildComputeType)) {
|
|
1863
|
-
errors.push(`Unsupported CodeBuild compute type: ${this.config.codebuildComputeType}. Supported types: ${this._getSupportedOptions().codebuildComputeTypes.join(', ')}`);
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
// Validate CodeBuild project name format
|
|
1867
|
-
if (this.config.codebuildProjectName) {
|
|
1868
|
-
const projectNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9\-_]{1,254}$/;
|
|
1869
|
-
if (!projectNamePattern.test(this.config.codebuildProjectName)) {
|
|
1870
|
-
errors.push(`Invalid CodeBuild project name: ${this.config.codebuildProjectName}. Project names must be 2-255 characters, start with a letter or number, and contain only letters, numbers, hyphens, and underscores.`);
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
// Validate model package ARN format if provided
|
|
1875
|
-
if (this.config.modelPackageArn) {
|
|
1876
|
-
const modelPackageArnPattern = /^arn:aws:sagemaker:[a-z0-9-]+:\d{12}:model-package\/[a-zA-Z0-9]([a-zA-Z0-9-])*\/\d+$/;
|
|
1877
|
-
if (!modelPackageArnPattern.test(this.config.modelPackageArn)) {
|
|
1878
|
-
errors.push('❌ Invalid model package ARN format. Expected: arn:aws:sagemaker:<region>:<account>:model-package/<name>/<version>');
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
// Only validate required parameters if we're skipping prompts
|
|
1883
|
-
// If prompts are available, missing parameters can be collected later
|
|
1884
|
-
if (this.skipPrompts) {
|
|
1885
|
-
Object.entries(this.parameterMatrix).forEach(([param, config]) => {
|
|
1886
|
-
if (config.required &&
|
|
1887
|
-
(this.config[param] === null || this.config[param] === undefined)) {
|
|
1888
|
-
|
|
1889
|
-
// Special case: modelFormat is not required for transformers/triton/diffusors
|
|
1890
|
-
if (param === 'modelFormat') {
|
|
1891
|
-
try {
|
|
1892
|
-
const parts = this.deploymentConfigResolver.decompose(this.config.deploymentConfig);
|
|
1893
|
-
if (parts.architecture === 'transformers' || parts.architecture === 'triton' || parts.architecture === 'diffusors') {
|
|
1894
|
-
return;
|
|
1895
|
-
}
|
|
1896
|
-
} catch {
|
|
1897
|
-
// If deploymentConfig is invalid, skip this check
|
|
1898
|
-
return;
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
// Only error for promptable required parameters that have no default and can't be auto-generated
|
|
1903
|
-
if (config.promptable && config.default === null && !this._canAutoGenerate(param)) {
|
|
1904
|
-
errors.push(`Required parameter '${param}' is missing and prompts are disabled`);
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
});
|
|
1908
|
-
|
|
1909
|
-
// Validate that modelName is provided for diffusors architecture
|
|
1910
|
-
if (this.config.deploymentConfig) {
|
|
1911
|
-
try {
|
|
1912
|
-
const parts = this.deploymentConfigResolver.decompose(this.config.deploymentConfig);
|
|
1913
|
-
if (parts.architecture === 'diffusors') {
|
|
1914
|
-
const explicitModelName = this.explicitConfig && this.explicitConfig.modelName;
|
|
1915
|
-
if (!explicitModelName) {
|
|
1916
|
-
errors.push('Model name is required for diffusors architecture. Use --model-name to specify a HuggingFace diffusion model.');
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
} catch {
|
|
1920
|
-
// deploymentConfig already flagged as invalid above
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
// Validate schema-validated parameters (endpoint, iC)
|
|
1926
|
-
Object.entries(this.parameterMatrix).forEach(([param, config]) => {
|
|
1927
|
-
if (config.schemaValidated && this.config[param] !== null && this.config[param] !== undefined) {
|
|
1928
|
-
const result = this.schemaValidator.validate(param, this.config[param], this.config.deploymentTarget);
|
|
1929
|
-
if (!result.valid) {
|
|
1930
|
-
errors.push(result.error);
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
});
|
|
1934
|
-
|
|
1935
|
-
return errors;
|
|
729
|
+
return this._validator.validateConfiguration();
|
|
1936
730
|
}
|
|
1937
731
|
|
|
1938
732
|
/**
|
|
1939
733
|
* Validates required parameters before file generation
|
|
1940
|
-
* This is called after all configuration sources have been processed and prompting is complete
|
|
1941
734
|
* @param {Object} finalConfig - The complete configuration object
|
|
1942
735
|
* @returns {Array} Array of validation errors for missing required parameters
|
|
1943
736
|
*/
|
|
1944
737
|
validateRequiredParameters(finalConfig) {
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
// First, validate individual parameter values
|
|
1948
|
-
Object.entries(finalConfig).forEach(([param, value]) => {
|
|
1949
|
-
if (value !== null && value !== undefined && value !== '') {
|
|
1950
|
-
try {
|
|
1951
|
-
this._validateParameterValue(param, value, finalConfig);
|
|
1952
|
-
} catch (error) {
|
|
1953
|
-
if (error instanceof ValidationError) {
|
|
1954
|
-
errors.push(error.message);
|
|
1955
|
-
} else {
|
|
1956
|
-
errors.push(`Invalid value for parameter '${param}': ${error.message}`);
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
});
|
|
1961
|
-
|
|
1962
|
-
// Then, validate required parameters are present
|
|
1963
|
-
Object.entries(this.parameterMatrix).forEach(([param, config]) => {
|
|
1964
|
-
if (config.required) {
|
|
1965
|
-
const value = finalConfig[param];
|
|
1966
|
-
const isEmpty = value === null || value === undefined || value === '';
|
|
1967
|
-
|
|
1968
|
-
// Special case: modelFormat is not required for transformers/triton/diffusors/marketplace
|
|
1969
|
-
if (param === 'modelFormat' && (finalConfig.architecture === 'transformers' || finalConfig.architecture === 'triton' || finalConfig.architecture === 'diffusors' || finalConfig.architecture === 'marketplace')) {
|
|
1970
|
-
return; // Skip validation
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
// Special case: marketplace projects don't need container-related parameters
|
|
1974
|
-
if (finalConfig.architecture === 'marketplace' && (param === 'includeSampleModel' || param === 'buildTarget')) {
|
|
1975
|
-
return; // Skip validation — marketplace has no container to build
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
// Special case: instanceType is not required for hyperpod-eks
|
|
1979
|
-
// when not provided (backward compatibility) — but it IS prompted now
|
|
1980
|
-
// so it should normally be present
|
|
1981
|
-
if (param === 'instanceType' && finalConfig.deploymentTarget === 'hyperpod-eks' && !finalConfig.instanceType) {
|
|
1982
|
-
return; // Skip validation only if truly missing for backward compat
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
// Special case: instanceType is not required when attaching to an existing endpoint
|
|
1986
|
-
// The instance type is inherited from the existing endpoint configuration
|
|
1987
|
-
if (param === 'instanceType' && finalConfig.existingEndpointName) {
|
|
1988
|
-
return; // Skip validation — instance is inherited from existing endpoint
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
if (isEmpty) {
|
|
1992
|
-
if (config.promptable) {
|
|
1993
|
-
// Promptable required parameter is missing - this should not happen after prompting
|
|
1994
|
-
errors.push(`Required parameter '${param}' is missing. This parameter is required for ${finalConfig.architecture || 'the selected'} architecture.`);
|
|
1995
|
-
} else {
|
|
1996
|
-
// Non-promptable required parameter is missing - this is a configuration error
|
|
1997
|
-
errors.push(`Required non-promptable parameter '${param}' is missing. This parameter must be provided through CLI options, environment variables, or configuration files.`);
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
});
|
|
2002
|
-
|
|
2003
|
-
// Finally, validate parameter combinations and dependencies
|
|
2004
|
-
const combinationErrors = this._validateParameterCombinations(finalConfig);
|
|
2005
|
-
errors.push(...combinationErrors);
|
|
2006
|
-
|
|
2007
|
-
return errors;
|
|
738
|
+
return this._validator.validateRequiredParameters(finalConfig);
|
|
2008
739
|
}
|
|
2009
740
|
|
|
2010
741
|
/**
|
|
2011
|
-
* Validates parameter combinations and dependencies
|
|
2012
|
-
* @param {Object} config - The configuration object to validate
|
|
2013
|
-
* @returns {Array} Array of validation errors for invalid combinations
|
|
2014
742
|
* @private
|
|
2015
743
|
*/
|
|
2016
744
|
_validateParameterCombinations(config) {
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
// Additional combination validations that aren't covered by individual parameter validation
|
|
2020
|
-
// For example, complex business rules that involve multiple parameters
|
|
2021
|
-
|
|
2022
|
-
// Validate that transformers architecture has sample model disabled
|
|
2023
|
-
if (config.architecture === 'transformers' && config.includeSampleModel === true) {
|
|
2024
|
-
errors.push(`Architecture '${config.architecture}' does not support sample models. The 'includeSampleModel' parameter will be automatically set to false.`);
|
|
2025
|
-
}
|
|
2026
|
-
// Validate that diffusors architecture has sample model disabled
|
|
2027
|
-
if (config.architecture === 'diffusors' && config.includeSampleModel === true) {
|
|
2028
|
-
errors.push(`Architecture '${config.architecture}' does not support sample models. The 'includeSampleModel' parameter will be automatically set to false.`);
|
|
2029
|
-
}
|
|
2030
|
-
// Validate that ineligible Triton backends have sample model disabled
|
|
2031
|
-
if (config.architecture === 'triton' && config.includeSampleModel === true) {
|
|
2032
|
-
const backendMeta = tritonBackends[config.backend];
|
|
2033
|
-
if (!backendMeta || !backendMeta.supportsSampleModel) {
|
|
2034
|
-
errors.push(`Triton backend '${config.backend}' does not support sample models. The 'includeSampleModel' parameter will be automatically set to false.`);
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
return errors;
|
|
745
|
+
return this._validator._validateParameterCombinations(config);
|
|
2039
746
|
}
|
|
2040
747
|
|
|
2041
748
|
/**
|
|
2042
|
-
* Checks if a parameter can be auto-generated when missing
|
|
2043
|
-
* @param {string} param - Parameter name
|
|
2044
|
-
* @returns {boolean} True if parameter can be auto-generated
|
|
2045
749
|
* @private
|
|
2046
750
|
*/
|
|
2047
751
|
_canAutoGenerate(param) {
|
|
2048
|
-
|
|
2049
|
-
const autoGeneratable = [
|
|
2050
|
-
'modelFormat', // Can be inferred from engine
|
|
2051
|
-
'includeSampleModel', // Has default
|
|
2052
|
-
'includeTesting', // Has default
|
|
2053
|
-
'instanceType' // Has default
|
|
2054
|
-
];
|
|
2055
|
-
|
|
2056
|
-
return autoGeneratable.includes(param);
|
|
752
|
+
return this._validator._canAutoGenerate(param);
|
|
2057
753
|
}
|
|
2058
754
|
|
|
2059
755
|
/**
|
|
2060
|
-
* Fills auto-prompt defaults for parameters that have sensible defaults
|
|
2061
|
-
* or can be inferred from the current config. Promotes these into
|
|
2062
|
-
* explicitConfig so the wizard skips them.
|
|
2063
|
-
*
|
|
2064
|
-
* Only fills parameters that:
|
|
2065
|
-
* - Have a non-null default in the parameter matrix, OR
|
|
2066
|
-
* - Can be auto-generated (instanceType, modelFormat, etc.)
|
|
2067
|
-
*
|
|
2068
|
-
* Does NOT fill parameters that are truly ambiguous and need user input
|
|
2069
|
-
* (e.g., deploymentConfig when not provided).
|
|
2070
756
|
* @private
|
|
2071
757
|
*/
|
|
2072
758
|
_fillAutoPromptDefaults() {
|
|
2073
|
-
|
|
2074
|
-
this.explicitConfig = {};
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
// Derive architecture from deploymentConfig if available
|
|
2078
|
-
let architecture = this.config.architecture;
|
|
2079
|
-
if (!architecture && this.config.deploymentConfig) {
|
|
2080
|
-
try {
|
|
2081
|
-
const parts = this.deploymentConfigResolver.decompose(this.config.deploymentConfig);
|
|
2082
|
-
architecture = parts.architecture;
|
|
2083
|
-
this.config.architecture = parts.architecture;
|
|
2084
|
-
this.config.backend = parts.backend;
|
|
2085
|
-
this.config.engine = parts.engine;
|
|
2086
|
-
} catch {
|
|
2087
|
-
// Invalid deploymentConfig — will be caught by validation
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
Object.entries(this.parameterMatrix).forEach(([param, config]) => {
|
|
2092
|
-
// Skip if already explicitly set
|
|
2093
|
-
if (this.explicitConfig[param] !== undefined && this.explicitConfig[param] !== null) {
|
|
2094
|
-
return;
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
// For optional parameters: mark them as explicit (with null) so the wizard skips them.
|
|
2098
|
-
// The downstream template logic handles defaults for optional params.
|
|
2099
|
-
if (!config.required) {
|
|
2100
|
-
// Don't override if there's already a value in config
|
|
2101
|
-
if (this.config[param] !== undefined && this.config[param] !== null) {
|
|
2102
|
-
this.explicitConfig[param] = this.config[param];
|
|
2103
|
-
} else if (config.default !== null && config.default !== undefined) {
|
|
2104
|
-
this.config[param] = config.default;
|
|
2105
|
-
this.explicitConfig[param] = config.default;
|
|
2106
|
-
}
|
|
2107
|
-
return;
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
// For required parameters: fill auto-generatable values
|
|
2111
|
-
if (this.config[param] === undefined || this.config[param] === null) {
|
|
2112
|
-
if (param === 'instanceType') {
|
|
2113
|
-
// If instance-sizer is configured and model is known, defer to sizer
|
|
2114
|
-
// The sizer query happens in PromptRunner after model is selected
|
|
2115
|
-
// For now, set a heuristic default that may be overridden by the sizer
|
|
2116
|
-
const arch = architecture || 'http';
|
|
2117
|
-
this.config[param] = arch === 'http' ? 'ml.m5.large' : 'ml.g5.xlarge';
|
|
2118
|
-
} else if (param === 'modelFormat') {
|
|
2119
|
-
if (architecture === 'transformers' || architecture === 'triton' || architecture === 'diffusors') {
|
|
2120
|
-
return; // Not needed for these architectures
|
|
2121
|
-
}
|
|
2122
|
-
const engine = this.config.engine || 'sklearn';
|
|
2123
|
-
const formatMap = { sklearn: 'pkl', xgboost: 'json', tensorflow: 'keras' };
|
|
2124
|
-
this.config[param] = formatMap[engine] || 'pkl';
|
|
2125
|
-
} else if (param === 'projectName') {
|
|
2126
|
-
this.config[param] = this._generateProjectName(architecture);
|
|
2127
|
-
} else {
|
|
2128
|
-
return; // Can't fill — leave for prompting
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
// Promote non-null values to explicitConfig so the wizard skips them
|
|
2133
|
-
if (this.config[param] !== undefined && this.config[param] !== null) {
|
|
2134
|
-
if (config.default !== null || this._canAutoGenerate(param)) {
|
|
2135
|
-
this.explicitConfig[param] = this.config[param];
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
});
|
|
759
|
+
return this._validator._fillAutoPromptDefaults();
|
|
2139
760
|
}
|
|
2140
761
|
|
|
2141
762
|
/**
|
|
@@ -2143,49 +764,15 @@ export default class ConfigManager {
|
|
|
2143
764
|
* @returns {boolean}
|
|
2144
765
|
*/
|
|
2145
766
|
isAutoPrompt() {
|
|
2146
|
-
return this.
|
|
767
|
+
return this._validator.isAutoPrompt();
|
|
2147
768
|
}
|
|
2148
769
|
|
|
2149
770
|
/**
|
|
2150
|
-
* Gets the list of required parameters that are truly missing
|
|
2151
|
-
* auto-generated or defaulted. Used by auto-prompt mode to determine which
|
|
2152
|
-
* specific prompts to show.
|
|
2153
|
-
*
|
|
771
|
+
* Gets the list of required parameters that are truly missing.
|
|
2154
772
|
* @returns {string[]} Array of parameter names that need prompting
|
|
2155
773
|
*/
|
|
2156
774
|
getMissingRequiredParameters() {
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
Object.entries(this.parameterMatrix).forEach(([param, config]) => {
|
|
2160
|
-
if (!config.required || !config.promptable) return;
|
|
2161
|
-
|
|
2162
|
-
const value = this.config[param];
|
|
2163
|
-
const hasValue = value !== undefined && value !== null;
|
|
2164
|
-
|
|
2165
|
-
if (hasValue) return;
|
|
2166
|
-
|
|
2167
|
-
// Special case: modelFormat is not required for transformers/triton/diffusors
|
|
2168
|
-
if (param === 'modelFormat') {
|
|
2169
|
-
const architecture = this.config.architecture;
|
|
2170
|
-
if (architecture === 'transformers' || architecture === 'triton' || architecture === 'diffusors') {
|
|
2171
|
-
return;
|
|
2172
|
-
}
|
|
2173
|
-
// Can be inferred from engine
|
|
2174
|
-
if (this.config.engine || this.config.deploymentConfig) {
|
|
2175
|
-
return;
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
|
-
// Skip params that can be auto-generated
|
|
2180
|
-
if (this._canAutoGenerate(param)) return;
|
|
2181
|
-
|
|
2182
|
-
// Skip params that have a non-null default
|
|
2183
|
-
if (config.default !== null && config.default !== undefined) return;
|
|
2184
|
-
|
|
2185
|
-
missing.push(param);
|
|
2186
|
-
});
|
|
2187
|
-
|
|
2188
|
-
return missing;
|
|
775
|
+
return this._validator.getMissingRequiredParameters();
|
|
2189
776
|
}
|
|
2190
777
|
|
|
2191
778
|
/**
|
|
@@ -2253,227 +840,31 @@ export default class ConfigManager {
|
|
|
2253
840
|
}
|
|
2254
841
|
|
|
2255
842
|
/**
|
|
2256
|
-
* Validates a single parameter value
|
|
2257
|
-
* @param {string} parameter - Parameter name
|
|
2258
|
-
* @param {*} value - Parameter value
|
|
2259
|
-
* @param {Object} context - Additional context (e.g., other parameter values)
|
|
2260
|
-
* @throws {ValidationError} If parameter value is invalid
|
|
2261
843
|
* @private
|
|
2262
844
|
*/
|
|
2263
845
|
_validateParameterValue(parameter, value, context = {}) {
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
switch (parameter) {
|
|
2267
|
-
case 'deploymentConfig':
|
|
2268
|
-
if (value) {
|
|
2269
|
-
// Check for old-format configs with migration messages
|
|
2270
|
-
const oldFormatMigration = {
|
|
2271
|
-
'sklearn-flask': 'Use --deployment-config=http-flask --engine=sklearn instead',
|
|
2272
|
-
'sklearn-fastapi': 'Use --deployment-config=http-fastapi --engine=sklearn instead',
|
|
2273
|
-
'xgboost-flask': 'Use --deployment-config=http-flask --engine=xgboost instead',
|
|
2274
|
-
'xgboost-fastapi': 'Use --deployment-config=http-fastapi --engine=xgboost instead',
|
|
2275
|
-
'tensorflow-flask': 'Use --deployment-config=http-flask --engine=tensorflow instead',
|
|
2276
|
-
'tensorflow-fastapi': 'Use --deployment-config=http-fastapi --engine=tensorflow instead'
|
|
2277
|
-
};
|
|
2278
|
-
const migrationMsg = oldFormatMigration[value];
|
|
2279
|
-
if (migrationMsg) {
|
|
2280
|
-
throw new ValidationError(
|
|
2281
|
-
`Unsupported deployment-config: ${value}. This value has been replaced. ${migrationMsg}`,
|
|
2282
|
-
parameter,
|
|
2283
|
-
value
|
|
2284
|
-
);
|
|
2285
|
-
}
|
|
2286
|
-
if (!this.deploymentConfigResolver.isValid(value)) {
|
|
2287
|
-
const valid = this.deploymentConfigResolver.getAllConfigs().join(', ');
|
|
2288
|
-
throw new ValidationError(
|
|
2289
|
-
`Unsupported deployment-config: ${value}. Valid configs: ${valid}`,
|
|
2290
|
-
parameter,
|
|
2291
|
-
value
|
|
2292
|
-
);
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
break;
|
|
2296
|
-
|
|
2297
|
-
case 'engine':
|
|
2298
|
-
if (value) {
|
|
2299
|
-
const validEngines = ['sklearn', 'xgboost', 'tensorflow'];
|
|
2300
|
-
if (!validEngines.includes(value)) {
|
|
2301
|
-
throw new ValidationError(
|
|
2302
|
-
`Unsupported engine: ${value}. Supported: ${validEngines.join(', ')}`,
|
|
2303
|
-
parameter,
|
|
2304
|
-
value
|
|
2305
|
-
);
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
break;
|
|
2309
|
-
|
|
2310
|
-
case 'modelFormat':
|
|
2311
|
-
if (value && context.architecture === 'http' && context.engine) {
|
|
2312
|
-
const validFormats = supportedOptions.modelFormats[context.engine] || [];
|
|
2313
|
-
if (validFormats.length > 0 && !validFormats.includes(value)) {
|
|
2314
|
-
throw new ValidationError(
|
|
2315
|
-
`Model format '${value}' is not compatible with engine '${context.engine}'. Compatible formats: ${validFormats.join(', ')}`,
|
|
2316
|
-
parameter,
|
|
2317
|
-
value
|
|
2318
|
-
);
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
break;
|
|
2322
|
-
|
|
2323
|
-
case 'instanceType':
|
|
2324
|
-
if (value) {
|
|
2325
|
-
// Validate AWS SageMaker instance type format
|
|
2326
|
-
const instancePattern = /^ml\.[a-z0-9]+\.(nano|micro|small|medium|large|xlarge|[0-9]+xlarge)$/;
|
|
2327
|
-
if (!instancePattern.test(value)) {
|
|
2328
|
-
throw new ValidationError(
|
|
2329
|
-
`Invalid instance type format: ${value}. Expected format: ml.{family}.{size} (e.g., ml.m5.large, ml.g4dn.xlarge)`,
|
|
2330
|
-
parameter,
|
|
2331
|
-
value
|
|
2332
|
-
);
|
|
2333
|
-
}
|
|
2334
|
-
// Warn about CPU instances for transformers/triton (but don't block)
|
|
2335
|
-
if (context.architecture === 'transformers' || context.architecture === 'triton') {
|
|
2336
|
-
const cpuFamilies = ['t2', 't3', 't3a', 't4g', 'm4', 'm5', 'm5a', 'm5ad', 'm5d', 'm5dn', 'm5n', 'm5zn', 'm6a', 'm6g', 'm6gd', 'm6i', 'm6id', 'm6idn', 'm6in', 'c4', 'c5', 'c5a', 'c5ad', 'c5d', 'c5n', 'c6a', 'c6g', 'c6gd', 'c6gn', 'c6i', 'c6id', 'c6in', 'r4', 'r5', 'r5a', 'r5ad', 'r5b', 'r5d', 'r5dn', 'r5n', 'r6a', 'r6g', 'r6gd', 'r6i', 'r6id', 'r6idn', 'r6in'];
|
|
2337
|
-
const instanceFamily = value.split('.')[1];
|
|
2338
|
-
if (cpuFamilies.includes(instanceFamily)) {
|
|
2339
|
-
console.warn(`⚠️ Warning: Using CPU instance ${value} with ${context.architecture} architecture. GPU instances are recommended for better performance.`);
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
break;
|
|
2344
|
-
|
|
2345
|
-
case 'awsRegion':
|
|
2346
|
-
if (value && !supportedOptions.awsRegions.includes(value)) {
|
|
2347
|
-
throw new ValidationError(
|
|
2348
|
-
`Unsupported AWS region: ${value}. Supported regions: ${supportedOptions.awsRegions.join(', ')}`,
|
|
2349
|
-
parameter,
|
|
2350
|
-
value
|
|
2351
|
-
);
|
|
2352
|
-
}
|
|
2353
|
-
break;
|
|
2354
|
-
|
|
2355
|
-
case 'awsRoleArn':
|
|
2356
|
-
if (value) {
|
|
2357
|
-
this._isValidArn(value);
|
|
2358
|
-
}
|
|
2359
|
-
break;
|
|
2360
|
-
|
|
2361
|
-
case 'buildTarget':
|
|
2362
|
-
case 'deployTarget':
|
|
2363
|
-
if (value && !supportedOptions.buildTargets.includes(value)) {
|
|
2364
|
-
throw new ValidationError(
|
|
2365
|
-
`Unsupported build target: ${value}. Supported targets: ${supportedOptions.buildTargets.join(', ')}`,
|
|
2366
|
-
parameter,
|
|
2367
|
-
value
|
|
2368
|
-
);
|
|
2369
|
-
}
|
|
2370
|
-
break;
|
|
2371
|
-
|
|
2372
|
-
case 'codebuildComputeType':
|
|
2373
|
-
if (value && !supportedOptions.codebuildComputeTypes.includes(value)) {
|
|
2374
|
-
throw new ValidationError(
|
|
2375
|
-
`Unsupported CodeBuild compute type: ${value}. Supported types: ${supportedOptions.codebuildComputeTypes.join(', ')}`,
|
|
2376
|
-
parameter,
|
|
2377
|
-
value
|
|
2378
|
-
);
|
|
2379
|
-
}
|
|
2380
|
-
break;
|
|
2381
|
-
|
|
2382
|
-
case 'codebuildProjectName':
|
|
2383
|
-
if (value) {
|
|
2384
|
-
// AWS CodeBuild project names must follow specific naming rules
|
|
2385
|
-
const projectNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9\-_]{1,254}$/;
|
|
2386
|
-
if (!projectNamePattern.test(value)) {
|
|
2387
|
-
throw new ValidationError(
|
|
2388
|
-
`Invalid CodeBuild project name: ${value}. Project names must be 2-255 characters, start with a letter or number, and contain only letters, numbers, hyphens, and underscores.`,
|
|
2389
|
-
parameter,
|
|
2390
|
-
value
|
|
2391
|
-
);
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
break;
|
|
2395
|
-
|
|
2396
|
-
case 'modelPackageArn':
|
|
2397
|
-
if (value) {
|
|
2398
|
-
const modelPackageArnPattern = /^arn:aws:sagemaker:[a-z0-9-]+:\d{12}:model-package\/[a-zA-Z0-9]([a-zA-Z0-9-])*\/\d+$/;
|
|
2399
|
-
if (!modelPackageArnPattern.test(value)) {
|
|
2400
|
-
throw new ValidationError(
|
|
2401
|
-
'❌ Invalid model package ARN format. Expected: arn:aws:sagemaker:<region>:<account>:model-package/<name>/<version>',
|
|
2402
|
-
parameter,
|
|
2403
|
-
value
|
|
2404
|
-
);
|
|
2405
|
-
}
|
|
2406
|
-
}
|
|
2407
|
-
break;
|
|
2408
|
-
}
|
|
846
|
+
return this._validator._validateParameterValue(parameter, value, context);
|
|
2409
847
|
}
|
|
2410
848
|
|
|
2411
849
|
/**
|
|
2412
|
-
* Resolves HF_TOKEN references to actual token values
|
|
2413
|
-
* @param {string} tokenValue - The token value or "$HF_TOKEN" reference
|
|
2414
|
-
* @returns {string|null} Resolved token value
|
|
2415
850
|
* @private
|
|
2416
851
|
*/
|
|
2417
852
|
_resolveHfToken(tokenValue) {
|
|
2418
|
-
|
|
2419
|
-
return null;
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
// Check if it's an environment variable reference
|
|
2423
|
-
if (tokenValue.trim() === '$HF_TOKEN') {
|
|
2424
|
-
const envToken = process.env.HF_TOKEN;
|
|
2425
|
-
if (!envToken) {
|
|
2426
|
-
console.warn('⚠️ Warning: $HF_TOKEN specified but HF_TOKEN environment variable is not set');
|
|
2427
|
-
console.warn(' The container will be built without authentication.');
|
|
2428
|
-
return null;
|
|
2429
|
-
}
|
|
2430
|
-
return envToken;
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
// Direct token value
|
|
2434
|
-
return tokenValue;
|
|
853
|
+
return this._validator._resolveHfToken(tokenValue);
|
|
2435
854
|
}
|
|
2436
855
|
|
|
2437
856
|
/**
|
|
2438
|
-
* Validates AWS Role ARN format
|
|
2439
|
-
* @param {string} arn - The ARN to validate
|
|
2440
|
-
* @throws {ValidationError} If ARN format is invalid
|
|
2441
857
|
* @private
|
|
2442
858
|
*/
|
|
2443
859
|
_isValidArn(arn) {
|
|
2444
|
-
|
|
2445
|
-
if (!arnPattern.test(arn)) {
|
|
2446
|
-
throw new ValidationError(
|
|
2447
|
-
`Invalid AWS Role ARN format: ${arn}. Expected format: arn:aws:iam::123456789012:role/RoleName`,
|
|
2448
|
-
'awsRoleArn',
|
|
2449
|
-
arn
|
|
2450
|
-
);
|
|
2451
|
-
}
|
|
2452
|
-
return true;
|
|
860
|
+
return this._validator._isValidArn(arn);
|
|
2453
861
|
}
|
|
2454
862
|
|
|
2455
863
|
/**
|
|
2456
|
-
* Gets supported options for validation
|
|
2457
864
|
* @private
|
|
2458
865
|
*/
|
|
2459
866
|
_getSupportedOptions() {
|
|
2460
|
-
return
|
|
2461
|
-
deploymentConfigs: this.deploymentConfigResolver.getAllConfigs(),
|
|
2462
|
-
engines: ['sklearn', 'xgboost', 'tensorflow'],
|
|
2463
|
-
modelFormats: {
|
|
2464
|
-
'sklearn': ['pkl', 'joblib'],
|
|
2465
|
-
'xgboost': ['json', 'model', 'ubj'],
|
|
2466
|
-
'tensorflow': ['keras', 'h5', 'SavedModel']
|
|
2467
|
-
},
|
|
2468
|
-
buildTargets: ['codebuild'],
|
|
2469
|
-
codebuildComputeTypes: ['BUILD_GENERAL1_SMALL', 'BUILD_GENERAL1_MEDIUM', 'BUILD_GENERAL1_LARGE'],
|
|
2470
|
-
awsRegions: [
|
|
2471
|
-
'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
|
|
2472
|
-
'eu-west-1', 'eu-west-2', 'eu-central-1', 'eu-north-1',
|
|
2473
|
-
'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1',
|
|
2474
|
-
'ca-central-1', 'sa-east-1'
|
|
2475
|
-
]
|
|
2476
|
-
};
|
|
867
|
+
return this._validator._getSupportedOptions();
|
|
2477
868
|
}
|
|
2478
869
|
}
|
|
2479
870
|
|