1
1
mirror of https://github.com/mcuadros/ascode synced 2024-11-22 17:02:03 +01:00

starlark: provider aliasing support

Signed-off-by: Máximo Cuadros <mcuadros@gmail.com>
This commit is contained in:
Máximo Cuadros 2019-07-08 00:28:42 +02:00
parent 814a0bd668
commit 014a32752e
14 changed files with 102 additions and 70 deletions

@ -16,3 +16,6 @@ root.size = 4 * 1024 * 1024
home = disk.partition()
home.start = root.size + root.start
home.size = 4 * 1024 * 1024
config = ignition.data.config(disks=[disk.id], users=[user.id])

@ -35,6 +35,7 @@ func main() {
out, err := starlark.ExecFile(thread, os.Args[1], nil, predeclared)
if err != nil {
fmt.Println(err)
if err, ok := err.(*starlark.EvalError); ok {
log.Fatal(err.Backtrace())
}

@ -9,13 +9,13 @@ import (
type ResourceCollection struct {
typ string
kind ResourceKind
kind Kind
block *configschema.Block
parent *Resource
*starlark.List
}
func NewResourceCollection(typ string, k ResourceKind, block *configschema.Block, parent *Resource) *ResourceCollection {
func NewResourceCollection(typ string, k Kind, block *configschema.Block, parent *Resource) *ResourceCollection {
return &ResourceCollection{
typ: typ,
kind: k,
@ -64,7 +64,7 @@ func (c *ResourceCollection) CallInternal(thread *starlark.Thread, args starlark
return nil, fmt.Errorf("resource: expected dict, go %s", args.Index(0).Type())
}
default:
if c.kind != NestedK {
if c.kind != NestedKind {
return nil, fmt.Errorf("resource: unexpected positional arguments count")
}
}

@ -24,13 +24,8 @@ func NewComputed(r *Resource, t cty.Type, name string) *Computed {
child := r
for {
if child.parent == nil {
name, err := child.Name()
if err != nil {
panic(err)
}
path = fmt.Sprintf("%s.%s.%s", child.kind, child.typ, name)
if child.parent.kind == ProviderKind {
path = fmt.Sprintf("%s.%s.%s", child.kind, child.typ, child.Name())
break
}
@ -51,7 +46,7 @@ func NewComputedWithPath(r *Resource, t cty.Type, name, path string) *Computed {
t: t,
name: name,
path: path,
sString: starlark.String(fmt.Sprintf("${%s}", path)),
sString: starlark.String(fmt.Sprintf("$${%s}", path)),
}
}

@ -1,6 +1,8 @@
package types
import "testing"
import (
"testing"
)
func TestComputed(t *testing.T) {
test(t, "testdata/computed.star")

@ -33,7 +33,9 @@ func BuiltinHCL() starlark.Value {
func (s *Provider) ToHCL(b *hclwrite.Body) {
block := b.AppendNewBlock("provider", []string{s.name})
block.Body().SetAttributeValue("alias", cty.StringVal(s.Name()))
block.Body().SetAttributeValue("version", cty.StringVal(string(s.meta.Version)))
s.Resource.doToHCLAttributes(block.Body())
s.dataSources.ToHCL(b)
s.resources.ToHCL(b)
@ -58,18 +60,24 @@ func (r *Resource) ToHCL(b *hclwrite.Body) {
}
var block *hclwrite.Block
if r.kind != NestedK {
name, err := r.Name()
if err != nil {
panic(err)
}
block = b.AppendNewBlock(string(r.kind), []string{r.typ, name})
if r.kind != NestedKind {
block = b.AppendNewBlock(string(r.kind), []string{r.typ, r.Name()})
} else {
block = b.AppendNewBlock(r.typ, nil)
}
body := block.Body()
if r.parent.kind == ProviderKind {
body.SetAttributeTraversal("provider", hcl.Traversal{hcl.TraverseRoot{
Name: fmt.Sprintf("%s.%s", r.parent.typ, r.parent.Name()),
}})
}
r.doToHCLAttributes(body)
}
func (r *Resource) doToHCLAttributes(body *hclwrite.Body) {
for k := range r.block.Attributes {
v, ok := r.values[k]
if !ok {
@ -95,7 +103,7 @@ func (r *Resource) ToHCL(b *hclwrite.Body) {
}
if collection, ok := v.Value().(HCLCompatible); ok {
collection.ToHCL(block.Body())
collection.ToHCL(body)
}
}
}

@ -20,7 +20,7 @@ type Provider struct {
dataSources *MapSchema
resources *MapSchema
r *Resource
*Resource
}
func MakeProvider(pm *terraform.PluginManager, name, version string) (*Provider, error) {
@ -39,14 +39,18 @@ func MakeProvider(pm *terraform.PluginManager, name, version string) (*Provider,
response := provider.GetSchema()
defer cli.Kill()
return &Provider{
name: name,
provider: provider,
meta: meta,
r: MakeResource("", ResourceK, response.Provider.Block, nil),
dataSources: NewMapSchema(name, DataResourceK, response.DataSources),
resources: NewMapSchema(name, ResourceK, response.ResourceTypes),
}, nil
p := &Provider{
name: name,
provider: provider,
meta: meta,
Resource: MakeResource(name, ProviderKind, response.Provider.Block, nil),
}
p.dataSources = NewMapSchema(p, name, DataSourceKind, response.DataSources)
p.resources = NewMapSchema(p, name, ResourceKind, response.ResourceTypes)
return p, nil
}
func (p *Provider) String() string {
@ -57,9 +61,6 @@ func (p *Provider) Type() string {
return "provider"
}
func (p *Provider) Freeze() {}
func (p *Provider) Truth() starlark.Bool { return true }
func (p *Provider) Hash() (uint32, error) { return 1, nil }
func (p *Provider) Attr(name string) (starlark.Value, error) {
switch name {
case "version":
@ -70,22 +71,25 @@ func (p *Provider) Attr(name string) (starlark.Value, error) {
return p.resources, nil
}
return starlark.None, nil
return p.Resource.Attr(name)
}
func (p *Provider) AttrNames() []string {
return []string{"data", "resource"}
return append(p.Resource.AttrNames(), "data", "resource", "version")
}
type MapSchema struct {
p *Provider
prefix string
kind ResourceKind
kind Kind
schemas map[string]providers.Schema
collections map[string]*ResourceCollection
}
func NewMapSchema(prefix string, k ResourceKind, schemas map[string]providers.Schema) *MapSchema {
func NewMapSchema(p *Provider, prefix string, k Kind, schemas map[string]providers.Schema) *MapSchema {
return &MapSchema{
p: p,
prefix: prefix,
kind: k,
schemas: schemas,
@ -114,7 +118,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, nil)
m.collections[name] = NewResourceCollection(name, m.kind, schema.Block, m.p.Resource)
return m.collections[name], nil
}

@ -13,6 +13,12 @@ import (
)
func init() {
var id int
NameGenerator = func() string {
id++
return fmt.Sprintf("id_%d", id)
}
// The tests make extensive use of these not-yet-standard features.
resolve.AllowLambda = true
resolve.AllowNestedDef = true

@ -2,29 +2,48 @@ package types
import (
"fmt"
"math/rand"
"time"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/oklog/ulid"
"go.starlark.net/starlark"
"go.starlark.net/syntax"
)
type ResourceKind string
// NameGenerator function used to generate Resource names, by default is based
// on a ULID generator.
var NameGenerator = func() string {
t := time.Now()
entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0)
return fmt.Sprintf("id_%s", ulid.MustNew(ulid.Timestamp(t), entropy))
}
// Kind describes what kind of resource is represented by a Resource isntance.
type Kind string
const (
ResourceK ResourceKind = "resource"
DataResourceK ResourceKind = "data"
NestedK ResourceKind = "nested"
ProviderKind Kind = "provider"
ResourceKind Kind = "resource"
DataSourceKind Kind = "data"
NestedKind Kind = "nested"
)
// Resource represents a resource as a starlark.Value, it can be of four kinds,
// provider, resource, data source or a nested resource.
type Resource struct {
name string
typ string
kind ResourceKind
kind Kind
block *configschema.Block
parent *Resource
values map[string]*Value
}
func MakeResource(typ string, k ResourceKind, b *configschema.Block, parent *Resource) *Resource {
// MakeResource returns a new resource of the given kind, type based on the
// given configschema.Block.
func MakeResource(typ string, k Kind, b *configschema.Block, parent *Resource) *Resource {
return &Resource{
typ: typ,
kind: k,
@ -76,17 +95,12 @@ func (r *Resource) Truth() starlark.Bool {
func (r *Resource) Freeze() {}
// Name returns the resource name based on the hash.
func (r *Resource) Name() (string, error) {
if r.kind == NestedK {
return "", fmt.Errorf("name is not supported on nested resources")
func (r *Resource) Name() string {
if r.name == "" {
r.name = NameGenerator()
}
hash, err := r.Hash()
if err != nil {
return "", err
}
return fmt.Sprintf("id_%d", hash), nil
return r.name
}
// Hash honors the starlark.Value interface.
@ -128,11 +142,11 @@ func (r *Resource) Attr(name string) (starlark.Value, error) {
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))
r.values[name] = MustValue(NewResourceCollection(name, NestedKind, &b.Block, r))
}
} else {
if _, ok := r.values[name]; !ok {
r.values[name] = MustValue(MakeResource(name, NestedK, &b.Block, r))
r.values[name] = MustValue(MakeResource(name, NestedKind, &b.Block, r))
}
}

@ -6,27 +6,20 @@ aws = provider("aws", "2.13.0")
web = aws.resource.instance()
web.ami = aws.data.ami().id
assert.eq(type(web.ami), "computed")
assert.eq(str(web.ami), '"${data.aws_ami.id_8731.id}"')
assert.eq(str(web.ami), '"$${data.aws_ami.id_1.id}"')
# compute of set
table = aws.data.dynamodb_table()
assert.eq(str(table.ttl), '"${data.aws_dynamodb_table.id_8731.ttl}"')
assert.eq(str(table.ttl[0]), '"${data.aws_dynamodb_table.id_8731.ttl.0}"')
assert.eq(str(table.ttl[0].attribute_name), '"${data.aws_dynamodb_table.id_8731.ttl.0.attribute_name}"')
assert.eq(str(table.ttl), '"$${data.aws_dynamodb_table.id_2.ttl}"')
assert.eq(str(table.ttl[0]), '"$${data.aws_dynamodb_table.id_2.ttl.0}"')
assert.eq(str(table.ttl[0].attribute_name), '"$${data.aws_dynamodb_table.id_2.ttl.0.attribute_name}"')
# compute of list
instance = aws.data.instance()
assert.eq(str(instance.credit_specification), '"${data.aws_instance.id_8731.credit_specification}"')
assert.eq(str(instance.credit_specification[0]), '"${data.aws_instance.id_8731.credit_specification.0}"')
assert.eq(str(instance.credit_specification[0].cpu_credits), '"${data.aws_instance.id_8731.credit_specification.0.cpu_credits}"')
assert.eq(str(instance.credit_specification), '"$${data.aws_instance.id_3.credit_specification}"')
assert.eq(str(instance.credit_specification[0]), '"$${data.aws_instance.id_3.credit_specification.0}"')
assert.eq(str(instance.credit_specification[0].cpu_credits), '"$${data.aws_instance.id_3.credit_specification.0.cpu_credits}"')
# compute of map
# {resource.aws_instance.id_8731.root_block_device.volume_size}
computed = str(aws.resource.instance().root_block_device.volume_size)
parts = computed[3:len(computed)-2].split(".")
assert.eq(len(parts), 5)
assert.eq(parts[0], "resource")
assert.eq(parts[1], "aws_instance")
assert.eq(parts[3], "root_block_device")
assert.eq(parts[4], "volume_size")
assert.eq(computed, '"$${resource.aws_instance.id_4.root_block_device.volume_size}"')

@ -1,6 +1,7 @@
load("assert.star", "assert")
aws = provider("aws", "2.13.0")
aws.region = "us-west-2"
ubuntu = aws.data.ami()
ubuntu.most_recent = True

@ -13,3 +13,6 @@ assert.eq(type(p.resource.instance), "collection")
p.resource.instance()
p.resource.instance()
assert.eq(len(p.resource.instance), 2)
p.region = "us-west-2"
assert.eq(p.region, "us-west-2")

@ -15,7 +15,7 @@ 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.id_3399129522.id}"')
assert.eq(str(qux.id), '"$${data.ignition_user.id_5.id}"')
# attr output assignation
aws = provider("aws", "2.13.0")

@ -67,6 +67,8 @@ func (v *Value) Cty() cty.Value {
}
return cty.ListVal(values)
case "computed":
return cty.StringVal(v.v.(*Computed).GoString())
default:
return cty.StringVal(fmt.Sprintf("unhandled: %s", v.t.typ))
}