func TestResourcePropertyRequiredUnlessValidation(t *testing.T) { template := &parse.Template{} res := Resource{ Properties: Properties{ "Option1": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("Option2"), }, "Option2": Schema{ Type: ValueString, }, }, } ctx := NewInitialContext(template, NewResourceDefinitions(map[string]Resource{ "TestResource": res, }), ValidationOptions{}) nothingSet := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{}), res, } option1Set := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option1": "value", }), res, } option2Set := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option2": "value", }), res, } bothSet := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option1": "value", "Option2": "value", }), res, } if _, errs := res.Validate(NewResourceContext(ctx, nothingSet)); errs == nil { t.Error("Resource should fail if neither Option1 or Option2 are set") } if _, errs := res.Validate(NewResourceContext(ctx, option1Set)); errs != nil { t.Error("Resource should pass if only Option1 set", errs) } if _, errs := res.Validate(NewResourceContext(ctx, option2Set)); errs != nil { t.Error("Resource should pass if only Option2 set", errs) } if _, errs := res.Validate(NewResourceContext(ctx, bothSet)); errs != nil { t.Error("Resource should pass if both Option1 and Option2 are set", errs) } }
"github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html var healthCheckConfig = NestedResource{ Description: "Route 53 HealthCheckConfig", Properties: Properties{ "FailureThreshold": Schema{ Type: ValueNumber, }, "FullyQualifiedDomainName": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("IPAddress"), }, "IPAddress": Schema{ Type: IPAddress, Required: constraints.PropertyNotExists("FullyQualifiedDomainName"), }, "Port": Schema{ Type: ValueNumber, Required: constraints.PropertyIs("Type", "TCP"), }, "RequestInterval": Schema{ Type: ValueNumber, },
package cloud_front import ( "github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) var viewerCertificate = NestedResource{ Description: "CloudFront DistributionConfiguration ViewerCertificate", Properties: Properties{ "CloudFrontDefaultCertificate": Schema{ Type: ValueBool, Conflicts: constraints.PropertyExists("IamCertificateId"), Required: constraints.PropertyNotExists("IamCertificateId"), }, "IamCertificateId": Schema{ Type: ValueString, Conflicts: constraints.PropertyExists("CloudFrontDefaultCertificate"), Required: constraints.PropertyNotExists("CloudFrontDefaultCertificate"), }, "MinimumProtocolVersion": Schema{ Type: ValueString, // TODO: If you specify the IamCertificateId property and specify SNI only // for the SslSupportMethod property, you must use TLSv1 for the // minimum protocol version. If you don't specify a value, AWS // CloudFormation specifies SSLv3. }, "SslSupportMethod": Schema{
. "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-security-group-egress.html var SecurityGroupEgress = Resource{ AwsType: "AWS::EC2::SecurityGroupEgress", // Name ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "CidrIp": Schema{ Type: CIDR, Required: constraints.PropertyNotExists("DestinationSecurityGroupId"), Conflicts: constraints.PropertyExists("DestinationSecurityGroupId"), }, "DestinationSecurityGroupId": Schema{ Type: SecurityGroupID, Required: constraints.PropertyNotExists("CidrIp"), Conflicts: constraints.PropertyExists("CidrIp"), }, "FromPort": Schema{ Type: ValueNumber, Required: constraints.Always, }, "GroupId": Schema{
"Description": Schema{ Type: ValueString, }, "EnvironmentName": Schema{ Type: ValueString, }, "OptionSettings": Schema{ Type: Multiple(optionsSettings), }, "SolutionStackName": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("TemplateName"), }, "Tags": Schema{ Type: Multiple(common.ResourceTag), }, "TemplateName": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("SolutionStackName"), }, "Tier": Schema{ Type: tier, },
"CidrIp": Schema{ Type: CIDR, Conflicts: constraints.Any{ constraints.PropertyExists("SourceSecurityGroupName"), constraints.PropertyExists("SourceSecurityGroupId"), }, }, "FromPort": Schema{ Type: ValueNumber, Required: constraints.Always, }, "GroupId": Schema{ Type: SecurityGroupID, Required: constraints.PropertyNotExists("GroupName"), }, "GroupName": Schema{ Type: SecurityGroupName, Required: constraints.PropertyNotExists("GroupId"), }, "IpProtocol": Schema{ Required: constraints.Always, Type: ipProtocol, }, "SourceSecurityGroupId": Schema{ Type: SecurityGroupID, Conflicts: constraints.PropertyExists("CidrIp"),
. "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html var AutoScalingGroup = Resource{ AwsType: "AWS::AutoScaling::AutoScalingGroup", // Name ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "AvailabilityZones": Schema{ Type: Multiple(AvailabilityZone), Required: constraints.PropertyNotExists("VPCZoneIdentifier"), }, "Cooldown": Schema{ Type: ValueString, }, "DesiredCapacity": Schema{ Type: ValueString, }, "HealthCheckGracePeriod": Schema{ Type: ValueNumber, }, "HealthCheckType": Schema{
. "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-gateway-attachment.html var VPCGatewayAttachment = Resource{ AwsType: "AWS::EC2::VPCGatewayAttachment", // Name ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "InternetGatewayId": Schema{ Type: InternetGatewayID, Required: constraints.PropertyNotExists("VpnGatewayId"), Conflicts: constraints.PropertyExists("VpnGatewayId"), }, "VpcId": Schema{ Required: constraints.Always, Type: VpcID, }, "VpnGatewayId": Schema{ Type: VpnGatewayID, Required: constraints.PropertyNotExists("InternetGatewayId"), Conflicts: constraints.PropertyExists("InternetGatewayId"), }, }, }
Required: constraints.PropertyExists("KmsKeyId"), }, "Iops": Schema{ Type: ValueNumber, Required: constraints.PropertyIs("VolumeType", "io1"), ValidateFunc: IntegerRangeValidate(1, 4000), }, "KmsKeyId": Schema{ Type: ARN, }, "Size": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("SnapshotId"), }, "SnapshotId": Schema{ Type: ValueString, }, "Tags": Schema{ Type: Multiple(common.ResourceTag), }, "VolumeType": Schema{ Type: common.EbsVolumeType, }, }, }
"EngineVersion": Schema{ Type: ValueString, }, "KmsKeyId": Schema{ Type: ARN, }, "MasterUsername": Schema{ Type: ValueString, ValidateFunc: RegexpValidate( `^[a-zA-Z][a-zA-Z0-9]{1,15}$`, "Must be 1 to 16 alphanumeric characters. First character must be a letter.", ), Required: constraints.PropertyNotExists("SnapshotIdentifier"), Conflicts: constraints.PropertyExists("SnapshotIdentifier"), }, "MasterUserPassword": Schema{ Type: ValueString, ValidateFunc: RegexpValidate( `^[^\/"@]{8,41}$`, `This password can contain any printable ASCII character except "/", """, or "@". Must contain from 8 to 41 characters.`, ), Required: constraints.PropertyNotExists("SnapshotIdentifier"), Conflicts: constraints.PropertyExists("SnapshotIdentifier"), }, "Port": Schema{ Type: ValueNumber,
package cloud_front import ( "github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-origin.html var origin = NestedResource{ Description: "CloudFront DistributionConfig Origin", Properties: Properties{ "CustomOriginConfig": Schema{ Type: customOriginConfig, Conflicts: constraints.PropertyExists("S3OriginConfig"), Required: constraints.PropertyNotExists("S3OriginConfig"), }, "DomainName": Schema{ Type: ValueString, Required: constraints.Always, }, "Id": Schema{ Type: ValueString, Required: constraints.Always, }, "OriginPath": Schema{ Type: ValueString, ValidateFunc: RegexpValidate(`^\/.*?[^\/]$`, "The value must start with a slash mark (/) and cannot end with a slash mark."), },
package s3 import ( "github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-lifecycleconfig-rule-transition.html var lifecycleRuleTransition = NestedResource{ Description: "S3 Lifecycle Rule Transition", Properties: Properties{ "StorageClass": Schema{ Type: storageClass, Required: constraints.Always, }, "TransitionDate": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("TransitionInDays"), }, "TransitionInDays": Schema{ Type: ValueNumber, Required: constraints.PropertyNotExists("TransitionDate"), }, }, }
package s3 import ( "github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-websiteconfiguration-routingrules-routingrulecondition.html var websiteConfigurationRoutingRuleCondition = NestedResource{ Description: "S3 Website Configuration Routing Rules Routing Rule Condition", Properties: Properties{ "HttpErrorCodeReturnedEquals": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("KeyPrefixEquals"), }, "KeyPrefixEquals": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("HttpErrorCodeReturnedEquals"), }, }, }
) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html var Policy = Resource{ AwsType: "AWS::IAM::Policy", // Name ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "Groups": Schema{ Type: Multiple(ValueString), Required: constraints.All{ constraints.PropertyNotExists("Roles"), constraints.PropertyNotExists("Users"), }, }, "PolicyDocument": Schema{ Type: JSON, Required: constraints.Always, }, "PolicyName": Schema{ Type: ValueString, Required: constraints.Always, }, "Roles": Schema{
package s3 import ( "github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-lifecycleconfig-rule.html var lifecycleRule = NestedResource{ Description: "AWS::S3::LifecycleRule", Properties: Properties{ "ExpirationDate": Schema{ Type: ValueString, Required: constraints.All{ constraints.PropertyNotExists("ExpirationInDays"), constraints.PropertyNotExists("NoncurrentVersionExpirationInDays"), constraints.PropertyNotExists("NoncurrentVersionTransition"), constraints.PropertyNotExists("Transition"), }, }, "ExpirationInDays": Schema{ Type: ValueNumber, Required: constraints.All{ constraints.PropertyNotExists("ExpirationDate"), constraints.PropertyNotExists("NoncurrentVersionExpirationInDays"), constraints.PropertyNotExists("NoncurrentVersionTransition"), constraints.PropertyNotExists("Transition"), }, },
func recordSetProperties(includeComment bool) Properties { properties := Properties{ "AliasTarget": Schema{ Type: aliasTarget, Conflicts: constraints.Any{ constraints.PropertyExists("ResourceRecords"), constraints.PropertyExists("TTL"), }, }, "Failover": Schema{ Type: EnumValue{ Description: "Failover", Options: []string{"PRIMARY", "SECONDARY"}, }, }, "GeoLocation": Schema{ Type: geoLocation, }, "HealthCheckId": Schema{ Type: ValueString, }, "HostedZoneId": Schema{ Type: HostedZoneID, Conflicts: constraints.PropertyExists("HostedZoneName"), }, "HostedZoneName": Schema{ Type: ValueString, Conflicts: constraints.PropertyExists("HostedZoneId"), }, "Name": Schema{ Type: ValueString, Required: constraints.Always, }, // TODO: Region validation: http://docs.aws.amazon.com/general/latest/gr/rande.html "Region": Schema{ Type: ValueString, }, "ResourceRecords": Schema{ Type: Multiple(ValueString), Conflicts: constraints.PropertyExists("AliasTarget"), Required: constraints.PropertyNotExists("AliasTarget"), }, "SetIdentifier": Schema{ Type: ValueString, Required: constraints.Any{ constraints.PropertyExists("Weight"), constraints.PropertyExists("Latency"), constraints.PropertyExists("Failover"), constraints.PropertyExists("GeoLocation"), }, }, "TTL": Schema{ Type: ValueString, Conflicts: constraints.PropertyExists("AliasTarget"), }, "Type": Schema{ Type: recordSetType, Required: constraints.Always, }, "Weight": Schema{ Type: ValueNumber, }, } if includeComment { properties["Comment"] = Schema{ Type: ValueString, } } return properties }
"PrivateIpAddress": Schema{ Type: IPAddress, }, "PrivateIpAddresses": Schema{ Type: Multiple(privateIPAddressSpecification), }, "SecondaryPrivateIpAddressCount": Schema{ Type: ValueNumber, }, "SubnetId": Schema{ Type: SubnetID, Required: constraints.PropertyNotExists("NetworkInterfaceId"), }, }, } // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html var Instance = Resource{ AwsType: "AWS::EC2::Instance", Attributes: map[string]Schema{ "AvailabilityZone": Schema{ Type: AvailabilityZone, }, "PrivateDnsName": Schema{ Type: ValueString,
}, Properties: Properties{ "ApplicationName": Schema{ Type: ValueString, Required: constraints.Always, }, "Description": Schema{ Type: ValueString, }, "EnvironmentId": Schema{ Type: ValueString, Required: constraints.All{ constraints.PropertyNotExists("SolutionStackName"), constraints.PropertyNotExists("SourceConfiguration"), }, }, "OptionSettings": Schema{ Type: Multiple(optionsSettings), }, "SolutionStackName": Schema{ Type: ValueString, Required: constraints.All{ constraints.PropertyNotExists("EnvironmentId"), constraints.PropertyNotExists("SourceConfiguration"), }, },
. "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata-launchspecifications-blockdevicemappings.html var spotFleetRequestConfigDataLaunchSpecificationBlockDeviceMapping = NestedResource{ Description: "SpotFleet SpotFleetRequestConfigData LaunchSpecifications BlockDeviceMapping", Properties: Properties{ "DeviceName": Schema{ Type: ValueString, Required: constraints.Always, }, "Ebs": Schema{ Type: spotFleetRequestConfigDataLaunchSpecificationBlockDeviceMappingEbs, Required: constraints.PropertyNotExists("VirtualName"), Conflicts: constraints.PropertyExists("VirtualName"), }, "NoDevice": Schema{ Type: ValueBool, }, "VirtualName": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("Ebs"), Conflicts: constraints.PropertyExists("Ebs"), ValidateFunc: RegexpValidate( "^ephemeral\\d+$", "The name must be in the form ephemeralX where X is a number starting from zero (0), for example, ephemeral0", ),
package route_53 import ( "github.com/jagregory/cfval/constraints" "github.com/jagregory/cfval/reporting" "github.com/jagregory/cfval/resources/common" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordset-geolocation.html var geoLocation = NestedResource{ Description: "Route 53 Record Set GeoLocation", Properties: Properties{ "ContinentCode": Schema{ Type: continentCode, Required: constraints.PropertyNotExists("CountryCode"), Conflicts: constraints.Any{ constraints.PropertyExists("CountryCode"), constraints.PropertyExists("SubdivisionCode"), }, }, "CountryCode": Schema{ Type: common.CountryCode, Required: constraints.PropertyNotExists("ContinentCode"), Conflicts: constraints.PropertyExists("ContinentCode"), }, "SubdivisionCode": Schema{ Type: subdivisionCode, Conflicts: constraints.PropertyExists("ContinentCode"),
// Name ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "DestinationCidrBlock": Schema{ Type: CIDR, Required: constraints.Always, }, "GatewayId": Schema{ Type: InternetGatewayID, Required: constraints.All{ constraints.PropertyNotExists("InstanceId"), constraints.PropertyNotExists("NatGatewayId"), constraints.PropertyNotExists("NetworkInterfaceId"), constraints.PropertyNotExists("VpcPeeringConnectionId"), }, Conflicts: constraints.Any{ constraints.PropertyExists("InstanceId"), constraints.PropertyExists("NatGatewayId"), constraints.PropertyExists("NetworkInterfaceId"), constraints.PropertyExists("VpcPeeringConnectionId"), }, }, "InstanceId": Schema{ Type: InstanceID, Required: constraints.All{
// see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordsetgroup.html var RecordSetGroup = Resource{ AwsType: "AWS::Route53::RecordSetGroup", // Name ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "Comment": Schema{ Type: ValueString, }, "HostedZoneId": Schema{ Type: HostedZoneID, Required: constraints.PropertyNotExists("HostedZoneName"), }, "HostedZoneName": Schema{ Type: ValueString, Required: constraints.PropertyNotExists("HostedZoneId"), }, "RecordSets": Schema{ Type: Multiple(recordSetGroupRecordSet), Required: constraints.Always, }, }, }
AwsType: "AWS::EC2::DHCPOptions", // Name ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "DomainName": Schema{ Type: ValueString, }, "DomainNameServers": Schema{ Type: Multiple(IPAddress), Required: constraints.All{ constraints.PropertyNotExists("NetbiosNameServers"), constraints.PropertyNotExists("NtpServers"), }, }, "NetbiosNameServers": Schema{ Type: Multiple(IPAddress), Required: constraints.All{ constraints.PropertyNotExists("DomainNameServers"), constraints.PropertyNotExists("NtpServers"), }, }, "NetbiosNodeType": Schema{ Type: ValueNumber, Required: constraints.PropertyExists("NetBiosNameServers"),
import ( "github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html var code = NestedResource{ Description: "Lambda Function Code", Properties: Properties{ "S3Bucket": Schema{ Type: ValueString, Required: constraints.Any{ constraints.PropertyExists("S3Key"), constraints.PropertyNotExists("ZipFile"), }, }, "S3Key": Schema{ Type: ValueString, Required: constraints.Any{ constraints.PropertyExists("S3Bucket"), constraints.PropertyNotExists("ZipFile"), }, }, "S3ObjectVersion": Schema{ Type: ValueString, Conflicts: constraints.Any{ constraints.PropertyNotExists("S3Bucket"),
}, // ID ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "AllocatedStorage": Schema{ Type: ValueString, // TODO: If any value is used in the Iops parameter, AllocatedStorage // must be at least 100 GB, which corresponds to the minimum Iops // value of 1000. If Iops is increased (in 1000 IOPS increments), // then AllocatedStorage must also be increased (in 100 GB // increments) correspondingly. Required: constraints.PropertyNotExists("DBClusterIdentifier"), Conflicts: constraints.PropertyExists("DBClusterIdentifier"), }, "AllowMajorVersionUpgrade": Schema{ Type: ValueBool, // TODO: This parameter must be set to true when you specify an // EngineVersion that differs from the DB instance's current // major version. }, "AutoMinorVersionUpgrade": Schema{ Type: ValueBool, Default: true, },