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:
parent
6daa4b37b4
commit
76dd3d631a
@ -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
|
||||
}
|
||||
|
7
provider/computed_test.go
Normal file
7
provider/computed_test.go
Normal file
@ -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
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}"')
|
2
provider/testdata/hcl.star
vendored
2
provider/testdata/hcl.star
vendored
@ -34,4 +34,4 @@ group.mixed_instances_policy = {
|
||||
ami2 = aws.data.ami()
|
||||
ami2.most_recent = True
|
||||
|
||||
print(hcl(aws))
|
||||
#print(hcl(aws))
|
||||
|
12
provider/testdata/resource-output.star
vendored
12
provider/testdata/resource-output.star
vendored
@ -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}"')
|
28
provider/testdata/resource.star
vendored
28
provider/testdata/resource.star
vendored
@ -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())
|
||||
|
Loading…
Reference in New Issue
Block a user