kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/configs/escaping_blocks_test.go (about) 1 package configs 2 3 import ( 4 "testing" 5 6 "github.com/google/go-cmp/cmp" 7 "github.com/hashicorp/hcl/v2" 8 "github.com/zclconf/go-cty/cty" 9 ) 10 11 // "Escaping Blocks" are a special mechanism we have inside our block types 12 // that accept a mixture of meta-arguments and externally-defined arguments, 13 // which allow an author to force particular argument names to be interpreted 14 // as externally-defined even if they have the same name as a meta-argument. 15 // 16 // An escaping block is a block with the special type name "_" (just an 17 // underscore), and is allowed at the top-level of any resource, data, or 18 // module block. It intentionally has a rather "odd" look so that it stands 19 // out as something special and rare. 20 // 21 // This is not something we expect to see used a lot, but it's an important 22 // part of our strategy to evolve the Terraform language in future using 23 // editions, so that later editions can define new meta-arguments without 24 // blocking access to externally-defined arguments of the same name. 25 // 26 // We should still define new meta-arguments with care to avoid squatting on 27 // commonly-used names, but we can't see all modules and all providers in 28 // the world and so this is an escape hatch for edge cases. Module migration 29 // tools for future editions that define new meta-arguments should detect 30 // collisions and automatically migrate existing arguments into an escaping 31 // block. 32 33 func TestEscapingBlockResource(t *testing.T) { 34 // (this also tests escaping blocks in provisioner blocks, because 35 // they only appear nested inside resource blocks.) 36 37 parser := NewParser(nil) 38 mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/resource") 39 assertNoDiagnostics(t, diags) 40 if mod == nil { 41 t.Fatal("got nil root module; want non-nil") 42 } 43 44 rc := mod.ManagedResources["foo.bar"] 45 if rc == nil { 46 t.Fatal("no managed resource named foo.bar") 47 } 48 49 t.Run("resource body", func(t *testing.T) { 50 if got := rc.Count; got == nil { 51 t.Errorf("count not set; want count = 2") 52 } else { 53 got, diags := got.Value(nil) 54 assertNoDiagnostics(t, diags) 55 if want := cty.NumberIntVal(2); !want.RawEquals(got) { 56 t.Errorf("wrong count\ngot: %#v\nwant: %#v", got, want) 57 } 58 } 59 if got, want := rc.ForEach, hcl.Expression(nil); got != want { 60 // Shouldn't have any count because our test fixture only has 61 // for_each in the escaping block. 62 t.Errorf("wrong for_each\ngot: %#v\nwant: %#v", got, want) 63 } 64 65 schema := &hcl.BodySchema{ 66 Attributes: []hcl.AttributeSchema{ 67 {Name: "normal", Required: true}, 68 {Name: "count", Required: true}, 69 {Name: "for_each", Required: true}, 70 }, 71 Blocks: []hcl.BlockHeaderSchema{ 72 {Type: "normal_block"}, 73 {Type: "lifecycle"}, 74 {Type: "_"}, 75 }, 76 } 77 content, diags := rc.Config.Content(schema) 78 assertNoDiagnostics(t, diags) 79 80 normalVal, diags := content.Attributes["normal"].Expr.Value(nil) 81 assertNoDiagnostics(t, diags) 82 if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) { 83 t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want) 84 } 85 86 countVal, diags := content.Attributes["count"].Expr.Value(nil) 87 assertNoDiagnostics(t, diags) 88 if got, want := countVal, cty.StringVal("not actually count"); !want.RawEquals(got) { 89 t.Errorf("wrong value for 'count'\ngot: %#v\nwant: %#v", got, want) 90 } 91 92 var gotBlockTypes []string 93 for _, block := range content.Blocks { 94 gotBlockTypes = append(gotBlockTypes, block.Type) 95 } 96 wantBlockTypes := []string{"normal_block", "lifecycle", "_"} 97 if diff := cmp.Diff(gotBlockTypes, wantBlockTypes); diff != "" { 98 t.Errorf("wrong block types\n%s", diff) 99 } 100 }) 101 t.Run("provisioner body", func(t *testing.T) { 102 if got, want := len(rc.Managed.Provisioners), 1; got != want { 103 t.Fatalf("wrong number of provisioners %d; want %d", got, want) 104 } 105 pc := rc.Managed.Provisioners[0] 106 107 schema := &hcl.BodySchema{ 108 Attributes: []hcl.AttributeSchema{ 109 {Name: "when", Required: true}, 110 {Name: "normal", Required: true}, 111 }, 112 Blocks: []hcl.BlockHeaderSchema{ 113 {Type: "normal_block"}, 114 {Type: "lifecycle"}, 115 {Type: "_"}, 116 }, 117 } 118 content, diags := pc.Config.Content(schema) 119 assertNoDiagnostics(t, diags) 120 121 normalVal, diags := content.Attributes["normal"].Expr.Value(nil) 122 assertNoDiagnostics(t, diags) 123 if got, want := normalVal, cty.StringVal("yep"); !want.RawEquals(got) { 124 t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want) 125 } 126 whenVal, diags := content.Attributes["when"].Expr.Value(nil) 127 assertNoDiagnostics(t, diags) 128 if got, want := whenVal, cty.StringVal("hell freezes over"); !want.RawEquals(got) { 129 t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want) 130 } 131 }) 132 } 133 134 func TestEscapingBlockData(t *testing.T) { 135 parser := NewParser(nil) 136 mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/data") 137 assertNoDiagnostics(t, diags) 138 if mod == nil { 139 t.Fatal("got nil root module; want non-nil") 140 } 141 142 rc := mod.DataResources["data.foo.bar"] 143 if rc == nil { 144 t.Fatal("no data resource named data.foo.bar") 145 } 146 147 if got := rc.Count; got == nil { 148 t.Errorf("count not set; want count = 2") 149 } else { 150 got, diags := got.Value(nil) 151 assertNoDiagnostics(t, diags) 152 if want := cty.NumberIntVal(2); !want.RawEquals(got) { 153 t.Errorf("wrong count\ngot: %#v\nwant: %#v", got, want) 154 } 155 } 156 if got, want := rc.ForEach, hcl.Expression(nil); got != want { 157 // Shouldn't have any count because our test fixture only has 158 // for_each in the escaping block. 159 t.Errorf("wrong for_each\ngot: %#v\nwant: %#v", got, want) 160 } 161 162 schema := &hcl.BodySchema{ 163 Attributes: []hcl.AttributeSchema{ 164 {Name: "normal", Required: true}, 165 {Name: "count", Required: true}, 166 {Name: "for_each", Required: true}, 167 }, 168 Blocks: []hcl.BlockHeaderSchema{ 169 {Type: "normal_block"}, 170 {Type: "lifecycle"}, 171 {Type: "_"}, 172 }, 173 } 174 content, diags := rc.Config.Content(schema) 175 assertNoDiagnostics(t, diags) 176 177 normalVal, diags := content.Attributes["normal"].Expr.Value(nil) 178 assertNoDiagnostics(t, diags) 179 if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) { 180 t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want) 181 } 182 183 countVal, diags := content.Attributes["count"].Expr.Value(nil) 184 assertNoDiagnostics(t, diags) 185 if got, want := countVal, cty.StringVal("not actually count"); !want.RawEquals(got) { 186 t.Errorf("wrong value for 'count'\ngot: %#v\nwant: %#v", got, want) 187 } 188 189 var gotBlockTypes []string 190 for _, block := range content.Blocks { 191 gotBlockTypes = append(gotBlockTypes, block.Type) 192 } 193 wantBlockTypes := []string{"normal_block", "lifecycle", "_"} 194 if diff := cmp.Diff(gotBlockTypes, wantBlockTypes); diff != "" { 195 t.Errorf("wrong block types\n%s", diff) 196 } 197 198 } 199 200 func TestEscapingBlockModule(t *testing.T) { 201 parser := NewParser(nil) 202 mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/module") 203 assertNoDiagnostics(t, diags) 204 if mod == nil { 205 t.Fatal("got nil root module; want non-nil") 206 } 207 208 mc := mod.ModuleCalls["foo"] 209 if mc == nil { 210 t.Fatal("no module call named foo") 211 } 212 213 if got := mc.Count; got == nil { 214 t.Errorf("count not set; want count = 2") 215 } else { 216 got, diags := got.Value(nil) 217 assertNoDiagnostics(t, diags) 218 if want := cty.NumberIntVal(2); !want.RawEquals(got) { 219 t.Errorf("wrong count\ngot: %#v\nwant: %#v", got, want) 220 } 221 } 222 if got, want := mc.ForEach, hcl.Expression(nil); got != want { 223 // Shouldn't have any count because our test fixture only has 224 // for_each in the escaping block. 225 t.Errorf("wrong for_each\ngot: %#v\nwant: %#v", got, want) 226 } 227 228 schema := &hcl.BodySchema{ 229 Attributes: []hcl.AttributeSchema{ 230 {Name: "normal", Required: true}, 231 {Name: "count", Required: true}, 232 {Name: "for_each", Required: true}, 233 }, 234 Blocks: []hcl.BlockHeaderSchema{ 235 {Type: "normal_block"}, 236 {Type: "lifecycle"}, 237 {Type: "_"}, 238 }, 239 } 240 content, diags := mc.Config.Content(schema) 241 assertNoDiagnostics(t, diags) 242 243 normalVal, diags := content.Attributes["normal"].Expr.Value(nil) 244 assertNoDiagnostics(t, diags) 245 if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) { 246 t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want) 247 } 248 249 countVal, diags := content.Attributes["count"].Expr.Value(nil) 250 assertNoDiagnostics(t, diags) 251 if got, want := countVal, cty.StringVal("not actually count"); !want.RawEquals(got) { 252 t.Errorf("wrong value for 'count'\ngot: %#v\nwant: %#v", got, want) 253 } 254 255 var gotBlockTypes []string 256 for _, block := range content.Blocks { 257 gotBlockTypes = append(gotBlockTypes, block.Type) 258 } 259 wantBlockTypes := []string{"normal_block", "lifecycle", "_"} 260 if diff := cmp.Diff(gotBlockTypes, wantBlockTypes); diff != "" { 261 t.Errorf("wrong block types\n%s", diff) 262 } 263 264 } 265 266 func TestEscapingBlockProvider(t *testing.T) { 267 parser := NewParser(nil) 268 mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/provider") 269 assertNoDiagnostics(t, diags) 270 if mod == nil { 271 t.Fatal("got nil root module; want non-nil") 272 } 273 274 pc := mod.ProviderConfigs["foo.bar"] 275 if pc == nil { 276 t.Fatal("no provider configuration named foo.bar") 277 } 278 279 if got, want := pc.Alias, "bar"; got != want { 280 t.Errorf("wrong alias\ngot: %#v\nwant: %#v", got, want) 281 } 282 283 schema := &hcl.BodySchema{ 284 Attributes: []hcl.AttributeSchema{ 285 {Name: "normal", Required: true}, 286 {Name: "alias", Required: true}, 287 {Name: "version", Required: true}, 288 }, 289 } 290 content, diags := pc.Config.Content(schema) 291 assertNoDiagnostics(t, diags) 292 293 normalVal, diags := content.Attributes["normal"].Expr.Value(nil) 294 assertNoDiagnostics(t, diags) 295 if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) { 296 t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want) 297 } 298 aliasVal, diags := content.Attributes["alias"].Expr.Value(nil) 299 assertNoDiagnostics(t, diags) 300 if got, want := aliasVal, cty.StringVal("not actually alias"); !want.RawEquals(got) { 301 t.Errorf("wrong value for 'alias'\ngot: %#v\nwant: %#v", got, want) 302 } 303 versionVal, diags := content.Attributes["version"].Expr.Value(nil) 304 assertNoDiagnostics(t, diags) 305 if got, want := versionVal, cty.StringVal("not actually version"); !want.RawEquals(got) { 306 t.Errorf("wrong value for 'version'\ngot: %#v\nwant: %#v", got, want) 307 } 308 }