Skip to content

Commit

Permalink
starlark/types: Terraform global object
Browse files Browse the repository at this point in the history
  • Loading branch information
mcuadros committed Mar 20, 2020
1 parent c00d6e9 commit 74c404f
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 22 deletions.
11 changes: 1 addition & 10 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"os"

"github.com/hashicorp/hcl2/hclwrite"
"github.com/mcuadros/ascode/starlark/types"
"go.starlark.net/starlark"
)

Expand Down Expand Up @@ -53,15 +52,7 @@ func (c *RunCmd) dumpToHCL(ctx starlark.StringDict) error {
}

f := hclwrite.NewEmptyFile()
for _, v := range ctx {
// TODO(mcuadros): replace this logic with a global object terraform
switch o := v.(type) {
case *types.Provider:
o.ToHCL(f.Body())
case *types.Backend:
o.ToHCL(f.Body())
}
}
c.runtime.Terraform.ToHCL(f.Body())

return ioutil.WriteFile(c.ToHCL, f.Bytes(), 0644)
}
5 changes: 5 additions & 0 deletions starlark/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func init() {
type LoadModuleFunc func() (starlark.StringDict, error)

type Runtime struct {
Terraform *types.Terraform
predeclared starlark.StringDict
modules map[string]LoadModuleFunc
moduleCache map[string]*moduleCache
Expand All @@ -38,7 +39,10 @@ type Runtime struct {
}

func NewRuntime(pm *terraform.PluginManager) *Runtime {
tf := types.MakeTerraform(pm)

return &Runtime{
Terraform: tf,
moduleCache: make(map[string]*moduleCache),
modules: map[string]LoadModuleFunc{
filepath.ModuleName: filepath.LoadModule,
Expand All @@ -53,6 +57,7 @@ func NewRuntime(pm *terraform.PluginManager) *Runtime {
"http": http.LoadModule,
},
predeclared: starlark.StringDict{
"tf": tf,
"provider": types.BuiltinProvider(pm),
"provisioner": types.BuiltinProvisioner(pm),
"backend": types.BuiltinBackend(pm),
Expand Down
23 changes: 16 additions & 7 deletions starlark/types/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,26 @@ func BuiltinBackend(pm *terraform.PluginManager) starlark.Value {
// Backend represent a Terraform Backend.
// https://www.terraform.io/docs/backends/index.html
type Backend struct {
pm *terraform.PluginManager
b backend.Backend
typ string
pm *terraform.PluginManager
b backend.Backend
*Resource
}

// MakeBackend returns a new Backend instance based on given arguments,
func MakeBackend(pm *terraform.PluginManager, name string) (*Backend, error) {
fn := binit.Backend(name)
func MakeBackend(pm *terraform.PluginManager, typ string) (*Backend, error) {
fn := binit.Backend(typ)
if fn == nil {
return nil, fmt.Errorf("unable to find backend %q", name)
return nil, fmt.Errorf("unable to find backend %q", typ)
}

b := fn()

return &Backend{
typ: typ,
pm: pm,
b: b,
Resource: MakeResource(name, "", BackendKind, b.ConfigSchema(), nil, nil),
Resource: MakeResource(typ, "", BackendKind, b.ConfigSchema(), nil, nil),
}, nil
}

Expand All @@ -73,14 +75,16 @@ func (b *Backend) Attr(name string) (starlark.Value, error) {
switch name {
case "state":
return starlark.NewBuiltin("state", b.state), nil
case "__type__":
return starlark.String(b.typ), nil
}

return b.Resource.Attr(name)
}

// AttrNames honors the starlark.HasAttrs interface.
func (b *Backend) AttrNames() []string {
return append(b.Resource.AttrNames(), "state")
return append(b.Resource.AttrNames(), "state", "__type__")
}

func (b *Backend) getStateMgr(workspace string) (statemgr.Full, error) {
Expand Down Expand Up @@ -144,6 +148,11 @@ func (b *Backend) state(

}

// Type honors the starlark.Value interface.
func (b *Backend) Type() string {
return fmt.Sprintf("Backend<%s>", b.typ)
}

// State represents a Terraform state read by a backed.
// https://www.terraform.io/docs/state/index.html
type State struct {
Expand Down
109 changes: 108 additions & 1 deletion starlark/types/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/hashicorp/terraform/configs/configschema"
"github.com/mcuadros/ascode/terraform"
"go.starlark.net/starlark"
"go.starlark.net/syntax"
)
Expand Down Expand Up @@ -67,7 +68,9 @@ func (c *ResourceCollection) Truth() starlark.Bool {
func (c *ResourceCollection) Freeze() {}

// Hash honors the starlark.Value interface.
func (c *ResourceCollection) Hash() (uint32, error) { return 42, nil }
func (c *ResourceCollection) Hash() (uint32, error) {
return 0, fmt.Errorf("unhashable type: ResourceCollection")
}

// Name honors the starlark.Callable interface.
func (c *ResourceCollection) Name() string {
Expand Down Expand Up @@ -248,3 +251,107 @@ func getValue(r *Resource, key string) starlark.Value {

return r.values.Get(key).Starlark()
}

type ProviderCollection struct {
pm *terraform.PluginManager
*AttrDict
}

func NewProviderCollection(pm *terraform.PluginManager) *ProviderCollection {
return &ProviderCollection{
pm: pm,
AttrDict: NewAttrDict(),
}
}

// String honors the starlark.Value interface.
func (c *ProviderCollection) String() string {
return "foo"
}

// Type honors the starlark.Value interface.
func (c *ProviderCollection) Type() string {
return "ProviderCollection"
}

// Truth honors the starlark.Value interface.
func (c *ProviderCollection) Truth() starlark.Bool {
return true // even when empty
}

// Freeze honors the starlark.Value interface.
func (c *ProviderCollection) Freeze() {}

// Hash honors the starlark.Value interface.
func (c *ProviderCollection) Hash() (uint32, error) {
return 0, fmt.Errorf("unhashable type: ProviderCollection")
}

// Name honors the starlark.Callable interface.
func (c *ProviderCollection) Name() string {
return "foo"
}

// CallInternal honors the starlark.Callable interface.
func (c *ProviderCollection) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
name, version, alias, err := c.unpackArgs(args)
if err != nil {
return nil, err
}

return c.MakeProvider(name, version, alias, kwargs)
}

func (c *ProviderCollection) MakeProvider(name, version, alias string, kwargs []starlark.Tuple) (*Provider, error) {
n := starlark.String(name)
a := starlark.String(alias)

if _, ok, _ := c.Get(n); !ok {
c.SetKey(n, NewAttrDict())
}
providers, _, _ := c.Get(n)
if _, ok, _ := providers.(*AttrDict).Get(a); ok {
return nil, fmt.Errorf("already exists a provider %q with the alias %q", name, alias)
}

p, err := MakeProvider(c.pm, name, version, alias)
if err != nil {
return nil, err
}

if err := providers.(*AttrDict).SetKey(starlark.String(p.alias), p); err != nil {
return nil, err
}

return p, p.loadKeywordArgs(kwargs)
}

func (c *ProviderCollection) unpackArgs(args starlark.Tuple) (string, string, string, error) {
var name, version, alias starlark.String
switch len(args) {
case 3:
var ok bool
alias, ok = args.Index(2).(starlark.String)
if !ok {
return "", "", "", fmt.Errorf("expected string, got %s", args.Index(2).Type())
}
fallthrough
case 2:
var ok bool
version, ok = args.Index(1).(starlark.String)
if !ok {
return "", "", "", fmt.Errorf("expected string, got %s", args.Index(1).Type())
}
fallthrough
case 1:
var ok bool
name, ok = args.Index(0).(starlark.String)
if !ok {
return "", "", "", fmt.Errorf("expected string, got %s", args.Index(0).Type())
}
default:
return "", "", "", fmt.Errorf("unexpected positional arguments count")
}

return string(name), string(version), string(alias), nil
}
22 changes: 22 additions & 0 deletions starlark/types/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ func BuiltinHCL() starlark.Value {
})
}

func (s *Terraform) ToHCL(b *hclwrite.Body) {
if s.b != nil {
s.b.ToHCL(b)
}

s.p.ToHCL(b)
}

func (s *AttrDict) ToHCL(b *hclwrite.Body) {
for _, v := range s.Keys() {
p, _, _ := s.Get(v)
hcl, ok := p.(HCLCompatible)
if !ok {
continue
}

hcl.ToHCL(b)
}
}

func (s *Provider) ToHCL(b *hclwrite.Body) {
block := b.AppendNewBlock("provider", []string{s.name})

Expand All @@ -42,6 +62,7 @@ func (s *Provider) ToHCL(b *hclwrite.Body) {

s.dataSources.ToHCL(b)
s.resources.ToHCL(b)
b.AppendNewline()
}

func (s *Provisioner) ToHCL(b *hclwrite.Body) {
Expand All @@ -54,6 +75,7 @@ func (s *Backend) ToHCL(b *hclwrite.Body) {

block := parent.Body().AppendNewBlock("backend", []string{s.name})
s.Resource.doToHCLAttributes(block.Body())
b.AppendNewline()
}

func (t *MapSchema) ToHCL(b *hclwrite.Body) {
Expand Down
3 changes: 2 additions & 1 deletion starlark/types/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ func init() {
return fmt.Sprintf("id_%d", id)
}

// The tests make extensive use of these not-yet-standard features.
resolve.AllowLambda = true
resolve.AllowNestedDef = true
resolve.AllowFloat = true
resolve.AllowSet = true
resolve.AllowGlobalReassign = true
}

func TestProvider(t *testing.T) {
Expand Down Expand Up @@ -70,6 +70,7 @@ func doTest(t *testing.T, filename string) {
"hcl": BuiltinHCL(),
"fn": BuiltinFunctionComputed(),
"evaluate": BuiltinEvaluate(),
"tf": MakeTerraform(pm),
}

_, err := starlark.ExecFile(thread, filename, nil, predeclared)
Expand Down
80 changes: 80 additions & 0 deletions starlark/types/terraform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package types

import (
"fmt"

"github.com/mcuadros/ascode/terraform"
"go.starlark.net/starlark"
)

type Terraform struct {
b *Backend
p *ProviderCollection
}

func MakeTerraform(pm *terraform.PluginManager) *Terraform {
return &Terraform{
p: NewProviderCollection(pm),
}
}

// Attr honors the starlark.HasAttrs interface.
func (t *Terraform) Attr(name string) (starlark.Value, error) {
switch name {
case "provider":
return t.p, nil
case "backend":
if t.b == nil {
return starlark.None, nil
}

return t.b, nil
}

return starlark.None, nil
}

// SetField honors the starlark.HasSetField interface.
func (t *Terraform) SetField(name string, val starlark.Value) error {
if name != "backend" {
errmsg := fmt.Sprintf("terraform has no .%s field or method", name)
return starlark.NoSuchAttrError(errmsg)
}

if b, ok := val.(*Backend); ok {
t.b = b
return nil
}

return fmt.Errorf("unexpected value %s at %s", val.Type(), name)
}

// AttrNames honors the starlark.HasAttrs interface.
func (t *Terraform) AttrNames() []string {
return []string{"provider", "backend"}
}

// Freeze honors the starlark.Value interface.
func (t *Terraform) Freeze() {} // immutable

// Hash honors the starlark.Value interface.
func (t *Terraform) Hash() (uint32, error) {
return 0, fmt.Errorf("unhashable type: Terraform")
}

// String honors the starlark.Value interface.
func (t *Terraform) String() string {
return "terraform"
}

// Truth honors the starlark.Value interface.
func (t *Terraform) Truth() starlark.Bool {
return t.p.Len() != 0
}

// Type honors the starlark.Value interface.
func (t *Terraform) Type() string {
return "Terraform"
}

var _ starlark.Value = &Terraform{}
9 changes: 9 additions & 0 deletions starlark/types/terraform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package types

import (
"testing"
)

func TestTerraform(t *testing.T) {
doTest(t, "testdata/terraform.star")
}
Loading

0 comments on commit 74c404f

Please sign in to comment.