예제 #1
0
파일: schema.go 프로젝트: jagregory/cfval
// 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)
}
예제 #2
0
파일: if_join.go 프로젝트: jagregory/cfval
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)}
}
예제 #3
0
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)}
}
예제 #4
0
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)
}
예제 #5
0
파일: if_if.go 프로젝트: jagregory/cfval
// 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)
}
예제 #6
0
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)
}
예제 #7
0
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)
}
예제 #8
0
파일: if_join.go 프로젝트: jagregory/cfval
// 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)
}
예제 #9
0
파일: resource.go 프로젝트: jagregory/cfval
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)
}
예제 #10
0
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)
}
예제 #11
0
// 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)}
}
예제 #12
0
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)
}
예제 #13
0
파일: schema.go 프로젝트: jagregory/cfval
// 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)
}
예제 #14
0
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)
}
예제 #15
0
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)
}
예제 #16
0
파일: schema.go 프로젝트: jagregory/cfval
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())}
	}
}
예제 #17
0
// 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)
}