2018-03-30 22:18:59 -05:00
package addrs
import (
"testing"
"github.com/go-test/deep"
2019-09-09 17:58:44 -05:00
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
2021-05-17 12:11:06 -05:00
"github.com/hashicorp/terraform/internal/tfdiags"
2018-03-30 22:18:59 -05:00
"github.com/zclconf/go-cty/cty"
)
func TestParseRef ( t * testing . T ) {
tests := [ ] struct {
Input string
Want * Reference
WantErr string
} {
// count
{
` count.index ` ,
& Reference {
Subject : CountAttr {
Name : "index" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 12 , Byte : 11 } ,
} ,
} ,
` ` ,
} ,
{
` count.index.blah ` ,
& Reference {
Subject : CountAttr {
Name : "index" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 12 , Byte : 11 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "blah" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 12 , Byte : 11 } ,
End : hcl . Pos { Line : 1 , Column : 17 , Byte : 16 } ,
} ,
} ,
} ,
} ,
` ` , // valid at this layer, but will fail during eval because "index" is a number
} ,
{
` count ` ,
nil ,
` The "count" object cannot be accessed directly. Instead, access one of its attributes. ` ,
} ,
{
` count["hello"] ` ,
nil ,
` The "count" object does not support this operation. ` ,
} ,
2019-06-12 10:07:32 -05:00
// each
{
` each.key ` ,
& Reference {
Subject : ForEachAttr {
Name : "key" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 9 , Byte : 8 } ,
} ,
} ,
` ` ,
} ,
{
` each.value.blah ` ,
& Reference {
Subject : ForEachAttr {
Name : "value" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 11 , Byte : 10 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "blah" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 11 , Byte : 10 } ,
End : hcl . Pos { Line : 1 , Column : 16 , Byte : 15 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
{
` each ` ,
nil ,
` The "each" object cannot be accessed directly. Instead, access one of its attributes. ` ,
} ,
{
` each["hello"] ` ,
nil ,
` The "each" object does not support this operation. ` ,
} ,
2018-03-30 22:18:59 -05:00
// data
{
` data.external.foo ` ,
& Reference {
2019-09-19 08:53:23 -05:00
Subject : Resource {
Mode : DataResourceMode ,
Type : "external" ,
Name : "foo" ,
2018-03-30 22:18:59 -05:00
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 18 , Byte : 17 } ,
} ,
} ,
` ` ,
} ,
{
` data.external.foo.bar ` ,
& Reference {
Subject : ResourceInstance {
Resource : Resource {
Mode : DataResourceMode ,
Type : "external" ,
Name : "foo" ,
} ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 18 , Byte : 17 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "bar" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 18 , Byte : 17 } ,
End : hcl . Pos { Line : 1 , Column : 22 , Byte : 21 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
{
` data.external.foo["baz"].bar ` ,
& Reference {
Subject : ResourceInstance {
Resource : Resource {
Mode : DataResourceMode ,
Type : "external" ,
Name : "foo" ,
} ,
Key : StringKey ( "baz" ) ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 25 , Byte : 24 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "bar" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 25 , Byte : 24 } ,
End : hcl . Pos { Line : 1 , Column : 29 , Byte : 28 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
{
` data.external.foo["baz"] ` ,
& Reference {
Subject : ResourceInstance {
Resource : Resource {
Mode : DataResourceMode ,
Type : "external" ,
Name : "foo" ,
} ,
Key : StringKey ( "baz" ) ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 25 , Byte : 24 } ,
} ,
} ,
` ` ,
} ,
{
` data ` ,
nil ,
` The "data" object must be followed by two attribute names: the data source type and the resource name. ` ,
} ,
{
` data.external ` ,
nil ,
` The "data" object must be followed by two attribute names: the data source type and the resource name. ` ,
} ,
// local
{
` local.foo ` ,
& Reference {
Subject : LocalValue {
Name : "foo" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 10 , Byte : 9 } ,
} ,
} ,
` ` ,
} ,
{
` local.foo.blah ` ,
& Reference {
Subject : LocalValue {
Name : "foo" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 10 , Byte : 9 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "blah" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 10 , Byte : 9 } ,
End : hcl . Pos { Line : 1 , Column : 15 , Byte : 14 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
{
` local.foo["blah"] ` ,
& Reference {
Subject : LocalValue {
Name : "foo" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 10 , Byte : 9 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseIndex {
Key : cty . StringVal ( "blah" ) ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 10 , Byte : 9 } ,
End : hcl . Pos { Line : 1 , Column : 18 , Byte : 17 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
{
` local ` ,
nil ,
` The "local" object cannot be accessed directly. Instead, access one of its attributes. ` ,
} ,
{
` local["foo"] ` ,
nil ,
` The "local" object does not support this operation. ` ,
} ,
// module
{
` module.foo ` ,
& Reference {
2020-04-12 10:26:44 -05:00
Subject : ModuleCall {
Name : "foo" ,
2018-03-30 22:18:59 -05:00
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 11 , Byte : 10 } ,
} ,
} ,
` ` ,
} ,
{
` module.foo.bar ` ,
& Reference {
2021-06-29 17:06:00 -05:00
Subject : ModuleCallInstanceOutput {
2018-03-30 22:18:59 -05:00
Call : ModuleCallInstance {
Call : ModuleCall {
Name : "foo" ,
} ,
} ,
Name : "bar" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 15 , Byte : 14 } ,
} ,
} ,
` ` ,
} ,
{
` module.foo.bar.baz ` ,
& Reference {
2021-06-29 17:06:00 -05:00
Subject : ModuleCallInstanceOutput {
2018-03-30 22:18:59 -05:00
Call : ModuleCallInstance {
Call : ModuleCall {
Name : "foo" ,
} ,
} ,
Name : "bar" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 15 , Byte : 14 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "baz" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 15 , Byte : 14 } ,
End : hcl . Pos { Line : 1 , Column : 19 , Byte : 18 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
{
` module.foo["baz"] ` ,
& Reference {
Subject : ModuleCallInstance {
Call : ModuleCall {
Name : "foo" ,
} ,
Key : StringKey ( "baz" ) ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 18 , Byte : 17 } ,
} ,
} ,
` ` ,
} ,
{
` module.foo["baz"].bar ` ,
& Reference {
2021-06-29 17:06:00 -05:00
Subject : ModuleCallInstanceOutput {
2018-03-30 22:18:59 -05:00
Call : ModuleCallInstance {
Call : ModuleCall {
Name : "foo" ,
} ,
Key : StringKey ( "baz" ) ,
} ,
Name : "bar" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 22 , Byte : 21 } ,
} ,
} ,
` ` ,
} ,
{
` module.foo["baz"].bar.boop ` ,
& Reference {
2021-06-29 17:06:00 -05:00
Subject : ModuleCallInstanceOutput {
2018-03-30 22:18:59 -05:00
Call : ModuleCallInstance {
Call : ModuleCall {
Name : "foo" ,
} ,
Key : StringKey ( "baz" ) ,
} ,
Name : "bar" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 22 , Byte : 21 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "boop" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 22 , Byte : 21 } ,
End : hcl . Pos { Line : 1 , Column : 27 , Byte : 26 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
{
` module ` ,
nil ,
` The "module" object cannot be accessed directly. Instead, access one of its attributes. ` ,
} ,
{
` module["foo"] ` ,
nil ,
` The "module" object does not support this operation. ` ,
} ,
// path
{
` path.module ` ,
& Reference {
Subject : PathAttr {
Name : "module" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 12 , Byte : 11 } ,
} ,
} ,
` ` ,
} ,
{
` path.module.blah ` ,
& Reference {
Subject : PathAttr {
Name : "module" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 12 , Byte : 11 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "blah" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 12 , Byte : 11 } ,
End : hcl . Pos { Line : 1 , Column : 17 , Byte : 16 } ,
} ,
} ,
} ,
} ,
` ` , // valid at this layer, but will fail during eval because "module" is a string
} ,
{
` path ` ,
nil ,
` The "path" object cannot be accessed directly. Instead, access one of its attributes. ` ,
} ,
{
` path["module"] ` ,
nil ,
` The "path" object does not support this operation. ` ,
} ,
// self
{
` self ` ,
& Reference {
Subject : Self ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 5 , Byte : 4 } ,
} ,
} ,
` ` ,
} ,
{
` self.blah ` ,
& Reference {
Subject : Self ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 5 , Byte : 4 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "blah" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 5 , Byte : 4 } ,
End : hcl . Pos { Line : 1 , Column : 10 , Byte : 9 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
// terraform
{
` terraform.workspace ` ,
& Reference {
Subject : TerraformAttr {
Name : "workspace" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 20 , Byte : 19 } ,
} ,
} ,
` ` ,
} ,
{
` terraform.workspace.blah ` ,
& Reference {
Subject : TerraformAttr {
Name : "workspace" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 20 , Byte : 19 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "blah" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 20 , Byte : 19 } ,
End : hcl . Pos { Line : 1 , Column : 25 , Byte : 24 } ,
} ,
} ,
} ,
} ,
` ` , // valid at this layer, but will fail during eval because "workspace" is a string
} ,
{
` terraform ` ,
nil ,
` The "terraform" object cannot be accessed directly. Instead, access one of its attributes. ` ,
} ,
{
` terraform["workspace"] ` ,
nil ,
` The "terraform" object does not support this operation. ` ,
} ,
// var
{
` var.foo ` ,
& Reference {
Subject : InputVariable {
Name : "foo" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 8 , Byte : 7 } ,
} ,
} ,
` ` ,
} ,
{
` var.foo.blah ` ,
& Reference {
Subject : InputVariable {
Name : "foo" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 8 , Byte : 7 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "blah" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 8 , Byte : 7 } ,
End : hcl . Pos { Line : 1 , Column : 13 , Byte : 12 } ,
} ,
} ,
} ,
} ,
` ` , // valid at this layer, but will fail during eval because "module" is a string
} ,
{
` var ` ,
nil ,
` The "var" object cannot be accessed directly. Instead, access one of its attributes. ` ,
} ,
{
` var["foo"] ` ,
nil ,
` The "var" object does not support this operation. ` ,
} ,
2021-05-14 18:42:30 -05:00
// the "resource" prefix forces interpreting the next name as a
// resource type name. This is an alias for just using a resource
// type name at the top level, to be used only if a later edition
// of the Terraform language introduces a new reserved word that
// overlaps with a resource type name.
{
` resource.boop_instance.foo ` ,
& Reference {
Subject : Resource {
Mode : ManagedResourceMode ,
Type : "boop_instance" ,
Name : "foo" ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 27 , Byte : 26 } ,
} ,
} ,
` ` ,
} ,
2021-05-14 18:58:02 -05:00
// We have some names reserved which might be used by a
// still-under-discussion proposal for template values or lazy
// expressions.
{
` template.foo ` ,
nil ,
` The symbol name "template" is reserved for use in a future Terraform version. If you are using a provider that already uses this as a resource type name, add the prefix "resource." to force interpretation as a resource type name. ` ,
} ,
{
` lazy.foo ` ,
nil ,
` The symbol name "lazy" is reserved for use in a future Terraform version. If you are using a provider that already uses this as a resource type name, add the prefix "resource." to force interpretation as a resource type name. ` ,
} ,
{
` arg.foo ` ,
nil ,
` The symbol name "arg" is reserved for use in a future Terraform version. If you are using a provider that already uses this as a resource type name, add the prefix "resource." to force interpretation as a resource type name. ` ,
} ,
2018-03-30 22:18:59 -05:00
// anything else, interpreted as a managed resource reference
{
` boop_instance.foo ` ,
& Reference {
2019-09-19 08:53:23 -05:00
Subject : Resource {
Mode : ManagedResourceMode ,
Type : "boop_instance" ,
Name : "foo" ,
2018-03-30 22:18:59 -05:00
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 18 , Byte : 17 } ,
} ,
} ,
` ` ,
} ,
{
` boop_instance.foo.bar ` ,
& Reference {
Subject : ResourceInstance {
Resource : Resource {
Mode : ManagedResourceMode ,
Type : "boop_instance" ,
Name : "foo" ,
} ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 18 , Byte : 17 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "bar" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 18 , Byte : 17 } ,
End : hcl . Pos { Line : 1 , Column : 22 , Byte : 21 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
{
` boop_instance.foo["baz"].bar ` ,
& Reference {
Subject : ResourceInstance {
Resource : Resource {
Mode : ManagedResourceMode ,
Type : "boop_instance" ,
Name : "foo" ,
} ,
Key : StringKey ( "baz" ) ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 25 , Byte : 24 } ,
} ,
Remaining : hcl . Traversal {
hcl . TraverseAttr {
Name : "bar" ,
SrcRange : hcl . Range {
Start : hcl . Pos { Line : 1 , Column : 25 , Byte : 24 } ,
End : hcl . Pos { Line : 1 , Column : 29 , Byte : 28 } ,
} ,
} ,
} ,
} ,
` ` ,
} ,
{
` boop_instance.foo["baz"] ` ,
& Reference {
Subject : ResourceInstance {
Resource : Resource {
Mode : ManagedResourceMode ,
Type : "boop_instance" ,
Name : "foo" ,
} ,
Key : StringKey ( "baz" ) ,
} ,
SourceRange : tfdiags . SourceRange {
Start : tfdiags . SourcePos { Line : 1 , Column : 1 , Byte : 0 } ,
End : tfdiags . SourcePos { Line : 1 , Column : 25 , Byte : 24 } ,
} ,
} ,
` ` ,
} ,
{
` boop_instance ` ,
nil ,
` A reference to a resource type must be followed by at least one attribute access, specifying the resource name. ` ,
} ,
}
for _ , test := range tests {
t . Run ( test . Input , func ( t * testing . T ) {
traversal , travDiags := hclsyntax . ParseTraversalAbs ( [ ] byte ( test . Input ) , "" , hcl . Pos { Line : 1 , Column : 1 } )
if travDiags . HasErrors ( ) {
t . Fatal ( travDiags . Error ( ) )
}
got , diags := ParseRef ( traversal )
switch len ( diags ) {
case 0 :
if test . WantErr != "" {
t . Fatalf ( "succeeded; want error: %s" , test . WantErr )
}
case 1 :
if test . WantErr == "" {
t . Fatalf ( "unexpected diagnostics: %s" , diags . Err ( ) )
}
if got , want := diags [ 0 ] . Description ( ) . Detail , test . WantErr ; got != want {
t . Fatalf ( "wrong error\ngot: %s\nwant: %s" , got , want )
}
default :
t . Fatalf ( "too many diagnostics: %s" , diags . Err ( ) )
}
if diags . HasErrors ( ) {
return
}
for _ , problem := range deep . Equal ( got , test . Want ) {
t . Errorf ( problem )
}
} )
}
}