// validateValue takes a value and validates it against the Type of the // current Schema and optionally runs any custom validation functions. // // This function is used for single value properties, and each item in array // properties. func validateValue(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { results := make(reporting.Reports, 0, 50) result, errs := ctx.Property().Type.Validate(value, ctx) if result == reporting.ValidateAbort { // type validation instructed us to abort, so we bail with whatever results // have been reported so far return reporting.ValidateAbort, reporting.Safe(errs) } results = append(results, errs...) // run the custom validation if there is any, optionally bailing if the // validate tells us to, otherwise combining the results with any prior // results if fn := ctx.Property().ValidateFunc; fn != nil { result, errs := fn(value, ctx) if result == reporting.ValidateAbort { return reporting.ValidateAbort, reporting.Safe(errs) } results = append(results, errs...) } return reporting.ValidateOK, reporting.Safe(results) }
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 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 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) }
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 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) }
// 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 (rd Resource) Validate(ctx ResourceContext) (reporting.ValidateResult, reporting.Reports) { if rd.ValidateFunc != nil { return rd.ValidateFunc(ctx) } failures := rd.Properties.Validate(ctx) return reporting.ValidateOK, reporting.Safe(failures) }
func (vt ValueType) Validate(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { if isValidValueForType(vt, value) { return reporting.ValidateOK, nil } switch t := value.(type) { case parse.IntrinsicFunction: return ValidateIntrinsicFunctions(t, ctx, SupportedFunctionsAll) case map[string]interface{}: return reporting.ValidateAbort, reporting.Reports{reporting.NewFailure(ctx, "Object used in %s property", vt.Describe())} } errs := coerce(valueTypeOf(value), vt, ctx) if errs != nil { // We've either dangerously coerced, or failed to coerce, the value here // so we should abort further validations to prevent any type-specific // validations from running. e.g. StringLength or IntegerRange return reporting.ValidateAbort, reporting.Safe(errs) } return reporting.ValidateOK, reporting.Safe(errs) }
// 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 (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) }
// validateMapWhereArrayShouldBe runs validations against a map which was found // where an Array was expected; this is possibly valid, and could either be a // function reference or some attempt at coercion. func validateMapWhereArrayShouldBe(arrayType ArrayPropertyType, itemSchema Schema, value map[string]interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { if ctx.Options()[OptionExperimentDisableObjectArrayCoercion] { return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "%s used instead of %s", arrayType.Unwrap().Describe(), arrayType.Describe())} } // CloudFormation appears to allow you to flatten a single item array // for array properties, e.g. X: [Y] can be specified as X: Y // // So in this case if we get a map here just validate it against the // schema for the item of the array and provide a warning. results := make(reporting.Reports, 0, 25) if _, errs := itemSchema.Validate(value, ctx); errs != nil { results = append(results, errs...) } results = append(results, reporting.NewWarning(ctx, "%s used instead of %s", arrayType.Unwrap().Describe(), arrayType.Describe())) return reporting.ValidateOK, reporting.Safe(results) }
func validateSelectParameters(builtin parse.IntrinsicFunction, args []interface{}, ctx PropertyContext) reporting.Reports { if len(args) != 2 { return reporting.Reports{reporting.NewFailure(ctx, "Wrong number of arguments to Fn::Select (expected 2, got %d)", len(args))} } reports := make(reporting.Reports, 0, 10) index := args[0] array := args[1] if errs := validateSelectIndex(builtin, index, array, PropertyContextAdd(ctx, "Index")); errs != nil { reports = append(reports, errs...) } if errs := validateSelectArray(builtin, array, PropertyContextAdd(ctx, "Array")); errs != nil { reports = append(reports, errs...) } return reporting.Safe(reports) }
func ValidateIntrinsicFunctions(value parse.IntrinsicFunction, ctx PropertyContext, supportedFunctions SupportedFunctions) (reporting.ValidateResult, reporting.Reports) { if !supportedFunctions[value.Key] { return reporting.ValidateAbort, reporting.Reports{reporting.NewFailure(ctx, "%s not valid in this location", value.Key)} } var reports reporting.Reports switch value.Key { case parse.FnAnd: reports = validateAnd(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnBase64: reports = validateBase64(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnCondition: reports = validateCondition(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnEquals: reports = validateEquals(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnFindInMap: reports = validateFindInMap(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnGetAtt: reports = validateGetAtt(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnGetAZs: reports = validateGetAZs(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnIf: reports = validateIf(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnJoin: reports = validateJoin(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnNot: reports = validateNot(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnOr: reports = validateOr(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnRef: reports = validateRef(value, PropertyContextAdd(ctx, string(value.Key))) case parse.FnSelect: reports = validateSelect(value, PropertyContextAdd(ctx, string(value.Key))) default: panic(fmt.Errorf("Unexpected Intrinsic Function %s", value.Key)) } return reporting.ValidateAbort, reporting.Safe(reports) }
func validateArray(arrayType ArrayPropertyType, value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { itemSchema := Schema{ Type: arrayType.Unwrap(), } switch t := value.(type) { case []interface{}: results := make(reporting.Reports, 0, 25) for i, item := range t { if _, errs := itemSchema.Validate(item, ResourceContextAdd(ctx, strconv.Itoa(i))); errs != nil { results = append(results, errs...) } } return reporting.ValidateOK, reporting.Safe(results) case parse.IntrinsicFunction: return ValidateIntrinsicFunctions(t, ctx, SupportedFunctionsAll) case map[string]interface{}: return validateMapWhereArrayShouldBe(arrayType, itemSchema, t, ctx) default: return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "%T used in %s property", t, arrayType.Describe())} } }
// 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) }