Skip to content

Commit 5f01207

Browse files
committed
starlark/types: State type
1 parent d713bb6 commit 5f01207

File tree

8 files changed

+1142
-7
lines changed

8 files changed

+1142
-7
lines changed

starlark/types/backend.go

Lines changed: 250 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
package types
22

33
import (
4+
"encoding/json"
45
"fmt"
56

6-
backend "github.com/hashicorp/terraform/backend/init"
7+
"github.com/hashicorp/terraform/addrs"
8+
"github.com/hashicorp/terraform/backend"
9+
binit "github.com/hashicorp/terraform/backend/init"
10+
"github.com/hashicorp/terraform/providers"
11+
"github.com/hashicorp/terraform/states"
12+
"github.com/hashicorp/terraform/states/statemgr"
13+
"github.com/mcuadros/ascode/terraform"
14+
"github.com/qri-io/starlib/util"
715
"go.starlark.net/starlark"
816
)
917

1018
func init() {
11-
backend.Init(nil)
19+
binit.Init(nil)
1220
}
1321

14-
func BuiltinBackend() starlark.Value {
22+
func BuiltinBackend(pm *terraform.PluginManager) starlark.Value {
1523
return starlark.NewBuiltin("backend", func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
1624
var name starlark.String
1725
switch len(args) {
@@ -25,7 +33,7 @@ func BuiltinBackend() starlark.Value {
2533
return nil, fmt.Errorf("unexpected positional arguments count")
2634
}
2735

28-
p, err := MakeBackend(name.GoString())
36+
p, err := MakeBackend(pm, name.GoString())
2937
if err != nil {
3038
return nil, err
3139
}
@@ -35,16 +43,251 @@ func BuiltinBackend() starlark.Value {
3543
}
3644

3745
type Backend struct {
46+
pm *terraform.PluginManager
47+
b backend.Backend
3848
*Resource
3949
}
4050

41-
func MakeBackend(name string) (*Backend, error) {
42-
fn := backend.Backend(name)
51+
func MakeBackend(pm *terraform.PluginManager, name string) (*Backend, error) {
52+
fn := binit.Backend(name)
4353
if fn == nil {
4454
return nil, fmt.Errorf("unable to find backend %q", name)
4555
}
4656

57+
b := fn()
58+
4759
return &Backend{
48-
Resource: MakeResource(name, "", BackendKind, fn().ConfigSchema(), nil, nil),
60+
pm: pm,
61+
b: b,
62+
Resource: MakeResource(name, "", BackendKind, b.ConfigSchema(), nil, nil),
4963
}, nil
5064
}
65+
66+
func (c *Backend) Attr(name string) (starlark.Value, error) {
67+
switch name {
68+
case "state":
69+
return starlark.NewBuiltin("state", c.state), nil
70+
}
71+
72+
return c.Resource.Attr(name)
73+
}
74+
75+
func (b *Backend) getStateMgr(workspace string) (statemgr.Full, error) {
76+
values, diag := b.b.PrepareConfig(b.values.Cty(b.b.ConfigSchema()))
77+
if err := diag.Err(); err != nil {
78+
return nil, err
79+
}
80+
81+
diag = b.b.Configure(values)
82+
if err := diag.Err(); err != nil {
83+
return nil, err
84+
}
85+
86+
workspaces, err := b.b.Workspaces()
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
var found bool
92+
for _, w := range workspaces {
93+
if w == workspace {
94+
found = true
95+
break
96+
}
97+
}
98+
99+
if !found {
100+
return nil, fmt.Errorf("unable to find %q workspace", workspace)
101+
}
102+
103+
return b.b.StateMgr(workspace)
104+
}
105+
106+
func (b *Backend) state(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
107+
108+
workspace := "default"
109+
module := ""
110+
111+
err := starlark.UnpackArgs("state", args, kwargs, "module?", &module, "workspace?", &workspace)
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
sm, err := b.getStateMgr(workspace)
117+
if err != nil {
118+
return nil, err
119+
}
120+
121+
if err := sm.RefreshState(); err != nil {
122+
return nil, err
123+
}
124+
125+
state := sm.State()
126+
if state == nil {
127+
return starlark.None, nil
128+
}
129+
130+
return MakeState(b.pm, module, state)
131+
132+
}
133+
134+
type State struct {
135+
*AttrDict
136+
pm *terraform.PluginManager
137+
}
138+
139+
func MakeState(pm *terraform.PluginManager, module string, state *states.State) (*State, error) {
140+
var mod *states.Module
141+
for _, m := range state.Modules {
142+
if m.Addr.String() == module {
143+
mod = m
144+
}
145+
}
146+
147+
if mod == nil {
148+
return nil, fmt.Errorf("unable to find module with addr %q", module)
149+
}
150+
151+
s := &State{
152+
AttrDict: &AttrDict{starlark.NewDict(0)},
153+
pm: pm,
154+
}
155+
return s, s.initialize(state, mod)
156+
}
157+
158+
func (s *State) initialize(state *states.State, mod *states.Module) error {
159+
providers := make(map[string]*Provider, 0)
160+
addrs := state.ProviderAddrs()
161+
for _, addr := range addrs {
162+
typ := addr.ProviderConfig.Type.Type
163+
p, err := MakeProvider(s.pm, typ, "", addr.ProviderConfig.Alias)
164+
if err != nil {
165+
return err
166+
}
167+
168+
providers[addr.ProviderConfig.String()] = p
169+
}
170+
171+
for _, r := range mod.Resources {
172+
provider := r.ProviderConfig.String()
173+
if err := s.initializeResource(providers[provider], r); err != nil {
174+
return err
175+
}
176+
}
177+
178+
return nil
179+
}
180+
181+
func (s *State) initializeResource(p *Provider, r *states.Resource) error {
182+
typ := r.Addr.Type
183+
name := r.Addr.Name
184+
185+
mode := addrsResourceModeString(r.Addr.Mode)
186+
187+
var schema providers.Schema
188+
switch r.Addr.Mode {
189+
case addrs.DataResourceMode:
190+
schema = p.dataSources.schemas[typ]
191+
case addrs.ManagedResourceMode:
192+
schema = p.resources.schemas[typ]
193+
default:
194+
return fmt.Errorf("invalid resource type")
195+
}
196+
197+
multi := r.EachMode != states.NoEach
198+
for _, instance := range r.Instances {
199+
r := MakeResource(name, typ, ResourceKind, schema.Block, p, p.Resource)
200+
201+
var val interface{}
202+
if err := json.Unmarshal(instance.Current.AttrsJSON, &val); err != nil {
203+
return err
204+
}
205+
206+
values, _ := util.Marshal(val)
207+
if err := r.LoadDict(values.(*starlark.Dict)); err != nil {
208+
return err
209+
}
210+
211+
if err := s.set(mode, typ, name, r, multi); err != nil {
212+
return err
213+
}
214+
}
215+
216+
return nil
217+
}
218+
219+
func addrsResourceModeString(m addrs.ResourceMode) string {
220+
switch m {
221+
case addrs.ManagedResourceMode:
222+
return "resource"
223+
case addrs.DataResourceMode:
224+
return "data"
225+
}
226+
227+
return ""
228+
}
229+
func (s *State) set(mode, typ, name string, r *Resource, multi bool) error {
230+
p := starlark.String(r.provider.name)
231+
m := starlark.String(mode)
232+
t := starlark.String(typ[len(r.provider.name)+1:])
233+
n := starlark.String(name)
234+
235+
if _, ok, _ := s.Get(p); !ok {
236+
s.SetKey(p, NewAttrDict())
237+
}
238+
239+
providers, _, _ := s.Get(p)
240+
if _, ok, _ := providers.(*AttrDict).Get(m); !ok {
241+
providers.(*AttrDict).SetKey(m, NewAttrDict())
242+
}
243+
244+
modes, _, _ := providers.(*AttrDict).Get(m)
245+
if _, ok, _ := modes.(*AttrDict).Get(t); !ok {
246+
modes.(*AttrDict).SetKey(t, NewAttrDict())
247+
}
248+
249+
resources, _, _ := modes.(*AttrDict).Get(t)
250+
251+
if !multi {
252+
return resources.(*AttrDict).SetKey(n, r)
253+
}
254+
255+
if _, ok, _ := resources.(*AttrDict).Get(n); !ok {
256+
resources.(*AttrDict).SetKey(n, starlark.NewList(nil))
257+
}
258+
259+
instances, _, _ := resources.(*AttrDict).Get(n)
260+
if err := instances.(*starlark.List).Append(r); err != nil {
261+
return err
262+
}
263+
264+
return nil
265+
}
266+
267+
type AttrDict struct {
268+
*starlark.Dict
269+
}
270+
271+
func NewAttrDict() *AttrDict {
272+
return &AttrDict{Dict: starlark.NewDict(0)}
273+
}
274+
275+
// Attr honors the starlark.Attr interface.
276+
func (d *AttrDict) Attr(name string) (starlark.Value, error) {
277+
v, _, err := d.Get(starlark.String(name))
278+
if err != nil {
279+
return starlark.None, err
280+
}
281+
282+
return v, nil
283+
}
284+
285+
// AttrNames honors the starlark.HasAttrs interface.
286+
func (d *AttrDict) AttrNames() []string {
287+
var names []string
288+
for _, k := range d.Keys() {
289+
names = append(names, k.(starlark.String).GoString())
290+
}
291+
292+
return names
293+
}

starlark/types/backend_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ import (
77
func TestBackend(t *testing.T) {
88
doTest(t, "testdata/backend.star")
99
}
10+
11+
func TestState(t *testing.T) {
12+
doTest(t, "testdata/state.star")
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module "moduleA" {
2+
source = "./moduleA"
3+
}
4+
5+
resource "null_resource" "foo" {
6+
triggers = {
7+
foo = "foo-value"
8+
bar = module.moduleA.bar
9+
qux = module.moduleA.qux
10+
}
11+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
resource "null_resource" "bar" {
2+
triggers = {
3+
bar = "bar-value"
4+
}
5+
}
6+
7+
module "moduleB" {
8+
source = "../moduleB"
9+
}
10+
11+
12+
output "qux" {
13+
value = module.moduleB.qux
14+
}
15+
16+
output "bar" {
17+
value = null_resource.bar.triggers.bar
18+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
resource "null_resource" "qux" {
2+
triggers = {
3+
qux = "qux-value"
4+
}
5+
}
6+
7+
output "qux" {
8+
value = null_resource.qux.triggers.qux
9+
}

0 commit comments

Comments
 (0)