mirror of
https://github.com/adammck/terraform-inventory
synced 2024-11-22 15:52:01 +01:00
Support Terraform 0.12 state format (#114)
This commit is contained in:
parent
3a1f433061
commit
94a66e3c5e
@ -1,8 +1,9 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.5
|
- "1.8"
|
||||||
- 1.6
|
- "1.11.x"
|
||||||
|
- "1.x" # latest
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go test -v ./...
|
- go test -v ./...
|
||||||
|
108
cli.go
108
cli.go
@ -42,7 +42,107 @@ func appendUniq(strs []string, item string) []string {
|
|||||||
return strs
|
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{})
|
outputGroups := make(map[string]interface{})
|
||||||
|
|
||||||
all := &allGroup{Hosts: make([]string, 0), Vars: make(map[string]interface{})}
|
all := &allGroup{Hosts: make([]string, 0), Vars: make(map[string]interface{})}
|
||||||
@ -132,11 +232,11 @@ func gatherResources(s *state) map[string]interface{} {
|
|||||||
return outputGroups
|
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))
|
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)
|
groups := gatherResources(s)
|
||||||
group_names := []string{}
|
group_names := []string{}
|
||||||
for group, _ := range groups {
|
for group, _ := range groups {
|
||||||
@ -190,7 +290,7 @@ func checkErr(err error, stderr io.Writer) int {
|
|||||||
return 0
|
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() {
|
for _, res := range s.resources() {
|
||||||
if hostname == res.Hostname() {
|
if hostname == res.Hostname() {
|
||||||
attributes := res.Attributes()
|
attributes := res.Attributes()
|
||||||
|
34
main.go
34
main.go
@ -51,7 +51,7 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var s state
|
var s stateAnyTerraformVersion
|
||||||
|
|
||||||
if !f.IsDir() {
|
if !f.IsDir() {
|
||||||
stateFile, err := os.Open(path)
|
stateFile, err := os.Open(path)
|
||||||
@ -69,39 +69,51 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if f.IsDir() {
|
if f.IsDir() {
|
||||||
cmd := exec.Command("terraform", "state", "pull")
|
cmd := exec.Command("terraform", "show", "-json")
|
||||||
cmd.Dir = path
|
cmd.Dir = path
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error running `terraform state pull` in directory %s, %s\n", path, err)
|
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)
|
||||||
os.Exit(1)
|
|
||||||
|
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)
|
err = s.read(&out)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Modules == nil {
|
if s.TerraformVersion == TerraformVersionUnknown {
|
||||||
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])
|
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.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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *list {
|
if *list {
|
||||||
os.Exit(cmdList(os.Stdout, os.Stderr, &s))
|
os.Exit(cmdList(os.Stdout, os.Stderr, &s))
|
||||||
|
|
||||||
} else if *inventory {
|
} else if *inventory {
|
||||||
os.Exit(cmdInventory(os.Stdout, os.Stderr, &s))
|
os.Exit(cmdInventory(os.Stdout, os.Stderr, &s))
|
||||||
|
|
||||||
} else if *host != "" {
|
} else if *host != "" {
|
||||||
os.Exit(cmdHost(os.Stdout, os.Stderr, &s, *host))
|
os.Exit(cmdHost(os.Stdout, os.Stderr, &s, *host))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ func NewOutput(keyName string, value interface{}) (*Output, error) {
|
|||||||
|
|
||||||
// TODO: Warn instead of silently ignore error?
|
// TODO: Warn instead of silently ignore error?
|
||||||
if len(keyName) == 0 {
|
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{
|
return &Output{
|
||||||
|
270
parser.go
270
parser.go
@ -2,29 +2,94 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TerraformVersion defines which version of Terraform state applies
|
||||||
|
type TerraformVersion int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TerraformVersionUnknown means unknown version
|
||||||
|
TerraformVersionUnknown TerraformVersion = 0
|
||||||
|
|
||||||
|
// TerraformVersionPre0dot12 means < 0.12
|
||||||
|
TerraformVersionPre0dot12 TerraformVersion = 1
|
||||||
|
|
||||||
|
// TerraformVersion0dot12 means >= 0.12
|
||||||
|
TerraformVersion0dot12 TerraformVersion = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type stateAnyTerraformVersion struct {
|
||||||
|
StatePre0dot12 state
|
||||||
|
State0dot12 stateTerraform0dot12
|
||||||
|
TerraformVersion TerraformVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terraform < v0.12
|
||||||
type state struct {
|
type state struct {
|
||||||
Modules []moduleState `json:"modules"`
|
Modules []moduleState `json:"modules"`
|
||||||
}
|
}
|
||||||
|
type moduleState struct {
|
||||||
|
Path []string `json:"path"`
|
||||||
|
ResourceStates map[string]resourceState `json:"resources"`
|
||||||
|
Outputs map[string]interface{} `json:"outputs"`
|
||||||
|
}
|
||||||
|
type resourceState struct {
|
||||||
|
// Populated from statefile
|
||||||
|
Type string `json:"type"`
|
||||||
|
Primary instanceState `json:"primary"`
|
||||||
|
}
|
||||||
|
type instanceState struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Attributes map[string]string `json:"attributes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terraform <= v0.12
|
||||||
|
type stateTerraform0dot12 struct {
|
||||||
|
Values valuesStateTerraform0dot12 `json:"values"`
|
||||||
|
}
|
||||||
|
type valuesStateTerraform0dot12 struct {
|
||||||
|
RootModule *moduleStateTerraform0dot12 `json:"root_module"`
|
||||||
|
Outputs map[string]interface{} `json:"outputs"`
|
||||||
|
}
|
||||||
|
type moduleStateTerraform0dot12 struct {
|
||||||
|
ResourceStates []resourceStateTerraform0dot12 `json:"resources"`
|
||||||
|
ChildModules []moduleStateTerraform0dot12 `json:"child_modules"`
|
||||||
|
Address string `json:"address"` // empty for root module, else e.g. `module.mymodulename`
|
||||||
|
}
|
||||||
|
type resourceStateTerraform0dot12 struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Index *int `json:"index"` // only set by Terraform for counted resources
|
||||||
|
Name string `json:"name"`
|
||||||
|
RawValues map[string]interface{} `json:"values"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
// read populates the state object from a statefile.
|
// read populates the state object from a statefile.
|
||||||
func (s *state) read(stateFile io.Reader) error {
|
func (s *stateAnyTerraformVersion) read(stateFile io.Reader) error {
|
||||||
|
s.TerraformVersion = TerraformVersionUnknown
|
||||||
|
|
||||||
// read statefile contents
|
b, readErr := ioutil.ReadAll(stateFile)
|
||||||
b, err := ioutil.ReadAll(stateFile)
|
if readErr != nil {
|
||||||
if err != nil {
|
return readErr
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse into struct
|
err0dot12 := json.Unmarshal(b, &(*s).State0dot12)
|
||||||
err = json.Unmarshal(b, s)
|
if err0dot12 == nil && s.State0dot12.Values.RootModule != nil {
|
||||||
if err != nil {
|
s.TerraformVersion = TerraformVersion0dot12
|
||||||
return err
|
} else {
|
||||||
|
errPre0dot12 := json.Unmarshal(b, &(*s).StatePre0dot12)
|
||||||
|
if errPre0dot12 == nil && s.StatePre0dot12.Modules != nil {
|
||||||
|
s.TerraformVersion = TerraformVersionPre0dot12
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("0.12 format error: %v; pre-0.12 format error: %v (nil error means no content/modules found in the respective format)", err0dot12, errPre0dot12)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -53,6 +118,25 @@ func (s *state) outputs() []*Output {
|
|||||||
return inst
|
return inst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// outputs returns a slice of the Outputs found in the statefile.
|
||||||
|
func (s *stateTerraform0dot12) outputs() []*Output {
|
||||||
|
inst := make([]*Output, 0)
|
||||||
|
|
||||||
|
for k, v := range s.Values.Outputs {
|
||||||
|
var o *Output
|
||||||
|
switch v := v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
o, _ = NewOutput(k, v["value"])
|
||||||
|
default: // not expected
|
||||||
|
o, _ = NewOutput(k, "<error>")
|
||||||
|
}
|
||||||
|
|
||||||
|
inst = append(inst, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
|
||||||
// map of resource ID -> resource Name
|
// map of resource ID -> resource Name
|
||||||
func (s *state) mapResourceIDNames() map[string]string {
|
func (s *state) mapResourceIDNames() map[string]string {
|
||||||
t := map[string]string{}
|
t := map[string]string{}
|
||||||
@ -68,17 +152,91 @@ func (s *state) mapResourceIDNames() map[string]string {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// map of resource ID -> resource Name
|
||||||
|
func (s *stateTerraform0dot12) mapResourceIDNames() map[string]string {
|
||||||
|
t := map[string]string{}
|
||||||
|
|
||||||
|
for _, module := range s.getAllModules() {
|
||||||
|
for _, resourceState := range module.ResourceStates {
|
||||||
|
id, typeOk := resourceState.RawValues["id"].(string)
|
||||||
|
if typeOk && id != "" && resourceState.Name != "" {
|
||||||
|
k := strings.ToLower(id)
|
||||||
|
t[k] = resourceState.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stateTerraform0dot12) getAllModules() []*moduleStateTerraform0dot12 {
|
||||||
|
var allModules []*moduleStateTerraform0dot12
|
||||||
|
allModules = append(allModules, s.Values.RootModule)
|
||||||
|
addChildModules(&allModules, s.Values.RootModule)
|
||||||
|
return allModules
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively adds all child modules to the slice
|
||||||
|
func addChildModules(out *[]*moduleStateTerraform0dot12, from *moduleStateTerraform0dot12) {
|
||||||
|
for i := range from.ChildModules {
|
||||||
|
addChildModules(out, &from.ChildModules[i])
|
||||||
|
*out = append(*out, &from.ChildModules[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resources returns a slice of the Resources found in the statefile.
|
||||||
|
func (s *stateAnyTerraformVersion) resources() []*Resource {
|
||||||
|
switch s.TerraformVersion {
|
||||||
|
case TerraformVersionPre0dot12:
|
||||||
|
return s.StatePre0dot12.resources()
|
||||||
|
case TerraformVersion0dot12:
|
||||||
|
return s.State0dot12.resources()
|
||||||
|
case TerraformVersionUnknown:
|
||||||
|
}
|
||||||
|
panic("Unimplemented Terraform version enum")
|
||||||
|
}
|
||||||
|
|
||||||
// resources returns a slice of the Resources found in the statefile.
|
// resources returns a slice of the Resources found in the statefile.
|
||||||
func (s *state) resources() []*Resource {
|
func (s *state) resources() []*Resource {
|
||||||
inst := make([]*Resource, 0)
|
inst := make([]*Resource, 0)
|
||||||
|
|
||||||
for _, m := range s.Modules {
|
for _, m := range s.Modules {
|
||||||
for _, k := range m.resourceKeys() {
|
for _, k := range m.resourceKeys() {
|
||||||
|
if strings.HasPrefix(k, "data.") {
|
||||||
|
// This does not represent a host (e.g. AWS AMI)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a module is used, the resource key may not be unique, for instance:
|
||||||
|
//
|
||||||
|
// The module cannot use dynamic resource naming and thus has to use some hardcoded name:
|
||||||
|
//
|
||||||
|
// resource "aws_instance" "host" { ... }
|
||||||
|
//
|
||||||
|
// The main file then uses the module twice:
|
||||||
|
//
|
||||||
|
// module "application1" { source = "./modules/mymodulename" }
|
||||||
|
// module "application2" { source = "./modules/mymodulename" }
|
||||||
|
//
|
||||||
|
// Avoid key clashes by prepending module name to the key. If path is ["root"], don't
|
||||||
|
// prepend anything.
|
||||||
|
//
|
||||||
|
// In the above example: `aws_instance.host` -> `aws_instance.application1_host`
|
||||||
|
fullKey := k
|
||||||
|
resourceNameIndex := strings.Index(fullKey, ".") + 1
|
||||||
|
if len(m.Path) > 1 && resourceNameIndex > 0 {
|
||||||
|
for i := len(m.Path) - 1; i >= 1; i-- {
|
||||||
|
fullKey = fullKey[:resourceNameIndex] + strings.Replace(m.Path[i], ".", "_", -1) + "_" + fullKey[resourceNameIndex:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Terraform stores resources in a name->map map, but we need the name to
|
// Terraform stores resources in a name->map map, but we need the name to
|
||||||
// decide which groups to include the resource in. So wrap it in a higher-
|
// decide which groups to include the resource in. So wrap it in a higher-
|
||||||
// level object with both properties.
|
// level object with both properties.
|
||||||
r, err := NewResource(k, m.ResourceStates[k])
|
r, err := NewResource(fullKey, m.ResourceStates[k])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
asJSON, _ := json.Marshal(m.ResourceStates[k])
|
||||||
|
fmt.Fprintf(os.Stderr, "Warning: failed to parse resource %s (%v)\n", asJSON, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if r.IsSupported() {
|
if r.IsSupported() {
|
||||||
@ -90,9 +248,81 @@ func (s *state) resources() []*Resource {
|
|||||||
return inst
|
return inst
|
||||||
}
|
}
|
||||||
|
|
||||||
type moduleState struct {
|
func encodeTerraform0Dot12ValuesAsAttributes(rawValues *map[string]interface{}) map[string]string {
|
||||||
ResourceStates map[string]resourceState `json:"resources"`
|
ret := make(map[string]string)
|
||||||
Outputs map[string]interface{} `json:"outputs"`
|
for k, v := range *rawValues {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
ret[k+".#"] = strconv.Itoa(len(v))
|
||||||
|
for kk, vv := range v {
|
||||||
|
if str, typeOk := vv.(string); typeOk {
|
||||||
|
ret[k+"."+kk] = str
|
||||||
|
} else {
|
||||||
|
ret[k+"."+kk] = "<error>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
ret[k] = v
|
||||||
|
default:
|
||||||
|
ret[k] = "<error>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// resources returns a slice of the Resources found in the statefile.
|
||||||
|
func (s *stateTerraform0dot12) resources() []*Resource {
|
||||||
|
inst := make([]*Resource, 0)
|
||||||
|
|
||||||
|
for _, module := range s.getAllModules() {
|
||||||
|
for _, rs := range module.ResourceStates {
|
||||||
|
id, typeOk := rs.RawValues["id"].(string)
|
||||||
|
if !typeOk {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(rs.Address, "data.") {
|
||||||
|
// This does not represent a host (e.g. AWS AMI)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
modulePrefix := ""
|
||||||
|
if module.Address != "" {
|
||||||
|
modulePrefix = strings.Replace(module.Address, ".", "_", -1) + "_"
|
||||||
|
}
|
||||||
|
resourceKeyName := rs.Type + "." + modulePrefix + rs.Name
|
||||||
|
if rs.Index != nil {
|
||||||
|
resourceKeyName += "." + strconv.Itoa(*rs.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terraform stores resources in a name->map map, but we need the name to
|
||||||
|
// decide which groups to include the resource in. So wrap it in a higher-
|
||||||
|
// level object with both properties.
|
||||||
|
//
|
||||||
|
// Convert to the pre-0.12 structure for backwards compatibility of code.
|
||||||
|
r, err := NewResource(resourceKeyName, resourceState{
|
||||||
|
Type: rs.Type,
|
||||||
|
Primary: instanceState{
|
||||||
|
ID: id,
|
||||||
|
Attributes: encodeTerraform0Dot12ValuesAsAttributes(&rs.RawValues),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
asJSON, _ := json.Marshal(rs)
|
||||||
|
fmt.Fprintf(os.Stderr, "Warning: failed to parse resource %s (%v)\n", asJSON, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.IsSupported() {
|
||||||
|
inst = append(inst, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(inst, func(i, j int) bool {
|
||||||
|
return inst[i].baseName < inst[j].baseName
|
||||||
|
})
|
||||||
|
|
||||||
|
return inst
|
||||||
}
|
}
|
||||||
|
|
||||||
// resourceKeys returns a sorted slice of the key names of the resources in this
|
// resourceKeys returns a sorted slice of the key names of the resources in this
|
||||||
@ -105,21 +335,9 @@ func (ms *moduleState) resourceKeys() []string {
|
|||||||
|
|
||||||
for k := range ms.ResourceStates {
|
for k := range ms.ResourceStates {
|
||||||
keys[i] = k
|
keys[i] = k
|
||||||
i += 1
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
type resourceState struct {
|
|
||||||
|
|
||||||
// Populated from statefile
|
|
||||||
Type string `json:"type"`
|
|
||||||
Primary instanceState `json:"primary"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type instanceState struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Attributes map[string]string `json:"attributes,omitempty"`
|
|
||||||
}
|
|
||||||
|
296
parser_test.go
296
parser_test.go
@ -797,11 +797,13 @@ const expectedHostOneOutput = `
|
|||||||
`
|
`
|
||||||
|
|
||||||
func TestListCommand(t *testing.T) {
|
func TestListCommand(t *testing.T) {
|
||||||
var s state
|
var s stateAnyTerraformVersion
|
||||||
r := strings.NewReader(exampleStateFile)
|
r := strings.NewReader(exampleStateFile)
|
||||||
err := s.read(r)
|
err := s.read(r)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, TerraformVersionPre0dot12, s.TerraformVersion)
|
||||||
|
|
||||||
// Decode expectation as JSON
|
// Decode expectation as JSON
|
||||||
var exp interface{}
|
var exp interface{}
|
||||||
err = json.Unmarshal([]byte(expectedListOutput), &exp)
|
err = json.Unmarshal([]byte(expectedListOutput), &exp)
|
||||||
@ -822,11 +824,13 @@ func TestListCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestListCommandEnvHostname(t *testing.T) {
|
func TestListCommandEnvHostname(t *testing.T) {
|
||||||
var s state
|
var s stateAnyTerraformVersion
|
||||||
r := strings.NewReader(exampleStateFileEnvHostname)
|
r := strings.NewReader(exampleStateFileEnvHostname)
|
||||||
err := s.read(r)
|
err := s.read(r)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, TerraformVersionPre0dot12, s.TerraformVersion)
|
||||||
|
|
||||||
// Decode expectation as JSON
|
// Decode expectation as JSON
|
||||||
var exp interface{}
|
var exp interface{}
|
||||||
err = json.Unmarshal([]byte(expectedListOutputEnvHostname), &exp)
|
err = json.Unmarshal([]byte(expectedListOutputEnvHostname), &exp)
|
||||||
@ -849,11 +853,13 @@ func TestListCommandEnvHostname(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHostCommand(t *testing.T) {
|
func TestHostCommand(t *testing.T) {
|
||||||
var s state
|
var s stateAnyTerraformVersion
|
||||||
r := strings.NewReader(exampleStateFile)
|
r := strings.NewReader(exampleStateFile)
|
||||||
err := s.read(r)
|
err := s.read(r)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, TerraformVersionPre0dot12, s.TerraformVersion)
|
||||||
|
|
||||||
// Decode expectation as JSON
|
// Decode expectation as JSON
|
||||||
var exp interface{}
|
var exp interface{}
|
||||||
err = json.Unmarshal([]byte(expectedHostOneOutput), &exp)
|
err = json.Unmarshal([]byte(expectedHostOneOutput), &exp)
|
||||||
@ -874,11 +880,13 @@ func TestHostCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInventoryCommand(t *testing.T) {
|
func TestInventoryCommand(t *testing.T) {
|
||||||
var s state
|
var s stateAnyTerraformVersion
|
||||||
r := strings.NewReader(exampleStateFile)
|
r := strings.NewReader(exampleStateFile)
|
||||||
err := s.read(r)
|
err := s.read(r)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, TerraformVersionPre0dot12, s.TerraformVersion)
|
||||||
|
|
||||||
// Run the command, capture the output
|
// Run the command, capture the output
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
exitCode := cmdInventory(&stdout, &stderr, &s)
|
exitCode := cmdInventory(&stdout, &stderr, &s)
|
||||||
@ -887,3 +895,283 @@ func TestInventoryCommand(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, expectedInventoryOutput, stdout.String())
|
assert.Equal(t, expectedInventoryOutput, stdout.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Terraform 0.12 BEGIN
|
||||||
|
//
|
||||||
|
|
||||||
|
const exampleStateFileTerraform0dot12 = `
|
||||||
|
{
|
||||||
|
"format_version": "0.1",
|
||||||
|
"terraform_version": "0.12.1",
|
||||||
|
"values": {
|
||||||
|
"outputs": {
|
||||||
|
"my_endpoint": {
|
||||||
|
"sensitive": false,
|
||||||
|
"value": "a.b.c.d.example.com"
|
||||||
|
},
|
||||||
|
"my_password": {
|
||||||
|
"sensitive": true,
|
||||||
|
"value": "1234"
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"sensitive": false,
|
||||||
|
"value": {
|
||||||
|
"first": "a",
|
||||||
|
"second": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root_module": {
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "aws_instance.one",
|
||||||
|
"type": "aws_instance",
|
||||||
|
"name": "one",
|
||||||
|
"provider_name": "aws",
|
||||||
|
"schema_version": 1,
|
||||||
|
"values": {
|
||||||
|
"ami": "ami-00000000000000000",
|
||||||
|
"id": "i-11111111111111111",
|
||||||
|
"private_ip": "10.0.0.1",
|
||||||
|
"public_ip": "35.159.25.34",
|
||||||
|
"tags": {
|
||||||
|
"Name": "one-aws-instance"
|
||||||
|
},
|
||||||
|
"volume_tags": {
|
||||||
|
"Ignored": "stuff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"child_modules": [
|
||||||
|
{
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "aws_instance.host",
|
||||||
|
"type": "aws_instance",
|
||||||
|
"name": "host",
|
||||||
|
"values": {
|
||||||
|
"ami": "ami-00000000000000001",
|
||||||
|
"id": "i-22222222222222222",
|
||||||
|
"private_ip": "10.0.0.2",
|
||||||
|
"public_ip": "",
|
||||||
|
"tags": {
|
||||||
|
"Name": "two-aws-instance"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"address": "module.my-module-two"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "aws_instance.host",
|
||||||
|
"type": "aws_instance",
|
||||||
|
"name": "host",
|
||||||
|
"index": 0,
|
||||||
|
"values": {
|
||||||
|
"ami": "ami-00000000000000001",
|
||||||
|
"id": "i-33333333333333333",
|
||||||
|
"private_ip": "10.0.0.3",
|
||||||
|
"public_ip": "",
|
||||||
|
"tags": {
|
||||||
|
"Name": "three-aws-instance"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "aws_instance.host",
|
||||||
|
"type": "aws_instance",
|
||||||
|
"name": "host",
|
||||||
|
"index": 1,
|
||||||
|
"values": {
|
||||||
|
"ami": "ami-00000000000000001",
|
||||||
|
"id": "i-11133333333333333",
|
||||||
|
"private_ip": "10.0.1.3",
|
||||||
|
"public_ip": "",
|
||||||
|
"tags": {
|
||||||
|
"Name": "three-aws-instance"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"address": "module.my-module-three"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedListOutputTerraform0dot12 = `
|
||||||
|
{
|
||||||
|
"all": {
|
||||||
|
"hosts": [
|
||||||
|
"10.0.0.2",
|
||||||
|
"10.0.0.3",
|
||||||
|
"10.0.1.3",
|
||||||
|
"35.159.25.34"
|
||||||
|
],
|
||||||
|
"vars": {
|
||||||
|
"my_endpoint": "a.b.c.d.example.com",
|
||||||
|
"my_password": "1234",
|
||||||
|
"map": {"first": "a", "second": "b"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"one.0": ["35.159.25.34"],
|
||||||
|
"one": ["35.159.25.34"],
|
||||||
|
"module_my-module-two_host.0": ["10.0.0.2"],
|
||||||
|
"module_my-module-two_host": ["10.0.0.2"],
|
||||||
|
"module_my-module-three_host.0": ["10.0.0.3"],
|
||||||
|
"module_my-module-three_host.1": ["10.0.1.3"],
|
||||||
|
"module_my-module-three_host": ["10.0.0.3", "10.0.1.3"],
|
||||||
|
|
||||||
|
"type_aws_instance": ["10.0.0.2", "10.0.0.3", "10.0.1.3", "35.159.25.34"],
|
||||||
|
|
||||||
|
"name_one-aws-instance": ["35.159.25.34"],
|
||||||
|
"name_two-aws-instance": ["10.0.0.2"],
|
||||||
|
"name_three-aws-instance": ["10.0.0.3", "10.0.1.3"]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedInventoryOutputTerraform0dot12 = `[all]
|
||||||
|
10.0.0.2
|
||||||
|
10.0.0.3
|
||||||
|
10.0.1.3
|
||||||
|
35.159.25.34
|
||||||
|
|
||||||
|
[all:vars]
|
||||||
|
map={"first":"a","second":"b"}
|
||||||
|
my_endpoint="a.b.c.d.example.com"
|
||||||
|
my_password="1234"
|
||||||
|
|
||||||
|
[module_my-module-three_host]
|
||||||
|
10.0.0.3
|
||||||
|
10.0.1.3
|
||||||
|
|
||||||
|
[module_my-module-three_host.0]
|
||||||
|
10.0.0.3
|
||||||
|
|
||||||
|
[module_my-module-three_host.1]
|
||||||
|
10.0.1.3
|
||||||
|
|
||||||
|
[module_my-module-two_host]
|
||||||
|
10.0.0.2
|
||||||
|
|
||||||
|
[module_my-module-two_host.0]
|
||||||
|
10.0.0.2
|
||||||
|
|
||||||
|
[name_one-aws-instance]
|
||||||
|
35.159.25.34
|
||||||
|
|
||||||
|
[name_three-aws-instance]
|
||||||
|
10.0.0.3
|
||||||
|
10.0.1.3
|
||||||
|
|
||||||
|
[name_two-aws-instance]
|
||||||
|
10.0.0.2
|
||||||
|
|
||||||
|
[one]
|
||||||
|
35.159.25.34
|
||||||
|
|
||||||
|
[one.0]
|
||||||
|
35.159.25.34
|
||||||
|
|
||||||
|
[type_aws_instance]
|
||||||
|
10.0.0.2
|
||||||
|
10.0.0.3
|
||||||
|
10.0.1.3
|
||||||
|
35.159.25.34
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedHostOneOutputTerraform0dot12 = `
|
||||||
|
{
|
||||||
|
"ami": "ami-00000000000000000",
|
||||||
|
"ansible_host": "35.159.25.34",
|
||||||
|
"id":"i-11111111111111111",
|
||||||
|
"private_ip":"10.0.0.1",
|
||||||
|
"public_ip": "35.159.25.34",
|
||||||
|
"tags.#": "1",
|
||||||
|
"tags.Name": "one-aws-instance",
|
||||||
|
"volume_tags.#":"1",
|
||||||
|
"volume_tags.Ignored":"stuff"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestListCommandTerraform0dot12(t *testing.T) {
|
||||||
|
var s stateAnyTerraformVersion
|
||||||
|
r := strings.NewReader(exampleStateFileTerraform0dot12)
|
||||||
|
err := s.read(r)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, TerraformVersion0dot12, s.TerraformVersion)
|
||||||
|
|
||||||
|
// Decode expectation as JSON
|
||||||
|
var exp interface{}
|
||||||
|
err = json.Unmarshal([]byte(expectedListOutputTerraform0dot12), &exp)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Run the command, capture the output
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
exitCode := cmdList(&stdout, &stderr, &s)
|
||||||
|
assert.Equal(t, 0, exitCode)
|
||||||
|
assert.Equal(t, "", stderr.String())
|
||||||
|
|
||||||
|
// Decode the output to compare
|
||||||
|
var act interface{}
|
||||||
|
err = json.Unmarshal([]byte(stdout.String()), &act)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, exp, act)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostCommandTerraform0dot12(t *testing.T) {
|
||||||
|
var s stateAnyTerraformVersion
|
||||||
|
r := strings.NewReader(exampleStateFileTerraform0dot12)
|
||||||
|
err := s.read(r)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, TerraformVersion0dot12, s.TerraformVersion)
|
||||||
|
|
||||||
|
// Decode expectation as JSON
|
||||||
|
var exp interface{}
|
||||||
|
err = json.Unmarshal([]byte(expectedHostOneOutputTerraform0dot12), &exp)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Run the command, capture the output
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
exitCode := cmdHost(&stdout, &stderr, &s, "35.159.25.34")
|
||||||
|
assert.Equal(t, 0, exitCode)
|
||||||
|
assert.Equal(t, "", stderr.String())
|
||||||
|
|
||||||
|
// Decode the output to compare
|
||||||
|
var act interface{}
|
||||||
|
err = json.Unmarshal([]byte(stdout.String()), &act)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, exp, act)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInventoryCommandTerraform0dot12(t *testing.T) {
|
||||||
|
var s stateAnyTerraformVersion
|
||||||
|
r := strings.NewReader(exampleStateFileTerraform0dot12)
|
||||||
|
err := s.read(r)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, TerraformVersion0dot12, s.TerraformVersion)
|
||||||
|
|
||||||
|
// Run the command, capture the output
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
exitCode := cmdInventory(&stdout, &stderr, &s)
|
||||||
|
assert.Equal(t, 0, exitCode)
|
||||||
|
assert.Equal(t, "", stderr.String())
|
||||||
|
|
||||||
|
assert.Equal(t, expectedInventoryOutputTerraform0dot12, stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Terraform 0.12 END
|
||||||
|
//
|
||||||
|
86
parser_test.go.example.tfstate
Normal file
86
parser_test.go.example.tfstate
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"format_version": "0.1",
|
||||||
|
"terraform_version": "0.12.1",
|
||||||
|
"values": {
|
||||||
|
"outputs": {
|
||||||
|
"my_endpoint": {
|
||||||
|
"sensitive": false,
|
||||||
|
"value": "a.b.c.d.example.com"
|
||||||
|
},
|
||||||
|
"my_password": {
|
||||||
|
"sensitive": true,
|
||||||
|
"value": "1234"
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"sensitive": false,
|
||||||
|
"value": {
|
||||||
|
"first": "a",
|
||||||
|
"second": "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root_module": {
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "aws_instance.one",
|
||||||
|
"type": "aws_instance",
|
||||||
|
"name": "one",
|
||||||
|
"provider_name": "aws",
|
||||||
|
"schema_version": 1,
|
||||||
|
"values": {
|
||||||
|
"ami": "ami-00000000000000000",
|
||||||
|
"id": "i-11111111111111111",
|
||||||
|
"private_ip": "10.0.0.1",
|
||||||
|
"public_ip": "35.159.25.34",
|
||||||
|
"tags": {
|
||||||
|
"Name": "one-aws-instance"
|
||||||
|
},
|
||||||
|
"volume_tags": {
|
||||||
|
"Ignored": "stuff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"child_modules": [
|
||||||
|
{
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "aws_instance.host",
|
||||||
|
"type": "aws_instance",
|
||||||
|
"name": "host",
|
||||||
|
"values": {
|
||||||
|
"ami": "ami-00000000000000001",
|
||||||
|
"id": "i-22222222222222222",
|
||||||
|
"private_ip": "10.0.0.2",
|
||||||
|
"public_ip": "",
|
||||||
|
"tags": {
|
||||||
|
"Name": "two-aws-instance"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"address": "module.my-module-two"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "aws_instance.host",
|
||||||
|
"type": "aws_instance",
|
||||||
|
"name": "host",
|
||||||
|
"values": {
|
||||||
|
"ami": "ami-00000000000000001",
|
||||||
|
"id": "i-33333333333333333",
|
||||||
|
"private_ip": "10.0.0.3",
|
||||||
|
"public_ip": "",
|
||||||
|
"tags": {
|
||||||
|
"Name": "three-aws-instance"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"address": "module.my-module-three"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
resource.go
31
resource.go
@ -16,16 +16,16 @@ var nameParser *regexp.Regexp
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
keyNames = []string{
|
keyNames = []string{
|
||||||
"ipv4_address", // DO and SoftLayer
|
"ipv4_address", // DO and SoftLayer
|
||||||
"public_ip", // AWS
|
"public_ip", // AWS
|
||||||
"public_ipv6", // Scaleway
|
"public_ipv6", // Scaleway
|
||||||
"ipaddress", // CS
|
"ipaddress", // CS
|
||||||
"ip_address", // VMware, Docker, Linode
|
"ip_address", // VMware, Docker, Linode
|
||||||
"private_ip", // AWS
|
"private_ip", // AWS
|
||||||
"network_interface.0.ipv4_address", // VMware
|
"network_interface.0.ipv4_address", // VMware
|
||||||
"default_ip_address", // provider.vsphere v1.1.1
|
"default_ip_address", // provider.vsphere v1.1.1
|
||||||
"access_ip_v4", // OpenStack
|
"access_ip_v4", // OpenStack
|
||||||
"floating_ip", // OpenStack
|
"floating_ip", // OpenStack
|
||||||
"network_interface.0.access_config.0.nat_ip", // GCE
|
"network_interface.0.access_config.0.nat_ip", // GCE
|
||||||
"network_interface.0.access_config.0.assigned_nat_ip", // GCE
|
"network_interface.0.access_config.0.assigned_nat_ip", // GCE
|
||||||
"network_interface.0.address", // GCE
|
"network_interface.0.address", // GCE
|
||||||
@ -38,8 +38,11 @@ func init() {
|
|||||||
"nic_list.0.ip_endpoint_list.0.ip", // Nutanix
|
"nic_list.0.ip_endpoint_list.0.ip", // Nutanix
|
||||||
}
|
}
|
||||||
|
|
||||||
// type.name.0
|
// Formats:
|
||||||
nameParser = regexp.MustCompile(`^(\w+)\.([\w\-]+)(?:\.(\d+))?$`)
|
// - type.[module_]name (no `count` attribute; contains module name if we're not in the root module)
|
||||||
|
// - type.[module_]name.0 (if resource has `count` attribute)
|
||||||
|
// - "data." prefix should not parse and be ignored by caller (does not represent a host)
|
||||||
|
nameParser = regexp.MustCompile(`^([\w\-]+)\.([\w\-]+)(?:\.(\d+))?$`)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
@ -62,15 +65,13 @@ func NewResource(keyName string, state resourceState) (*Resource, error) {
|
|||||||
m := nameParser.FindStringSubmatch(keyName)
|
m := nameParser.FindStringSubmatch(keyName)
|
||||||
|
|
||||||
// This should not happen unless our regex changes.
|
// This should not happen unless our regex changes.
|
||||||
// TODO: Warn instead of silently ignore error?
|
|
||||||
if len(m) != 4 {
|
if len(m) != 4 {
|
||||||
return nil, fmt.Errorf("couldn't parse keyName: %s", keyName)
|
return nil, fmt.Errorf("couldn't parse resource keyName: %s", keyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
var c int
|
var c int
|
||||||
var err error
|
var err error
|
||||||
if m[3] != "" {
|
if m[3] != "" {
|
||||||
|
|
||||||
// The third section should be the index, if it's present. Not sure what
|
// The third section should be the index, if it's present. Not sure what
|
||||||
// else we can do other than panic (which seems highly undesirable) if that
|
// else we can do other than panic (which seems highly undesirable) if that
|
||||||
// isn't the case.
|
// isn't the case.
|
||||||
|
Loading…
Reference in New Issue
Block a user