Skip to content

Commit 94a66e3

Browse files
AndiDogadammck
authored andcommitted
Support Terraform 0.12 state format (#114)
1 parent 3a1f433 commit 94a66e3

File tree

8 files changed

+768
-62
lines changed

8 files changed

+768
-62
lines changed

.travis.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
language: go
22

33
go:
4-
- 1.5
5-
- 1.6
4+
- "1.8"
5+
- "1.11.x"
6+
- "1.x" # latest
67

78
script:
89
- go test -v ./...

cli.go

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,107 @@ func appendUniq(strs []string, item string) []string {
4242
return strs
4343
}
4444

45-
func gatherResources(s *state) map[string]interface{} {
45+
func gatherResources(s *stateAnyTerraformVersion) map[string]interface{} {
46+
if s.TerraformVersion == TerraformVersionPre0dot12 {
47+
return gatherResourcesPre0dot12(&s.StatePre0dot12)
48+
} else if s.TerraformVersion == TerraformVersion0dot12 {
49+
return gatherResources0dot12(&s.State0dot12)
50+
} else {
51+
panic("Unimplemented Terraform version enum")
52+
}
53+
}
54+
55+
func gatherResourcesPre0dot12(s *state) map[string]interface{} {
56+
outputGroups := make(map[string]interface{})
57+
58+
all := &allGroup{Hosts: make([]string, 0), Vars: make(map[string]interface{})}
59+
types := make(map[string][]string)
60+
individual := make(map[string][]string)
61+
ordered := make(map[string][]string)
62+
tags := make(map[string][]string)
63+
64+
unsortedOrdered := make(map[string][]*Resource)
65+
66+
resourceIDNames := s.mapResourceIDNames()
67+
for _, res := range s.resources() {
68+
// place in list of all resources
69+
all.Hosts = appendUniq(all.Hosts, res.Hostname())
70+
71+
// place in list of resource types
72+
tp := fmt.Sprintf("type_%s", res.resourceType)
73+
types[tp] = appendUniq(types[tp], res.Hostname())
74+
75+
unsortedOrdered[res.baseName] = append(unsortedOrdered[res.baseName], res)
76+
77+
// store as invdividual host (eg. <name>.<count>)
78+
invdName := fmt.Sprintf("%s.%d", res.baseName, res.counter)
79+
if old, exists := individual[invdName]; exists {
80+
fmt.Fprintf(os.Stderr, "overwriting already existing individual key %s, old: %v, new: %v\n", invdName, old, res.Hostname())
81+
}
82+
individual[invdName] = []string{res.Hostname()}
83+
84+
// inventorize tags
85+
for k, v := range res.Tags() {
86+
// Valueless
87+
tag := k
88+
if v != "" {
89+
tag = fmt.Sprintf("%s_%s", k, v)
90+
}
91+
// if v is a resource ID, then tag should be resource name
92+
if _, exists := resourceIDNames[v]; exists {
93+
tag = resourceIDNames[v]
94+
}
95+
tags[tag] = appendUniq(tags[tag], res.Hostname())
96+
}
97+
}
98+
99+
// inventorize outputs as variables
100+
if len(s.outputs()) > 0 {
101+
for _, out := range s.outputs() {
102+
all.Vars[out.keyName] = out.value
103+
}
104+
}
105+
106+
// sort the ordered groups
107+
for basename, resources := range unsortedOrdered {
108+
cs := counterSorter{resources}
109+
sort.Sort(cs)
110+
111+
for i := range resources {
112+
ordered[basename] = append(ordered[basename], resources[i].Hostname())
113+
}
114+
}
115+
116+
outputGroups["all"] = all
117+
for k, v := range individual {
118+
if old, exists := outputGroups[k]; exists {
119+
fmt.Fprintf(os.Stderr, "individual overwriting already existing output with key %s, old: %v, new: %v", k, old, v)
120+
}
121+
outputGroups[k] = v
122+
}
123+
for k, v := range ordered {
124+
if old, exists := outputGroups[k]; exists {
125+
fmt.Fprintf(os.Stderr, "ordered overwriting already existing output with key %s, old: %v, new: %v", k, old, v)
126+
}
127+
outputGroups[k] = v
128+
}
129+
for k, v := range types {
130+
if old, exists := outputGroups[k]; exists {
131+
fmt.Fprintf(os.Stderr, "types overwriting already existing output key %s, old: %v, new: %v", k, old, v)
132+
}
133+
outputGroups[k] = v
134+
}
135+
for k, v := range tags {
136+
if old, exists := outputGroups[k]; exists {
137+
fmt.Fprintf(os.Stderr, "tags overwriting already existing output key %s, old: %v, new: %v", k, old, v)
138+
}
139+
outputGroups[k] = v
140+
}
141+
142+
return outputGroups
143+
}
144+
145+
func gatherResources0dot12(s *stateTerraform0dot12) map[string]interface{} {
46146
outputGroups := make(map[string]interface{})
47147

48148
all := &allGroup{Hosts: make([]string, 0), Vars: make(map[string]interface{})}
@@ -132,11 +232,11 @@ func gatherResources(s *state) map[string]interface{} {
132232
return outputGroups
133233
}
134234

135-
func cmdList(stdout io.Writer, stderr io.Writer, s *state) int {
235+
func cmdList(stdout io.Writer, stderr io.Writer, s *stateAnyTerraformVersion) int {
136236
return output(stdout, stderr, gatherResources(s))
137237
}
138238

139-
func cmdInventory(stdout io.Writer, stderr io.Writer, s *state) int {
239+
func cmdInventory(stdout io.Writer, stderr io.Writer, s *stateAnyTerraformVersion) int {
140240
groups := gatherResources(s)
141241
group_names := []string{}
142242
for group, _ := range groups {
@@ -190,7 +290,7 @@ func checkErr(err error, stderr io.Writer) int {
190290
return 0
191291
}
192292

193-
func cmdHost(stdout io.Writer, stderr io.Writer, s *state, hostname string) int {
293+
func cmdHost(stdout io.Writer, stderr io.Writer, s *stateAnyTerraformVersion, hostname string) int {
194294
for _, res := range s.resources() {
195295
if hostname == res.Hostname() {
196296
attributes := res.Attributes()

main.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func main() {
5151
os.Exit(1)
5252
}
5353

54-
var s state
54+
var s stateAnyTerraformVersion
5555

5656
if !f.IsDir() {
5757
stateFile, err := os.Open(path)
@@ -69,39 +69,51 @@ func main() {
6969
}
7070

7171
if f.IsDir() {
72-
cmd := exec.Command("terraform", "state", "pull")
72+
cmd := exec.Command("terraform", "show", "-json")
7373
cmd.Dir = path
7474
var out bytes.Buffer
7575
cmd.Stdout = &out
7676

7777
err = cmd.Run()
7878
if err != nil {
79-
fmt.Fprintf(os.Stderr, "Error running `terraform state pull` in directory %s, %s\n", path, err)
80-
os.Exit(1)
79+
fmt.Fprintf(os.Stderr, "Error running `terraform show -json` in directory %s, %s, falling back to trying Terraform pre-0.12 command\n", path, err)
80+
81+
cmd = exec.Command("terraform", "state", "pull")
82+
cmd.Dir = path
83+
out.Reset()
84+
cmd.Stdout = &out
85+
err = cmd.Run()
86+
87+
if err != nil {
88+
fmt.Fprintf(os.Stderr, "Error running `terraform state pull` in directory %s, %s\n", path, err)
89+
os.Exit(1)
90+
}
8191
}
8292

8393
err = s.read(&out)
8494

8595
if err != nil {
86-
fmt.Fprintf(os.Stderr, "Error reading `terraform state pull` output: %s\n", err)
96+
fmt.Fprintf(os.Stderr, "Error reading Terraform state: %s\n", err)
8797
os.Exit(1)
8898
}
99+
}
89100

101+
if s.TerraformVersion == TerraformVersionUnknown {
102+
fmt.Fprintf(os.Stderr, "Unknown state format\n\nUsage: %s [options] path\npath: this is either a path to a state file or a folder from which `terraform commands` are valid\n", os.Args[0])
103+
os.Exit(1)
90104
}
91105

92-
if s.Modules == nil {
93-
fmt.Printf("Usage: %s [options] path\npath: this is either a path to a state file or a folder from which `terraform commands` are valid\n", os.Args[0])
106+
if (s.TerraformVersion == TerraformVersionPre0dot12 && s.StatePre0dot12.Modules == nil) ||
107+
(s.TerraformVersion == TerraformVersion0dot12 && s.State0dot12.Values.RootModule == nil) {
108+
fmt.Fprintf(os.Stderr, "No modules found in state\n\nUsage: %s [options] path\npath: this is either a path to a state file or a folder from which `terraform commands` are valid\n", os.Args[0])
94109
os.Exit(1)
95110
}
96111

97112
if *list {
98113
os.Exit(cmdList(os.Stdout, os.Stderr, &s))
99-
100114
} else if *inventory {
101115
os.Exit(cmdInventory(os.Stdout, os.Stderr, &s))
102-
103116
} else if *host != "" {
104117
os.Exit(cmdHost(os.Stdout, os.Stderr, &s, *host))
105-
106118
}
107119
}

output.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func NewOutput(keyName string, value interface{}) (*Output, error) {
1515

1616
// TODO: Warn instead of silently ignore error?
1717
if len(keyName) == 0 {
18-
return nil, fmt.Errorf("couldn't parse keyName: %s", keyName)
18+
return nil, fmt.Errorf("couldn't parse output keyName: %s", keyName)
1919
}
2020

2121
return &Output{

0 commit comments

Comments
 (0)