1
1
mirror of https://github.com/adammck/terraform-inventory synced 2024-11-22 15:52:01 +01:00

Add support for remote state (#60)

* add support for remote state in version 0.9
* remove .terraform/terraform.tfstate as a source
This commit is contained in:
Husayn Arrah 2017-06-09 21:25:34 +02:00 committed by Adam Mckaig
parent 898a7270c4
commit 313d404eb3
4 changed files with 80 additions and 22 deletions

@ -94,11 +94,25 @@ Can be provisioned separately with:
## More Usage ## More Usage
Ansible doesn't seem to support calling a dynamic inventory script with params, Ansible doesn't seem to support calling a dynamic inventory script with params,
so if you need to specify the location of your state file, set the `TF_STATE` so if you need to specify the location of your state file or terraform directory, set the `TF_STATE`
environment variable before running `ansible-playbook`, like: environment variable before running `ansible-playbook`, like:
TF_STATE=deploy/terraform.tfstate ansible-playbook --inventory-file=/path/to/terraform-inventory deploy/playbook.yml TF_STATE=deploy/terraform.tfstate ansible-playbook --inventory-file=/path/to/terraform-inventory deploy/playbook.yml
or
TF_STATE=../terraform ansible-playbook --inventory-file=/path/to/terraform-inventory deploy/playbook.yml
If `TF_STATE` is a file, it parses the file as json, if `TF_STATE` is a directory, it runs `terraform state pull` inside the directory, which is supports both local and remote terraform state.
It looks for state config in this order
- `TF_STATE`: environment variable of where to find either a statefile or a terraform project
- `TI_TFSTATE`: another environment variable similar to TF_STATE
- `terraform.tfstate`: it looks in the state file in the current directory.
- `.`: lastly it assumes you are at the root of a terraform project.
Alternately, if you need to do something fancier (like downloading your state Alternately, if you need to do something fancier (like downloading your state
file from S3 before running), you might wrap this tool with a shell script, and file from S3 before running), you might wrap this tool with a shell script, and
call that instead. Something like: call that instead. Something like:

@ -25,11 +25,5 @@ func GetInputPath(fs vfs.Filesystem, env venv.Env) string {
return fn return fn
} }
fn = ".terraform/terraform.tfstate" return "."
_, err = fs.Stat(fn)
if err == nil {
return fn
}
return ""
} }

@ -13,11 +13,12 @@ import (
) )
func TestGetInputPath(t *testing.T) { func TestGetInputPath(t *testing.T) {
assert.Equal(t, "", GetInputPath(memfs.Create(), venv.Mock())) assert.Equal(t, ".", GetInputPath(memfs.Create(), venv.Mock()))
assert.Equal(t, "aaa", GetInputPath(memfs.Create(), envWith(map[string]string{"TF_STATE": "aaa"}))) assert.Equal(t, "aaa", GetInputPath(memfs.Create(), envWith(map[string]string{"TF_STATE": "aaa"})))
assert.Equal(t, "bbb", GetInputPath(memfs.Create(), envWith(map[string]string{"TI_TFSTATE": "bbb"}))) assert.Equal(t, "bbb", GetInputPath(memfs.Create(), envWith(map[string]string{"TI_TFSTATE": "bbb"})))
assert.Equal(t, "terraform.tfstate", GetInputPath(fsWithFiles([]string{"terraform.tfstate"}), venv.Mock())) assert.Equal(t, "terraform.tfstate", GetInputPath(fsWithFiles([]string{"terraform.tfstate"}), venv.Mock()))
assert.Equal(t, ".terraform/terraform.tfstate", GetInputPath(fsWithFiles([]string{".terraform/terraform.tfstate"}), venv.Mock())) assert.Equal(t, ".", GetInputPath(fsWithFiles([]string{".terraform/terraform.tfstate"}), venv.Mock()))
assert.Equal(t, "terraform", GetInputPath(fsWithDirs([]string{"terraform"}), envWith(map[string]string{"TF_STATE": "terraform"})))
} }
func envWith(env map[string]string) venv.Env { func envWith(env map[string]string) venv.Env {
@ -53,6 +54,21 @@ func fsWithFiles(filenames []string) vfs.Filesystem {
return fs return fs
} }
func fsWithDirs(dirs []string) vfs.Filesystem {
fs := memfs.Create()
var err error
for _, fp := range dirs {
err = vfs.MkdirAll(fs, fp, 0700)
if err != nil {
panic(err)
}
}
return fs
}
// TODO: Upgrade this later with file contents. // TODO: Upgrade this later with file contents.
func touchFile(fs vfs.Filesystem, filename string) error { func touchFile(fs vfs.Filesystem, filename string) error {
return writeFile(fs, filename, []byte{}, 0600) return writeFile(fs, filename, []byte{}, 0600)

58
main.go

@ -1,11 +1,13 @@
package main package main
import ( import (
"bytes"
"flag" "flag"
"fmt" "fmt"
"github.com/adammck/venv" "github.com/adammck/venv"
"github.com/blang/vfs" "github.com/blang/vfs"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
) )
@ -23,17 +25,13 @@ func main() {
return return
} }
fs := vfs.OS()
if file == "" { if file == "" {
fs := vfs.OS()
env := venv.OS() env := venv.OS()
file = GetInputPath(fs, env) file = GetInputPath(fs, env)
} }
if file == "" {
fmt.Printf("Usage: %s [options] path\n", os.Args[0])
os.Exit(1)
}
if !*list && *host == "" && !*inventory { if !*list && *host == "" && !*inventory {
fmt.Fprint(os.Stderr, "Either --host or --list must be specified") fmt.Fprint(os.Stderr, "Either --host or --list must be specified")
os.Exit(1) os.Exit(1)
@ -45,17 +43,53 @@ func main() {
os.Exit(1) os.Exit(1)
} }
stateFile, err := os.Open(path) f, err := fs.Stat(path)
defer stateFile.Close()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error opening tfstate file: %s\n", err) fmt.Fprintf(os.Stderr, "Invalid file: %s\n", err)
os.Exit(1) os.Exit(1)
} }
var s state var s state
err = s.read(stateFile)
if err != nil { if !f.IsDir() {
fmt.Fprintf(os.Stderr, "Error reading tfstate file: %s\n", err) stateFile, err := os.Open(path)
defer stateFile.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening tfstate file: %s\n", err)
os.Exit(1)
}
err = s.read(stateFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading tfstate file: %s\n", err)
os.Exit(1)
}
}
if f.IsDir() {
cmd := exec.Command("terraform", "state", "pull")
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)
}
err = s.read(&out)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading `terraform state pull` output: %s\n", err)
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])
os.Exit(1) os.Exit(1)
} }