Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for remote state in version 0.9 #60

Merged
merged 2 commits into from
Jun 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,25 @@ Can be provisioned separately with:
## More Usage

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:


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
file from S3 before running), you might wrap this tool with a shell script, and
call that instead. Something like:
Expand Down
8 changes: 1 addition & 7 deletions input.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,5 @@ func GetInputPath(fs vfs.Filesystem, env venv.Env) string {
return fn
}

fn = ".terraform/terraform.tfstate"
_, err = fs.Stat(fn)
if err == nil {
return fn
}

return ""
return "."
}
20 changes: 18 additions & 2 deletions input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import (
)

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, "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/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 {
Expand Down Expand Up @@ -53,6 +54,21 @@ func fsWithFiles(filenames []string) vfs.Filesystem {
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.
func touchFile(fs vfs.Filesystem, filename string) error {
return writeFile(fs, filename, []byte{}, 0600)
Expand Down
58 changes: 46 additions & 12 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package main

import (
"bytes"
"flag"
"fmt"
"github.com/adammck/venv"
"github.com/blang/vfs"
"os"
"os/exec"
"path/filepath"
)

Expand All @@ -23,17 +25,13 @@ func main() {
return
}

fs := vfs.OS()
if file == "" {
fs := vfs.OS()

env := venv.OS()
file = GetInputPath(fs, env)
}

if file == "" {
fmt.Printf("Usage: %s [options] path\n", os.Args[0])
os.Exit(1)
}

if !*list && *host == "" && !*inventory {
fmt.Fprint(os.Stderr, "Either --host or --list must be specified")
os.Exit(1)
Expand All @@ -45,17 +43,53 @@ func main() {
os.Exit(1)
}

stateFile, err := os.Open(path)
defer stateFile.Close()
f, err := fs.Stat(path)

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)
}

var s state
err = s.read(stateFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading tfstate file: %s\n", err)

if !f.IsDir() {
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)
}

Expand Down