2019-02-09 04:22:55 +01:00
package objchange
import (
"testing"
"github.com/apparentlymart/go-dump/dump"
"github.com/zclconf/go-cty/cty"
2021-05-17 21:17:09 +02:00
"github.com/hashicorp/terraform/internal/configs/configschema"
2021-05-17 19:11:06 +02:00
"github.com/hashicorp/terraform/internal/tfdiags"
2019-02-09 04:22:55 +01:00
)
func TestAssertPlanValid ( t * testing . T ) {
tests := map [ string ] struct {
Schema * configschema . Block
Prior cty . Value
Config cty . Value
Planned cty . Value
WantErrs [ ] string
} {
"all empty" : {
& configschema . Block { } ,
cty . EmptyObjectVal ,
cty . EmptyObjectVal ,
cty . EmptyObjectVal ,
nil ,
} ,
"no computed, all match" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingList ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
"no computed, plan matches, no prior" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingList ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"a" : cty . String ,
"b" : cty . List ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
"no computed, invalid change in plan" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingList ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"a" : cty . String ,
"b" : cty . List ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "new c value" ) ,
} ) ,
} ) ,
} ) ,
[ ] string {
` .b[0].c: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value") ` ,
} ,
} ,
2019-02-11 23:18:58 +01:00
"no computed, invalid change in plan sensitive" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingList ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
Sensitive : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"a" : cty . String ,
"b" : cty . List ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "new c value" ) ,
} ) ,
} ) ,
} ) ,
[ ] string {
` .b[0].c: sensitive planned value does not match config value ` ,
} ,
} ,
2019-02-09 04:22:55 +01:00
"no computed, diff suppression in plan" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingList ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "new c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . StringVal ( "a value" ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) , // plan uses value from prior object
} ) ,
} ) ,
} ) ,
nil ,
} ,
"no computed, all null" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingList ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NullVal ( cty . String ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NullVal ( cty . String ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NullVal ( cty . String ) ,
"b" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
2019-03-17 01:49:16 +01:00
"nested map, normal update" : {
& configschema . Block {
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingMap ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . MapVal ( map [ string ] cty . Value {
"boop" : cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "hello" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . MapVal ( map [ string ] cty . Value {
"boop" : cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "howdy" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . MapVal ( map [ string ] cty . Value {
"boop" : cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "howdy" ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
2019-02-09 04:22:55 +01:00
// Nested block collections are never null
"nested list, null in plan" : {
& configschema . Block {
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingList ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"b" : cty . List ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . ListValEmpty ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . NullVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ) ,
} ) ,
[ ] string {
` .b: attribute representing a list of nested blocks must be empty to indicate no blocks, not null ` ,
} ,
} ,
2021-06-11 19:08:26 +02:00
// blocks can be unknown when using dynamic
"nested list, unknown nested dynamic" : {
& configschema . Block {
BlockTypes : map [ string ] * configschema . NestedBlock {
"a" : {
Nesting : configschema . NestingList ,
Block : configschema . Block {
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingList ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
"computed" : {
Type : cty . String ,
Computed : true ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value { cty . ObjectVal ( map [ string ] cty . Value {
"computed" : cty . NullVal ( cty . String ) ,
"b" : cty . ListVal ( [ ] cty . Value { cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "x" ) ,
} ) } ) ,
} ) } ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value { cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . UnknownVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
"computed" : cty . String ,
} ) ) ) ,
} ) } ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value { cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . UnknownVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
"computed" : cty . String ,
} ) ) ) ,
} ) } ) ,
} ) ,
[ ] string { } ,
} ,
"nested set, unknown dynamic cannot be planned" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"computed" : {
Type : cty . String ,
Computed : true ,
} ,
} ,
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingSet ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"computed" : cty . NullVal ( cty . String ) ,
"b" : cty . SetVal ( [ ] cty . Value { cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "x" ) ,
} ) } ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"computed" : cty . NullVal ( cty . String ) ,
"b" : cty . UnknownVal ( cty . Set ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"computed" : cty . StringVal ( "default" ) ,
"b" : cty . SetVal ( [ ] cty . Value { cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "oops" ) ,
} ) } ) ,
} ) ,
[ ] string {
` .b: planned value cty.SetVal([]cty.Value { cty.ObjectVal(map[string]cty.Value { "c":cty.StringVal("oops")})}) for unknown dynamic block ` ,
} ,
} ,
2019-02-09 04:22:55 +01:00
"nested set, null in plan" : {
& configschema . Block {
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingSet ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"b" : cty . Set ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetValEmpty ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . NullVal ( cty . Set ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ) ,
} ) ,
[ ] string {
` .b: attribute representing a set of nested blocks must be empty to indicate no blocks, not null ` ,
} ,
} ,
"nested map, null in plan" : {
& configschema . Block {
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingMap ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"b" : cty . Map ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . MapValEmpty ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . NullVal ( cty . Map ( cty . Object ( map [ string ] cty . Type {
"c" : cty . String ,
} ) ) ) ,
} ) ,
[ ] string {
` .b: attribute representing a map of nested blocks must be empty to indicate no blocks, not null ` ,
} ,
} ,
// We don't actually do any validation for nested set blocks, and so
// the remaining cases here are just intending to ensure we don't
// inadvertently start generating errors incorrectly in future.
"nested set, no computed, no changes" : {
& configschema . Block {
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingSet ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
"nested set, no computed, invalid change in plan" : {
& configschema . Block {
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingSet ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "new c value" ) , // matches neither prior nor config
} ) ,
} ) ,
} ) ,
nil ,
} ,
"nested set, no computed, diff suppressed" : {
& configschema . Block {
BlockTypes : map [ string ] * configschema . NestedBlock {
"b" : {
Nesting : configschema . NestingSet ,
Block : configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"c" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "new c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"c" : cty . StringVal ( "c value" ) , // plan uses value from prior object
} ) ,
} ) ,
} ) ,
nil ,
} ,
2021-02-05 19:41:06 +01:00
// Attributes with NestedTypes
"NestedType attr, no computed, all match" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"b" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "b value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "b value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "b value" ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
"NestedType attr, no computed, plan matches, no prior" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"b" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"a" : cty . List ( cty . Object ( map [ string ] cty . Type {
"b" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
"NestedType, no computed, invalid change in plan" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"b" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"a" : cty . List ( cty . Object ( map [ string ] cty . Type {
"b" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "c value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "new c value" ) ,
} ) ,
} ) ,
} ) ,
[ ] string {
` .a[0].b: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value") ` ,
} ,
} ,
"NestedType attr, no computed, invalid change in plan sensitive" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"b" : {
Type : cty . String ,
Optional : true ,
Sensitive : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"a" : cty . List ( cty . Object ( map [ string ] cty . Type {
"b" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "b value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "new b value" ) ,
} ) ,
} ) ,
} ) ,
[ ] string {
` .a[0].b: sensitive planned value does not match config value ` ,
} ,
} ,
"NestedType attr, no computed, diff suppression in plan" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"b" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "b value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "new b value" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"b" : cty . StringVal ( "b value" ) , // plan uses value from prior object
} ) ,
} ) ,
} ) ,
nil ,
} ,
"NestedType attr, no computed, all null" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"b" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NullVal ( cty . DynamicPseudoType ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NullVal ( cty . DynamicPseudoType ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NullVal ( cty . DynamicPseudoType ) ,
} ) ,
nil ,
} ,
"NestedType attr, no computed, all zero value" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"a" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"b" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NullVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"b" : cty . String ,
} ) ) ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NullVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"b" : cty . String ,
} ) ) ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"a" : cty . NullVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"b" : cty . String ,
} ) ) ) ,
} ) ,
nil ,
} ,
"NestedType NestingSet attribute to null" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"bloop" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingSet ,
Attributes : map [ string ] * configschema . Attribute {
"blop" : {
Type : cty . String ,
Required : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blop" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . NullVal ( cty . Set ( cty . Object ( map [ string ] cty . Type {
"blop" : cty . String ,
} ) ) ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . NullVal ( cty . Set ( cty . Object ( map [ string ] cty . Type {
"blop" : cty . String ,
} ) ) ) ,
} ) ,
nil ,
} ,
"NestedType deep nested optional set attribute to null" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"bleep" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"bloop" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingSet ,
Attributes : map [ string ] * configschema . Attribute {
"blome" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"bleep" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blome" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"bleep" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . NullVal ( cty . Set (
cty . Object ( map [ string ] cty . Type {
"blome" : cty . String ,
} ) ,
) ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"bleep" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . NullVal ( cty . List (
cty . Object ( map [ string ] cty . Type {
"blome" : cty . String ,
} ) ,
) ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
"NestedType deep nested set" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"bleep" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"bloop" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingSet ,
Attributes : map [ string ] * configschema . Attribute {
"blome" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"bleep" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blome" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
} ) ,
} ) ,
// Note: bloop is null in the config
cty . ObjectVal ( map [ string ] cty . Value {
"bleep" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . NullVal ( cty . Set (
cty . Object ( map [ string ] cty . Type {
"blome" : cty . String ,
} ) ,
) ) ,
} ) ,
} ) ,
} ) ,
// provider sends back the prior value, not matching the config
cty . ObjectVal ( map [ string ] cty . Value {
"bleep" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blome" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
} ) ,
} ) ,
nil , // we cannot validate individual set elements, and trust the provider's response
} ,
"NestedType nested computed list attribute" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"bloop" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"blop" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Computed : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blop" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . NullVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"blop" : cty . String ,
} ) ) ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blop" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
"NestedType nested list attribute to null" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"bloop" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"blop" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blop" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . NullVal ( cty . List ( cty . Object ( map [ string ] cty . Type {
"blop" : cty . String ,
} ) ) ) ,
} ) ,
// provider returned the old value
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blop" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
2021-02-24 18:13:12 +01:00
[ ] string { ` .bloop: planned value cty.ListVal([]cty.Value { cty.ObjectVal(map[string]cty.Value { "blop":cty.StringVal("ok")})}) for a non-computed attribute ` } ,
2021-02-05 19:41:06 +01:00
} ,
"NestedType nested set attribute to null" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"bloop" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingSet ,
Attributes : map [ string ] * configschema . Attribute {
"blop" : {
Type : cty . String ,
Optional : true ,
} ,
} ,
} ,
Optional : true ,
} ,
} ,
} ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blop" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . NullVal ( cty . Set ( cty . Object ( map [ string ] cty . Type {
"blop" : cty . String ,
} ) ) ) ,
} ) ,
// provider returned the old value
cty . ObjectVal ( map [ string ] cty . Value {
"bloop" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"blop" : cty . StringVal ( "ok" ) ,
} ) ,
} ) ,
} ) ,
2021-02-24 18:13:12 +01:00
[ ] string { ` .bloop: planned value cty.ListVal([]cty.Value { cty.ObjectVal(map[string]cty.Value { "blop":cty.StringVal("ok")})}) for a non-computed attribute ` } ,
2021-02-05 19:41:06 +01:00
} ,
2021-08-30 19:35:47 +02:00
"computed in nested objects" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"map" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingMap ,
Attributes : map [ string ] * configschema . Attribute {
"name" : {
Type : cty . String ,
Computed : true ,
} ,
} ,
} ,
} ,
// When an object has dynamic attrs, the map may be
// handled as an object.
"map_as_obj" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingMap ,
Attributes : map [ string ] * configschema . Attribute {
"name" : {
Type : cty . String ,
Computed : true ,
} ,
} ,
} ,
} ,
"list" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingList ,
Attributes : map [ string ] * configschema . Attribute {
"name" : {
Type : cty . String ,
Computed : true ,
} ,
} ,
} ,
} ,
"set" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingSet ,
Attributes : map [ string ] * configschema . Attribute {
"name" : {
Type : cty . String ,
Computed : true ,
} ,
} ,
} ,
} ,
"single" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingSingle ,
Attributes : map [ string ] * configschema . Attribute {
"name" : {
Type : cty . DynamicPseudoType ,
Computed : true ,
} ,
} ,
} ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"map" : cty . Map ( cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ) ,
"map_as_obj" : cty . Map ( cty . Object ( map [ string ] cty . Type {
"name" : cty . DynamicPseudoType ,
} ) ) ,
"list" : cty . List ( cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ) ,
"set" : cty . Set ( cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ) ,
"single" : cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"map" : cty . MapVal ( map [ string ] cty . Value {
"one" : cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
"map_as_obj" : cty . ObjectVal ( map [ string ] cty . Value {
"one" : cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . DynamicPseudoType ) ,
} ) ,
} ) ,
"list" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
"set" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
"single" : cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"map" : cty . MapVal ( map [ string ] cty . Value {
"one" : cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
"map_as_obj" : cty . ObjectVal ( map [ string ] cty . Value {
"one" : cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . StringVal ( "computed" ) ,
} ) ,
} ) ,
"list" : cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
"set" : cty . SetVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
"single" : cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . String ) ,
} ) ,
} ) ,
nil ,
} ,
}
for name , test := range tests {
t . Run ( name , func ( t * testing . T ) {
errs := AssertPlanValid ( test . Schema , test . Prior , test . Config , test . Planned )
wantErrs := make ( map [ string ] struct { } )
gotErrs := make ( map [ string ] struct { } )
for _ , err := range errs {
gotErrs [ tfdiags . FormatError ( err ) ] = struct { } { }
}
for _ , msg := range test . WantErrs {
wantErrs [ msg ] = struct { } { }
}
t . Logf (
"\nprior: %sconfig: %splanned: %s" ,
dump . Value ( test . Planned ) ,
dump . Value ( test . Config ) ,
dump . Value ( test . Planned ) ,
)
for msg := range wantErrs {
if _ , ok := gotErrs [ msg ] ; ! ok {
t . Errorf ( "missing expected error: %s" , msg )
}
}
for msg := range gotErrs {
if _ , ok := wantErrs [ msg ] ; ! ok {
t . Errorf ( "unexpected extra error: %s" , msg )
}
}
} )
2019-02-09 04:22:55 +01:00
}
2021-08-30 19:35:47 +02:00
}
2019-02-09 04:22:55 +01:00
2021-08-30 19:35:47 +02:00
func TestAssertPlanValidTEST ( t * testing . T ) {
tests := map [ string ] struct {
Schema * configschema . Block
Prior cty . Value
Config cty . Value
Planned cty . Value
WantErrs [ ] string
} {
"computed in map" : {
& configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"items" : {
NestedType : & configschema . Object {
Nesting : configschema . NestingMap ,
Attributes : map [ string ] * configschema . Attribute {
"name" : {
Type : cty . String ,
Computed : true ,
Optional : true ,
} ,
} ,
} ,
Required : true ,
} ,
} ,
} ,
cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"items" : cty . Map ( cty . Object ( map [ string ] cty . Type {
"name" : cty . String ,
} ) ) ,
} ) ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"items" : cty . MapVal ( map [ string ] cty . Value {
"one" : cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . NullVal ( cty . String ) ,
//"name": cty.StringVal("computed"),
} ) ,
} ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"items" : cty . MapVal ( map [ string ] cty . Value {
"one" : cty . ObjectVal ( map [ string ] cty . Value {
"name" : cty . StringVal ( "computed" ) ,
} ) ,
} ) ,
} ) ,
nil ,
} ,
}
2019-02-09 04:22:55 +01:00
for name , test := range tests {
t . Run ( name , func ( t * testing . T ) {
errs := AssertPlanValid ( test . Schema , test . Prior , test . Config , test . Planned )
wantErrs := make ( map [ string ] struct { } )
gotErrs := make ( map [ string ] struct { } )
for _ , err := range errs {
gotErrs [ tfdiags . FormatError ( err ) ] = struct { } { }
}
for _ , msg := range test . WantErrs {
wantErrs [ msg ] = struct { } { }
}
t . Logf (
"\nprior: %sconfig: %splanned: %s" ,
dump . Value ( test . Planned ) ,
dump . Value ( test . Config ) ,
dump . Value ( test . Planned ) ,
)
for msg := range wantErrs {
if _ , ok := gotErrs [ msg ] ; ! ok {
t . Errorf ( "missing expected error: %s" , msg )
}
}
for msg := range gotErrs {
if _ , ok := wantErrs [ msg ] ; ! ok {
t . Errorf ( "unexpected extra error: %s" , msg )
}
}
} )
}
}