diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index 6513da0..4bb65de 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -43,69 +43,19 @@ func NewResourceFromConfig(resourceIdentifier ruleset.ResourceIdentifier, resour } func (r *resource) CompareResult(values map[string]interface{}) *CompareResult { - enforcedArgs := make(map[string]interface{}) - failedArgs := make(map[string]interface{}) - ignored := make(map[string]interface{}) - extraArgs := make(map[string]interface{}) - - // Passed in the plan's values - // Iterate over each key/value - for k, v := range values { - // If the key is ignored, record and continue - if _, ok := r.Ignored[k]; ok { - ignored[k] = true - continue - } - // If the key is enforced... - if enforced, ok := r.Enforced[k]; ok { - switch { - case enforced.Value != nil: - // Verify the value is what is expected - if !equal(enforced.Value, v) { - // Not equal - record as failed - failedArgs[k] = FailedArg{ - Expected: enforced.Value, - Actual: v, - } - } else { - // equal - enforcedArgs[k] = enforced - } - case enforced.MatchAny != nil: - found := false - for _, val := range enforced.MatchAny { - // Verify the value is what is expected - if equal(val, v) { - // equal - enforcedArgs[k] = enforced - found = true - break - } - } - if !found { - failedArgs[k] = FailedArg{ - Expected: fmt.Sprintf("one of: %v", enforced.MatchAny), - Actual: v, - MatchAny: true, - } - } - default: - // TODO: Tests that key exists and that's it - intended? - } - // key is not enforced or ignored - } else { - extraArgs[k] = true - } + result := &CompareResult{ + Enforced: make(map[string]interface{}), + Failed: make(map[string]interface{}), + Ignored: make(map[string]interface{}), + Extra: make(map[string]interface{}), } - return &CompareResult{ - Enforced: enforcedArgs, - Failed: failedArgs, - Ignored: ignored, - Extra: extraArgs, - MissingEnforced: setDifference(enforcedSetDifference(r.Enforced, enforcedArgs), failedArgs), - MissingIgnored: setDifference(setDifference(r.Ignored, ignored), failedArgs), - } + result.checkValues(r.Enforced, r.Ignored, values, "") + + result.MissingEnforced = setDifference(enforcedSetDifference(make(map[string]interface{}), "", r.Enforced, result.Enforced), result.Failed) + result.MissingIgnored = setDifference(setDifference(r.Ignored, result.Ignored), result.Failed) + + return result } func (r *resource) Compare(rv ResourceValues) bool { @@ -206,10 +156,14 @@ func setDifference(a, b map[string]interface{}) map[string]interface{} { // enforcedSetDifference returns elements in A but not in B // only checks for key equality - ignores values -func enforcedSetDifference(a map[string]ruleset.EnforceChange, b map[string]interface{}) map[string]interface{} { - result := make(map[string]interface{}) +func enforcedSetDifference(result map[string]interface{}, keyPrefix string, a map[string]ruleset.EnforceChange, b map[string]interface{}) map[string]interface{} { for k, v := range a { - if _, ok := b[k]; !ok { + if keyPrefix != "" { + k = fmt.Sprintf("%s.%s", keyPrefix, k) + } + if v.EnforceChange != nil { + result = enforcedSetDifference(result, k, v.EnforceChange, b) + } else if _, ok := b[k]; !ok { result[k] = v } } diff --git a/pkg/resource/resource_test.go b/pkg/resource/resource_test.go index de5105b..d49c4a9 100644 --- a/pkg/resource/resource_test.go +++ b/pkg/resource/resource_test.go @@ -225,6 +225,37 @@ func TestResourceCompareResult(t *testing.T) { }, }, }, + "nested enforced value": { + resource: &resource{ + Enforced: map[string]ruleset.EnforceChange{ + "key": { + EnforceChange: map[string]ruleset.EnforceChange{ + "nested-key": { + Value: true, + }, + }, + }, + }, + CompareOptions: &CompareOptions{}, + }, + values: map[string]interface{}{ + "key": map[string]interface{}{ + "nested-key": true, + }, + }, + expected: &CompareResult{ + Enforced: map[string]interface{}{ + "key.nested-key": ruleset.EnforceChange{ + Value: true, + }, + }, + Failed: map[string]interface{}{}, + Ignored: map[string]interface{}{}, + Extra: map[string]interface{}{}, + MissingEnforced: map[string]interface{}{}, + MissingIgnored: map[string]interface{}{}, + }, + }, } for name, tc := range cases { diff --git a/pkg/resource/result.go b/pkg/resource/result.go index cd71e74..7f8fa96 100644 --- a/pkg/resource/result.go +++ b/pkg/resource/result.go @@ -1,5 +1,11 @@ package resource +import ( + "fmt" + + "github.com/drlau/akashi/pkg/ruleset" +) + type CompareResult struct { // args that had a matching EnforcedValue and are equal Enforced map[string]interface{} @@ -20,6 +26,70 @@ type CompareResult struct { MissingIgnored map[string]interface{} } +func (cr *CompareResult) checkValues(enforced map[string]ruleset.EnforceChange, ignored map[string]interface{}, values map[string]interface{}, keyPrefix string) { + // Passed in the plan's values + // Iterate over each key/value + for k, v := range values { + key := k + if keyPrefix != "" { + key = fmt.Sprintf("%s.%s", keyPrefix, k) + } + // If the key is ignored, record and continue + if _, ok := ignored[k]; ok { + // TODO: handle nested ignore + cr.Ignored[k] = true + continue + } + // If the key is enforced... + if enforced, ok := enforced[k]; ok { + switch { + case enforced.Value != nil: + // Verify the value is what is expected + if !equal(enforced.Value, v) { + // Not equal - record as failed + cr.Failed[key] = FailedArg{ + Expected: enforced.Value, + Actual: v, + } + } else { + // equal + cr.Enforced[key] = enforced + } + case enforced.MatchAny != nil: + found := false + for _, val := range enforced.MatchAny { + // Verify the value is what is expected + if equal(val, v) { + // equal + cr.Enforced[key] = enforced + found = true + break + } + } + if !found { + cr.Failed[key] = FailedArg{ + Expected: fmt.Sprintf("one of: %v", enforced.MatchAny), + Actual: v, + MatchAny: true, + } + } + case enforced.EnforceChange != nil: + casted, ok := values[k].(map[string]interface{}) + if ok { + cr.checkValues(enforced.EnforceChange, ignored, casted, k) + } else { + // failed + fmt.Println("failed to cast - failed enforced") + } + default: + // TODO: Tests that key exists and that's it - intended? + } + } else { + cr.Extra[key] = true + } + } +} + func (cr *CompareResult) GetEnforced() map[string]interface{} { return cr.Enforced } diff --git a/pkg/ruleset/ruleset.go b/pkg/ruleset/ruleset.go index 4e40f44..72d8ace 100644 --- a/pkg/ruleset/ruleset.go +++ b/pkg/ruleset/ruleset.go @@ -77,6 +77,7 @@ type ResourceRules struct { } type EnforceChange struct { - Value interface{} `yaml:"value,omitempty"` - MatchAny []interface{} `yaml:"matchAny,omitempty"` + Value interface{} `yaml:"value,omitempty"` + MatchAny []interface{} `yaml:"matchAny,omitempty"` + EnforceChange map[string]EnforceChange `yaml:",inline"` }