mirror of
https://github.com/adammck/terraform-inventory
synced 2024-11-23 04:22:08 +01:00
Merge branch 'digitalocean'
This commit is contained in:
commit
56f9b9ff07
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
/terraform-inventory
|
/terraform-inventory
|
||||||
|
/fixtures/secrets.tfvars
|
||||||
|
12
README.md
12
README.md
@ -1,9 +1,11 @@
|
|||||||
# Terraformed Inventory
|
# Terraformed Inventory
|
||||||
|
|
||||||
This is a little Go app which generates an dynamic [Ansible] [ansible] inventory
|
This is a little Go app which generates an dynamic [Ansible] [ansible] inventory
|
||||||
from an AWS-based [Terraform] [tf] state file. It allows one to spawn a bunch of EC2 VMs with
|
from a [Terraform] [tf] state file. It allows one to spawn a bunch of VMs with
|
||||||
Terraform, then (re-)provision them with Ansible. It's pretty neat.
|
Terraform, then (re-)provision them with Ansible. It's pretty neat.
|
||||||
|
|
||||||
|
Currently, only **AWS** and **DigitalOcean** are supported.
|
||||||
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@ -41,6 +43,14 @@ It's just a Go app, so the usual:
|
|||||||
cd $GOPATH/adammck/terraform-inventory
|
cd $GOPATH/adammck/terraform-inventory
|
||||||
go build
|
go build
|
||||||
|
|
||||||
|
To test against an example statefile, run:
|
||||||
|
|
||||||
|
terraform-inventory --list fixtures/example.tfstate
|
||||||
|
terraform-inventory --host=web-aws fixtures/example.tfstate
|
||||||
|
|
||||||
|
To update the fixtures, populate `fixtures/secrets.tfvars` with your DO and AWS
|
||||||
|
account details, and run `fixtures/update`. You probably don't need to do this.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
10
cli.go
10
cli.go
@ -11,17 +11,17 @@ func cmdList(stdout io.Writer, stderr io.Writer, s *state) int {
|
|||||||
|
|
||||||
// add each instance as a pseudo-group, so they can be provisioned
|
// add each instance as a pseudo-group, so they can be provisioned
|
||||||
// individually where necessary.
|
// individually where necessary.
|
||||||
for name, inst := range s.instances() {
|
for name, res := range s.resources() {
|
||||||
groups[name] = []string{inst.Attributes["private_ip"]}
|
groups[name] = []string{res.Address()}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output(stdout, stderr, groups)
|
return output(stdout, stderr, groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdHost(stdout io.Writer, stderr io.Writer, s *state, hostname string) int {
|
func cmdHost(stdout io.Writer, stderr io.Writer, s *state, hostname string) int {
|
||||||
for _, inst := range s.instances() {
|
for name, res := range s.resources() {
|
||||||
if hostname == inst.Attributes["private_ip"] {
|
if hostname == name {
|
||||||
return output(stdout, stderr, inst.Attributes)
|
return output(stdout, stderr, res.Attributes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
31
fixtures/example.tf
Normal file
31
fixtures/example.tf
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
variable "do_token" {}
|
||||||
|
variable "aws_access_key" {}
|
||||||
|
variable "aws_secret_key" {}
|
||||||
|
variable "aws_subnet_id" {}
|
||||||
|
|
||||||
|
provider "aws" {
|
||||||
|
access_key = "${var.aws_access_key}"
|
||||||
|
secret_key = "${var.aws_secret_key}"
|
||||||
|
region = "us-east-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "digitalocean" {
|
||||||
|
token = "${var.do_token}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "web-aws" {
|
||||||
|
ami = "ami-96a818fe"
|
||||||
|
instance_type = "t2.micro"
|
||||||
|
subnet_id = "${var.aws_subnet_id}"
|
||||||
|
root_block_device = {
|
||||||
|
delete_on_termination = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "digitalocean_droplet" "web-do" {
|
||||||
|
image = "centos-7-0-x64"
|
||||||
|
name = "terraform-inventory-1"
|
||||||
|
region = "nyc1"
|
||||||
|
size = "512mb"
|
||||||
|
ssh_keys = [55015]
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"serial": 1,
|
"serial": 11,
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"path": [
|
"path": [
|
||||||
@ -8,103 +8,53 @@
|
|||||||
],
|
],
|
||||||
"outputs": {},
|
"outputs": {},
|
||||||
"resources": {
|
"resources": {
|
||||||
"aws_instance.one": {
|
"aws_instance.web-aws": {
|
||||||
"type": "aws_instance",
|
"type": "aws_instance",
|
||||||
"depends_on": [
|
|
||||||
"aws_security_group.example"
|
|
||||||
],
|
|
||||||
"primary": {
|
"primary": {
|
||||||
"id": "i-aaaaaaaa",
|
"id": "i-366736e6",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"ami": "ami-XXXXXXXX",
|
"ami": "ami-96a818fe",
|
||||||
"availability_zone": "us-east-1b",
|
"availability_zone": "us-east-1d",
|
||||||
"id": "i-aaaaaaaa",
|
"ebs_block_device.#": "0",
|
||||||
|
"ebs_optimized": "false",
|
||||||
|
"ephemeral_block_device.#": "0",
|
||||||
|
"id": "i-366736e6",
|
||||||
"instance_type": "t2.micro",
|
"instance_type": "t2.micro",
|
||||||
"key_name": "",
|
"private_dns": "ip-10-0-0-4.ec2.internal",
|
||||||
"private_dns": "ip-1-1-1-1.ec2.internal",
|
"private_ip": "10.0.0.4",
|
||||||
"private_ip": "1.1.1.1",
|
|
||||||
"public_dns": "",
|
"public_dns": "",
|
||||||
"public_ip": "",
|
"root_block_device.#": "1",
|
||||||
"security_groups.#": "1",
|
"root_block_device.0.delete_on_termination": "true",
|
||||||
"security_groups.0": "sg-cccccccc",
|
"root_block_device.0.iops": "0",
|
||||||
"subnet_id": "subnet-XXXXXXXX",
|
"root_block_device.0.volume_size": "8",
|
||||||
"tenancy": "default"
|
"root_block_device.0.volume_type": "standard",
|
||||||
|
"security_groups.#": "0",
|
||||||
|
"subnet_id": "subnet-59f9b32e",
|
||||||
|
"tags.#": "0",
|
||||||
|
"tenancy": "default",
|
||||||
|
"vpc_security_group_ids.#": "1",
|
||||||
|
"vpc_security_group_ids.2076429742": "sg-b42329d0"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"schema_version": "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"aws_instance.two": {
|
"digitalocean_droplet.web-do": {
|
||||||
"type": "aws_instance",
|
"type": "digitalocean_droplet",
|
||||||
"depends_on": [
|
|
||||||
"aws_security_group.example"
|
|
||||||
],
|
|
||||||
"primary": {
|
"primary": {
|
||||||
"id": "i-bbbbbbbb",
|
"id": "5579362",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"ami": "ami-XXXXXXXX",
|
"id": "5579362",
|
||||||
"availability_zone": "us-east-1b",
|
"image": "centos-7-0-x64",
|
||||||
"id": "i-bbbbbbbb",
|
"ipv4_address": "192.241.136.44",
|
||||||
"instance_type": "t2.micro",
|
"locked": "false",
|
||||||
"key_name": "",
|
"name": "terraform-inventory-1",
|
||||||
"private_dns": "ip-2-2-2-2.ec2.internal",
|
"region": "nyc1",
|
||||||
"private_ip": "2.2.2.2",
|
"size": "512mb",
|
||||||
"public_dns": "",
|
"ssh_keys.#": "1",
|
||||||
"public_ip": "",
|
"ssh_keys.0": "55015",
|
||||||
"security_groups.#": "1",
|
"status": "active"
|
||||||
"security_groups.0": "sg-cccccccc",
|
|
||||||
"subnet_id": "subnet-XXXXXXXX",
|
|
||||||
"tenancy": "default"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"aws_route53_record.example": {
|
|
||||||
"type": "aws_route53_record",
|
|
||||||
"depends_on": [
|
|
||||||
"aws_instance.one",
|
|
||||||
"aws_instance.two"
|
|
||||||
],
|
|
||||||
"primary": {
|
|
||||||
"id": "XXXXXXXXXXXXXX_something.example.com_CNAME",
|
|
||||||
"attributes": {
|
|
||||||
"id": "XXXXXXXXXXXXXX_something.example.com_CNAME",
|
|
||||||
"name": "something.example.com",
|
|
||||||
"records.#": "2",
|
|
||||||
"records.0": "i-aaaaaaaa",
|
|
||||||
"records.1": "i-bbbbbbbb",
|
|
||||||
"ttl": "300",
|
|
||||||
"type": "CNAME",
|
|
||||||
"zone_id": "XXXXXXXXXXXXXX"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"aws_security_group.example": {
|
|
||||||
"type": "aws_security_group",
|
|
||||||
"primary": {
|
|
||||||
"id": "sg-cccccccc",
|
|
||||||
"attributes": {
|
|
||||||
"description": "Allow SSH and HTTP from inside the firewall",
|
|
||||||
"id": "sg-cccccccc",
|
|
||||||
"ingress.#": "2",
|
|
||||||
"ingress.0.cidr_blocks.#": "2",
|
|
||||||
"ingress.0.cidr_blocks.0": "10.0.0.0/8",
|
|
||||||
"ingress.0.cidr_blocks.1": "192.168.0.0/16",
|
|
||||||
"ingress.0.from_port": "22",
|
|
||||||
"ingress.0.protocol": "tcp",
|
|
||||||
"ingress.0.security_groups.#": "0",
|
|
||||||
"ingress.0.self": "false",
|
|
||||||
"ingress.0.to_port": "22",
|
|
||||||
"ingress.1.cidr_blocks.#": "2",
|
|
||||||
"ingress.1.cidr_blocks.0": "10.0.0.0/8",
|
|
||||||
"ingress.1.cidr_blocks.1": "192.168.0.0/16",
|
|
||||||
"ingress.1.from_port": "80",
|
|
||||||
"ingress.1.protocol": "tcp",
|
|
||||||
"ingress.1.security_groups.#": "0",
|
|
||||||
"ingress.1.self": "false",
|
|
||||||
"ingress.1.to_port": "80",
|
|
||||||
"name": "example",
|
|
||||||
"owner_id": "111111111111",
|
|
||||||
"tags.App": "my_app",
|
|
||||||
"tags.Environment": "my_env",
|
|
||||||
"vpc_id": "vpc-XXXXXXXX"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
fixtures/secrets.tfvars.example
Normal file
4
fixtures/secrets.tfvars.example
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
do_token = ""
|
||||||
|
aws_access_key = ""
|
||||||
|
aws_secret_key = ""
|
||||||
|
aws_subnet_id = ""
|
3
fixtures/update
Executable file
3
fixtures/update
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
cd $(cd `dirname "$0"`; cd ..; pwd)
|
||||||
|
terraform apply -refresh -var-file="fixtures/secrets.tfvars" -state="fixtures/example.tfstate" -backup="-" fixtures
|
2
main.go
2
main.go
@ -23,7 +23,7 @@ func main() {
|
|||||||
env.MustProcess(cfg)
|
env.MustProcess(cfg)
|
||||||
|
|
||||||
if *version == true {
|
if *version == true {
|
||||||
fmt.Printf("%s version %d\n", os.Args[0], versionInfo())
|
fmt.Printf("%s version %s\n", os.Args[0], versionInfo())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
parser.go
41
parser.go
@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type state struct {
|
type state struct {
|
||||||
@ -29,16 +29,17 @@ func (s *state) read(stateFile io.Reader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hosts returns a map of name to instanceState, for each of the aws_instance
|
// resources returns a map of name to resourceState, for any supported resources
|
||||||
// resources found in the statefile.
|
// found in the statefile.
|
||||||
func (s *state) instances() map[string]instanceState {
|
func (s *state) resources() map[string]resourceState {
|
||||||
inst := make(map[string]instanceState)
|
typeRemover := regexp.MustCompile(`^[\w_]+\.`)
|
||||||
|
inst := make(map[string]resourceState)
|
||||||
|
|
||||||
for _, m := range s.Modules {
|
for _, m := range s.Modules {
|
||||||
for k, r := range m.Resources {
|
for k, r := range m.Resources {
|
||||||
if r.Type == "aws_instance" {
|
if r.isSupported() {
|
||||||
name := strings.TrimPrefix(k, "aws_instance.")
|
name := typeRemover.ReplaceAllString(k, "")
|
||||||
inst[name] = r.Primary
|
inst[name] = r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,6 +56,30 @@ type resourceState struct {
|
|||||||
Primary instanceState `json:"primary"`
|
Primary instanceState `json:"primary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isSupported returns true if terraform-inventory supports this resource.
|
||||||
|
func (s *resourceState) isSupported() bool {
|
||||||
|
return s.Address() != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address returns the IP address of this resource.
|
||||||
|
func (s *resourceState) Address() string {
|
||||||
|
switch s.Type {
|
||||||
|
case "aws_instance":
|
||||||
|
return s.Primary.Attributes["private_ip"]
|
||||||
|
|
||||||
|
case "digitalocean_droplet":
|
||||||
|
return s.Primary.Attributes["ipv4_address"]
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes returns a map containing everything we know about this resource.
|
||||||
|
func (s *resourceState) Attributes() map[string]string {
|
||||||
|
return s.Primary.Attributes
|
||||||
|
}
|
||||||
|
|
||||||
type instanceState struct {
|
type instanceState struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Attributes map[string]string `json:"attributes,omitempty"`
|
Attributes map[string]string `json:"attributes,omitempty"`
|
||||||
|
@ -22,8 +22,8 @@ const exampleStateFile = `
|
|||||||
"primary": {
|
"primary": {
|
||||||
"id": "i-aaaaaaaa",
|
"id": "i-aaaaaaaa",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"ami": "ami-XXXXXXXX",
|
"id": "i-aaaaaaaa",
|
||||||
"id": "i-aaaaaaaa"
|
"private_ip": "10.0.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -32,8 +32,8 @@ const exampleStateFile = `
|
|||||||
"primary": {
|
"primary": {
|
||||||
"id": "i-bbbbbbbb",
|
"id": "i-bbbbbbbb",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"ami": "ami-YYYYYYYY",
|
"id": "i-bbbbbbbb",
|
||||||
"id": "i-bbbbbbbb"
|
"private_ip": "10.0.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -42,8 +42,18 @@ const exampleStateFile = `
|
|||||||
"primary": {
|
"primary": {
|
||||||
"id": "sg-cccccccc",
|
"id": "sg-cccccccc",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"description": "Whatever",
|
"id": "sg-cccccccc",
|
||||||
"id": "sg-cccccccc"
|
"description": "Whatever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"digitalocean_droplet.three": {
|
||||||
|
"type": "digitalocean_droplet",
|
||||||
|
"primary": {
|
||||||
|
"id": "ddddddd",
|
||||||
|
"attributes": {
|
||||||
|
"id": "ddddddd",
|
||||||
|
"ipv4_address": "192.168.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,8 +79,8 @@ func TestStateRead(t *testing.T) {
|
|||||||
Primary: instanceState{
|
Primary: instanceState{
|
||||||
ID: "i-aaaaaaaa",
|
ID: "i-aaaaaaaa",
|
||||||
Attributes: map[string]string{
|
Attributes: map[string]string{
|
||||||
"ami": "ami-XXXXXXXX",
|
|
||||||
"id": "i-aaaaaaaa",
|
"id": "i-aaaaaaaa",
|
||||||
|
"private_ip": "10.0.0.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -79,8 +89,8 @@ func TestStateRead(t *testing.T) {
|
|||||||
Primary: instanceState{
|
Primary: instanceState{
|
||||||
ID: "i-bbbbbbbb",
|
ID: "i-bbbbbbbb",
|
||||||
Attributes: map[string]string{
|
Attributes: map[string]string{
|
||||||
"ami": "ami-YYYYYYYY",
|
|
||||||
"id": "i-bbbbbbbb",
|
"id": "i-bbbbbbbb",
|
||||||
|
"private_ip": "10.0.0.2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -89,8 +99,18 @@ func TestStateRead(t *testing.T) {
|
|||||||
Primary: instanceState{
|
Primary: instanceState{
|
||||||
ID: "sg-cccccccc",
|
ID: "sg-cccccccc",
|
||||||
Attributes: map[string]string{
|
Attributes: map[string]string{
|
||||||
"description": "Whatever",
|
|
||||||
"id": "sg-cccccccc",
|
"id": "sg-cccccccc",
|
||||||
|
"description": "Whatever",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"digitalocean_droplet.three": resourceState{
|
||||||
|
Type: "digitalocean_droplet",
|
||||||
|
Primary: instanceState{
|
||||||
|
ID: "ddddddd",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"id": "ddddddd",
|
||||||
|
"ipv4_address": "192.168.0.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -102,15 +122,44 @@ func TestStateRead(t *testing.T) {
|
|||||||
assert.Equal(t, exp, s)
|
assert.Equal(t, exp, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstances(t *testing.T) {
|
func TestResources(t *testing.T) {
|
||||||
r := strings.NewReader(exampleStateFile)
|
r := strings.NewReader(exampleStateFile)
|
||||||
|
|
||||||
var s state
|
var s state
|
||||||
err := s.read(r)
|
err := s.read(r)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
inst := s.instances()
|
inst := s.resources()
|
||||||
assert.Equal(t, 2, len(inst))
|
assert.Equal(t, 3, len(inst))
|
||||||
assert.Equal(t, "i-aaaaaaaa", inst["one"].ID)
|
assert.Equal(t, "aws_instance", inst["one"].Type)
|
||||||
assert.Equal(t, "i-bbbbbbbb", inst["two"].ID)
|
assert.Equal(t, "aws_instance", inst["two"].Type)
|
||||||
|
assert.Equal(t, "digitalocean_droplet", inst["three"].Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsSupported(t *testing.T) {
|
||||||
|
|
||||||
|
r := resourceState{
|
||||||
|
Type: "something",
|
||||||
|
}
|
||||||
|
assert.Equal(t, false, r.isSupported())
|
||||||
|
|
||||||
|
r = resourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: instanceState{
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"private_ip": "10.0.0.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, true, r.isSupported())
|
||||||
|
|
||||||
|
r = resourceState{
|
||||||
|
Type: "digitalocean_droplet",
|
||||||
|
Primary: instanceState{
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"ipv4_address": "192.168.0.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, true, r.isSupported())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user