func validateGetAttAttributeID(builtin parse.IntrinsicFunction, resourceID, attributeID interface{}, ctx PropertyContext) reporting.Reports { resource := ctx.Template().Resources[resourceID.(string)] definition := ctx.Definitions().Lookup(resource.Type) switch t := attributeID.(type) { case string: if attribute, ok := definition.Attributes[t]; ok { targetType := attribute.Type switch targetType.CoercibleTo(ctx.Property().Type) { case CoercionNever: return reporting.Reports{reporting.NewFailure(ctx, "GetAtt value of %s.%s is %s but is being assigned to a %s property", resourceID, t, targetType.Describe(), ctx.Property().Type.Describe())} case CoercionBegrudgingly: return reporting.Reports{reporting.NewWarning(ctx, "GetAtt value of %s.%s is %s but is being dangerously coerced to a %s property", resourceID, t, targetType.Describe(), ctx.Property().Type.Describe())} } return nil } case parse.IntrinsicFunction: getAttAttributeNameType := Schema{Type: ValueString} _, errs := ValidateIntrinsicFunctions(t, NewPropertyContext(ctx, getAttAttributeNameType), SupportedFunctions{ parse.FnRef: true, }) return errs } return reporting.Reports{reporting.NewFailure(ctx, "%s is not an attribute of %s", attributeID, resource.Type)} }
func validateSelectArray(builtin parse.IntrinsicFunction, array interface{}, ctx PropertyContext) reporting.Reports { if array == nil { return reporting.Reports{reporting.NewFailure(ctx, "Array cannot be null")} } switch t := array.(type) { case []interface{}: reports := make(reporting.Reports, 0, 10) for i, item := range t { if item == nil { reports = append(reports, reporting.NewFailure(PropertyContextAdd(ctx, strconv.Itoa(i)), "Array item cannot be null")) } } return reporting.Safe(reports) case parse.IntrinsicFunction: arrayType := Schema{Type: Multiple(ValueString)} _, errs := ValidateIntrinsicFunctions(t, NewPropertyContext(ctx, arrayType), SupportedFunctions{ parse.FnRef: true, parse.FnFindInMap: true, parse.FnGetAtt: true, parse.FnGetAZs: true, parse.FnIf: true, }) return errs } return reporting.Reports{reporting.NewFailure(ctx, "Invalid value for array %s", array)} }
func validateGetAtt(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports { if errs := validateIntrinsicFunctionBasicCriteria(parse.FnGetAtt, builtin, ctx); errs != nil { return errs } value := builtin.UnderlyingMap[string(parse.FnGetAtt)] args, ok := value.([]interface{}) if !ok || args == nil { return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"Fn::GetAtt\" key: %T", value)} } if len(args) != 2 { return reporting.Reports{reporting.NewFailure(ctx, "GetAtt has incorrect number of arguments (expected: 2, actual: %d)", len(args))} } reports := make(reporting.Reports, 0, 10) resourceID := args[0] attributeID := args[1] if errs := validateGetAttResourceID(builtin, resourceID, ctx); errs != nil { reports = append(reports, errs...) } else if errs := validateGetAttAttributeID(builtin, resourceID, attributeID, ctx); errs != nil { reports = append(reports, errs...) } return reporting.Safe(reports) }
func validateEquals(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports { if errs := validateIntrinsicFunctionBasicCriteria(parse.FnEquals, builtin, ctx); errs != nil { return errs } value := builtin.UnderlyingMap[string(parse.FnEquals)] args, ok := value.([]interface{}) if !ok || args == nil { return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"Fn::Equals\" key: %T", value)} } if len(args) != 2 { return reporting.Reports{reporting.NewFailure(ctx, "Incorrect number of arguments (expected: 2, actual: %d)", len(args))} } reports := make(reporting.Reports, 0, 10) left := args[0] if errs := validateEqualsItem(left, PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "Value-1")); errs != nil { reports = append(reports, errs...) } right := args[1] if errs := validateEqualsItem(right, PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "Value-2")); errs != nil { reports = append(reports, errs...) } return reporting.Safe(reports) }
func validateAndOr(key parse.IntrinsicFunctionSignature, builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports { value, found := builtin.UnderlyingMap[string(key)] if !found || value == nil { return reporting.Reports{reporting.NewFailure(ctx, "Missing \"%s\" key", key)} } args, ok := value.([]interface{}) if !ok || args == nil { return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"%s\" key: %T", key, value)} } if len(builtin.UnderlyingMap) > 1 { return reporting.Reports{reporting.NewFailure(ctx, "Unexpected extra keys: %s", keysExcept(builtin.UnderlyingMap, string(key)))} } if len(args) < 2 || len(args) > 10 { return reporting.Reports{reporting.NewFailure(ctx, "Incorrect number of conditions (expected between 2 and 10, actual: %d)", len(args))} } reports := make(reporting.Reports, 0, 10) for i, condition := range args { if errs := validateAndOrItem(condition, PropertyContextAdd(ctx, strconv.Itoa(i))); errs != nil { reports = append(reports, errs...) } } return reporting.Safe(reports) }
// see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#d0e97711 func validateIf(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports { if errs := validateIntrinsicFunctionBasicCriteria(parse.FnIf, builtin, ctx); errs != nil { return errs } value := builtin.UnderlyingMap[string(parse.FnIf)] args, ok := value.([]interface{}) if !ok || args == nil { return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"Fn::If\" key: %T", value)} } if len(args) != 3 { return reporting.Reports{reporting.NewFailure(ctx, "Incorrect number of arguments [condition_name, true_value, false_value]", len(args))} } reports := make(reporting.Reports, 0, 10) conditionName := args[0] trueValue := args[1] falseValue := args[2] if errs := validateIfConditionName(conditionName, PropertyContextAdd(ctx, "ConditionName")); errs != nil { reports = append(reports, errs...) } if errs := validateIfValue(trueValue, PropertyContextAdd(ctx, "TrueValue")); errs != nil { reports = append(reports, errs...) } if errs := validateIfValue(falseValue, PropertyContextAdd(ctx, "FalseValue")); errs != nil { reports = append(reports, errs...) } return reporting.Safe(reports) }
// see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-join.html func validateJoin(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports { if errs := validateIntrinsicFunctionBasicCriteria(parse.FnJoin, builtin, ctx); errs != nil { return errs } value := builtin.UnderlyingMap[string(parse.FnJoin)] items, ok := value.([]interface{}) if !ok || items == nil { return reporting.Reports{reporting.NewFailure(ctx, "Invalid \"Fn::Join\" key: %s", items)} } if len(items) != 2 { return reporting.Reports{reporting.NewFailure(ctx, "Join has incorrect number of arguments (expected: 2, actual: %d)", len(items))} } reports := make(reporting.Reports, 0, 10) delimiter := items[0] values := items[1] if errs := validateJoinDelimiter(builtin, delimiter, ctx); errs != nil { reports = append(reports, errs...) } if errs := validateJoinList(builtin, values, ctx); errs != nil { reports = append(reports, errs...) } return reporting.Safe(reports) }
func validateIndexNumericalValue(index float64, array interface{}, ctx PropertyContext) reporting.Reports { if index < 0 { return reporting.Reports{reporting.NewFailure(ctx, "Index cannot less than zero")} } else if arr, ok := array.([]interface{}); ok && int(index) >= len(arr) { return reporting.Reports{reporting.NewFailure(ctx, "Index cannot greater than array length")} } return nil }
func validateIfConditionName(value interface{}, ctx PropertyContext) reporting.Reports { if name, ok := value.(string); ok { if _, found := ctx.Template().Conditions[name]; found { return nil } return reporting.Reports{reporting.NewFailure(ctx, "%s is not defined in the Conditions of the template", name)} } return reporting.Reports{reporting.NewFailure(ctx, "Invalid condition %s", value)} }
// validateIntrinsicFunctionBasicCriteria checks the essentials of an intrinsic // function call: // // 1. Does it have a key matching the IF's name // 2. Does that key have a value // 3. No unexpected properties alongside the IF key // // Validation will fail if any of those criteria don't pass. func validateIntrinsicFunctionBasicCriteria(key parse.IntrinsicFunctionSignature, builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports { value, found := builtin.UnderlyingMap[string(key)] if !found || value == nil { return reporting.Reports{reporting.NewFailure(ctx, "Missing \"%s\" key", key)} } if len(builtin.UnderlyingMap) > 1 { return reporting.Reports{reporting.NewFailure(ctx, "Unexpected extra keys: %s", keysExcept(builtin.UnderlyingMap, string(key)))} } return nil }
func validateGetAttResourceID(builtin parse.IntrinsicFunction, resourceID interface{}, ctx PropertyContext) reporting.Reports { switch t := resourceID.(type) { case string: if _, found := ctx.Template().Resources[t]; found { return nil } return reporting.Reports{reporting.NewFailure(ctx, "GetAtt %s is not a resource", t)} } return reporting.Reports{reporting.NewFailure(ctx, "GetAtt %s is not a valid resource name", resourceID)} }
func NewUnrecognisedResource(awsType string) Resource { return Resource{ ValidateFunc: func(ctx ResourceContext) (reporting.ValidateResult, reporting.Reports) { return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Unrecognised resource %s", awsType)} }, } }
func validateJoinDelimiter(builtin parse.IntrinsicFunction, delimiter interface{}, ctx PropertyContext) reporting.Reports { if _, ok := delimiter.(string); !ok { return reporting.Reports{reporting.NewFailure(ctx, "\"%s\" is not a valid delimiter", delimiter)} } return nil }
func validateJoinList(builtin parse.IntrinsicFunction, values interface{}, ctx PropertyContext) reporting.Reports { valuesCtx := PropertyContextAdd(ctx, "Values") switch parts := values.(type) { case []interface{}: reports := make(reporting.Reports, 0, 10) valueType := Schema{Type: ValueString} for i, part := range parts { if errs := validateJoinListValue(part, PropertyContextAdd(NewPropertyContext(valuesCtx, valueType), strconv.Itoa(i))); errs != nil { reports = append(reports, errs...) } } return reporting.Safe(reports) case parse.IntrinsicFunction: listType := Schema{Type: Multiple(ValueString)} _, errs := ValidateIntrinsicFunctions(parts, NewPropertyContext(valuesCtx, listType), SupportedFunctions{ parse.FnBase64: true, parse.FnFindInMap: true, parse.FnGetAtt: true, parse.FnGetAZs: true, parse.FnIf: true, parse.FnJoin: true, parse.FnSelect: true, parse.FnRef: true, }) return errs } return reporting.Reports{reporting.NewFailure(ctx, "Join items are not valid: %s", values)} }
func (p Properties) Validate(ctx ResourceContext) reporting.Reports { failures := make(reporting.Reports, 0, len(p)*2) visited := make(map[string]bool) self := ctx.CurrentResource() for key, schema := range p { visited[key] = true value, _ := self.PropertyValue(key) keyCtx := ResourceContextAdd(ctx, key) // Validate conflicting properties if value != nil && schema.Conflicts != nil && schema.Conflicts.Pass(self) { failures = append(failures, reporting.NewFailure(keyCtx, "Conflict: %s", schema.Conflicts.Describe(self))) } // Validate Required if value == nil && schema.Required != nil && schema.Required.Pass(self) { failures = append(failures, reporting.NewFailure(keyCtx, "%s is required because %s", key, schema.Required.Describe(self))) } // Validate Deprecated if value != nil && schema.Deprecated != nil { failures = append(failures, reporting.NewWarning(keyCtx, schema.Deprecated.Describe())) continue } // assuming the above either failed and logged some failures, or passed and // we can safely skip over a nil property if value == nil { continue } if _, errs := schema.Validate(value, keyCtx); errs != nil { failures = append(failures, errs...) } } // Reject any properties we weren't expecting for _, key := range self.Properties() { if !visited[key] { failures = append(failures, reporting.NewFailure(ResourceContextAdd(ctx, key), "%s is not a property of %s", key, self.AwsType())) } } return reporting.Safe(failures) }
// validateCondition validates the inline { "Condition": "X" } structure. It // isn't technically a condition itself, acting more like a Ref. func validateCondition(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports { if errs := validateIntrinsicFunctionBasicCriteria(parse.FnCondition, builtin, ctx); errs != nil { return errs } value := builtin.UnderlyingMap[string(parse.FnCondition)] condition, ok := value.(string) if !ok || condition == "" { return reporting.Reports{reporting.NewFailure(ctx, `Invalid type for "Condition" key: %T`, value)} } if _, found := ctx.Template().Conditions[condition]; !found { return reporting.Reports{reporting.NewFailure(ctx, "%s is not defined in the Conditions of the template", condition)} } return nil }
func SingleValueValidate(expected interface{}) ValidateFunc { return func(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { if value != expected { return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Value must be %d but is %d", expected, value)} } return reporting.ValidateOK, nil } }
func automaticFailoverEnabledValidation(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { if version, found := ctx.CurrentResource().PropertyValueOrDefault("EngineVersion"); found { if versionNumber, err := strconv.ParseFloat(version.(string), 64); err == nil { if versionNumber < 2.8 { return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "EngineVersion must be 2.8 or higher for Automatic Failover")} } } } if nodeType, found := ctx.CurrentResource().PropertyValueOrDefault("CacheNodeType"); found { split := strings.Split(nodeType.(string), ".") if split[1] == "t1" || split[1] == "t2" { return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "CacheNodeType must not be T1 or T2 Automatic Failover")} } } return reporting.ValidateOK, nil }
func validateFindInMapMapName(builtin parse.IntrinsicFunction, mapName interface{}, ctx PropertyContext) reporting.Reports { if mapName == nil { return reporting.Reports{reporting.NewFailure(ctx, "Cannot be null")} } switch t := mapName.(type) { case string: // TODO: validate actual map exists return nil case parse.IntrinsicFunction: _, errs := ValidateIntrinsicFunctions(t, ctx, SupportedFunctions{ parse.FnFindInMap: true, parse.FnRef: true, }) return errs } return reporting.Reports{reporting.NewFailure(ctx, "Invalid MapName: %v", mapName)} }
func NumberOptions(numbers ...float64) ValidateFunc { return func(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { for _, n := range numbers { if n == value.(float64) { return reporting.ValidateOK, nil } } return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Number must be one of %v", numbers)} } }
func validateFindInMapSecondLevelKey(builtin parse.IntrinsicFunction, mapName, topLevelKey, secondLevelKey interface{}, ctx PropertyContext) reporting.Reports { if secondLevelKey == nil { return reporting.Reports{reporting.NewFailure(ctx, "Cannot be null")} } switch t := secondLevelKey.(type) { case string: // TODO: validate actual map second level key exists, if possible return nil case parse.IntrinsicFunction: _, errs := ValidateIntrinsicFunctions(t, ctx, SupportedFunctions{ parse.FnFindInMap: true, parse.FnRef: true, }) return errs } return reporting.Reports{reporting.NewFailure(ctx, "Invalid SecondLevelKey: %v", secondLevelKey)} }
func validateAndOrItem(value interface{}, ctx PropertyContext) reporting.Reports { if value == nil { return reporting.Reports{reporting.NewFailure(ctx, "Value is null")} } switch t := value.(type) { case parse.IntrinsicFunction: _, errs := ValidateIntrinsicFunctions(t, ctx, SupportedFunctions{ parse.FnAnd: true, parse.FnCondition: true, parse.FnEquals: true, parse.FnIf: true, parse.FnNot: true, parse.FnOr: true, }) return errs } return reporting.Reports{reporting.NewFailure(ctx, "Invalid condition: %#v", value)} }
func azModeValidate(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { if str, ok := value.(string); ok { if availabilityZones, ok := ctx.CurrentResource().PropertyValueOrDefault("PreferredAvailabilityZones"); ok { if str == "cross-az" && len(availabilityZones.([]interface{})) < 2 { return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Cross-AZ clusters must have multiple preferred availability zones")} } } } return reporting.ValidateOK, nil }
func validateSelectIndex(builtin parse.IntrinsicFunction, index interface{}, array interface{}, ctx PropertyContext) reporting.Reports { if index == nil { return reporting.Reports{reporting.NewFailure(ctx, "Index cannot be null")} } switch t := index.(type) { case string: return reporting.Reports{reporting.NewWarning(ctx, "Wrong type for index %T (can't ensure validity of index)", index)} case float64: return validateIndexNumericalValue(t, array, ctx) case parse.IntrinsicFunction: indexType := Schema{Type: ValueNumber} _, errs := ValidateIntrinsicFunctions(t, NewPropertyContext(ctx, indexType), SupportedFunctions{ parse.FnRef: true, parse.FnFindInMap: true, }) return errs } return reporting.Reports{reporting.NewFailure(ctx, "Invalid value for index %#v", index)} }
// TODO: fixme func FixedArrayValidate(options ...[]string) ValidateFunc { return func(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { for _, option := range options { if match(option, value.([]interface{})) { return reporting.ValidateOK, nil } } // TODO: this should be []TypeString but we can't specify that with this method return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Invalid list value: %s, expected one of [%s]", value, options)} } }
// TODO: This is all a bit hairy. We shouldn't need to be creating the // TemplateNestedResource here, ideally `self` should already refer to // one and value should already be a map[string]inteface{} func (res NestedResource) Validate(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { if values, ok := value.(map[string]interface{}); ok { property := ctx.Property() tnr := parse.NewTemplateResource(property.Type.Describe(), values) nestedResourceContext := NewResourceContext(ctx, ResourceWithDefinition{tnr, property.Type}) failures := res.Properties.Validate(nestedResourceContext) return reporting.ValidateOK, reporting.Safe(failures) } return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Invalid type %T for nested resource %s", value, res.Description)} }
func (id resourceID) Validate(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { if result, errs := ValueString.Validate(value, ctx); result == reporting.ValidateAbort || errs != nil { return reporting.ValidateOK, errs } if !id.regex.MatchString(value.(string)) { return reporting.ValidateOK, reporting.Reports{ reporting.NewFailure(ctx, "Value '%s' is not a valid %s, format: %s", value, id.description, id.example), } } return reporting.ValidateOK, nil }
// see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-select.html func validateSelect(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports { if errs := validateIntrinsicFunctionBasicCriteria(parse.FnSelect, builtin, ctx); errs != nil { return errs } value := builtin.UnderlyingMap[string(parse.FnSelect)] switch t := value.(type) { case []interface{}: return validateSelectParameters(builtin, t, ctx) } return reporting.Reports{reporting.NewFailure(ctx, "Invalid \"Fn::Select\" key: %s", value)} }
func outputValidate(o parse.Output, ctx Context) (reporting.ValidateResult, reporting.Reports) { if o.Description != nil { if _, ok := o.Description.(string); !ok { return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ContextAdd(ctx, "Description"), "Expected a string")} } } outputContext := NewResourceContext(ctx, emptyCurrentResource{}) if _, errs := outputSchema.Validate(o.Value, ResourceContextAdd(outputContext, "Value")); errs != nil { return reporting.ValidateOK, errs } return reporting.ValidateOK, nil }
// see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-findinmap.html func validateFindInMap(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports { if errs := validateIntrinsicFunctionBasicCriteria(parse.FnFindInMap, builtin, ctx); errs != nil { return errs } value := builtin.UnderlyingMap[string(parse.FnFindInMap)] args, ok := value.([]interface{}) if !ok { return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"Fn::FindInMap\" key: %T", value)} } if len(args) != 3 { return reporting.Reports{reporting.NewFailure(ctx, "Options has wrong number of items, expected: 3, actual: %d", len(args))} } reports := make(reporting.Reports, 0, 10) mapName := args[0] mapNameCtx := PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "MapName") if errs := validateFindInMapMapName(builtin, mapName, mapNameCtx); errs != nil { reports = append(reports, errs...) } topLevelKey := args[1] topLevelKeyCtx := PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "TopLevelKey") if errs := validateFindInMapTopLevelKey(builtin, mapName, topLevelKey, topLevelKeyCtx); errs != nil { reports = append(reports, errs...) } secondLevelKey := args[2] secondLevelKeyCtx := PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "SecondLevelKey") if errs := validateFindInMapSecondLevelKey(builtin, mapName, topLevelKey, secondLevelKey, secondLevelKeyCtx); errs != nil { reports = append(reports, errs...) } return reporting.Safe(reports) }