diff --git a/src/schema/transformers/MutuallyExclusivePropertiesForValidation.ts b/src/schema/transformers/MutuallyExclusivePropertiesForValidation.ts index 987549cf..2ba63fb7 100644 --- a/src/schema/transformers/MutuallyExclusivePropertiesForValidation.ts +++ b/src/schema/transformers/MutuallyExclusivePropertiesForValidation.ts @@ -4,7 +4,7 @@ export interface DependentExcluded { export const dependentExcludedMap = getDependentExcludedMap(); -function getDependentExcludedMap(): Map { +function getDependentExcludedMap() { const dependentExcludedMap = new Map(); dependentExcludedMap.set('AWS::CloudFront::Distribution', { @@ -66,5 +66,73 @@ function getDependentExcludedMap(): Map { HealthCheckCustomConfig: ['HealthCheckConfig'], }); - return dependentExcludedMap; + return dependentExcludedMap as ReadonlyMap; +} + +export type RequiredXor = string[]; +export const requiredXorMap = getRequiredXorMap(); + +function getRequiredXorMap() { + const requiredXorMap = new Map(); + + requiredXorMap.set('AWS::ApplicationAutoScaling::ScalingPolicy', [['ScalingTargetId', 'ResourceId']]); + + requiredXorMap.set('AWS::AutoScaling::AutoScalingGroup', [ + ['InstanceId', 'LaunchConfigurationName', 'LaunchTemplate', 'MixedInstancesPolicy'], + ['LaunchTemplateId', 'LaunchTemplateName'], // path: /definitions/LaunchTemplateSpecification + ]); + + requiredXorMap.set('AWS::AutoScaling::LaunchConfiguration', [ + ['VirtualName', 'Ebs', 'NoDevice'], // path: /definitions/BlockDeviceMapping + ]); + + requiredXorMap.set('AWS::CloudFront::Distribution', [ + ['AcmCertificateArn', 'CloudFrontDefaultCertificate', 'IamCertificateId'], // path: /definitions/ViewerCertificate + ]); + + requiredXorMap.set('AWS::CloudWatch::Alarm', [['Metrics', 'MetricName']]); + + requiredXorMap.set('AWS::CodePipeline::Pipeline', [['ArtifactStore', 'ArtifactStores']]); + + requiredXorMap.set('AWS::EC2::Instance', [ + ['VirtualName', 'Ebs', 'NoDevice'], // path: /definitions/BlockDeviceMapping + ]); + + requiredXorMap.set('AWS::EC2::LaunchTemplate', [ + ['VirtualName', 'Ebs', 'NoDevice'], // path: /definitions/BlockDeviceMapping + ]); + + requiredXorMap.set('AWS::EC2::NetworkAclEntry', [['Ipv6CidrBlock', 'CidrBlock']]); + + requiredXorMap.set('AWS::EC2::SecurityGroup', [ + ['CidrIp', 'CidrIpv6', 'DestinationSecurityGroupId', 'DestinationPrefixListId'], // path: /definitions/Egress + ['CidrIp', 'CidrIpv6', 'SourcePrefixListId', 'SourceSecurityGroupId', 'SourceSecurityGroupName'], // path: /definitions/Ingress + ]); + + requiredXorMap.set('AWS::EC2::SecurityGroupEgress', [ + ['CidrIp', 'CidrIpv6', 'DestinationPrefixListId', 'DestinationSecurityGroupId'], + ]); + + requiredXorMap.set('AWS::EC2::SecurityGroupIngress', [ + ['CidrIp', 'CidrIpv6', 'SourcePrefixListId', 'SourceSecurityGroupId', 'SourceSecurityGroupName'], + ]); + + requiredXorMap.set('AWS::EC2::SpotFleet', [ + ['VirtualName', 'Ebs', 'NoDevice'], // path: /definitions/BlockDeviceMapping + ['LaunchSpecifications', 'LaunchTemplateConfigs'], // path: /definitions/SpotFleetRequestConfigData + ]); + + requiredXorMap.set('AWS::EC2::VPC', [['CidrBlock', 'Ipv4IpamPoolId']]); + + requiredXorMap.set('AWS::ElasticLoadBalancingV2::LoadBalancer', [['Subnets', 'SubnetMappings']]); + + requiredXorMap.set('AWS::OpsWorks::Instance', [ + ['VirtualName', 'Ebs', 'NoDevice'], // path: /definitions/BlockDeviceMapping + ]); + + requiredXorMap.set('AWS::Route53::RecordSet', [['HostedZoneId', 'HostedZoneName']]); + + requiredXorMap.set('AWS::Route53::RecordSetGroup', [['HostedZoneId', 'HostedZoneName']]); + + return requiredXorMap as ReadonlyMap; } diff --git a/src/schema/transformers/RemoveRequiredXorPropertiesTransformer.ts b/src/schema/transformers/RemoveRequiredXorPropertiesTransformer.ts new file mode 100644 index 00000000..54121157 --- /dev/null +++ b/src/schema/transformers/RemoveRequiredXorPropertiesTransformer.ts @@ -0,0 +1,42 @@ +import { ResourceSchema } from '../ResourceSchema'; +import { RequiredXor, requiredXorMap } from './MutuallyExclusivePropertiesForValidation'; +import { ResourceTemplateTransformer } from './ResourceTemplateTransformer'; + +export class RemoveRequiredXorPropertiesTransformer implements ResourceTemplateTransformer { + transform(resourceProperties: Record, schema: ResourceSchema): void { + const resourceType = schema.typeName; + const requiredXorData = requiredXorMap.get(resourceType); + if (!requiredXorData) { + return; + } + this.removeXorProperties(resourceProperties, requiredXorData); + } + + private removeXorProperties(obj: Record, requiredXorData: RequiredXor[]) { + for (const keysArray of requiredXorData) { + const firstFound = keysArray.find((key) => key in obj); + + if (firstFound) { + // Remove all other keys in the xor array + for (const key of keysArray) { + if (key !== firstFound && key in obj) { + delete obj[key]; + } + } + } + } + + // Recursively process nested objects + for (const value of Object.values(obj)) { + if (value && typeof value === 'object' && !Array.isArray(value)) { + this.removeXorProperties(value as Record, requiredXorData); + } else if (Array.isArray(value)) { + for (const item of value) { + if (item && typeof item === 'object') { + this.removeXorProperties(item as Record, requiredXorData); + } + } + } + } + } +} diff --git a/src/schema/transformers/TransformersUtil.ts b/src/schema/transformers/TransformersUtil.ts index 2841cec5..468345e7 100644 --- a/src/schema/transformers/TransformersUtil.ts +++ b/src/schema/transformers/TransformersUtil.ts @@ -1,6 +1,7 @@ import { ResourceStatePurpose } from '../../resourceState/ResourceStateTypes'; import { RemoveMutuallyExclusivePropertiesTransformer } from './RemoveMutuallyExclusivePropertiesTransformer'; import { RemoveReadonlyPropertiesTransformer } from './RemoveReadonlyPropertiesTransformer'; +import { RemoveRequiredXorPropertiesTransformer } from './RemoveRequiredXorPropertiesTransformer'; import { RemoveSystemTagsTransformer } from './RemoveSystemTagsTransformer'; import { ReplacePrimaryIdentifierTransformer } from './ReplacePrimaryIdentifierTransformer'; import type { ResourceTemplateTransformer } from './ResourceTemplateTransformer'; @@ -12,6 +13,7 @@ export class TransformersUtil { new RemoveReadonlyPropertiesTransformer(), new RemoveMutuallyExclusivePropertiesTransformer(), new RemoveSystemTagsTransformer(), + new RemoveRequiredXorPropertiesTransformer(), ]; } else if (purpose === ResourceStatePurpose.CLONE) { return [ @@ -19,6 +21,7 @@ export class TransformersUtil { new RemoveMutuallyExclusivePropertiesTransformer(), new RemoveSystemTagsTransformer(), new ReplacePrimaryIdentifierTransformer(), + new RemoveRequiredXorPropertiesTransformer(), ]; } return [];