mirror of
https://github.com/mcuadros/ascode
synced 2024-11-23 01:11:59 +01:00
f6f233160e
Signed-off-by: Máximo Cuadros <mcuadros@gmail.com>
303 lines
6.6 KiB
Go
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)"
|
|
}
|