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