Skip to content

Commit 50e6962

Browse files
committed
Merge pull request #2 from adammck/remove_terraform_dependency
Remove Terraform Dependency
2 parents 9f33a7e + 0a27ca6 commit 50e6962

File tree

9 files changed

+428
-139
lines changed

9 files changed

+428
-139
lines changed

cli.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
)
8+
9+
func cmdList(stdout io.Writer, stderr io.Writer, s *state) int {
10+
groups := make(map[string][]string, 0)
11+
12+
// add each instance as a pseudo-group, so they can be provisioned
13+
// individually where necessary.
14+
for name, inst := range s.instances() {
15+
groups[name] = []string{inst.Attributes["private_ip"]}
16+
}
17+
18+
return output(stdout, stderr, groups)
19+
}
20+
21+
func cmdHost(stdout io.Writer, stderr io.Writer, s *state, hostname string) int {
22+
for _, inst := range s.instances() {
23+
if hostname == inst.Attributes["private_ip"] {
24+
return output(stdout, stderr, inst.Attributes)
25+
}
26+
}
27+
28+
fmt.Fprintf(stderr, "No such host: %s\n", hostname)
29+
return 1
30+
}
31+
32+
// output marshals an arbitrary JSON object and writes it to stdout, or writes
33+
// an error to stderr, then returns the appropriate exit code.
34+
func output(stdout io.Writer, stderr io.Writer, whatever interface{}) int {
35+
b, err := json.Marshal(whatever)
36+
if err != nil {
37+
fmt.Fprintf(stderr, "Error encoding JSON: %s\n", err)
38+
return 1
39+
}
40+
41+
_, err = stdout.Write(b)
42+
if err != nil {
43+
fmt.Fprintf(stderr, "Error writing JSON: %s\n", err)
44+
return 1
45+
}
46+
47+
return 0
48+
}

cli_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package main

fixtures/example.tfstate

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
{
2+
"version": 1,
3+
"serial": 1,
4+
"modules": [
5+
{
6+
"path": [
7+
"root"
8+
],
9+
"outputs": {},
10+
"resources": {
11+
"aws_instance.one": {
12+
"type": "aws_instance",
13+
"depends_on": [
14+
"aws_security_group.example"
15+
],
16+
"primary": {
17+
"id": "i-aaaaaaaa",
18+
"attributes": {
19+
"ami": "ami-XXXXXXXX",
20+
"availability_zone": "us-east-1b",
21+
"id": "i-aaaaaaaa",
22+
"instance_type": "t2.micro",
23+
"key_name": "",
24+
"private_dns": "ip-1-1-1-1.ec2.internal",
25+
"private_ip": "1.1.1.1",
26+
"public_dns": "",
27+
"public_ip": "",
28+
"security_groups.#": "1",
29+
"security_groups.0": "sg-cccccccc",
30+
"subnet_id": "subnet-XXXXXXXX",
31+
"tenancy": "default"
32+
}
33+
}
34+
},
35+
"aws_instance.two": {
36+
"type": "aws_instance",
37+
"depends_on": [
38+
"aws_security_group.example"
39+
],
40+
"primary": {
41+
"id": "i-bbbbbbbb",
42+
"attributes": {
43+
"ami": "ami-XXXXXXXX",
44+
"availability_zone": "us-east-1b",
45+
"id": "i-bbbbbbbb",
46+
"instance_type": "t2.micro",
47+
"key_name": "",
48+
"private_dns": "ip-2-2-2-2.ec2.internal",
49+
"private_ip": "2.2.2.2",
50+
"public_dns": "",
51+
"public_ip": "",
52+
"security_groups.#": "1",
53+
"security_groups.0": "sg-cccccccc",
54+
"subnet_id": "subnet-XXXXXXXX",
55+
"tenancy": "default"
56+
}
57+
}
58+
},
59+
"aws_route53_record.example": {
60+
"type": "aws_route53_record",
61+
"depends_on": [
62+
"aws_instance.one",
63+
"aws_instance.two"
64+
],
65+
"primary": {
66+
"id": "XXXXXXXXXXXXXX_something.example.com_CNAME",
67+
"attributes": {
68+
"id": "XXXXXXXXXXXXXX_something.example.com_CNAME",
69+
"name": "something.example.com",
70+
"records.#": "2",
71+
"records.0": "i-aaaaaaaa",
72+
"records.1": "i-bbbbbbbb",
73+
"ttl": "300",
74+
"type": "CNAME",
75+
"zone_id": "XXXXXXXXXXXXXX"
76+
}
77+
}
78+
},
79+
"aws_security_group.example": {
80+
"type": "aws_security_group",
81+
"primary": {
82+
"id": "sg-cccccccc",
83+
"attributes": {
84+
"description": "Allow SSH and HTTP from inside the firewall",
85+
"id": "sg-cccccccc",
86+
"ingress.#": "2",
87+
"ingress.0.cidr_blocks.#": "2",
88+
"ingress.0.cidr_blocks.0": "10.0.0.0/8",
89+
"ingress.0.cidr_blocks.1": "192.168.0.0/16",
90+
"ingress.0.from_port": "22",
91+
"ingress.0.protocol": "tcp",
92+
"ingress.0.security_groups.#": "0",
93+
"ingress.0.self": "false",
94+
"ingress.0.to_port": "22",
95+
"ingress.1.cidr_blocks.#": "2",
96+
"ingress.1.cidr_blocks.0": "10.0.0.0/8",
97+
"ingress.1.cidr_blocks.1": "192.168.0.0/16",
98+
"ingress.1.from_port": "80",
99+
"ingress.1.protocol": "tcp",
100+
"ingress.1.security_groups.#": "0",
101+
"ingress.1.self": "false",
102+
"ingress.1.to_port": "80",
103+
"name": "example",
104+
"owner_id": "111111111111",
105+
"tags.App": "my_app",
106+
"tags.Environment": "my_env",
107+
"vpc_id": "vpc-XXXXXXXX"
108+
}
109+
}
110+
}
111+
}
112+
}
113+
]
114+
}

main.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
)
9+
10+
var version = flag.Bool("version", false, "print version information and exit")
11+
var list = flag.Bool("list", false, "list mode")
12+
var host = flag.String("host", "", "host mode")
13+
14+
func main() {
15+
flag.Parse()
16+
file := flag.Arg(0)
17+
18+
if *version == true {
19+
fmt.Printf("%s version %d\n", os.Args[0], versionInfo())
20+
return
21+
}
22+
23+
if file == "" {
24+
fmt.Printf("Usage: %s [options] path\n", os.Args[0])
25+
os.Exit(1)
26+
}
27+
28+
if !*list && *host == "" {
29+
fmt.Println("Either --host or --list must be specified")
30+
os.Exit(1)
31+
}
32+
33+
path, err := filepath.Abs(file)
34+
if err != nil {
35+
fmt.Printf("Invalid file: %s\n", err)
36+
os.Exit(1)
37+
}
38+
39+
stateFile, err := os.Open(path)
40+
defer stateFile.Close()
41+
if err != nil {
42+
fmt.Printf("Error opening tfstate file: %s\n", err)
43+
os.Exit(1)
44+
}
45+
46+
var s state
47+
err = s.read(stateFile)
48+
if err != nil {
49+
fmt.Printf("Error reading tfstate file: %s\n", err)
50+
os.Exit(1)
51+
}
52+
53+
if *list {
54+
os.Exit(cmdList(os.Stdout, os.Stderr, &s))
55+
56+
} else if *host != "" {
57+
os.Exit(cmdHost(os.Stdout, os.Stderr, &s, *host))
58+
59+
}
60+
}

parser.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
import (
4+
"io"
5+
"io/ioutil"
6+
"encoding/json"
7+
"strings"
8+
)
9+
10+
type state struct {
11+
Modules []moduleState `json:"modules"`
12+
}
13+
14+
// read populates the state object from a statefile.
15+
func (s *state) read(stateFile io.Reader) error {
16+
17+
// read statefile contents
18+
b, err := ioutil.ReadAll(stateFile)
19+
if err != nil {
20+
return err
21+
}
22+
23+
// parse into struct
24+
err = json.Unmarshal(b, s)
25+
if err != nil {
26+
return err
27+
}
28+
29+
return nil
30+
}
31+
32+
// hosts returns a map of name to instanceState, for each of the aws_instance
33+
// resources found in the statefile.
34+
func (s *state) instances() map[string]instanceState {
35+
inst := make(map[string]instanceState)
36+
37+
for _, m := range s.Modules {
38+
for k, r := range m.Resources {
39+
if r.Type == "aws_instance" {
40+
name := strings.TrimPrefix(k, "aws_instance.")
41+
inst[name] = r.Primary
42+
}
43+
}
44+
}
45+
46+
return inst
47+
}
48+
49+
type moduleState struct {
50+
Resources map[string]resourceState `json:"resources"`
51+
}
52+
53+
type resourceState struct {
54+
Type string `json:"type"`
55+
Primary instanceState `json:"primary"`
56+
}
57+
58+
type instanceState struct {
59+
ID string `json:"id"`
60+
Attributes map[string]string `json:"attributes,omitempty"`
61+
}

0 commit comments

Comments
 (0)