-
Notifications
You must be signed in to change notification settings - Fork 0
/
parse.go
229 lines (197 loc) · 7.22 KB
/
parse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package main
import (
"errors"
"fmt"
"regexp"
"github.com/hashicorp/terraform/builtin/providers/aws"
"github.com/hashicorp/terraform/builtin/providers/cloudstack"
"github.com/hashicorp/terraform/builtin/providers/digitalocean"
"github.com/hashicorp/terraform/builtin/providers/openstack"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
// Type InstanceInfo captures the info we want for each instance,
// name, address, a slice of groups and a map of host variables.
type InstanceInfo struct {
Name string
Address string
Groups []string
HostVars map[string]string
}
// Func parseState loops over the resources in a terraform.State
// instance and returns a slice of InstanceInfo and an error. If
// there is an error, the slice of InstanceInfo will be empty.
func parseState(state terraform.State) ([]*InstanceInfo, error) {
instances := []*InstanceInfo{}
for _, m := range state.Modules {
for _, rs := range m.Resources {
var the_parser func(*terraform.ResourceState) (*InstanceInfo, error)
switch rs.Type {
case "openstack_compute_instance_v2":
the_parser = parse_os_compute_instance_v2
case "digitalocean_droplet":
the_parser = parse_digitalocean_droplet
case "aws_instance":
the_parser = parse_aws_instance
case "cloudstack_instance":
the_parser = parse_cloudstack_instance
}
if the_parser != nil {
info, err := the_parser(rs)
if err != nil {
return []*InstanceInfo{},
fmt.Errorf("Unable to parse %s resource", rs.Type)
}
instances = append(instances, info)
}
}
}
return instances, nil
}
func parse_digitalocean_droplet(rs *terraform.ResourceState) (*InstanceInfo, error) {
info := InstanceInfo{}
provider := digitalocean.Provider().(*schema.Provider)
instanceSchema := provider.ResourcesMap["digitalocean_droplet"].Schema
stateReader := &schema.MapFieldReader{
Schema: instanceSchema,
Map: schema.BasicMapReader(rs.Primary.Attributes),
}
nameResult, err := stateReader.ReadField([]string{"name"})
if err != nil {
return nil, fmt.Errorf("Unable to read name field: %s", err)
}
info.Name = nameResult.ValueOrZero(instanceSchema["name"]).(string)
accessResult, err := stateReader.ReadField([]string{"ipv4_address"})
if err != nil {
return nil, fmt.Errorf("Unable to read ipv4_address field: %s", err)
}
info.Address = accessResult.ValueOrZero(instanceSchema["ipv4_address"]).(string)
return &info, nil
}
// BUG(hartzell@alerce.com) parse_aws_instance doesn't do anything
// sensible with for an instance name. What to do?
func parse_aws_instance(rs *terraform.ResourceState) (*InstanceInfo, error) {
info := InstanceInfo{}
provider := aws.Provider().(*schema.Provider)
instanceSchema := provider.ResourcesMap["aws_instance"].Schema
stateReader := &schema.MapFieldReader{
Schema: instanceSchema,
Map: schema.BasicMapReader(rs.Primary.Attributes),
}
// nameResult, err := stateReader.ReadField([]string{"id"})
// if err != nil {
// return nil, fmt.Errorf("Unable to read id field: %s", err)
// }
// info.Name = nameResult.ValueOrZero(instanceSchema["id"]).(string)
info.Name = "moose"
accessResult, err := stateReader.ReadField([]string{"public_ip"})
if err != nil {
return nil, fmt.Errorf("Unable to read a public_ip field: %s", err)
}
info.Address = accessResult.ValueOrZero(instanceSchema["public_ip"]).(string)
if info.Address == "" {
accessResult, err := stateReader.ReadField([]string{"private_ip"})
if err != nil {
return nil, fmt.Errorf("Unable to read a private_ip field: %s", err)
}
info.Address = accessResult.ValueOrZero(instanceSchema["private_ip"]).(string)
}
return &info, nil
}
func parse_cloudstack_instance(rs *terraform.ResourceState) (*InstanceInfo, error) {
info := InstanceInfo{}
provider := cloudstack.Provider().(*schema.Provider)
instanceSchema := provider.ResourcesMap["cloudstack_instance"].Schema
stateReader := &schema.MapFieldReader{
Schema: instanceSchema,
Map: schema.BasicMapReader(rs.Primary.Attributes),
}
nameResult, err := stateReader.ReadField([]string{"name"})
if err != nil {
return nil, fmt.Errorf("Unable to read name field: %s", err)
}
info.Name = nameResult.ValueOrZero(instanceSchema["name"]).(string)
accessResult, err := stateReader.ReadField([]string{"ipaddress"})
if err != nil {
return nil, fmt.Errorf("Unable to read a ipaddress field: %s", err)
}
info.Address = accessResult.ValueOrZero(instanceSchema["ipaddress"]).(string)
return &info, nil
}
// Function parse_os_compute_instance_v2 uses terraform routines to
// parse info out of a terraform.ResourceState.
//
// HEADS UP: it's use of these routines is slightly underhanded (but
// better than reverse engineering the state file format...).
//
// Thanks to @apparentlymart for this bit of code.
// See: https://github.com/hashicorp/terraform/issues/3405
func parse_os_compute_instance_v2(rs *terraform.ResourceState) (*InstanceInfo, error) {
info := InstanceInfo{}
provider := openstack.Provider().(*schema.Provider)
instanceSchema := provider.ResourcesMap["openstack_compute_instance_v2"].Schema
stateReader := &schema.MapFieldReader{
Schema: instanceSchema,
Map: schema.BasicMapReader(rs.Primary.Attributes),
}
metadataResult, err := stateReader.ReadField([]string{"metadata"})
if err != nil {
return nil, errors.New("Unable to read metadata from ResourceState")
}
m := metadataResult.ValueOrZero(instanceSchema["metadata"])
for key, value := range m.(map[string]interface{}) {
if key == "ansible_groups" {
groups := splitOnComma(value.(string))
info.Groups = append(info.Groups, groups...)
} else if key == "ansible_hostvars" {
info.HostVars, err = parseVars(value.(string))
if err != nil {
return nil, fmt.Errorf("Unable to parse host variables: %s", err)
}
}
}
nameResult, err := stateReader.ReadField([]string{"name"})
if err != nil {
return nil, fmt.Errorf("Unable to read name field: %s", err)
}
info.Name = nameResult.ValueOrZero(instanceSchema["name"]).(string)
accessResult, err := stateReader.ReadField([]string{"access_ip_v4"})
if err != nil {
return nil, fmt.Errorf("Unable to read access_ip_v4 field: %s", err)
}
info.Address = accessResult.ValueOrZero(instanceSchema["access_ip_v4"]).(string)
return &info, nil
}
// Function parseVars converts a string like "var1 = val1, var2=val2"
// into a map[string]string{"var1": "val1", "var2": "val2}
func parseVars(s string) (map[string]string, error) {
vars := make(map[string]string)
if len(s) > 0 {
name_val_pairs := splitOnComma(s)
for _, nvp := range name_val_pairs { // each name value pair (nvp)
v, err := splitOnEqual(nvp)
if err != nil {
return nil, fmt.Errorf("Unable to parseVars: %s", err)
}
vars[v[0]] = v[1]
}
}
return vars, nil
}
// Convert a string like "a, b, something" into
// []string{"a", "b", "something"}.
func splitOnComma(s string) []string {
comma_sep := regexp.MustCompile("\\s*,\\s*")
return comma_sep.Split(s, -1)
}
// Convert a string like "a=bb" into []string{"a", "b"}.
func splitOnEqual(s string) ([]string, error) {
equal_sep := regexp.MustCompile("\\s*=\\s*")
parts := equal_sep.Split(s, -1)
if len(parts) != 2 {
return nil,
fmt.Errorf(
"Unable to split \"%s\" on an equal sign and get sensible result", s)
}
return parts, nil
}