Skip to content

Commit 74c404f

Browse files
committed
starlark/types: Terraform global object
1 parent c00d6e9 commit 74c404f

File tree

11 files changed

+248
-22
lines changed

11 files changed

+248
-22
lines changed

cmd/run.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"os"
77

88
"github.com/hashicorp/hcl2/hclwrite"
9-
"github.com/mcuadros/ascode/starlark/types"
109
"go.starlark.net/starlark"
1110
)
1211

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

5554
f := hclwrite.NewEmptyFile()
56-
for _, v := range ctx {
57-
// TODO(mcuadros): replace this logic with a global object terraform
58-
switch o := v.(type) {
59-
case *types.Provider:
60-
o.ToHCL(f.Body())
61-
case *types.Backend:
62-
o.ToHCL(f.Body())
63-
}
64-
}
55+
c.runtime.Terraform.ToHCL(f.Body())
6556

6657
return ioutil.WriteFile(c.ToHCL, f.Bytes(), 0644)
6758
}

starlark/runtime/runtime.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func init() {
3030
type LoadModuleFunc func() (starlark.StringDict, error)
3131

3232
type Runtime struct {
33+
Terraform *types.Terraform
3334
predeclared starlark.StringDict
3435
modules map[string]LoadModuleFunc
3536
moduleCache map[string]*moduleCache
@@ -38,7 +39,10 @@ type Runtime struct {
3839
}
3940

4041
func NewRuntime(pm *terraform.PluginManager) *Runtime {
42+
tf := types.MakeTerraform(pm)
43+
4144
return &Runtime{
45+
Terraform: tf,
4246
moduleCache: make(map[string]*moduleCache),
4347
modules: map[string]LoadModuleFunc{
4448
filepath.ModuleName: filepath.LoadModule,
@@ -53,6 +57,7 @@ func NewRuntime(pm *terraform.PluginManager) *Runtime {
5357
"http": http.LoadModule,
5458
},
5559
predeclared: starlark.StringDict{
60+
"tf": tf,
5661
"provider": types.BuiltinProvider(pm),
5762
"provisioner": types.BuiltinProvisioner(pm),
5863
"backend": types.BuiltinBackend(pm),

starlark/types/backend.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,26 @@ func BuiltinBackend(pm *terraform.PluginManager) starlark.Value {
4747
// Backend represent a Terraform Backend.
4848
// https://www.terraform.io/docs/backends/index.html
4949
type Backend struct {
50-
pm *terraform.PluginManager
51-
b backend.Backend
50+
typ string
51+
pm *terraform.PluginManager
52+
b backend.Backend
5253
*Resource
5354
}
5455

5556
// MakeBackend returns a new Backend instance based on given arguments,
56-
func MakeBackend(pm *terraform.PluginManager, name string) (*Backend, error) {
57-
fn := binit.Backend(name)
57+
func MakeBackend(pm *terraform.PluginManager, typ string) (*Backend, error) {
58+
fn := binit.Backend(typ)
5859
if fn == nil {
59-
return nil, fmt.Errorf("unable to find backend %q", name)
60+
return nil, fmt.Errorf("unable to find backend %q", typ)
6061
}
6162

6263
b := fn()
6364

6465
return &Backend{
66+
typ: typ,
6567
pm: pm,
6668
b: b,
67-
Resource: MakeResource(name, "", BackendKind, b.ConfigSchema(), nil, nil),
69+
Resource: MakeResource(typ, "", BackendKind, b.ConfigSchema(), nil, nil),
6870
}, nil
6971
}
7072

@@ -73,14 +75,16 @@ func (b *Backend) Attr(name string) (starlark.Value, error) {
7375
switch name {
7476
case "state":
7577
return starlark.NewBuiltin("state", b.state), nil
78+
case "__type__":
79+
return starlark.String(b.typ), nil
7680
}
7781

7882
return b.Resource.Attr(name)
7983
}
8084

8185
// AttrNames honors the starlark.HasAttrs interface.
8286
func (b *Backend) AttrNames() []string {
83-
return append(b.Resource.AttrNames(), "state")
87+
return append(b.Resource.AttrNames(), "state", "__type__")
8488
}
8589

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

145149
}
146150

151+
// Type honors the starlark.Value interface.
152+
func (b *Backend) Type() string {
153+
return fmt.Sprintf("Backend<%s>", b.typ)
154+
}
155+
147156
// State represents a Terraform state read by a backed.
148157
// https://www.terraform.io/docs/state/index.html
149158
type State struct {

starlark/types/collection.go

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/hashicorp/terraform/configs/configschema"
7+
"github.com/mcuadros/ascode/terraform"
78
"go.starlark.net/starlark"
89
"go.starlark.net/syntax"
910
)
@@ -67,7 +68,9 @@ func (c *ResourceCollection) Truth() starlark.Bool {
6768
func (c *ResourceCollection) Freeze() {}
6869

6970
// Hash honors the starlark.Value interface.
70-
func (c *ResourceCollection) Hash() (uint32, error) { return 42, nil }
71+
func (c *ResourceCollection) Hash() (uint32, error) {
72+
return 0, fmt.Errorf("unhashable type: ResourceCollection")
73+
}
7174

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

249252
return r.values.Get(key).Starlark()
250253
}
254+
255+
type ProviderCollection struct {
256+
pm *terraform.PluginManager
257+
*AttrDict
258+
}
259+
260+
func NewProviderCollection(pm *terraform.PluginManager) *ProviderCollection {
261+
return &ProviderCollection{
262+
pm: pm,
263+
AttrDict: NewAttrDict(),
264+
}
265+
}
266+
267+
// String honors the starlark.Value interface.
268+
func (c *ProviderCollection) String() string {
269+
return "foo"
270+
}
271+
272+
// Type honors the starlark.Value interface.
273+
func (c *ProviderCollection) Type() string {
274+
return "ProviderCollection"
275+
}
276+
277+
// Truth honors the starlark.Value interface.
278+
func (c *ProviderCollection) Truth() starlark.Bool {
279+
return true // even when empty
280+
}
281+
282+
// Freeze honors the starlark.Value interface.
283+
func (c *ProviderCollection) Freeze() {}
284+
285+
// Hash honors the starlark.Value interface.
286+
func (c *ProviderCollection) Hash() (uint32, error) {
287+
return 0, fmt.Errorf("unhashable type: ProviderCollection")
288+
}
289+
290+
// Name honors the starlark.Callable interface.
291+
func (c *ProviderCollection) Name() string {
292+
return "foo"
293+
}
294+
295+
// CallInternal honors the starlark.Callable interface.
296+
func (c *ProviderCollection) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
297+
name, version, alias, err := c.unpackArgs(args)
298+
if err != nil {
299+
return nil, err
300+
}
301+
302+
return c.MakeProvider(name, version, alias, kwargs)
303+
}
304+
305+
func (c *ProviderCollection) MakeProvider(name, version, alias string, kwargs []starlark.Tuple) (*Provider, error) {
306+
n := starlark.String(name)
307+
a := starlark.String(alias)
308+
309+
if _, ok, _ := c.Get(n); !ok {
310+
c.SetKey(n, NewAttrDict())
311+
}
312+
providers, _, _ := c.Get(n)
313+
if _, ok, _ := providers.(*AttrDict).Get(a); ok {
314+
return nil, fmt.Errorf("already exists a provider %q with the alias %q", name, alias)
315+
}
316+
317+
p, err := MakeProvider(c.pm, name, version, alias)
318+
if err != nil {
319+
return nil, err
320+
}
321+
322+
if err := providers.(*AttrDict).SetKey(starlark.String(p.alias), p); err != nil {
323+
return nil, err
324+
}
325+
326+
return p, p.loadKeywordArgs(kwargs)
327+
}
328+
329+
func (c *ProviderCollection) unpackArgs(args starlark.Tuple) (string, string, string, error) {
330+
var name, version, alias starlark.String
331+
switch len(args) {
332+
case 3:
333+
var ok bool
334+
alias, ok = args.Index(2).(starlark.String)
335+
if !ok {
336+
return "", "", "", fmt.Errorf("expected string, got %s", args.Index(2).Type())
337+
}
338+
fallthrough
339+
case 2:
340+
var ok bool
341+
version, ok = args.Index(1).(starlark.String)
342+
if !ok {
343+
return "", "", "", fmt.Errorf("expected string, got %s", args.Index(1).Type())
344+
}
345+
fallthrough
346+
case 1:
347+
var ok bool
348+
name, ok = args.Index(0).(starlark.String)
349+
if !ok {
350+
return "", "", "", fmt.Errorf("expected string, got %s", args.Index(0).Type())
351+
}
352+
default:
353+
return "", "", "", fmt.Errorf("unexpected positional arguments count")
354+
}
355+
356+
return string(name), string(version), string(alias), nil
357+
}

starlark/types/hcl.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ func BuiltinHCL() starlark.Value {
3333
})
3434
}
3535

36+
func (s *Terraform) ToHCL(b *hclwrite.Body) {
37+
if s.b != nil {
38+
s.b.ToHCL(b)
39+
}
40+
41+
s.p.ToHCL(b)
42+
}
43+
44+
func (s *AttrDict) ToHCL(b *hclwrite.Body) {
45+
for _, v := range s.Keys() {
46+
p, _, _ := s.Get(v)
47+
hcl, ok := p.(HCLCompatible)
48+
if !ok {
49+
continue
50+
}
51+
52+
hcl.ToHCL(b)
53+
}
54+
}
55+
3656
func (s *Provider) ToHCL(b *hclwrite.Body) {
3757
block := b.AppendNewBlock("provider", []string{s.name})
3858

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

4363
s.dataSources.ToHCL(b)
4464
s.resources.ToHCL(b)
65+
b.AppendNewline()
4566
}
4667

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

5576
block := parent.Body().AppendNewBlock("backend", []string{s.name})
5677
s.Resource.doToHCLAttributes(block.Body())
78+
b.AppendNewline()
5779
}
5880

5981
func (t *MapSchema) ToHCL(b *hclwrite.Body) {

starlark/types/provider_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ func init() {
2121
return fmt.Sprintf("id_%d", id)
2222
}
2323

24-
// The tests make extensive use of these not-yet-standard features.
2524
resolve.AllowLambda = true
2625
resolve.AllowNestedDef = true
2726
resolve.AllowFloat = true
2827
resolve.AllowSet = true
28+
resolve.AllowGlobalReassign = true
2929
}
3030

3131
func TestProvider(t *testing.T) {
@@ -70,6 +70,7 @@ func doTest(t *testing.T, filename string) {
7070
"hcl": BuiltinHCL(),
7171
"fn": BuiltinFunctionComputed(),
7272
"evaluate": BuiltinEvaluate(),
73+
"tf": MakeTerraform(pm),
7374
}
7475

7576
_, err := starlark.ExecFile(thread, filename, nil, predeclared)

starlark/types/terraform.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package types
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/mcuadros/ascode/terraform"
7+
"go.starlark.net/starlark"
8+
)
9+
10+
type Terraform struct {
11+
b *Backend
12+
p *ProviderCollection
13+
}
14+
15+
func MakeTerraform(pm *terraform.PluginManager) *Terraform {
16+
return &Terraform{
17+
p: NewProviderCollection(pm),
18+
}
19+
}
20+
21+
// Attr honors the starlark.HasAttrs interface.
22+
func (t *Terraform) Attr(name string) (starlark.Value, error) {
23+
switch name {
24+
case "provider":
25+
return t.p, nil
26+
case "backend":
27+
if t.b == nil {
28+
return starlark.None, nil
29+
}
30+
31+
return t.b, nil
32+
}
33+
34+
return starlark.None, nil
35+
}
36+
37+
// SetField honors the starlark.HasSetField interface.
38+
func (t *Terraform) SetField(name string, val starlark.Value) error {
39+
if name != "backend" {
40+
errmsg := fmt.Sprintf("terraform has no .%s field or method", name)
41+
return starlark.NoSuchAttrError(errmsg)
42+
}
43+
44+
if b, ok := val.(*Backend); ok {
45+
t.b = b
46+
return nil
47+
}
48+
49+
return fmt.Errorf("unexpected value %s at %s", val.Type(), name)
50+
}
51+
52+
// AttrNames honors the starlark.HasAttrs interface.
53+
func (t *Terraform) AttrNames() []string {
54+
return []string{"provider", "backend"}
55+
}
56+
57+
// Freeze honors the starlark.Value interface.
58+
func (t *Terraform) Freeze() {} // immutable
59+
60+
// Hash honors the starlark.Value interface.
61+
func (t *Terraform) Hash() (uint32, error) {
62+
return 0, fmt.Errorf("unhashable type: Terraform")
63+
}
64+
65+
// String honors the starlark.Value interface.
66+
func (t *Terraform) String() string {
67+
return "terraform"
68+
}
69+
70+
// Truth honors the starlark.Value interface.
71+
func (t *Terraform) Truth() starlark.Bool {
72+
return t.p.Len() != 0
73+
}
74+
75+
// Type honors the starlark.Value interface.
76+
func (t *Terraform) Type() string {
77+
return "Terraform"
78+
}
79+
80+
var _ starlark.Value = &Terraform{}

starlark/types/terraform_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package types
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestTerraform(t *testing.T) {
8+
doTest(t, "testdata/terraform.star")
9+
}

0 commit comments

Comments
 (0)