func TestResourcePropertyConflictValidation(t *testing.T) { template := &parse.Template{} res := Resource{ Properties: Properties{ "Option1": Schema{ Type: ValueString, Conflicts: constraints.PropertyExists("Option2"), }, "Option2": Schema{ Type: ValueString, Conflicts: constraints.PropertyExists("Option1"), }, }, } 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 pass if both neither Option1 or Option2 are set", errs) } 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 fail if both Option1 or Option2 are set") } }
ReturnValue: Schema{ Type: ARN, }, Properties: Properties{ "Cluster": Schema{ Type: ValueString, }, "DesiredCount": Schema{ Type: ValueString, Required: constraints.Always, }, "LoadBalancers": Schema{ Type: Multiple(loadBalancer), }, "Role": Schema{ Type: ValueString, Required: constraints.PropertyExists("LoadBalancers"), }, "TaskDefinition": Schema{ Type: ValueString, Required: constraints.Always, }, }, }
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{
) // 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{ Type: SecurityGroupID,
package ec2 import ( "github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html var SecurityGroupIngress = Resource{ AwsType: "AWS::EC2::SecurityGroupIngress", Properties: Properties{ "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{
}, Properties: Properties{ "Enabled": Schema{ Type: ValueBool, Default: true, }, "EventCategories": Schema{ Type: Multiple(ValueString), }, "SnsTopicArn": Schema{ Type: ARN, Required: constraints.Always, }, "SourceIds": Schema{ Type: Multiple(ValueString), }, "SourceType": Schema{ Type: ValueString, Required: constraints.Any{ constraints.PropertyExists("SourceIds"), constraints.PropertyExists("EventCategories"), }, }, }, }
. "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-websiteconfiguration-routingrules-redirectrule.html var websiteConfigurationRoutingRuleRedirectRule = NestedResource{ Description: "S3 Website Configuration Routing Rules Redirect Rule", Properties: Properties{ "HostName": Schema{ Type: ValueString, }, "HttpRedirectCode": Schema{ Type: ValueString, }, "Protocol": Schema{ Type: ValueString, }, "ReplaceKeyPrefixWith": Schema{ Type: ValueString, Conflicts: constraints.PropertyExists("ReplaceKeyWith"), }, "ReplaceKeyWith": Schema{ Type: ValueString, Conflicts: constraints.PropertyExists("ReplaceKeyPrefixWith"), }, }, }
ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "AccessLoggingPolicy": Schema{ Type: accessLoggingPolicy, }, "AppCookieStickinessPolicy": Schema{ Type: Multiple(appCookieStickinessPolicy), }, "AvailabilityZones": Schema{ Type: Multiple(AvailabilityZone), Conflicts: constraints.PropertyExists("Subnets"), }, "ConnectionDrainingPolicy": Schema{ Type: connectionDrainingPolicy, }, "ConnectionSettings": Schema{ Type: connectionSettings, }, "CrossZone": Schema{ Type: ValueBool, Default: false, },
"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, Default: 3306,
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."), },
"github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distributionconfig-customerrorresponse.html var customErrorResponse = NestedResource{ Description: "CloudFront DistributionConfig CustomErrorResponse", Properties: Properties{ "ErrorCachingMinTTL": Schema{ Type: ValueNumber, }, "ErrorCode": Schema{ Type: ValueNumber, Required: constraints.Always, ValidateFunc: NumberOptions(400, 403, 404, 405, 414, 500, 501, 502, 503, 504), }, "ResponseCode": Schema{ Type: ValueNumber, Required: constraints.PropertyExists("ResponsePagePath"), ValidateFunc: NumberOptions(200, 400, 403, 404, 405, 414, 500, 501, 502, 503, 504), }, "ResponsePagePath": Schema{ Type: ValueString, Required: constraints.PropertyExists("ResponseCode"), }, }, }
ValidateFunc: azModeValidate, Default: "single-az", }, "CacheNodeType": Schema{ Type: cacheNodeType, Required: constraints.Always, }, "CacheParameterGroupName": Schema{ Type: ValueString, }, "CacheSecurityGroupNames": Schema{ Type: Multiple(cacheSecurityGroupName), Conflicts: constraints.PropertyExists("VpcSecurityGroupIds"), }, "CacheSubnetGroupName": Schema{ Type: cacheSubnetGroupName, }, "ClusterName": Schema{ Type: ValueString, }, "Engine": Schema{ Type: engine, Required: constraints.Always, },
package rds import ( "github.com/jagregory/cfval/constraints" . "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-optiongroup-optionconfigurations.html var optionConfiguration = NestedResource{ Description: "RDS OptionGroup OptionConfiguration", Properties: Properties{ "DBSecurityGroupMemberships": Schema{ Type: Multiple(dbSecurityGroupName), Conflicts: constraints.PropertyExists("VPCSecurityGroupMemberships"), }, "OptionName": Schema{ Type: ValueString, Required: constraints.Always, }, "OptionSettings": Schema{ Type: optionSettings, }, "Port": Schema{ Type: ValueNumber, }, "VpcSecurityGroupMemberships": Schema{
func TestNestedResourceConstraints(t *testing.T) { res := Resource{ Properties: Properties{ "Nested": Schema{ Type: NestedResource{ Properties: Properties{ "One": Schema{ Type: ValueString, }, "Two": Schema{ Type: ValueString, Required: constraints.PropertyExists("One"), }, }, }, }, }, } template := &parse.Template{} data := func(properties map[string]interface{}) ResourceContext { return NewContextShorthand( template, NewResourceDefinitions(map[string]Resource{ "TestResource": res, }), ResourceWithDefinition{ parse.NewTemplateResource("TestResource", properties), res, }, Schema{}, ValidationOptions{}, ) } twoMissing := map[string]interface{}{ "Nested": map[string]interface{}{ "One": "abc", }, } if _, errs := res.Validate(data(twoMissing)); errs == nil { t.Error("Should fail with missing Two parameter") } oneInWrongPace := map[string]interface{}{ "One": "abc", "Nested": map[string]interface{}{}, } if _, errs := res.Validate(data(oneInWrongPace)); errs == nil { t.Error("Should fail with missing Two parameter") } allFine := map[string]interface{}{ "Nested": map[string]interface{}{ "One": "abc", "Two": "abc", }, } if _, errs := res.Validate(data(allFine)); errs != nil { t.Error("Should pass with One and Two", errs) } }
Properties: Properties{ "AssociatePublicIpAddress": Schema{ Type: ValueBool, }, "BlockDeviceMappings": Schema{ Type: Multiple(autoScalingBlockDeviceMapping), }, "ClassicLinkVPCId": Schema{ Type: VpcID, }, "ClassicLinkVPCSecurityGroups": Schema{ Type: Multiple(SecurityGroupID), Required: constraints.PropertyExists("ClassicLinkVPCId"), }, "EbsOptimized": Schema{ Type: ValueBool, Default: false, }, "IamInstanceProfile": Schema{ Type: ValueString, ValidateFunc: StringLengthValidate(1, 1600), }, "ImageId": Schema{ Type: ImageID, },
Type: VolumeID, }, Properties: Properties{ "AutoEnableIO": Schema{ Type: ValueBool, }, "AvailabilityZone": Schema{ Type: AvailabilityZone, Required: constraints.Always, }, "Encrypted": Schema{ Type: ValueBool, 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"),
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 }
"KernelId": Schema{ Type: ValueString, }, "KeyName": Schema{ Type: KeyName, }, "Monitoring": Schema{ Type: ValueBool, }, "NetworkInterfaces": Schema{ Type: Multiple(networkInterface), Conflicts: constraints.Any{ constraints.PropertyExists("SecurityGroupIds"), constraints.PropertyExists("SubnetId"), }, }, "PlacementGroupName": Schema{ Type: ValueString, }, "PrivateIpAddress": Schema{ Type: IPAddress, }, "RamdiskId": Schema{ Type: ValueString, },
) // 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"), }, }, }
) // 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", ), },
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"), ValidateFunc: func(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { if countryCode, found := ctx.CurrentResource().PropertyValueOrDefault("CountryCode"); found && countryCode != "US" {
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{ constraints.PropertyNotExists("GatewayId"), constraints.PropertyNotExists("NatGatewayId"), constraints.PropertyNotExists("NetworkInterfaceId"), constraints.PropertyNotExists("VpcPeeringConnectionId"), }, Conflicts: constraints.Any{
. "github.com/jagregory/cfval/schema" ) // see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudtrail-trail.html var Trail = Resource{ AwsType: "AWS::CloudTrail::Trail", // Name ReturnValue: Schema{ Type: ValueString, }, Properties: Properties{ "CloudWatchLogsLogGroupArn": Schema{ Type: ARN, Required: constraints.PropertyExists("CloudWatchLogsRoleArn"), }, "CloudWatchLogsRoleArn": Schema{ Type: ARN, }, "EnableLogFileValidation": Schema{ Type: ValueBool, }, "IncludeGlobalServiceEvents": Schema{ Type: ValueBool, }, "IsLogging": Schema{
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"), }, "NtpServers": Schema{ Type: Multiple(IPAddress), Required: constraints.All{ constraints.PropertyNotExists("DomainNameServers"), constraints.PropertyNotExists("NetbiosNameServers"), }, }, "Tags": Schema{ Type: Multiple(common.ResourceTag), }, }, }
package lambda 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{
// 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, }, "AvailabilityZone": Schema{