1
1
mirror of https://github.com/mcuadros/ascode synced 2024-11-26 06:01:08 +01:00

provider: COmputed support for nested attributes, list and sets

Signed-off-by: Máximo Cuadros <mcuadros@gmail.com>
This commit is contained in:
Máximo Cuadros 2019-07-05 02:27:28 +02:00
parent 6daa4b37b4
commit 76dd3d631a
11 changed files with 183 additions and 58 deletions

@ -8,18 +8,20 @@ import (
)
type ResourceCollection struct {
typ string
kind ResourceKind
block *configschema.Block
typ string
kind ResourceKind
block *configschema.Block
parent *Resource
*starlark.List
}
func NewResourceCollection(typ string, k ResourceKind, block *configschema.Block) *ResourceCollection {
func NewResourceCollection(typ string, k ResourceKind, block *configschema.Block, parent *Resource) *ResourceCollection {
return &ResourceCollection{
typ: typ,
kind: k,
block: block,
List: starlark.NewList(nil),
typ: typ,
kind: k,
block: block,
parent: parent,
List: starlark.NewList(nil),
}
}
@ -67,7 +69,7 @@ func (c *ResourceCollection) CallInternal(thread *starlark.Thread, args starlark
}
}
resource := MakeResource(c.typ, c.kind, c.block)
resource := MakeResource(c.typ, c.kind, c.block, c.parent)
if len(kwargs) != 0 {
if err := resource.loadKeywordArgs(kwargs); err != nil {
return nil, err

@ -3,7 +3,8 @@ package provider
import (
"fmt"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/zclconf/go-cty/cty"
"go.starlark.net/starlark"
)
@ -11,21 +12,98 @@ type sString = starlark.String
type Computed struct {
r *Resource
a *configschema.Attribute
t cty.Type
name string
path string
sString
}
func NewComputed(r *Resource, a *configschema.Attribute, name string) *Computed {
hash, _ := r.Hash()
func NewComputed(r *Resource, t cty.Type, name string) *Computed {
var parts []string
var path string
child := r
for {
if child.parent == nil {
hash, _ := r.Hash()
path = fmt.Sprintf("%s.%s.%d", child.kind, child.typ, hash)
break
}
parts = append(parts, child.typ)
child = child.parent
}
for i := len(parts) - 1; i >= 0; i-- {
path += "." + parts[i]
}
return NewComputedWithPath(r, t, name, path+"."+name)
}
func NewComputedWithPath(r *Resource, t cty.Type, name, path string) *Computed {
return &Computed{
r: r,
a: a,
t: t,
name: name,
sString: starlark.String(fmt.Sprintf("${%s.%s.%d.%s}", r.kind, r.typ, hash, name)),
path: path,
sString: starlark.String(fmt.Sprintf("${%s}", path)),
}
}
func (*Computed) Type() string {
return "computed"
}
func (c *Computed) InnerType() *Type {
t, _ := NewTypeFromCty(c.t)
return t
}
func (c *Computed) Attr(name string) (starlark.Value, error) {
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
}
func (c *Computed) AttrNames() []string {
return nil
}
func (c *Computed) doNested(name, path string, t cty.Type, index int) *Computed {
return &Computed{
r: c.r,
t: t,
name: c.name,
}
}
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
}
func (c *Computed) Len() int {
if !c.t.IsSetType() && !c.t.IsListType() {
return 0
}
return 1024
}

@ -0,0 +1,7 @@
package provider
import "testing"
func TestComputed(t *testing.T) {
test(t, "testdata/computed.star")
}

@ -111,7 +111,7 @@ func (m *MapSchema) Attr(name string) (starlark.Value, error) {
}
if schema, ok := m.schemas[name]; ok {
m.collections[name] = NewResourceCollection(name, m.kind, schema.Block)
m.collections[name] = NewResourceCollection(name, m.kind, schema.Block, nil)
return m.collections[name], nil
}

@ -31,10 +31,6 @@ func TestResource(t *testing.T) {
test(t, "testdata/resource.star")
}
func TestResourceOutput(t *testing.T) {
test(t, "testdata/resource-output.star")
}
func TestHCL(t *testing.T) {
test(t, "testdata/hcl.star")
}

@ -8,8 +8,6 @@ import (
"go.starlark.net/syntax"
)
type fnSignature func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error)
type ResourceKind string
const (
@ -22,14 +20,16 @@ type Resource struct {
typ string
kind ResourceKind
block *configschema.Block
parent *Resource
values map[string]*Value
}
func MakeResource(typ string, k ResourceKind, b *configschema.Block) *Resource {
func MakeResource(typ string, k ResourceKind, b *configschema.Block, parent *Resource) *Resource {
return &Resource{
typ: typ,
kind: k,
block: b,
parent: parent,
values: make(map[string]*Value),
}
}
@ -100,37 +100,41 @@ func (r *Resource) Attr(name string) (starlark.Value, error) {
return r.toDict(), nil
}
if a, ok := r.block.Attributes[name]; (ok && a.Computed) || name == "id" {
return r.attrComputed(name, a)
if a, ok := r.block.Attributes[name]; ok {
return r.attrValue(name, a)
}
if b, ok := r.block.BlockTypes[name]; ok {
return r.attrBlock(name, b)
}
if v, ok := r.values[name]; ok {
return v.Value(), nil
}
return nil, nil
}
func (r *Resource) attrComputed(name string, attr *configschema.Attribute) (starlark.Value, error) {
return NewComputed(r, attr, name), nil
}
func (r *Resource) attrBlock(name string, b *configschema.NestedBlock) (starlark.Value, error) {
if b.MaxItems != 1 {
if _, ok := r.values[name]; !ok {
r.values[name] = MustValue(NewResourceCollection(name, NestedK, &b.Block))
r.values[name] = MustValue(NewResourceCollection(name, NestedK, &b.Block, r))
}
} else {
if _, ok := r.values[name]; !ok {
r.values[name] = MustValue(MakeResource(name, NestedK, &b.Block, r))
}
}
if _, ok := r.values[name]; !ok {
r.values[name] = MustValue(MakeResource(name, NestedK, &b.Block))
return r.values[name].Value(), nil
}
func (r *Resource) attrValue(name string, attr *configschema.Attribute) (starlark.Value, error) {
if attr.Computed {
return NewComputed(r, attr.Type, name), nil
}
return r.values[name].Value(), nil
if v, ok := r.values[name]; ok {
return v.Value(), nil
}
return starlark.None, nil
}
// AttrNames honors the starlark.HasAttrs interface.
@ -163,6 +167,10 @@ func (r *Resource) SetField(name string, v starlark.Value) error {
return starlark.NoSuchAttrError(errmsg)
}
if attr.Computed && !attr.Optional {
return fmt.Errorf("%s: can't set computed %s attribute", r.typ, name)
}
if err := MustTypeFromCty(attr.Type).Validate(v); err != nil {
return err
}
@ -181,10 +189,6 @@ func (r *Resource) setFieldFromNestedBlock(name string, b *configschema.NestedBl
return fmt.Errorf("expected dict or list, got %s", v.Type())
}
func (r *Resource) localName() string {
return fmt.Sprintf("")
}
func (r *Resource) toDict() *starlark.Dict {
d := starlark.NewDict(len(r.values))
for k, v := range r.values {
@ -229,7 +233,6 @@ func (x *Resource) doCompareSameType(y *Resource, depth int) (bool, error) {
}
for key, xval := range x.values {
fmt.Printf("%s = %T, %T\n", key, xval.Value(), y.values[key].Value())
yval, found := y.values[key]
if !found {
return false, nil

24
provider/testdata/computed.star vendored Normal file

@ -0,0 +1,24 @@
load("assert.star", "assert")
aws = provider("aws", "2.13.0")
# compute of scalar
web = aws.resource.instance()
web.ami = aws.data.ami().id
assert.eq(type(web.ami), "computed")
assert.eq(str(web.ami), '"${data.aws_ami.8731.id}"')
# compute of set
table = aws.data.dynamodb_table()
assert.eq(str(table.ttl), '"${data.aws_dynamodb_table.8731.ttl}"')
assert.eq(str(table.ttl[0]), '"${data.aws_dynamodb_table.8731.ttl.0}"')
assert.eq(str(table.ttl[0].attribute_name), '"${data.aws_dynamodb_table.8731.ttl.0.attribute_name}"')
# compute of list
instance = aws.data.instance()
assert.eq(str(instance.credit_specification), '"${data.aws_instance.8731.credit_specification}"')
assert.eq(str(instance.credit_specification[0]), '"${data.aws_instance.8731.credit_specification.0}"')
assert.eq(str(instance.credit_specification[0].cpu_credits), '"${data.aws_instance.8731.credit_specification.0.cpu_credits}"')
# compute of map
assert.eq(str(aws.resource.instance().root_block_device.volume_size), '"${resource.aws_instance.8731.root_block_device.volume_size}"')

@ -34,4 +34,4 @@ group.mixed_instances_policy = {
ami2 = aws.data.ami()
ami2.most_recent = True
print(hcl(aws))
#print(hcl(aws))

@ -1,12 +0,0 @@
load("assert.star", "assert")
aws = provider("aws", "2.13.0")
ami = aws.data.ami()
assert.eq(type(ami.architecture), "computed")
assert.eq(str(ami.architecture), '"${data.aws_ami.8731.architecture}"')
web = aws.resource.instance()
web.ami = ami.id
assert.eq(type(web.ami), "computed")
assert.eq(str(web.ami), '"${data.aws_ami.8731.id}"')

@ -2,6 +2,32 @@ load("assert.star", "assert")
p = provider("ignition", "1.1.0")
# attr
qux = p.data.user()
qux.uid = 42
assert.eq(qux.uid, 42)
# attr not-set
assert.eq(qux.name, None)
# attr not-exists
assert.fails(lambda: qux.foo, "data has no .foo field or method")
# attr id
assert.eq(type(qux.id), "computed")
assert.eq(str(qux.id), '"${data.ignition_user.3399129522.id}"')
# attr output assignation
aws = provider("aws", "2.13.0")
def invalidOutput(): aws.data.instance().public_dns = "foo"
assert.fails(invalidOutput, "aws_instance: can't set computed public_dns attribute")
# attr output in asignation
web = aws.resource.instance()
web.ami = web.id
def invalidType(): web.get_password_data = web.id
assert.fails(invalidType, "expected bool, got string")
# comparasion simple values
assert.eq(p.data.disk(), p.data.disk())
assert.ne(p.data.disk(device="foo"), p.data.disk())
@ -60,8 +86,6 @@ home.label = "home"
home.start = root.size + root.start
home.size = 4 * 1024 * 1024
print(dict({"foo": disk}))
assert.eq(disk.__dict__, {
"partition": [{
"label": "root",

@ -226,9 +226,12 @@ func (t *Type) Validate(v starlark.Value) error {
return nil
}
case *Computed:
if t.cty == v.(*Computed).a.Type {
if t.cty == v.(*Computed).t {
return nil
}
vt := v.(*Computed).InnerType().Starlark()
return fmt.Errorf("expected %s, got %s", t.typ, vt)
case *starlark.List:
if t.cty.IsListType() || t.cty.IsSetType() {
return t.validateListType(v.(*starlark.List), t.cty.ElementType())