From c3cd97a5fc0c6e1ea16e52d895dc6e83c60a1db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Thu, 26 Mar 2020 07:13:24 +0100 Subject: [PATCH] starlark/types: Computed renamed to Attribute, added documentation --- starlark/runtime/runtime.go | 2 +- starlark/types/attribute.go | 205 ++++++++++++++++++ starlark/types/attribute_test.go | 9 + starlark/types/collection.go | 2 + starlark/types/computed.go | 162 -------------- starlark/types/computed_test.go | 9 - starlark/types/hcl.go | 17 +- starlark/types/provider.go | 11 +- starlark/types/provider_test.go | 2 +- starlark/types/resource.go | 25 ++- .../{computed.star => attribute.star} | 24 +- .../types/testdata/examples/attribute.star | 14 ++ starlark/types/testdata/resource.star | 2 +- starlark/types/type.go | 10 +- starlark/types/value.go | 6 +- terraform/plugins.go | 4 +- 16 files changed, 290 insertions(+), 214 deletions(-) create mode 100644 starlark/types/attribute.go create mode 100644 starlark/types/attribute_test.go delete mode 100644 starlark/types/computed.go delete mode 100644 starlark/types/computed_test.go rename starlark/types/testdata/{computed.star => attribute.star} (76%) create mode 100644 starlark/types/testdata/examples/attribute.star diff --git a/starlark/runtime/runtime.go b/starlark/runtime/runtime.go index b92bf77..33b26f2 100644 --- a/starlark/runtime/runtime.go +++ b/starlark/runtime/runtime.go @@ -63,7 +63,7 @@ func NewRuntime(pm *terraform.PluginManager) *Runtime { "provisioner": types.BuiltinProvisioner(), "backend": types.BuiltinBackend(), "hcl": types.BuiltinHCL(), - "fn": types.BuiltinFunctionComputed(), + "fn": types.BuiltinFunctionAttribute(), "evaluate": types.BuiltinEvaluate(), "struct": starlark.NewBuiltin("struct", starlarkstruct.Make), "module": starlark.NewBuiltin("module", starlarkstruct.MakeModule), diff --git a/starlark/types/attribute.go b/starlark/types/attribute.go new file mode 100644 index 0000000..c6acce5 --- /dev/null +++ b/starlark/types/attribute.go @@ -0,0 +1,205 @@ +package types + +import ( + "fmt" + + "github.com/zclconf/go-cty/cty" + "go.starlark.net/starlark" +) + +// sTring alias required to avoid name collision with the method String. +type sString = starlark.String + +// Attribute is a reference to an argument of a Resource. Used mainly +// for Computed arguments of Resources. +// +// outline: types +// types: +// Attribute +// Attribute is a reference to an argument of a Resource. Used mainly +// for Computed arguments of Resources. +// +// Attribute behaves as type of the argument represented, this means +// that their can be assigned to other resource arguments of the same +// type. And, if the type is a list are indexables. +// +// examples: +// attribute.star +// +// fields: +// __resource__ Resource +// Resource of the attribute. +// __type__ string +// Type of the attribute. Eg.: `string` +type Attribute struct { + r *Resource + t cty.Type + name string + path string + + sString +} + +var _ starlark.Value = &Attribute{} +var _ starlark.HasAttrs = &Attribute{} +var _ starlark.Indexable = &Attribute{} +var _ starlark.Comparable = &Attribute{} + +// NewAttribute returns a new Attribute for a given value or block of a Resource. +func NewAttribute(r *Resource, t cty.Type, name string) *Attribute { + var parts []string + var path string + + child := r + + for { + if child.parent.kind == ProviderKind { + if child.kind == ResourceKind { + path = fmt.Sprintf("%s.%s", child.typ, child.Name()) + } else { + path = fmt.Sprintf("%s.%s.%s", child.kind, child.typ, child.Name()) + } + + break + } + + parts = append(parts, child.typ) + child = child.parent + } + + for i := len(parts) - 1; i >= 0; i-- { + path += "." + parts[i] + } + + // handling of MaxItems equals 1 + block, ok := r.parent.block.BlockTypes[r.typ] + if ok && block.MaxItems == 1 { + name = "0." + name + } + + return NewAttributeWithPath(r, t, name, path+"."+name) +} + +func NewAttributeWithPath(r *Resource, t cty.Type, name, path string) *Attribute { + return &Attribute{ + r: r, + t: t, + name: name, + path: path, + sString: starlark.String(fmt.Sprintf("${%s}", path)), + } +} + +// Type honors the starlark.Value interface. +func (c *Attribute) Type() string { + return fmt.Sprintf("Attribute<%s>", MustTypeFromCty(c.t).Starlark()) +} + +func (c *Attribute) InnerType() *Type { + t, _ := NewTypeFromCty(c.t) + return t +} + +// Attr honors the starlark.HasAttrs interface. +func (c *Attribute) Attr(name string) (starlark.Value, error) { + switch name { + case "__resource__": + return c.r, nil + case "__type__": + return starlark.String(MustTypeFromCty(c.t).Starlark()), nil + } + + if !c.t.IsObjectType() { + return nil, fmt.Errorf("%s it's not a object", c.Type()) + } + + if !c.t.HasAttribute(name) { + errmsg := fmt.Sprintf("%s has no .%s field", c.Type(), name) + return nil, starlark.NoSuchAttrError(errmsg) + } + + path := fmt.Sprintf("%s.%s", c.path, name) + return NewAttributeWithPath(c.r, c.t.AttributeType(name), name, path), nil +} + +// AttrNames honors the starlark.HasAttrs interface. +func (c *Attribute) AttrNames() []string { + return []string{"__resource__", "__type__"} +} + +func (c *Attribute) doNested(name, path string, t cty.Type, index int) *Attribute { + return &Attribute{ + r: c.r, + t: t, + name: c.name, + } + +} + +// Index honors the starlark.Indexable interface. +func (c *Attribute) Index(i int) starlark.Value { + path := fmt.Sprintf("%s.%d", c.path, i) + + if c.t.IsSetType() { + return NewAttributeWithPath(c.r, *c.t.SetElementType(), c.name, path) + } + + if c.t.IsListType() { + return NewAttributeWithPath(c.r, *c.t.ListElementType(), c.name, path) + } + + return starlark.None +} + +// Len honors the starlark.Indexable interface. +func (c *Attribute) Len() int { + if !c.t.IsSetType() && !c.t.IsListType() { + return 0 + } + + return 1024 +} + +// BuiltinFunctionAttribute returns a built-in function that wraps Attributes +// in HCL functions. +// +// outline: types +// functions: +// fn(name, target) Attribute +// Fn wraps an Attribute in a HCL function. Since the Attributes value +// are only available in the `apply` phase of Terraform, the only method +// to manipulate this values is using the Terraform +// [HCL functions](https://www.terraform.io/docs/configuration/functions.html). +// +// +// params: +// name string +// Name of the HCL function to be applied. Eg.: `base64encode` +// target Attribute +// Target Attribute of the HCL function. +// +func BuiltinFunctionAttribute() starlark.Value { + // TODO(mcuadros): implement multiple arguments support. + return starlark.NewBuiltin("fn", func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var function starlark.String + var computed *Attribute + switch len(args) { + case 2: + var ok bool + function, ok = args.Index(0).(starlark.String) + if !ok { + return nil, fmt.Errorf("expected string, got %s", args.Index(0).Type()) + } + + computed, ok = args.Index(1).(*Attribute) + if !ok { + return nil, fmt.Errorf("expected Attribute, got %s", args.Index(1).Type()) + } + default: + return nil, fmt.Errorf("unexpected positional arguments count") + } + + path := fmt.Sprintf("%s(%s)", function.GoString(), computed.path) + return NewAttributeWithPath(computed.r, computed.t, computed.name, path), nil + }) +} diff --git a/starlark/types/attribute_test.go b/starlark/types/attribute_test.go new file mode 100644 index 0000000..a8716f9 --- /dev/null +++ b/starlark/types/attribute_test.go @@ -0,0 +1,9 @@ +package types + +import ( + "testing" +) + +func TestAttribute(t *testing.T) { + doTest(t, "testdata/attribute.star") +} diff --git a/starlark/types/collection.go b/starlark/types/collection.go index 0fa7b78..92b279c 100644 --- a/starlark/types/collection.go +++ b/starlark/types/collection.go @@ -68,6 +68,7 @@ var _ starlark.HasAttrs = &ResourceCollection{} var _ starlark.Callable = &ResourceCollection{} var _ starlark.Comparable = &ResourceCollection{} +// NewResourceCollection returns a new ResourceCollection for the given values. func NewResourceCollection( typ string, k Kind, block *configschema.Block, provider *Provider, parent *Resource, ) *ResourceCollection { @@ -271,6 +272,7 @@ var _ starlark.HasAttrs = &ProviderCollection{} var _ starlark.Callable = &ProviderCollection{} var _ starlark.Comparable = &ProviderCollection{} +// NewProviderCollection returns a new ProviderCollection. func NewProviderCollection(pm *terraform.PluginManager) *ProviderCollection { return &ProviderCollection{ pm: pm, diff --git a/starlark/types/computed.go b/starlark/types/computed.go deleted file mode 100644 index c7baf72..0000000 --- a/starlark/types/computed.go +++ /dev/null @@ -1,162 +0,0 @@ -package types - -import ( - "fmt" - - "github.com/zclconf/go-cty/cty" - "go.starlark.net/starlark" -) - -type sString = starlark.String - -type Computed struct { - r *Resource - t cty.Type - name string - path string - - sString -} - -var _ starlark.Value = &Computed{} -var _ starlark.HasAttrs = &Computed{} -var _ starlark.Indexable = &Computed{} -var _ starlark.Comparable = &Computed{} - -func NewComputed(r *Resource, t cty.Type, name string) *Computed { - var parts []string - var path string - - child := r - - for { - if child.parent.kind == ProviderKind { - if child.kind == ResourceKind { - path = fmt.Sprintf("%s.%s", child.typ, child.Name()) - } else { - path = fmt.Sprintf("%s.%s.%s", child.kind, child.typ, child.Name()) - } - - break - } - - parts = append(parts, child.typ) - child = child.parent - } - - for i := len(parts) - 1; i >= 0; i-- { - path += "." + parts[i] - } - - // handling of MaxItems equals 1 - block, ok := r.parent.block.BlockTypes[r.typ] - if ok && block.MaxItems == 1 { - name = "0." + name - } - - return NewComputedWithPath(r, t, name, path+"."+name) -} - -func NewComputedWithPath(r *Resource, t cty.Type, name, path string) *Computed { - return &Computed{ - r: r, - t: t, - name: name, - path: path, - sString: starlark.String(fmt.Sprintf("${%s}", path)), - } -} - -// Type honors the starlark.Value interface. -func (c *Computed) Type() string { - return fmt.Sprintf("Computed<%s>", MustTypeFromCty(c.t).Starlark()) -} - -func (c *Computed) InnerType() *Type { - t, _ := NewTypeFromCty(c.t) - return t -} - -// Attr honors the starlark.HasAttrs interface. -func (c *Computed) Attr(name string) (starlark.Value, error) { - switch name { - case "__resource__": - return c.r, nil - case "__type__": - return starlark.String(MustTypeFromCty(c.t).Starlark()), nil - } - - if !c.t.IsObjectType() { - return nil, nil - } - - if !c.t.HasAttribute(name) { - return nil, nil - } - - path := fmt.Sprintf("%s.%s", c.path, name) - return NewComputedWithPath(c.r, c.t.AttributeType(name), name, path), nil -} - -// AttrNames honors the starlark.HasAttrs interface. -func (c *Computed) AttrNames() []string { - return []string{"__resource__", "__type__"} -} - -func (c *Computed) doNested(name, path string, t cty.Type, index int) *Computed { - return &Computed{ - r: c.r, - t: t, - name: c.name, - } - -} - -// Index honors the starlark.Indexable interface. -func (c *Computed) Index(i int) starlark.Value { - path := fmt.Sprintf("%s.%d", c.path, i) - - if c.t.IsSetType() { - return NewComputedWithPath(c.r, *c.t.SetElementType(), c.name, path) - } - - if c.t.IsListType() { - return NewComputedWithPath(c.r, *c.t.ListElementType(), c.name, path) - } - - return starlark.None -} - -// Len honors the starlark.Indexable interface. -func (c *Computed) Len() int { - if !c.t.IsSetType() && !c.t.IsListType() { - return 0 - } - - return 1024 -} - -func BuiltinFunctionComputed() starlark.Value { - return starlark.NewBuiltin("fn", func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - var function starlark.String - var computed *Computed - switch len(args) { - case 2: - var ok bool - function, ok = args.Index(0).(starlark.String) - if !ok { - return nil, fmt.Errorf("expected string, got %s", args.Index(0).Type()) - } - - computed, ok = args.Index(1).(*Computed) - if !ok { - return nil, fmt.Errorf("expected Computed, got %s", args.Index(1).Type()) - } - default: - return nil, fmt.Errorf("unexpected positional arguments count") - } - - path := fmt.Sprintf("%s(%s)", function.GoString(), computed.path) - return NewComputedWithPath(computed.r, computed.t, computed.name, path), nil - }) -} diff --git a/starlark/types/computed_test.go b/starlark/types/computed_test.go deleted file mode 100644 index 20cb408..0000000 --- a/starlark/types/computed_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -import ( - "testing" -) - -func TestComputed(t *testing.T) { - doTest(t, "testdata/computed.star") -} diff --git a/starlark/types/hcl.go b/starlark/types/hcl.go index fa0f943..7086062 100644 --- a/starlark/types/hcl.go +++ b/starlark/types/hcl.go @@ -11,6 +11,7 @@ import ( "go.starlark.net/starlark" ) +// HCLCompatible defines if the struct is suitable of by encoded in HCL. type HCLCompatible interface { ToHCL(b *hclwrite.Body) } @@ -44,6 +45,7 @@ func BuiltinHCL() starlark.Value { }) } +// ToHCL honors the HCLCompatible interface. func (s *Terraform) ToHCL(b *hclwrite.Body) { if s.b != nil { s.b.ToHCL(b) @@ -52,6 +54,7 @@ func (s *Terraform) ToHCL(b *hclwrite.Body) { s.p.ToHCL(b) } +// ToHCL honors the HCLCompatible interface. func (s *Dict) ToHCL(b *hclwrite.Body) { for _, v := range s.Keys() { p, _, _ := s.Get(v) @@ -64,6 +67,7 @@ func (s *Dict) ToHCL(b *hclwrite.Body) { } } +// ToHCL honors the HCLCompatible interface. func (s *Provider) ToHCL(b *hclwrite.Body) { block := b.AppendNewBlock("provider", []string{s.typ}) @@ -76,11 +80,13 @@ func (s *Provider) ToHCL(b *hclwrite.Body) { b.AppendNewline() } +// ToHCL honors the HCLCompatible interface. func (s *Provisioner) ToHCL(b *hclwrite.Body) { block := b.AppendNewBlock("provisioner", []string{s.typ}) s.Resource.doToHCLAttributes(block.Body()) } +// ToHCL honors the HCLCompatible interface. func (s *Backend) ToHCL(b *hclwrite.Body) { parent := b.AppendNewBlock("terraform", nil) @@ -89,6 +95,7 @@ func (s *Backend) ToHCL(b *hclwrite.Body) { b.AppendNewline() } +// ToHCL honors the HCLCompatible interface. func (t *ResourceCollectionGroup) ToHCL(b *hclwrite.Body) { names := make(sort.StringSlice, len(t.collections)) var i int @@ -103,12 +110,14 @@ func (t *ResourceCollectionGroup) ToHCL(b *hclwrite.Body) { } } +// ToHCL honors the HCLCompatible interface. func (c *ResourceCollection) ToHCL(b *hclwrite.Body) { for i := 0; i < c.Len(); i++ { c.Index(i).(*Resource).ToHCL(b) } } +// ToHCL honors the HCLCompatible interface. func (r *Resource) ToHCL(b *hclwrite.Body) { if len(b.Blocks()) != 0 || len(b.Attributes()) != 0 { b.AppendNewline() @@ -142,7 +151,7 @@ func (r *Resource) doToHCLAttributes(body *hclwrite.Body) { return nil } - if c, ok := v.v.(*Computed); ok { + if c, ok := v.v.(*Attribute); ok { body.SetAttributeTraversal(v.Name, hcl.Traversal{ hcl.TraverseRoot{Name: c.String()}, }) @@ -168,7 +177,7 @@ func (r *Resource) doToHCLAttributes(body *hclwrite.Body) { } func (r *Resource) doToHCLDependencies(body *hclwrite.Body) { - if len(r.dependenies) == 0 { + if len(r.dependencies) == 0 { return } @@ -184,8 +193,8 @@ func (r *Resource) doToHCLDependencies(body *hclwrite.Body) { Type: hclsyntax.TokenOBrack, Bytes: []byte{'['}, }) - l := len(r.dependenies) - for i, dep := range r.dependenies { + l := len(r.dependencies) + for i, dep := range r.dependencies { name := fmt.Sprintf("%s.%s", dep.typ, dep.Name()) toks = append(toks, &hclwrite.Token{ Type: hclsyntax.TokenIdent, Bytes: []byte(name), diff --git a/starlark/types/provider.go b/starlark/types/provider.go index 88d8aa9..5e79df1 100644 --- a/starlark/types/provider.go +++ b/starlark/types/provider.go @@ -14,6 +14,7 @@ import ( ) const ( + // PluginManagerLocal is the key of the terraform.PluginManager in the thread. PluginManagerLocal = "plugin_manager" ) @@ -206,15 +207,15 @@ func (p *Provider) AttrNames() []string { } // CompareSameType honors starlark.Comparable interface. -func (x *Provider) CompareSameType(op syntax.Token, y_ starlark.Value, depth int) (bool, error) { - y := y_.(*Provider) +func (p *Provider) CompareSameType(op syntax.Token, yv starlark.Value, depth int) (bool, error) { + y := yv.(*Provider) switch op { case syntax.EQL: - return x == y, nil + return p == y, nil case syntax.NEQ: - return x != y, nil + return p != y, nil default: - return false, fmt.Errorf("%s %s %s not implemented", x.Type(), op, y.Type()) + return false, fmt.Errorf("%s %s %s not implemented", p.Type(), op, y.Type()) } } diff --git a/starlark/types/provider_test.go b/starlark/types/provider_test.go index c9e3d45..f743e35 100644 --- a/starlark/types/provider_test.go +++ b/starlark/types/provider_test.go @@ -72,7 +72,7 @@ func doTestPrint(t *testing.T, filename string, print func(*starlark.Thread, str "provisioner": BuiltinProvisioner(), "backend": BuiltinBackend(), "hcl": BuiltinHCL(), - "fn": BuiltinFunctionComputed(), + "fn": BuiltinFunctionAttribute(), "evaluate": BuiltinEvaluate(), "tf": NewTerraform(pm), } diff --git a/starlark/types/resource.go b/starlark/types/resource.go index f2429c2..39b1431 100644 --- a/starlark/types/resource.go +++ b/starlark/types/resource.go @@ -41,6 +41,7 @@ func (k Kind) IsProviderRelated() bool { return false } +// Resource Kind constants. const ( ProviderKind Kind = "provider" ProvisionerKind Kind = "provisioner" @@ -188,7 +189,7 @@ type Resource struct { provider *Provider parent *Resource - dependenies []*Resource + dependencies []*Resource provisioners []*Provisioner } @@ -329,7 +330,7 @@ func (r *Resource) attrBlock(name string, b *configschema.NestedBlock) (starlark func (r *Resource) attrValue(name string, attr *configschema.Attribute) (starlark.Value, error) { if attr.Computed { if !r.values.Has(name) { - return NewComputed(r, attr.Type, name), nil + return NewAttribute(r, attr.Type, name), nil } } @@ -468,7 +469,7 @@ func (r *Resource) dependsOn(_ *starlark.Thread, _ *starlark.Builtin, args starl resources[i] = resource } - r.dependenies = append(r.dependenies, resources...) + r.dependencies = append(r.dependencies, resources...) return starlark.None, nil } @@ -488,30 +489,30 @@ func (r *Resource) addProvisioner(_ *starlark.Thread, _ *starlark.Builtin, args } // CompareSameType honors starlark.Comparable interface. -func (x *Resource) CompareSameType(op syntax.Token, y_ starlark.Value, depth int) (bool, error) { - y := y_.(*Resource) +func (r *Resource) CompareSameType(op syntax.Token, yv starlark.Value, depth int) (bool, error) { + y := yv.(*Resource) switch op { case syntax.EQL: - ok, err := x.doCompareSameType(y, depth) + ok, err := r.doCompareSameType(y, depth) return ok, err case syntax.NEQ: - ok, err := x.doCompareSameType(y, depth) + ok, err := r.doCompareSameType(y, depth) return !ok, err default: - return false, fmt.Errorf("%s %s %s not implemented", x.Type(), op, y.Type()) + return false, fmt.Errorf("%s %s %s not implemented", r.Type(), op, y.Type()) } } -func (x *Resource) doCompareSameType(y *Resource, depth int) (bool, error) { - if x.typ != y.typ { +func (r *Resource) doCompareSameType(y *Resource, depth int) (bool, error) { + if r.typ != y.typ { return false, nil } - if x.values.Len() != y.values.Len() { + if r.values.Len() != y.values.Len() { return false, nil } - for _, xval := range x.values.List() { + for _, xval := range r.values.List() { yval := y.values.Get(xval.Name) if yval == nil { return false, nil diff --git a/starlark/types/testdata/computed.star b/starlark/types/testdata/attribute.star similarity index 76% rename from starlark/types/testdata/computed.star rename to starlark/types/testdata/attribute.star index 2288f56..4541036 100644 --- a/starlark/types/testdata/computed.star +++ b/starlark/types/testdata/attribute.star @@ -4,10 +4,10 @@ aws = tf.provider("aws", "2.13.0") ami = aws.data.ami() -# compute of scalar +# attribute of scalar web = aws.resource.instance() web.ami = ami.id -assert.eq(type(web.ami), "Computed") +assert.eq(type(web.ami), "Attribute") assert.eq(str(web.ami), '"${data.aws_ami.id_2.id}"') assert.eq(web.ami.__resource__, ami) assert.eq(web.ami.__type__, "string") @@ -16,33 +16,37 @@ assert.eq(web.ami.__type__, "string") assert.eq("__resource__" in dir(web.ami), True) assert.eq("__type__" in dir(web.ami), True) -# compute of set +# attribute of set table = aws.data.dynamodb_table() assert.eq(str(table.ttl), '"${data.aws_dynamodb_table.id_4.ttl}"') assert.eq(str(table.ttl[0]), '"${data.aws_dynamodb_table.id_4.ttl.0}"') assert.eq(str(table.ttl[0].attribute_name), '"${data.aws_dynamodb_table.id_4.ttl.0.attribute_name}"') -# compute of list +# attribute of list instance = aws.data.instance() assert.eq(str(instance.credit_specification), '"${data.aws_instance.id_5.credit_specification}"') assert.eq(str(instance.credit_specification[0]), '"${data.aws_instance.id_5.credit_specification.0}"') assert.eq(str(instance.credit_specification[0].cpu_credits), '"${data.aws_instance.id_5.credit_specification.0.cpu_credits}"') -# compute of map -computed = str(aws.resource.instance().root_block_device.volume_size) -assert.eq(computed, '"${aws_instance.id_6.root_block_device.0.volume_size}"') +# attribute of block +attribute = str(aws.resource.instance().root_block_device.volume_size) +assert.eq(attribute, '"${aws_instance.id_6.root_block_device.0.volume_size}"') -# compute on data source +# attribute on data source assert.eq(str(aws.resource.instance().id), '"${aws_instance.id_7.id}"') -# compute on resource +# attribute on resource assert.eq(str(aws.data.ami().id), '"${data.aws_ami.id_8.id}"') gcp = tf.provider("google", "3.13.0") -# computed on list with MaxItem:1 +# attribute on list with MaxItem:1 cluster = gcp.resource.container_cluster("foo") assert.eq(str(cluster.master_auth.client_certificate), '"${google_container_cluster.foo.master_auth.0.client_certificate}"') +# attr non-object +assert.fails(lambda: web.ami.foo, "Attribute it's not a object") + + # fn wrapping assert.eq(str(fn("base64encode", web.ami)), '"${base64encode(data.aws_ami.id_2.id)}"') diff --git a/starlark/types/testdata/examples/attribute.star b/starlark/types/testdata/examples/attribute.star new file mode 100644 index 0000000..9ba3ca9 --- /dev/null +++ b/starlark/types/testdata/examples/attribute.star @@ -0,0 +1,14 @@ +# When a Resource has an Attribute means that the value it's only available +# during the `apply` phase of Terraform. So in, AsCode an attribute behaves +# like a poor-man pointer. + +aws = tf.provider("aws") + +ami = aws.resource.ami("ubuntu") + +instance = aws.resource.instance("foo") +instance.ami = ami.id + +print(instance.ami) +# Output: +# "${aws_ami.ubuntu.id}" \ No newline at end of file diff --git a/starlark/types/testdata/resource.star b/starlark/types/testdata/resource.star index 09ef3cd..e45919b 100644 --- a/starlark/types/testdata/resource.star +++ b/starlark/types/testdata/resource.star @@ -31,7 +31,7 @@ assert.eq(qux.name, None) assert.fails(lambda: qux.foo, "Resource has no .foo field or method") # attr id -assert.eq(type(qux.id), "Computed") +assert.eq(type(qux.id), "Attribute") assert.eq(str(qux.id), '"${data.ignition_user.id_2.id}"') aws = tf.provider("aws", "2.13.0") diff --git a/starlark/types/type.go b/starlark/types/type.go index e645517..62d1b1d 100644 --- a/starlark/types/type.go +++ b/starlark/types/type.go @@ -46,7 +46,7 @@ func NewTypeFromStarlark(typ string) (*Type, error) { t.cty = cty.List(cty.NilType) case "dict", "Resource": t.cty = cty.Map(cty.NilType) - case "Computed": + case "Attribute": t.cty = cty.String default: return nil, fmt.Errorf("unexpected %q type", typ) @@ -108,7 +108,7 @@ func (t *Type) Cty() cty.Type { return t.cty } -// Validate validates a value againts the type. +// Validate validates a value against the type. func (t *Type) Validate(v starlark.Value) error { switch v.(type) { case starlark.String: @@ -123,12 +123,12 @@ func (t *Type) Validate(v starlark.Value) error { if t.cty == cty.Bool { return nil } - case *Computed: - if t.cty == v.(*Computed).t { + case *Attribute: + if t.cty == v.(*Attribute).t { return nil } - vt := v.(*Computed).InnerType().Starlark() + vt := v.(*Attribute).InnerType().Starlark() return fmt.Errorf("expected %s, got %s", t.typ, vt) case *starlark.List: if t.cty.IsListType() || t.cty.IsSetType() { diff --git a/starlark/types/value.go b/starlark/types/value.go index fce1e62..b88eb41 100644 --- a/starlark/types/value.go +++ b/starlark/types/value.go @@ -78,8 +78,8 @@ func (v *Value) Cty() cty.Value { } return cty.MapVal(values) - case "Computed": - return cty.StringVal(v.v.(*Computed).GoString()) + case "Attribute": + return cty.StringVal(v.v.(*Attribute).GoString()) default: return cty.StringVal(fmt.Sprintf("unhandled: %s", v.t.typ)) } @@ -264,10 +264,12 @@ func (a Values) Cty(schema *configschema.Block) cty.Value { return cty.ObjectVal(values) } +// Dict is a starlark.Dict HCLCompatible. type Dict struct { *starlark.Dict } +// NewDict returns a new empty Dict. func NewDict() *Dict { return &Dict{starlark.NewDict(0)} } diff --git a/terraform/plugins.go b/terraform/plugins.go index b88e2f6..e88e5e1 100644 --- a/terraform/plugins.go +++ b/terraform/plugins.go @@ -14,7 +14,7 @@ import ( "github.com/mitchellh/cli" ) -// PluginManager is a wrapper arround the terraform tools to download and execute +// PluginManager is a wrapper around the terraform tools to download and execute // terraform plugins, like providers and provisioners. type PluginManager struct { Path string @@ -28,7 +28,7 @@ func (m *PluginManager) Provider(provider, version string, forceLocal bool) (*pl meta, ok := m.getLocal("provider", provider, version) if !ok && !forceLocal { var err error - meta, ok, err = m.getProviderRemote(provider, version) + meta, _, err = m.getProviderRemote(provider, version) if err != nil { return nil, discovery.PluginMeta{}, err }