@@ -571,6 +571,192 @@ describe('scaleUp with GHES', () => {
571571 10000 ,
572572 ) ;
573573 } ) ;
574+
575+ describe ( 'Dynamic EC2 Configuration' , ( ) => {
576+ beforeEach ( ( ) => {
577+ process . env . ENABLE_ORGANIZATION_RUNNERS = 'true' ;
578+ process . env . ENABLE_DYNAMIC_EC2_CONFIG = 'true' ;
579+ process . env . ENABLE_EPHEMERAL_RUNNERS = 'true' ;
580+ process . env . ENABLE_JOB_QUEUED_CHECK = 'false' ;
581+ process . env . RUNNER_LABELS = 'base-label' ;
582+ process . env . INSTANCE_TYPES = 't3.medium,t3.large' ;
583+ process . env . RUNNER_NAME_PREFIX = 'unit-test' ;
584+ expectedRunnerParams = { ...EXPECTED_RUNNER_PARAMS } ;
585+ mockSSMClient . reset ( ) ;
586+ } ) ;
587+
588+ it ( 'appends EC2 labels to existing runner labels when EC2 labels are present' , async ( ) => {
589+ const testDataWithEc2Labels = [
590+ {
591+ ...TEST_DATA_SINGLE ,
592+ labels : [ 'ghr-ec2-instance-type:c5.2xlarge' , 'ghr-ec2-custom:value' ] ,
593+ messageId : 'test-1' ,
594+ } ,
595+ ] ;
596+
597+ await scaleUpModule . scaleUp ( testDataWithEc2Labels ) ;
598+
599+ // Verify createRunner was called with EC2 instance type extracted from labels
600+ expect ( createRunner ) . toBeCalledWith (
601+ expect . objectContaining ( {
602+ ec2instanceCriteria : expect . objectContaining ( {
603+ instanceTypes : [ 'c5.2xlarge' ] ,
604+ } ) ,
605+ } ) ,
606+ ) ;
607+ } ) ;
608+
609+ it ( 'extracts instance type from EC2 labels and overrides default instance types' , async ( ) => {
610+ const testDataWithEc2Labels = [
611+ {
612+ ...TEST_DATA_SINGLE ,
613+ labels : [ 'ghr-ec2-instance-type:m5.xlarge' , 'ghr-ec2-disk:100' ] ,
614+ messageId : 'test-2' ,
615+ } ,
616+ ] ;
617+
618+ await scaleUpModule . scaleUp ( testDataWithEc2Labels ) ;
619+
620+ expect ( createRunner ) . toBeCalledWith (
621+ expect . objectContaining ( {
622+ ec2instanceCriteria : expect . objectContaining ( {
623+ instanceTypes : [ 'm5.xlarge' ] ,
624+ } ) ,
625+ } ) ,
626+ ) ;
627+ } ) ;
628+
629+ it ( 'uses default instance types when no instance type EC2 label is provided' , async ( ) => {
630+ const testDataWithEc2Labels = [
631+ {
632+ ...TEST_DATA_SINGLE ,
633+ labels : [ 'ghr-ec2-custom:value' ] ,
634+ messageId : 'test-3' ,
635+ } ,
636+ ] ;
637+
638+ await scaleUpModule . scaleUp ( testDataWithEc2Labels ) ;
639+
640+ // Should use the default INSTANCE_TYPES from environment
641+ expect ( createRunner ) . toBeCalledWith (
642+ expect . objectContaining ( {
643+ ec2instanceCriteria : expect . objectContaining ( {
644+ instanceTypes : [ 't3.medium' , 't3.large' ] ,
645+ } ) ,
646+ } ) ,
647+ ) ;
648+ } ) ;
649+
650+ it ( 'does not modify labels when EC2 labels are not present' , async ( ) => {
651+ const testDataWithoutEc2Labels = [
652+ {
653+ ...TEST_DATA_SINGLE ,
654+ labels : [ 'regular-label' , 'another-label' ] ,
655+ messageId : 'test-4' ,
656+ } ,
657+ ] ;
658+
659+ await scaleUpModule . scaleUp ( testDataWithoutEc2Labels ) ;
660+
661+ // Should use default instance types
662+ expect ( createRunner ) . toBeCalledWith (
663+ expect . objectContaining ( {
664+ ec2instanceCriteria : expect . objectContaining ( {
665+ instanceTypes : [ 't3.medium' , 't3.large' ] ,
666+ } ) ,
667+ } ) ,
668+ ) ;
669+ } ) ;
670+
671+ it ( 'handles messages with no labels gracefully' , async ( ) => {
672+ const testDataWithNoLabels = [
673+ {
674+ ...TEST_DATA_SINGLE ,
675+ labels : undefined ,
676+ messageId : 'test-5' ,
677+ } ,
678+ ] ;
679+
680+ await scaleUpModule . scaleUp ( testDataWithNoLabels ) ;
681+
682+ expect ( createRunner ) . toBeCalledWith (
683+ expect . objectContaining ( {
684+ ec2instanceCriteria : expect . objectContaining ( {
685+ instanceTypes : [ 't3.medium' , 't3.large' ] ,
686+ } ) ,
687+ } ) ,
688+ ) ;
689+ } ) ;
690+
691+ it ( 'handles empty labels array' , async ( ) => {
692+ const testDataWithEmptyLabels = [
693+ {
694+ ...TEST_DATA_SINGLE ,
695+ labels : [ ] ,
696+ messageId : 'test-6' ,
697+ } ,
698+ ] ;
699+
700+ await scaleUpModule . scaleUp ( testDataWithEmptyLabels ) ;
701+
702+ expect ( createRunner ) . toBeCalledWith (
703+ expect . objectContaining ( {
704+ ec2instanceCriteria : expect . objectContaining ( {
705+ instanceTypes : [ 't3.medium' , 't3.large' ] ,
706+ } ) ,
707+ } ) ,
708+ ) ;
709+ } ) ;
710+
711+ it ( 'does not process EC2 labels when ENABLE_DYNAMIC_EC2_CONFIG is disabled' , async ( ) => {
712+ process . env . ENABLE_DYNAMIC_EC2_CONFIG = 'false' ;
713+
714+ const testDataWithEc2Labels = [
715+ {
716+ ...TEST_DATA_SINGLE ,
717+ labels : [ 'ghr-ec2-instance-type:c5.4xlarge' ] ,
718+ messageId : 'test-7' ,
719+ } ,
720+ ] ;
721+
722+ await scaleUpModule . scaleUp ( testDataWithEc2Labels ) ;
723+
724+ // Should ignore EC2 labels and use default instance types
725+ expect ( createRunner ) . toBeCalledWith (
726+ expect . objectContaining ( {
727+ ec2instanceCriteria : expect . objectContaining ( {
728+ instanceTypes : [ 't3.medium' , 't3.large' ] ,
729+ } ) ,
730+ } ) ,
731+ ) ;
732+ } ) ;
733+
734+ it ( 'handles multiple EC2 labels correctly' , async ( ) => {
735+ const testDataWithMultipleEc2Labels = [
736+ {
737+ ...TEST_DATA_SINGLE ,
738+ labels : [
739+ 'regular-label' ,
740+ 'ghr-ec2-instance-type:r5.2xlarge' ,
741+ 'ghr-ec2-ami:custom-ami' ,
742+ 'ghr-ec2-disk:200' ,
743+ ] ,
744+ messageId : 'test-8' ,
745+ } ,
746+ ] ;
747+
748+ await scaleUpModule . scaleUp ( testDataWithMultipleEc2Labels ) ;
749+
750+ expect ( createRunner ) . toBeCalledWith (
751+ expect . objectContaining ( {
752+ ec2instanceCriteria : expect . objectContaining ( {
753+ instanceTypes : [ 'r5.2xlarge' ] ,
754+ } ) ,
755+ } ) ,
756+ ) ;
757+ } ) ;
758+ } ) ;
759+
574760 describe ( 'on repo level' , ( ) => {
575761 beforeEach ( ( ) => {
576762 process . env . ENABLE_ORGANIZATION_RUNNERS = 'false' ;
0 commit comments