1
1
mirror of https://github.com/mcuadros/ascode synced 2024-11-23 01:11:59 +01:00
ascode/provider/resource.go
Máximo Cuadros f6f233160e provider: to_hcl function
Signed-off-by: Máximo Cuadros <mcuadros@gmail.com>
2019-07-02 15:03:13 +02:00

303 lines
6.6 KiB
Go

package provider
import (
"fmt"
"github.com/hashicorp/hcl2/hclwrite"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/zclconf/go-cty/cty"
"go.starlark.net/starlark"
)
type fnSignature func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error)
type Resource struct {
name string
typ string
nested bool
block *configschema.Block
values map[string]starlark.Value
}
func MakeResource(name, typ string, nested bool, b *configschema.Block, kwargs []starlark.Tuple) (*Resource, error) {
r := &Resource{
name: name,
typ: typ,
nested: nested,
block: b,
values: make(map[string]starlark.Value),
}
return r, r.loadKeywordArgs(kwargs)
}
func (r *Resource) loadDict(d *starlark.Dict) error {
for _, k := range d.Keys() {
name := k.(starlark.String)
value, _, _ := d.Get(k)
if err := r.SetField(string(name), value); err != nil {
return err
}
}
return nil
}
func (r *Resource) loadKeywordArgs(kwargs []starlark.Tuple) error {
for _, kwarg := range kwargs {
name := kwarg.Index(0).(starlark.String)
if err := r.SetField(string(name), kwarg.Index(1)); err != nil {
return err
}
}
return nil
}
// String honors the starlark.Value interface.
func (r *Resource) String() string {
return fmt.Sprintf("%s(%q)", r.typ, r.name)
}
// Type honors the starlark.Value interface.
func (r *Resource) Type() string {
return r.typ
}
// Truth honors the starlark.Value interface.
func (r *Resource) Truth() starlark.Bool {
return true // even when empty
}
// Freeze honors the starlark.Value interface.
func (r *Resource) Freeze() {}
// Hash honors the starlark.Value interface.
func (r *Resource) Hash() (uint32, error) {
// Same algorithm as Tuple.hash, but with different primes.
var x, m uint32 = 8731, 9839
for name, value := range r.values {
namehash, _ := starlark.String(name).Hash()
x = x ^ 3*namehash
y, err := value.Hash()
if err != nil {
return 0, err
}
x = x ^ y*m
m += 7349
}
fmt.Println(x)
return x, nil
}
// Attr honors the starlark.HasAttrs interface.
func (r *Resource) Attr(name string) (starlark.Value, error) {
if name == "__dict__" {
return r.toDict(), nil
}
if name == "to_hcl" {
return BuiltinToHCL(r, hclwrite.NewEmptyFile()), nil
}
if b, ok := r.block.BlockTypes[name]; ok {
if b.MaxItems != 1 {
if _, ok := r.values[name]; !ok {
r.values[name] = NewResourceCollection(name, true, &b.Block)
}
}
if _, ok := r.values[name]; !ok {
r.values[name], _ = MakeResource("", name, true, &b.Block, nil)
}
}
if v, ok := r.values[name]; ok {
return v, nil
}
return nil, nil
}
func (r *Resource) getNestedBlockAttr(name string, b *configschema.NestedBlock) (starlark.Value, error) {
if v, ok := r.values[name]; ok {
return v, nil
}
var err error
r.values[name], err = MakeResource("", name, true, &b.Block, nil)
return r.values[name], err
}
// AttrNames honors the starlark.HasAttrs interface.
func (r *Resource) AttrNames() []string {
names := make([]string, len(r.block.Attributes)+len(r.block.BlockTypes))
var i int
for k := range r.block.Attributes {
names[i] = k
i++
}
for k := range r.block.BlockTypes {
names[i] = k
i++
}
return names
}
// SetField honors the starlark.HasSetField interface.
func (r *Resource) SetField(name string, v starlark.Value) error {
if b, ok := r.block.BlockTypes[name]; ok {
return r.setFieldFromNestedBlock(name, b, v)
}
attr, ok := r.block.Attributes[name]
if !ok {
errmsg := fmt.Sprintf("%s has no .%s field or method", r.typ, name)
return starlark.NoSuchAttrError(errmsg)
}
if err := ValidateType(v, attr.Type); err != nil {
return err
}
r.values[name] = v
return nil
}
func (r *Resource) setFieldFromNestedBlock(name string, b *configschema.NestedBlock, v starlark.Value) error {
switch v.Type() {
case "dict":
resource, _ := r.Attr(name)
return resource.(*Resource).loadDict(v.(*starlark.Dict))
}
return fmt.Errorf("expected dict or list, got %s", v.Type())
}
func (r *Resource) toDict() *starlark.Dict {
d := starlark.NewDict(len(r.values))
for k, v := range r.values {
if r, ok := v.(*Resource); ok {
d.SetKey(starlark.String(k), r.toDict())
continue
}
if r, ok := v.(*ResourceCollection); ok {
d.SetKey(starlark.String(k), r.toDict())
continue
}
d.SetKey(starlark.String(k), v)
}
return d
}
func ValueToNative(v starlark.Value) interface{} {
switch cast := v.(type) {
case starlark.Bool:
return bool(cast)
case starlark.String:
return string(cast)
case starlark.Int:
i, _ := cast.Int64()
return i
case *ResourceCollection:
return ValueToNative(cast.List)
case *starlark.List:
out := make([]interface{}, cast.Len())
for i := 0; i < cast.Len(); i++ {
out[i] = ValueToNative(cast.Index(i))
}
return out
default:
return v
}
}
/*
NoneType # the type of None
bool # True or False
int # a signed integer of arbitrary magnitude
float # an IEEE 754 double-precision floating point number
string # a byte string
list # a modifiable sequence of values
tuple # an unmodifiable sequence of values
dict # a mapping from values to values
set # a set of values
function # a function implemented in Starlark
builtin_function_or_method
*/
func FromStarlarkType(typ string) cty.Type {
switch typ {
case "bool":
return cty.Bool
case "int":
case "float":
return cty.Number
case "string":
return cty.String
}
return cty.NilType
}
func ValidateListType(l *starlark.List, expected cty.Type) error {
for i := 0; i < l.Len(); i++ {
if err := ValidateType(l.Index(i), expected); err != nil {
return fmt.Errorf("index %d: %s", i, err)
}
}
return nil
}
func ValidateType(v starlark.Value, expected cty.Type) error {
switch v.(type) {
case starlark.String:
if expected == cty.String {
return nil
}
case starlark.Int:
if expected == cty.Number {
return nil
}
case starlark.Bool:
if expected == cty.Bool {
return nil
}
case *starlark.List:
if expected.IsListType() || expected.IsSetType() {
return ValidateListType(v.(*starlark.List), expected.ElementType())
}
}
return fmt.Errorf("expected %s, got %s", ToStarlarkType(expected), v.Type())
}
func ToStarlarkType(t cty.Type) string {
switch t {
case cty.String:
return "string"
case cty.Number:
return "int"
case cty.Bool:
return "bool"
}
if t.IsListType() {
return "list"
}
if t.IsSetType() {
return "set"
}
return "(unknown)"
}