@friggframework/devtools 2.0.0--canary.490.feacde9.0 → 2.0.0--canary.497.a3f25f9.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/frigg-cli/deploy-command/index.js +3 -9
- package/infrastructure/README.md +0 -28
- package/infrastructure/domains/database/migration-builder.js +13 -19
- package/infrastructure/domains/database/migration-builder.test.js +0 -57
- package/infrastructure/domains/integration/integration-builder.js +14 -19
- package/infrastructure/domains/integration/integration-builder.test.js +74 -0
- package/infrastructure/domains/networking/vpc-builder.js +18 -240
- package/infrastructure/domains/networking/vpc-builder.test.js +13 -711
- package/infrastructure/domains/networking/vpc-resolver.js +40 -221
- package/infrastructure/domains/networking/vpc-resolver.test.js +18 -318
- package/infrastructure/domains/security/kms-builder.js +6 -55
- package/infrastructure/domains/security/kms-builder.test.js +1 -19
- package/infrastructure/domains/shared/cloudformation-discovery.js +13 -310
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +0 -395
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +6 -41
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +0 -39
- package/infrastructure/domains/shared/resource-discovery.js +5 -17
- package/infrastructure/domains/shared/resource-discovery.test.js +0 -36
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +17 -27
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +0 -73
- package/infrastructure/infrastructure-composer.js +3 -11
- package/infrastructure/scripts/build-prisma-layer.js +81 -8
- package/infrastructure/scripts/build-prisma-layer.test.js +53 -1
- package/infrastructure/scripts/verify-prisma-layer.js +72 -0
- package/package.json +7 -7
- package/layers/prisma/.build-complete +0 -3
|
@@ -586,400 +586,5 @@ describe('CloudFormationDiscovery', () => {
|
|
|
586
586
|
expect(mockProvider.describeKmsKey).toHaveBeenCalledWith('alias/test-service-dev-frigg-kms');
|
|
587
587
|
});
|
|
588
588
|
});
|
|
589
|
-
|
|
590
|
-
describe('External VPC with routing infrastructure pattern', () => {
|
|
591
|
-
it('should discover routing resources when VPC is external', async () => {
|
|
592
|
-
// This tests the external VPC pattern: external VPC/subnets/KMS,
|
|
593
|
-
// but stack creates routing infrastructure (route table, NAT route, VPC endpoints)
|
|
594
|
-
const mockStack = {
|
|
595
|
-
StackName: 'create-frigg-app-production',
|
|
596
|
-
Outputs: [],
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
const mockResources = [
|
|
600
|
-
{
|
|
601
|
-
LogicalResourceId: 'FriggLambdaRouteTable',
|
|
602
|
-
PhysicalResourceId: 'rtb-0b83aca77ccde20a6',
|
|
603
|
-
ResourceType: 'AWS::EC2::RouteTable',
|
|
604
|
-
ResourceStatus: 'UPDATE_COMPLETE',
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
LogicalResourceId: 'FriggNATRoute',
|
|
608
|
-
PhysicalResourceId: 'rtb-0b83aca77ccde20a6|0.0.0.0/0',
|
|
609
|
-
ResourceType: 'AWS::EC2::Route',
|
|
610
|
-
ResourceStatus: 'UPDATE_COMPLETE',
|
|
611
|
-
},
|
|
612
|
-
{
|
|
613
|
-
LogicalResourceId: 'FriggSubnet1RouteAssociation',
|
|
614
|
-
PhysicalResourceId: 'rtbassoc-07245da0b447ca469',
|
|
615
|
-
ResourceType: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
616
|
-
ResourceStatus: 'CREATE_COMPLETE',
|
|
617
|
-
},
|
|
618
|
-
{
|
|
619
|
-
LogicalResourceId: 'FriggSubnet2RouteAssociation',
|
|
620
|
-
PhysicalResourceId: 'rtbassoc-0806f9783c4ea181f',
|
|
621
|
-
ResourceType: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
622
|
-
ResourceStatus: 'CREATE_COMPLETE',
|
|
623
|
-
},
|
|
624
|
-
{
|
|
625
|
-
LogicalResourceId: 'VPCEndpointS3',
|
|
626
|
-
PhysicalResourceId: 'vpce-0352ceac2124c14be',
|
|
627
|
-
ResourceType: 'AWS::EC2::VPCEndpoint',
|
|
628
|
-
ResourceStatus: 'CREATE_COMPLETE',
|
|
629
|
-
},
|
|
630
|
-
{
|
|
631
|
-
LogicalResourceId: 'VPCEndpointDynamoDB',
|
|
632
|
-
PhysicalResourceId: 'vpce-0b06c4f631199ea68',
|
|
633
|
-
ResourceType: 'AWS::EC2::VPCEndpoint',
|
|
634
|
-
ResourceStatus: 'CREATE_COMPLETE',
|
|
635
|
-
},
|
|
636
|
-
];
|
|
637
|
-
|
|
638
|
-
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
639
|
-
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
640
|
-
|
|
641
|
-
const result = await cfDiscovery.discoverFromStack('create-frigg-app-production');
|
|
642
|
-
|
|
643
|
-
// Verify routing infrastructure was discovered
|
|
644
|
-
expect(result.routeTableId).toBe('rtb-0b83aca77ccde20a6');
|
|
645
|
-
expect(result.privateRouteTableId).toBe('rtb-0b83aca77ccde20a6');
|
|
646
|
-
expect(result.natRoute).toBe('rtb-0b83aca77ccde20a6|0.0.0.0/0');
|
|
647
|
-
expect(result.routeTableAssociations).toEqual([
|
|
648
|
-
'rtbassoc-07245da0b447ca469',
|
|
649
|
-
'rtbassoc-0806f9783c4ea181f',
|
|
650
|
-
]);
|
|
651
|
-
|
|
652
|
-
// Verify VPC endpoints were discovered (both naming conventions)
|
|
653
|
-
expect(result.vpcEndpoints).toBeDefined();
|
|
654
|
-
expect(result.vpcEndpoints.s3).toBe('vpce-0352ceac2124c14be');
|
|
655
|
-
expect(result.vpcEndpoints.dynamodb).toBe('vpce-0b06c4f631199ea68');
|
|
656
|
-
expect(result.s3VpcEndpointId).toBe('vpce-0352ceac2124c14be');
|
|
657
|
-
expect(result.dynamoDbVpcEndpointId).toBe('vpce-0b06c4f631199ea68');
|
|
658
|
-
|
|
659
|
-
// Verify NO VPC/KMS resources (they're external)
|
|
660
|
-
expect(result.defaultVpcId).toBeUndefined();
|
|
661
|
-
expect(result.defaultKmsKeyId).toBeUndefined();
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
it('should work with legacy VPC endpoint naming (FriggS3VPCEndpoint)', async () => {
|
|
665
|
-
const mockStack = {
|
|
666
|
-
StackName: 'test-stack',
|
|
667
|
-
Outputs: [],
|
|
668
|
-
};
|
|
669
|
-
|
|
670
|
-
const mockResources = [
|
|
671
|
-
{
|
|
672
|
-
LogicalResourceId: 'FriggS3VPCEndpoint',
|
|
673
|
-
PhysicalResourceId: 'vpce-legacy-s3',
|
|
674
|
-
ResourceType: 'AWS::EC2::VPCEndpoint',
|
|
675
|
-
ResourceStatus: 'CREATE_COMPLETE',
|
|
676
|
-
},
|
|
677
|
-
{
|
|
678
|
-
LogicalResourceId: 'FriggDynamoDBVPCEndpoint',
|
|
679
|
-
PhysicalResourceId: 'vpce-legacy-ddb',
|
|
680
|
-
ResourceType: 'AWS::EC2::VPCEndpoint',
|
|
681
|
-
ResourceStatus: 'CREATE_COMPLETE',
|
|
682
|
-
},
|
|
683
|
-
];
|
|
684
|
-
|
|
685
|
-
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
686
|
-
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
687
|
-
|
|
688
|
-
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
689
|
-
|
|
690
|
-
// Both naming conventions should work
|
|
691
|
-
expect(result.vpcEndpoints.s3).toBe('vpce-legacy-s3');
|
|
692
|
-
expect(result.vpcEndpoints.dynamodb).toBe('vpce-legacy-ddb');
|
|
693
|
-
expect(result.s3VpcEndpointId).toBe('vpce-legacy-s3');
|
|
694
|
-
expect(result.dynamoDbVpcEndpointId).toBe('vpce-legacy-ddb');
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
it('should extract FriggLambdaSecurityGroup from stack', async () => {
|
|
698
|
-
const mockStack = {
|
|
699
|
-
StackName: 'test-stack',
|
|
700
|
-
Outputs: [],
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
const mockResources = [
|
|
704
|
-
{
|
|
705
|
-
LogicalResourceId: 'FriggLambdaSecurityGroup',
|
|
706
|
-
PhysicalResourceId: 'sg-01002240c6a446202',
|
|
707
|
-
ResourceType: 'AWS::EC2::SecurityGroup',
|
|
708
|
-
ResourceStatus: 'UPDATE_COMPLETE',
|
|
709
|
-
},
|
|
710
|
-
{
|
|
711
|
-
LogicalResourceId: 'FriggLambdaRouteTable',
|
|
712
|
-
PhysicalResourceId: 'rtb-08af43bbf0775602d',
|
|
713
|
-
ResourceType: 'AWS::EC2::RouteTable',
|
|
714
|
-
ResourceStatus: 'UPDATE_COMPLETE',
|
|
715
|
-
},
|
|
716
|
-
];
|
|
717
|
-
|
|
718
|
-
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
719
|
-
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
720
|
-
|
|
721
|
-
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
722
|
-
|
|
723
|
-
// Lambda security group should be extracted
|
|
724
|
-
expect(result.lambdaSecurityGroupId).toBe('sg-01002240c6a446202');
|
|
725
|
-
expect(result.defaultSecurityGroupId).toBe('sg-01002240c6a446202');
|
|
726
|
-
expect(result.existingLogicalIds).toContain('FriggLambdaSecurityGroup');
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
it('should support FriggPrivateRoute naming for NAT routes', async () => {
|
|
730
|
-
const mockStack = {
|
|
731
|
-
StackName: 'test-stack',
|
|
732
|
-
Outputs: [],
|
|
733
|
-
};
|
|
734
|
-
|
|
735
|
-
const mockResources = [
|
|
736
|
-
{
|
|
737
|
-
LogicalResourceId: 'FriggLambdaRouteTable',
|
|
738
|
-
PhysicalResourceId: 'rtb-123',
|
|
739
|
-
ResourceType: 'AWS::EC2::RouteTable',
|
|
740
|
-
ResourceStatus: 'UPDATE_COMPLETE',
|
|
741
|
-
},
|
|
742
|
-
{
|
|
743
|
-
LogicalResourceId: 'FriggPrivateRoute',
|
|
744
|
-
PhysicalResourceId: 'rtb-123|0.0.0.0/0',
|
|
745
|
-
ResourceType: 'AWS::EC2::Route',
|
|
746
|
-
ResourceStatus: 'UPDATE_COMPLETE',
|
|
747
|
-
},
|
|
748
|
-
];
|
|
749
|
-
|
|
750
|
-
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
751
|
-
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
752
|
-
|
|
753
|
-
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
754
|
-
|
|
755
|
-
// Both FriggNATRoute and FriggPrivateRoute should be recognized
|
|
756
|
-
expect(result.natRoute).toBe('rtb-123|0.0.0.0/0');
|
|
757
|
-
expect(result.routeTableId).toBe('rtb-123');
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
it('should extract external references from route table without stackName error', async () => {
|
|
761
|
-
const mockStack = {
|
|
762
|
-
StackName: 'test-stack',
|
|
763
|
-
Outputs: [],
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
const mockResources = [
|
|
767
|
-
{
|
|
768
|
-
LogicalResourceId: 'FriggLambdaRouteTable',
|
|
769
|
-
PhysicalResourceId: 'rtb-real-id',
|
|
770
|
-
ResourceType: 'AWS::EC2::RouteTable',
|
|
771
|
-
ResourceStatus: 'UPDATE_COMPLETE',
|
|
772
|
-
},
|
|
773
|
-
];
|
|
774
|
-
|
|
775
|
-
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
776
|
-
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
777
|
-
|
|
778
|
-
// Mock EC2 DescribeRouteTables to return route table with VPC info
|
|
779
|
-
mockProvider.getEC2Client = jest.fn().mockReturnValue({
|
|
780
|
-
send: jest.fn().mockResolvedValue({
|
|
781
|
-
RouteTables: [{
|
|
782
|
-
RouteTableId: 'rtb-real-id',
|
|
783
|
-
VpcId: 'vpc-extracted',
|
|
784
|
-
Routes: [
|
|
785
|
-
{ NatGatewayId: 'nat-extracted', DestinationCidrBlock: '0.0.0.0/0' }
|
|
786
|
-
],
|
|
787
|
-
Associations: [
|
|
788
|
-
{ SubnetId: 'subnet-1' },
|
|
789
|
-
{ SubnetId: 'subnet-2' }
|
|
790
|
-
]
|
|
791
|
-
}]
|
|
792
|
-
})
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
796
|
-
|
|
797
|
-
// Should extract VPC, NAT, and subnets from route table
|
|
798
|
-
expect(result.defaultVpcId).toBe('vpc-extracted');
|
|
799
|
-
expect(result.existingNatGatewayId).toBe('nat-extracted');
|
|
800
|
-
expect(result.privateSubnetId1).toBe('subnet-1');
|
|
801
|
-
expect(result.privateSubnetId2).toBe('subnet-2');
|
|
802
|
-
|
|
803
|
-
// Should NOT throw 'stackName is not defined' error
|
|
804
|
-
expect(result).toBeDefined();
|
|
805
|
-
});
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
describe('existingLogicalIds tracking', () => {
|
|
809
|
-
it('should track OLD VPC endpoint logical IDs (VPCEndpointS3 pattern) for backwards compatibility', async () => {
|
|
810
|
-
// CRITICAL: Frontify production uses OLD naming convention
|
|
811
|
-
const mockStack = {
|
|
812
|
-
StackName: 'create-frigg-app-production',
|
|
813
|
-
Outputs: []
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
const mockResources = [
|
|
817
|
-
{ LogicalResourceId: 'FriggLambdaRouteTable', PhysicalResourceId: 'rtb-123', ResourceType: 'AWS::EC2::RouteTable' },
|
|
818
|
-
{ LogicalResourceId: 'FriggNATRoute', PhysicalResourceId: 'rtb-123|0.0.0.0/0', ResourceType: 'AWS::EC2::Route' },
|
|
819
|
-
{ LogicalResourceId: 'FriggSubnet1RouteAssociation', PhysicalResourceId: 'rtbassoc-1', ResourceType: 'AWS::EC2::SubnetRouteTableAssociation' },
|
|
820
|
-
{ LogicalResourceId: 'FriggSubnet2RouteAssociation', PhysicalResourceId: 'rtbassoc-2', ResourceType: 'AWS::EC2::SubnetRouteTableAssociation' },
|
|
821
|
-
{ LogicalResourceId: 'VPCEndpointS3', PhysicalResourceId: 'vpce-s3-123', ResourceType: 'AWS::EC2::VPCEndpoint' },
|
|
822
|
-
{ LogicalResourceId: 'VPCEndpointDynamoDB', PhysicalResourceId: 'vpce-ddb-123', ResourceType: 'AWS::EC2::VPCEndpoint' }
|
|
823
|
-
];
|
|
824
|
-
|
|
825
|
-
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
826
|
-
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
827
|
-
|
|
828
|
-
const result = await cfDiscovery.discoverFromStack('create-frigg-app-production');
|
|
829
|
-
|
|
830
|
-
// CRITICAL: existingLogicalIds MUST contain old VPC endpoint names
|
|
831
|
-
expect(result.existingLogicalIds).toBeDefined();
|
|
832
|
-
expect(result.existingLogicalIds).toContain('FriggNATRoute');
|
|
833
|
-
expect(result.existingLogicalIds).toContain('FriggSubnet1RouteAssociation');
|
|
834
|
-
expect(result.existingLogicalIds).toContain('FriggSubnet2RouteAssociation');
|
|
835
|
-
expect(result.existingLogicalIds).toContain('VPCEndpointS3'); // OLD naming
|
|
836
|
-
expect(result.existingLogicalIds).toContain('VPCEndpointDynamoDB'); // OLD naming
|
|
837
|
-
|
|
838
|
-
// Should also have the flat discovery properties
|
|
839
|
-
expect(result.routeTableId).toBe('rtb-123');
|
|
840
|
-
expect(result.natRoute).toBe('rtb-123|0.0.0.0/0');
|
|
841
|
-
expect(result.s3VpcEndpointId).toBe('vpce-s3-123');
|
|
842
|
-
expect(result.dynamodbVpcEndpointId).toBe('vpce-ddb-123');
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
it('should track NEW VPC endpoint logical IDs (FriggS3VPCEndpoint pattern) for newer stacks', async () => {
|
|
846
|
-
const mockStack = {
|
|
847
|
-
StackName: 'test-stack',
|
|
848
|
-
Outputs: []
|
|
849
|
-
};
|
|
850
|
-
|
|
851
|
-
const mockResources = [
|
|
852
|
-
{ LogicalResourceId: 'FriggLambdaRouteTable', PhysicalResourceId: 'rtb-456', ResourceType: 'AWS::EC2::RouteTable' },
|
|
853
|
-
{ LogicalResourceId: 'FriggPrivateRoute', PhysicalResourceId: 'rtb-456|0.0.0.0/0', ResourceType: 'AWS::EC2::Route' },
|
|
854
|
-
{ LogicalResourceId: 'FriggS3VPCEndpoint', PhysicalResourceId: 'vpce-s3-456', ResourceType: 'AWS::EC2::VPCEndpoint' },
|
|
855
|
-
{ LogicalResourceId: 'FriggDynamoDBVPCEndpoint', PhysicalResourceId: 'vpce-ddb-456', ResourceType: 'AWS::EC2::VPCEndpoint' },
|
|
856
|
-
{ LogicalResourceId: 'FriggKMSVPCEndpoint', PhysicalResourceId: 'vpce-kms-456', ResourceType: 'AWS::EC2::VPCEndpoint' }
|
|
857
|
-
];
|
|
858
|
-
|
|
859
|
-
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
860
|
-
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
861
|
-
|
|
862
|
-
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
863
|
-
|
|
864
|
-
// Should track NEW naming pattern in existingLogicalIds
|
|
865
|
-
expect(result.existingLogicalIds).toContain('FriggPrivateRoute');
|
|
866
|
-
expect(result.existingLogicalIds).toContain('FriggS3VPCEndpoint');
|
|
867
|
-
expect(result.existingLogicalIds).toContain('FriggDynamoDBVPCEndpoint');
|
|
868
|
-
expect(result.existingLogicalIds).toContain('FriggKMSVPCEndpoint');
|
|
869
|
-
|
|
870
|
-
// Should NOT contain old naming patterns
|
|
871
|
-
expect(result.existingLogicalIds).not.toContain('FriggNATRoute');
|
|
872
|
-
expect(result.existingLogicalIds).not.toContain('VPCEndpointS3');
|
|
873
|
-
});
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
describe('Subnet extraction from VPC query (OLD reliable approach)', () => {
|
|
877
|
-
it('should extract subnets by querying ALL subnets in VPC then filtering by route table', async () => {
|
|
878
|
-
// Tests the proven method from aws-discovery.js
|
|
879
|
-
// 1. Query ALL subnets in VPC using vpc-id filter (not association filter!)
|
|
880
|
-
// 2. Query route table by ID (RouteTableIds parameter, not Filters!)
|
|
881
|
-
// 3. Extract subnet IDs from route table's Associations array
|
|
882
|
-
|
|
883
|
-
const mockStack = {
|
|
884
|
-
StackName: 'test-stack',
|
|
885
|
-
Outputs: []
|
|
886
|
-
};
|
|
887
|
-
|
|
888
|
-
const mockResources = [
|
|
889
|
-
{ LogicalResourceId: 'FriggLambdaRouteTable', PhysicalResourceId: 'rtb-123', ResourceType: 'AWS::EC2::RouteTable' },
|
|
890
|
-
{ LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-456', ResourceType: 'AWS::EC2::VPC' }
|
|
891
|
-
];
|
|
892
|
-
|
|
893
|
-
const sendMock = jest.fn();
|
|
894
|
-
sendMock
|
|
895
|
-
.mockResolvedValueOnce({
|
|
896
|
-
RouteTables: [{
|
|
897
|
-
RouteTableId: 'rtb-123',
|
|
898
|
-
VpcId: 'vpc-456',
|
|
899
|
-
Associations: [],
|
|
900
|
-
Routes: [{ NatGatewayId: 'nat-789', DestinationCidrBlock: '0.0.0.0/0' }]
|
|
901
|
-
}]
|
|
902
|
-
})
|
|
903
|
-
.mockResolvedValueOnce({ SecurityGroups: [{ GroupId: 'sg-default' }] })
|
|
904
|
-
.mockResolvedValueOnce({
|
|
905
|
-
Subnets: [
|
|
906
|
-
{ SubnetId: 'subnet-aaa', VpcId: 'vpc-456', AvailabilityZone: 'us-east-1a' },
|
|
907
|
-
{ SubnetId: 'subnet-bbb', VpcId: 'vpc-456', AvailabilityZone: 'us-east-1b' },
|
|
908
|
-
{ SubnetId: 'subnet-ccc', VpcId: 'vpc-456', AvailabilityZone: 'us-east-1c' }
|
|
909
|
-
]
|
|
910
|
-
})
|
|
911
|
-
.mockResolvedValueOnce({
|
|
912
|
-
RouteTables: [{
|
|
913
|
-
RouteTableId: 'rtb-123',
|
|
914
|
-
Associations: [
|
|
915
|
-
{ RouteTableAssociationId: 'rtbassoc-111', SubnetId: 'subnet-aaa' },
|
|
916
|
-
{ RouteTableAssociationId: 'rtbassoc-222', SubnetId: 'subnet-bbb' }
|
|
917
|
-
]
|
|
918
|
-
}]
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
const mockEC2Client = { send: sendMock };
|
|
922
|
-
|
|
923
|
-
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
924
|
-
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
925
|
-
mockProvider.getEC2Client = jest.fn().mockReturnValue(mockEC2Client);
|
|
926
|
-
|
|
927
|
-
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
928
|
-
|
|
929
|
-
// Should have extracted subnets using VPC query approach
|
|
930
|
-
expect(result.privateSubnetId1).toBe('subnet-aaa');
|
|
931
|
-
expect(result.privateSubnetId2).toBe('subnet-bbb');
|
|
932
|
-
});
|
|
933
|
-
|
|
934
|
-
it('should handle VPC with only 1 associated subnet (use second as fallback)', async () => {
|
|
935
|
-
const mockStack = {
|
|
936
|
-
StackName: 'test-stack',
|
|
937
|
-
Outputs: []
|
|
938
|
-
};
|
|
939
|
-
|
|
940
|
-
const mockResources = [
|
|
941
|
-
{ LogicalResourceId: 'FriggLambdaRouteTable', PhysicalResourceId: 'rtb-123', ResourceType: 'AWS::EC2::RouteTable' },
|
|
942
|
-
{ LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-456', ResourceType: 'AWS::EC2::VPC' }
|
|
943
|
-
];
|
|
944
|
-
|
|
945
|
-
const sendMock = jest.fn();
|
|
946
|
-
sendMock
|
|
947
|
-
.mockResolvedValueOnce({
|
|
948
|
-
RouteTables: [{
|
|
949
|
-
RouteTableId: 'rtb-123',
|
|
950
|
-
VpcId: 'vpc-456',
|
|
951
|
-
Associations: [],
|
|
952
|
-
Routes: [{ NatGatewayId: 'nat-789', DestinationCidrBlock: '0.0.0.0/0' }]
|
|
953
|
-
}]
|
|
954
|
-
})
|
|
955
|
-
.mockResolvedValueOnce({ SecurityGroups: [{ GroupId: 'sg-default' }] })
|
|
956
|
-
.mockResolvedValueOnce({
|
|
957
|
-
Subnets: [
|
|
958
|
-
{ SubnetId: 'subnet-aaa', VpcId: 'vpc-456' },
|
|
959
|
-
{ SubnetId: 'subnet-bbb', VpcId: 'vpc-456' }
|
|
960
|
-
]
|
|
961
|
-
})
|
|
962
|
-
.mockResolvedValueOnce({
|
|
963
|
-
RouteTables: [{
|
|
964
|
-
RouteTableId: 'rtb-123',
|
|
965
|
-
Associations: [
|
|
966
|
-
{ RouteTableAssociationId: 'rtbassoc-111', SubnetId: 'subnet-aaa' }
|
|
967
|
-
]
|
|
968
|
-
}]
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
const mockEC2Client = { send: sendMock };
|
|
972
|
-
|
|
973
|
-
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
974
|
-
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
975
|
-
mockProvider.getEC2Client = jest.fn().mockReturnValue(mockEC2Client);
|
|
976
|
-
|
|
977
|
-
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
978
|
-
|
|
979
|
-
// Should use first from route table, second from fallback
|
|
980
|
-
expect(result.privateSubnetId1).toBe('subnet-aaa');
|
|
981
|
-
expect(result.privateSubnetId2).toBe('subnet-bbb');
|
|
982
|
-
});
|
|
983
|
-
});
|
|
984
589
|
});
|
|
985
590
|
|
|
@@ -20,7 +20,7 @@ let KMSClient, ListKeysCommand, DescribeKeyCommand, ListAliasesCommand;
|
|
|
20
20
|
let RDSClient, DescribeDBClustersCommand, DescribeDBInstancesCommand;
|
|
21
21
|
let SSMClient, GetParameterCommand, GetParametersByPathCommand;
|
|
22
22
|
let SecretsManagerClient, ListSecretsCommand, GetSecretValueCommand;
|
|
23
|
-
let CloudFormationClient, DescribeStacksCommand, ListStackResourcesCommand
|
|
23
|
+
let CloudFormationClient, DescribeStacksCommand, ListStackResourcesCommand;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Lazy load EC2 SDK
|
|
@@ -518,57 +518,22 @@ class AWSProviderAdapter extends CloudProviderAdapter {
|
|
|
518
518
|
|
|
519
519
|
/**
|
|
520
520
|
* List CloudFormation stack resources
|
|
521
|
-
* Handles pagination to retrieve all resources (CloudFormation limits to 1 MB per page)
|
|
522
521
|
*
|
|
523
522
|
* @param {string} stackName - Name of the CloudFormation stack
|
|
524
|
-
* @returns {Promise<Array>} List of
|
|
523
|
+
* @returns {Promise<Array>} List of stack resources
|
|
525
524
|
*/
|
|
526
525
|
async listStackResources(stackName) {
|
|
527
526
|
const cf = this.getCloudFormationClient();
|
|
528
|
-
const allResources = [];
|
|
529
|
-
let nextToken = null;
|
|
530
527
|
|
|
531
528
|
try {
|
|
532
|
-
|
|
533
|
-
const response = await cf.send(new ListStackResourcesCommand({
|
|
534
|
-
StackName: stackName,
|
|
535
|
-
NextToken: nextToken
|
|
536
|
-
}));
|
|
537
|
-
|
|
538
|
-
if (response.StackResourceSummaries) {
|
|
539
|
-
allResources.push(...response.StackResourceSummaries);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
nextToken = response.NextToken || null;
|
|
543
|
-
} while (nextToken);
|
|
544
|
-
|
|
545
|
-
return allResources;
|
|
546
|
-
} catch (error) {
|
|
547
|
-
console.warn(`Failed to list stack resources for ${stackName}:`, error.message);
|
|
548
|
-
return [];
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Describe a specific stack resource to get its full details including properties
|
|
554
|
-
* @param {string} stackName - Stack name
|
|
555
|
-
* @param {string} logicalResourceId - Logical resource ID
|
|
556
|
-
* @returns {Promise<Object>} Resource details
|
|
557
|
-
*/
|
|
558
|
-
async describeStackResource(stackName, logicalResourceId) {
|
|
559
|
-
const cf = this.getCloudFormationClient();
|
|
560
|
-
|
|
561
|
-
try {
|
|
562
|
-
const { DescribeStackResourceCommand } = require('@aws-sdk/client-cloudformation');
|
|
563
|
-
const response = await cf.send(new DescribeStackResourceCommand({
|
|
529
|
+
const response = await cf.send(new ListStackResourcesCommand({
|
|
564
530
|
StackName: stackName,
|
|
565
|
-
LogicalResourceId: logicalResourceId,
|
|
566
531
|
}));
|
|
567
532
|
|
|
568
|
-
return response.
|
|
533
|
+
return response.StackResourceSummaries || [];
|
|
569
534
|
} catch (error) {
|
|
570
|
-
console.warn(`Failed to
|
|
571
|
-
return
|
|
535
|
+
console.warn(`Failed to list stack resources for ${stackName}:`, error.message);
|
|
536
|
+
return [];
|
|
572
537
|
}
|
|
573
538
|
}
|
|
574
539
|
}
|
|
@@ -14,45 +14,6 @@ jest.mock('@aws-sdk/client-ssm');
|
|
|
14
14
|
jest.mock('@aws-sdk/client-secrets-manager');
|
|
15
15
|
|
|
16
16
|
describe('AWSProviderAdapter', () => {
|
|
17
|
-
describe('listStackResources', () => {
|
|
18
|
-
it.skip('should handle pagination and return all resources across multiple pages', async () => {
|
|
19
|
-
const mockCfClient = {
|
|
20
|
-
send: jest.fn()
|
|
21
|
-
.mockResolvedValueOnce({
|
|
22
|
-
StackResourceSummaries: [
|
|
23
|
-
{ LogicalResourceId: 'Resource1', PhysicalResourceId: 'res-1', ResourceType: 'AWS::EC2::VPC' },
|
|
24
|
-
{ LogicalResourceId: 'Resource2', PhysicalResourceId: 'res-2', ResourceType: 'AWS::EC2::Subnet' }
|
|
25
|
-
],
|
|
26
|
-
NextToken: 'token-page-2' // More pages available
|
|
27
|
-
})
|
|
28
|
-
.mockResolvedValueOnce({
|
|
29
|
-
StackResourceSummaries: [
|
|
30
|
-
{ LogicalResourceId: 'VPCEndpointS3', PhysicalResourceId: 'vpce-s3', ResourceType: 'AWS::EC2::VPCEndpoint' },
|
|
31
|
-
{ LogicalResourceId: 'VPCEndpointDynamoDB', PhysicalResourceId: 'vpce-ddb', ResourceType: 'AWS::EC2::VPCEndpoint' }
|
|
32
|
-
],
|
|
33
|
-
NextToken: null // Last page
|
|
34
|
-
})
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const provider = new AWSProviderAdapter('us-east-1');
|
|
38
|
-
provider.getCloudFormationClient = jest.fn().mockReturnValue(mockCfClient);
|
|
39
|
-
|
|
40
|
-
const resources = await provider.listStackResources('test-stack');
|
|
41
|
-
|
|
42
|
-
// Should have ALL resources from ALL pages
|
|
43
|
-
expect(resources).toHaveLength(4);
|
|
44
|
-
expect(resources.map(r => r.LogicalResourceId)).toEqual([
|
|
45
|
-
'Resource1',
|
|
46
|
-
'Resource2',
|
|
47
|
-
'VPCEndpointS3',
|
|
48
|
-
'VPCEndpointDynamoDB'
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
|
-
// Should have called CloudFormation twice (once for each page)
|
|
52
|
-
expect(mockCfClient.send).toHaveBeenCalledTimes(2);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
17
|
let provider;
|
|
57
18
|
|
|
58
19
|
beforeEach(() => {
|
|
@@ -95,23 +95,11 @@ async function gatherDiscoveredResources(appDefinition) {
|
|
|
95
95
|
const cfDiscovery = new CloudFormationDiscovery(provider, { serviceName, stage });
|
|
96
96
|
const stackResources = await cfDiscovery.discoverFromStack(stackName);
|
|
97
97
|
|
|
98
|
-
// Validate CF discovery results -
|
|
99
|
-
const hasVpcData = stackResources?.defaultVpcId;
|
|
100
|
-
const hasKmsData = stackResources?.defaultKmsKeyId;
|
|
101
|
-
const hasAuroraData = stackResources?.auroraClusterId;
|
|
102
|
-
|
|
103
|
-
// Check for routing infrastructure (proves VPC config exists even with external VPC)
|
|
104
|
-
const hasRoutingInfra = stackResources?.routeTableId || // FriggLambdaRouteTable
|
|
105
|
-
stackResources?.natRoute || // FriggNATRoute
|
|
106
|
-
stackResources?.vpcEndpoints?.s3 || // VPC endpoints
|
|
107
|
-
stackResources?.vpcEndpoints?.dynamodb;
|
|
108
|
-
|
|
109
|
-
// Stack is useful if it has EITHER actual resources OR routing infrastructure
|
|
110
|
-
const hasSomeUsefulData = hasVpcData || hasKmsData || hasAuroraData || hasRoutingInfra;
|
|
111
|
-
|
|
112
|
-
if (hasRoutingInfra && !hasVpcData) {
|
|
113
|
-
console.log(' ✓ Found VPC routing infrastructure in stack (external VPC pattern)');
|
|
114
|
-
}
|
|
98
|
+
// Validate CF discovery results - only use if contains useful data
|
|
99
|
+
const hasVpcData = stackResources?.defaultVpcId;
|
|
100
|
+
const hasKmsData = stackResources?.defaultKmsKeyId;
|
|
101
|
+
const hasAuroraData = stackResources?.auroraClusterId;
|
|
102
|
+
const hasSomeUsefulData = hasVpcData || hasKmsData || hasAuroraData;
|
|
115
103
|
|
|
116
104
|
// Check if we're in isolated mode (each stage gets its own VPC/Aurora)
|
|
117
105
|
const isIsolatedMode = appDefinition.managementMode === 'managed' &&
|
|
@@ -415,42 +415,6 @@ describe('Resource Discovery', () => {
|
|
|
415
415
|
delete process.env.SLS_STAGE;
|
|
416
416
|
});
|
|
417
417
|
|
|
418
|
-
it('should recognize routing infrastructure as useful data', async () => {
|
|
419
|
-
const appDefinition = {
|
|
420
|
-
name: 'test-app',
|
|
421
|
-
vpc: { enable: true },
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
process.env.SLS_STAGE = 'production';
|
|
425
|
-
|
|
426
|
-
// Mock CloudFormation discovery to return routing infrastructure but no VPC resource
|
|
427
|
-
const mockCloudFormationDiscovery = {
|
|
428
|
-
discoverFromStack: jest.fn().mockResolvedValue({
|
|
429
|
-
fromCloudFormationStack: true,
|
|
430
|
-
routeTableId: 'rtb-123',
|
|
431
|
-
natRoute: 'rtb-123|0.0.0.0/0',
|
|
432
|
-
vpcEndpoints: {
|
|
433
|
-
s3: 'vpce-s3',
|
|
434
|
-
dynamodb: 'vpce-ddb'
|
|
435
|
-
},
|
|
436
|
-
existingLogicalIds: ['FriggLambdaRouteTable', 'FriggNATRoute']
|
|
437
|
-
// NO defaultVpcId, NO defaultKmsKeyId, NO auroraClusterId
|
|
438
|
-
})
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
const { CloudFormationDiscovery } = require('./cloudformation-discovery');
|
|
442
|
-
CloudFormationDiscovery.mockImplementation(() => mockCloudFormationDiscovery);
|
|
443
|
-
|
|
444
|
-
const result = await gatherDiscoveredResources(appDefinition);
|
|
445
|
-
|
|
446
|
-
// Should use CloudFormation data without falling back to AWS API
|
|
447
|
-
expect(result.routeTableId).toBe('rtb-123');
|
|
448
|
-
expect(result.vpcEndpoints.s3).toBe('vpce-s3');
|
|
449
|
-
|
|
450
|
-
// Should NOT call AWS API discovery
|
|
451
|
-
expect(mockVpcDiscovery.discover).not.toHaveBeenCalled();
|
|
452
|
-
});
|
|
453
|
-
|
|
454
418
|
it('should include secrets in SSM discovery by default', async () => {
|
|
455
419
|
const appDefinition = {
|
|
456
420
|
ssm: { enable: true },
|