Skip to content

Commit

Permalink
Support Terraform 0.12 state format (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
AndiDog authored and adammck committed Aug 1, 2019
1 parent 3a1f433 commit 94a66e3
Show file tree
Hide file tree
Showing 8 changed files with 768 additions and 62 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
language: go

go:
- 1.5
- 1.6
- "1.8"
- "1.11.x"
- "1.x" # latest

script:
- go test -v ./...
108 changes: 104 additions & 4 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,107 @@ func appendUniq(strs []string, item string) []string {
return strs
}

func gatherResources(s *state) map[string]interface{} {
func gatherResources(s *stateAnyTerraformVersion) map[string]interface{} {
if s.TerraformVersion == TerraformVersionPre0dot12 {
return gatherResourcesPre0dot12(&s.StatePre0dot12)
} else if s.TerraformVersion == TerraformVersion0dot12 {
return gatherResources0dot12(&s.State0dot12)
} else {
panic("Unimplemented Terraform version enum")
}
}

func gatherResourcesPre0dot12(s *state) map[string]interface{} {
outputGroups := make(map[string]interface{})

all := &allGroup{Hosts: make([]string, 0), Vars: make(map[string]interface{})}
types := make(map[string][]string)
individual := make(map[string][]string)
ordered := make(map[string][]string)
tags := make(map[string][]string)

unsortedOrdered := make(map[string][]*Resource)

resourceIDNames := s.mapResourceIDNames()
for _, res := range s.resources() {
// place in list of all resources
all.Hosts = appendUniq(all.Hosts, res.Hostname())

// place in list of resource types
tp := fmt.Sprintf("type_%s", res.resourceType)
types[tp] = appendUniq(types[tp], res.Hostname())

unsortedOrdered[res.baseName] = append(unsortedOrdered[res.baseName], res)

// store as invdividual host (eg. <name>.<count>)
invdName := fmt.Sprintf("%s.%d", res.baseName, res.counter)
if old, exists := individual[invdName]; exists {
fmt.Fprintf(os.Stderr, "overwriting already existing individual key %s, old: %v, new: %v\n", invdName, old, res.Hostname())
}
individual[invdName] = []string{res.Hostname()}

// inventorize tags
for k, v := range res.Tags() {
// Valueless
tag := k
if v != "" {
tag = fmt.Sprintf("%s_%s", k, v)
}
// if v is a resource ID, then tag should be resource name
if _, exists := resourceIDNames[v]; exists {
tag = resourceIDNames[v]
}
tags[tag] = appendUniq(tags[tag], res.Hostname())
}
}

// inventorize outputs as variables
if len(s.outputs()) > 0 {
for _, out := range s.outputs() {
all.Vars[out.keyName] = out.value
}
}

// sort the ordered groups
for basename, resources := range unsortedOrdered {
cs := counterSorter{resources}
sort.Sort(cs)

for i := range resources {
ordered[basename] = append(ordered[basename], resources[i].Hostname())
}
}

outputGroups["all"] = all
for k, v := range individual {
if old, exists := outputGroups[k]; exists {
fmt.Fprintf(os.Stderr, "individual overwriting already existing output with key %s, old: %v, new: %v", k, old, v)
}
outputGroups[k] = v
}
for k, v := range ordered {
if old, exists := outputGroups[k]; exists {
fmt.Fprintf(os.Stderr, "ordered overwriting already existing output with key %s, old: %v, new: %v", k, old, v)
}
outputGroups[k] = v
}
for k, v := range types {
if old, exists := outputGroups[k]; exists {
fmt.Fprintf(os.Stderr, "types overwriting already existing output key %s, old: %v, new: %v", k, old, v)
}
outputGroups[k] = v
}
for k, v := range tags {
if old, exists := outputGroups[k]; exists {
fmt.Fprintf(os.Stderr, "tags overwriting already existing output key %s, old: %v, new: %v", k, old, v)
}
outputGroups[k] = v
}

return outputGroups
}

func gatherResources0dot12(s *stateTerraform0dot12) map[string]interface{} {
outputGroups := make(map[string]interface{})

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

func cmdList(stdout io.Writer, stderr io.Writer, s *state) int {
func cmdList(stdout io.Writer, stderr io.Writer, s *stateAnyTerraformVersion) int {
return output(stdout, stderr, gatherResources(s))
}

func cmdInventory(stdout io.Writer, stderr io.Writer, s *state) int {
func cmdInventory(stdout io.Writer, stderr io.Writer, s *stateAnyTerraformVersion) int {
groups := gatherResources(s)
group_names := []string{}
for group, _ := range groups {
Expand Down Expand Up @@ -190,7 +290,7 @@ func checkErr(err error, stderr io.Writer) int {
return 0
}

func cmdHost(stdout io.Writer, stderr io.Writer, s *state, hostname string) int {
func cmdHost(stdout io.Writer, stderr io.Writer, s *stateAnyTerraformVersion, hostname string) int {
for _, res := range s.resources() {
if hostname == res.Hostname() {
attributes := res.Attributes()
Expand Down
32 changes: 22 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func main() {
os.Exit(1)
}

var s state
var s stateAnyTerraformVersion

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

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

err = cmd.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error running `terraform state pull` in directory %s, %s\n", path, err)
os.Exit(1)
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)

cmd = exec.Command("terraform", "state", "pull")
cmd.Dir = path
out.Reset()
cmd.Stdout = &out
err = cmd.Run()

if err != nil {
fmt.Fprintf(os.Stderr, "Error running `terraform state pull` in directory %s, %s\n", path, err)
os.Exit(1)
}
}

err = s.read(&out)

if err != nil {
fmt.Fprintf(os.Stderr, "Error reading `terraform state pull` output: %s\n", err)
fmt.Fprintf(os.Stderr, "Error reading Terraform state: %s\n", err)
os.Exit(1)
}
}

if s.TerraformVersion == TerraformVersionUnknown {
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])
os.Exit(1)
}

if s.Modules == nil {
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])
if (s.TerraformVersion == TerraformVersionPre0dot12 && s.StatePre0dot12.Modules == nil) ||
(s.TerraformVersion == TerraformVersion0dot12 && s.State0dot12.Values.RootModule == nil) {
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])
os.Exit(1)
}

if *list {
os.Exit(cmdList(os.Stdout, os.Stderr, &s))

} else if *inventory {
os.Exit(cmdInventory(os.Stdout, os.Stderr, &s))

} else if *host != "" {
os.Exit(cmdHost(os.Stdout, os.Stderr, &s, *host))

}
}
2 changes: 1 addition & 1 deletion output.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func NewOutput(keyName string, value interface{}) (*Output, error) {

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

return &Output{
Expand Down
Loading

0 comments on commit 94a66e3

Please sign in to comment.