├── release └── .gitignore ├── version.go ├── aws-inventory.example.ini ├── .gitignore ├── configuration.go ├── LICENSE ├── Makefile ├── main.go ├── README.md └── inventory.go /release/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | appName = "aws-inventory" 5 | appVersion = "0.0.1" 6 | ) 7 | -------------------------------------------------------------------------------- /aws-inventory.example.ini: -------------------------------------------------------------------------------- 1 | [aws] 2 | # https://console.aws.amazon.com/iam/home?#security_credential 3 | access-key = "" 4 | secret-key = "" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | *.ini 25 | !*.example.ini 26 | 27 | # Binaries 28 | bin/* 29 | release/* 30 | -------------------------------------------------------------------------------- /configuration.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "code.google.com/p/gcfg" 8 | ) 9 | 10 | type configuration struct { 11 | AccessKey string `gcfg:"access-key"` 12 | SecretKey string `gcfg:"secret-key"` 13 | } 14 | 15 | func getConfig(fileName string) (*configuration, error) { 16 | // first check directory where the executable is located 17 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 18 | if err != nil { 19 | return nil, err 20 | } 21 | path := dir + "/" + fileName 22 | if _, err := os.Stat(path); os.IsNotExist(err) { 23 | // fallback to working directory. This is usefull when using `go run` 24 | path = fileName 25 | } 26 | 27 | var cfg struct { 28 | AWS configuration 29 | } 30 | 31 | err = gcfg.ReadFileInto(&cfg, path) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return &cfg.AWS, nil 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Adam Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN=aws-inventory 2 | VERSION=0.0.1 3 | README=README.md 4 | LICENSE=LICENSE 5 | EXAMPLE_INI=aws-inventory.example.ini 6 | RELEASE_DIR=release 7 | 8 | # Go parameters 9 | GOCMD=go 10 | GOBUILD=$(GOCMD) build 11 | GOCLEAN=$(GOCMD) clean 12 | GOINSTALL=$(GOCMD) install 13 | GOTEST=$(GOCMD) test 14 | GODEP=$(GOCMD) get -d -v ./... 15 | GOFMT=gofmt -w 16 | 17 | default: build 18 | 19 | build: 20 | $(GODEP) 21 | GOARCH=amd64 GOOS=linux $(GOBUILD) -o bin/linux-amd64/$(BIN) 22 | GOARCH=386 GOOS=linux $(GOBUILD) -o bin/linux-386/$(BIN) 23 | GOARCH=amd64 GOOS=darwin $(GOBUILD) -o bin/darwin-amd64/$(BIN) 24 | 25 | package: 26 | rm -rf $(RELEASE_DIR)/$(BIN) 27 | mkdir $(RELEASE_DIR)/$(BIN) 28 | cp $(README) $(RELEASE_DIR)/$(BIN) 29 | cp $(LICENSE) $(RELEASE_DIR)/$(BIN) 30 | cp $(EXAMPLE_INI) $(RELEASE_DIR)/$(BIN)/aws-inventory.example.ini 31 | 32 | cp -f bin/linux-amd64/$(BIN) $(RELEASE_DIR)/$(BIN)/$(BIN) 33 | tar -czf $(RELEASE_DIR)/$(BIN)-linux-amd64-v$(VERSION).tar.gz -C $(RELEASE_DIR) $(BIN) 34 | 35 | cp -f bin/linux-386/$(BIN) $(RELEASE_DIR)/$(BIN)/$(BIN) 36 | tar -czf $(RELEASE_DIR)/$(BIN)-linux-386-v$(VERSION).tar.gz -C $(RELEASE_DIR) $(BIN) 37 | 38 | cp -f bin/darwin-amd64/$(BIN) $(RELEASE_DIR)/$(BIN)/$(BIN) 39 | tar -czf $(RELEASE_DIR)/$(BIN)-darwin-amd64-v$(VERSION).tar.gz -C $(RELEASE_DIR) $(BIN) 40 | 41 | rm -rf $(RELEASE_DIR)/$(BIN) 42 | 43 | format: 44 | $(GOFMT) ./**/*.go 45 | 46 | clean: 47 | $(GOCLEAN) 48 | 49 | test: 50 | $(GODEP) && $(GOTEST) -v ./... 51 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/mitchellh/goamz/aws" 9 | "github.com/mitchellh/goamz/ec2" 10 | ) 11 | 12 | var args = struct { 13 | list bool 14 | host bool 15 | version bool 16 | config string 17 | }{ 18 | false, 19 | false, 20 | false, 21 | "aws-inventory.ini", 22 | } 23 | 24 | func init() { 25 | flag.BoolVar(&args.list, "list", false, "Print Ansible formatted inventory") 26 | flag.BoolVar(&args.host, "host", false, "no-op since all information is given via --list") 27 | flag.BoolVar(&args.version, "v", false, "Print version") 28 | flag.StringVar(&args.config, "c", args.config, "Configuration filename") 29 | } 30 | 31 | func main() { 32 | flag.Parse() 33 | switch { 34 | case args.version: 35 | fmt.Printf("%s v%s\n", appName, appVersion) 36 | return 37 | case args.host: 38 | fmt.Fprint(os.Stdout, "{}") 39 | return 40 | case args.list: 41 | cfg, err := getConfig(args.config) 42 | if err != nil { 43 | die("Error reading configuration file:\n%s\n", err) 44 | } 45 | printList(cfg) 46 | return 47 | } 48 | 49 | flag.PrintDefaults() 50 | } 51 | 52 | func printList(cfg *configuration) { 53 | auth, err := aws.GetAuth(cfg.AccessKey, cfg.SecretKey) 54 | if err != nil { 55 | die("Error creating AWS auth:\n%s\n", err) 56 | } 57 | 58 | e := ec2.New(auth, aws.EUWest) 59 | instances, err := e.Instances([]string{}, nil) 60 | if err != nil { 61 | die("Error fetching EC2 instances:\n%s\n", err) 62 | } 63 | inv, err := newInventory(instances) 64 | if err != nil { 65 | die("Error creating inventory from EC2 instances:\n%s\n", err) 66 | } 67 | invJSON, err := inv.toJSON() 68 | if err != nil { 69 | die("Error generatin inventory JSON:\n%s\n", err) 70 | } 71 | os.Stdout.Write(invJSON) 72 | } 73 | 74 | func die(msg string, args ...interface{}) { 75 | fmt.Fprintf(os.Stderr, msg, args...) 76 | os.Exit(1) 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | aws-inventory 2 | ================ 3 | 4 | **Ansible dynamic inventory plugin for use with AWS EC2.** 5 | 6 | The Ansible repository already contains an [EC2 dynamic inventory plugin](https://github.com/ansible/ansible/tree/devel/plugins/inventory), which may be useful to you. 7 | 8 | This plugin differs from the 'official' one in the following ways: 9 | 10 | * For each host, sets the `ansible_ssh_host` variable using the public ip. This eliminates the need to reference hosts by their ip, or maintain your `/etc/hosts` file. You can then create another inventory file in the same directory, and reference the hosts by their EC2 Tag `Name`. 11 | 12 | * Returns host variables in the `_meta` top level element, reducing the number of api calls to AWS and speeding up the provisioning process. This eliminates the need to call the executable with `--host` for each host. 13 | 14 | * Only makes 2 requests to the AWS API when called with `--list`. 15 | 16 | * No external dependencies. 17 | 18 | * Creates less variables per host, but adding more would be trival. Open a pull-request if you need one defined. 19 | 20 | See [Developing Dynamic Inventory Sources](http://docs.ansible.com/developing_inventory.html) for more information. 21 | 22 | ## Download 23 | 24 | **Grab the latest release from [Releases](https://github.com/awilliams/aws-inventory/releases)** 25 | 26 | ## Usage 27 | 28 | * Download the appropriate package from releases. 29 | 30 | * Place the executable inside your ansible directory, alongside other inventory files in a directory or wherever you like. 31 | 32 | * Create a `aws-inventory.ini` file with your AWS credentials, in the same directory as the executable. See the inlcluded example ini file `aws-inventory.example.ini`. 33 | 34 | * Test the output 35 | 36 | `./aws-inventory --list` 37 | 38 | ## Building 39 | 40 | A hacked together `Makefile` is included. Try: 41 | 42 | make 43 | make package 44 | -------------------------------------------------------------------------------- /inventory.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Provides output for use as an Ansible inventory plugin 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | 9 | "github.com/mitchellh/goamz/ec2" 10 | ) 11 | 12 | func newInventory(instances *ec2.InstancesResp) (*inventory, error) { 13 | meta := make(map[string]map[string]map[string]string) 14 | hostvars := make(map[string]map[string]string) 15 | meta["hostvars"] = hostvars 16 | var hosts []string 17 | 18 | inv := inventory{Meta: meta, Hosts: &hosts} 19 | 20 | for _, reservation := range instances.Reservations { 21 | for _, instance := range reservation.Instances { 22 | tags := tagsToMap(instance.Tags) 23 | label, ok := tags["Name"] 24 | if !ok { 25 | return &inv, fmt.Errorf("instance %s does not have a 'Name' tag", instance.InstanceId) 26 | } 27 | displayGroup := tags["DisplayGroup"] 28 | hosts = append(hosts, label) 29 | hostvars[label] = map[string]string{ 30 | "ansible_ssh_host": instance.PublicIpAddress, 31 | "host_label": label, 32 | "host_aws_id": instance.InstanceId, 33 | "host_display_group": displayGroup, 34 | "host_arch": instance.Architecture, 35 | "host_aws_type": instance.InstanceType, 36 | "host_aws_avail_zone": instance.AvailZone, 37 | "host_private_ip": instance.PrivateIpAddress, 38 | "host_public_ip": instance.PublicIpAddress, 39 | "host_public_dns": instance.DNSName, 40 | "host_private_dns": instance.PrivateDNSName, 41 | } 42 | } 43 | } 44 | return &inv, nil 45 | } 46 | 47 | func tagsToMap(tags []ec2.Tag) map[string]string { 48 | m := make(map[string]string, len(tags)) 49 | for _, tag := range tags { 50 | m[tag.Key] = tag.Value 51 | } 52 | return m 53 | } 54 | 55 | type inventory struct { 56 | Meta map[string]map[string]map[string]string `json:"_meta"` 57 | Hosts *[]string `json:"hosts"` 58 | } 59 | 60 | func (i *inventory) toJSON() ([]byte, error) { 61 | return json.MarshalIndent(i, " ", " ") 62 | } 63 | --------------------------------------------------------------------------------