Skip to content

Commit

Permalink
cleans up code a bit, more readme info, ini example, makefile
Browse files Browse the repository at this point in the history
  • Loading branch information
awilliams committed Mar 19, 2014
1 parent d18767d commit add9e94
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 26 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ _testmain.go
*.exe

*.ini

# Binaries
bin/*
release/*
48 changes: 48 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
BIN=linode-inventory
VERSION=0.0.1
README=README.md
LICENSE=LICENSE
RELEASE_DIR=release

# Go parameters
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOINSTALL=$(GOCMD) install
GOTEST=$(GOCMD) test
GODEP=$(GOCMD) get -d -v ./...
GOFMT=gofmt -w

default: build

build:
$(GODEP)
GOARCH=amd64 GOOS=linux $(GOBUILD) -o bin/linux-amd64/$(BIN)
GOARCH=386 GOOS=linux $(GOBUILD) -o bin/linux-386/$(BIN)
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o bin/darwin-amd64/$(BIN)

package:
rm -rf $(RELEASE_DIR)/$(BIN)
mkdir $(RELEASE_DIR)/$(BIN)
cp $(README) $(RELEASE_DIR)/$(BIN)
cp $(LICENSE) $(RELEASE_DIR)/$(BIN)

cp -f bin/linux-amd64/$(BIN) $(RELEASE_DIR)/$(BIN)/$(BIN)
tar -czf $(RELEASE_DIR)/$(BIN)-linux-amd64-v$(VERSION).tar.gz -C $(RELEASE_DIR) $(BIN)

cp -f bin/linux-386/$(BIN) $(RELEASE_DIR)/$(BIN)/$(BIN)
tar -czf $(RELEASE_DIR)/$(BIN)-linux-386-v$(VERSION).tar.gz -C $(RELEASE_DIR) $(BIN)

cp -f bin/darwin-amd64/$(BIN) $(RELEASE_DIR)/$(BIN)/$(BIN)
tar -czf $(RELEASE_DIR)/$(BIN)-darwin-amd64-v$(VERSION).tar.gz -C $(RELEASE_DIR) $(BIN)

rm -rf $(RELEASE_DIR)/$(BIN)

format:
$(GOFMT) ./**/*.go

clean:
$(GOCLEAN)

test:
$(GODEP) && $(GOTEST) -v ./...
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,29 @@ linode-inventory
================

Ansible Inventory plugin for use with Linode

The Ansible repository contains an [Linode inventory plugin](https://github.com/ansible/ansible/tree/devel/plugins/inventory).

This plugin allows filtering based on the Display Group and has no external dependencies.
Also, it includes all information in the `--list` form (with only 2 api requests to Linode).
The `--host` form returns an empty hash.

See [Developing Dynamic Inventory Sources](http://docs.ansible.com/developing_inventory.html) for more information.

## Usage

Download the appropriate package from releases.

Create a `linode-inventory.ini` file with your Linode API key. See the example ini file.

**Command Line Usage**

Print out your available linodes

./linode-inventory <displaygroup>

**Ansible Usage**

Ansible will execute with the `--list` or `--host` flag. The `--host` flag will always return an empty hash.

./linode-inventory --list
33 changes: 20 additions & 13 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@ const (
API_URL = "https://api.linode.com/"
)

type ErrorJson struct {
Errors []struct {
Code int `json:"ERRORCODE"`
Message string `json:"ERRORMESSAGE"`
} `json:"ERRORARRAY,omitempty"`
}

type queryParams map[string]string

type apiAction struct {
Expand Down Expand Up @@ -93,25 +86,39 @@ func (self apiRequest) GetJson(data interface{}) error {
if err != nil {
return err
}

// the linode API does not use HTTP status codes to indicate errors,
// rather it embeds in the JSON document the errors. When there is an error
// the foramt of the `DATA` element changes as well, which would cause the json decode to fail.
//
// Here we first parse the json to see if it contains errors, then re-parse with the provided
// json structure
if linodeErr := checkForLinodeError(bytes.NewReader(body)); linodeErr != nil {
return linodeErr
}

decoder := json.NewDecoder(bytes.NewReader(body))
err = decoder.Decode(data)
if err != nil {
linodeErr := checkForLinodeError(bytes.NewReader(body))
if linodeErr != nil {
return linodeErr
}
return err
}

return nil
}

type errorJson struct {
Errors []struct {
Code int `json:"ERRORCODE"`
Message string `json:"ERRORMESSAGE"`
} `json:"ERRORARRAY,omitempty"`
}

func checkForLinodeError(body *bytes.Reader) error {
data := new(ErrorJson)
data := new(errorJson)
decoder := json.NewDecoder(body)
err := decoder.Decode(&data)
if err != nil {
// this is not actually an error
// this is not actually an error, since there is not always an error present in the JSON
return nil
}
if len(data.Errors) > 0 {
Expand Down
25 changes: 21 additions & 4 deletions api/linode.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package api

import (
"strconv"
//"fmt"
//"bytes"
"sort"
)

type Linode struct {
Expand Down Expand Up @@ -37,6 +36,23 @@ func (self Linode) PrivateIp() string {
return ip
}

type sortedLinodeIps []LinodeIp

func (self sortedLinodeIps) Len() int {
return len(self)
}
func (self sortedLinodeIps) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
}
// Public first
func (self sortedLinodeIps) Less(i, j int) bool {
return self[i].Public > self[j].Public
}

func (self Linode) SortIps() {
sort.Sort(sortedLinodeIps(self.Ips))
}

type Linodes map[int]*Linode

func (self Linodes) FilterByDisplayGroup(group string) Linodes {
Expand Down Expand Up @@ -75,7 +91,6 @@ func LinodeList(apiKey string) (Linodes, error) {

linodes := make(Linodes)
for _, linode := range data.Linodes {
//linode.Ips = []LinodeIp{}
l := linode
linodes[linode.Id] = &l
}
Expand All @@ -89,6 +104,8 @@ type LinodeIp struct {
Public int `json:"ISPUBLIC"`
}

// first fetch the list of linodes,
// then use a batch request to list all the ips associated with those linodes
func LinodeListWithIps(apiKey string) (Linodes, error) {
linodes, err := LinodeList(apiKey)
if err != nil {
Expand Down Expand Up @@ -116,7 +133,7 @@ func LinodeListWithIps(apiKey string) (Linodes, error) {
for _, ipList := range data {
for _, linodeIp := range ipList.LinodeIps {
if linode, ok := linodes[linodeIp.LinodeId]; ok {
linode.Ips = append(linode.Ips, linodeIp)
linode.Ips = append(linode.Ips, linodeIp)
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions inventory.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package main

// Provides output for use as an Ansible inventory plugin

import (
"encoding/json"
"github.com/awilliams/linode-inventory/api"
Expand Down
3 changes: 3 additions & 0 deletions linode-inventory.ini.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[linode]
api-key = "supersecretlinodekey"
display-group = "Staging US"
35 changes: 26 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,33 @@ import (
"github.com/mgutz/ansi"
"log"
"os"
"sort"
"path/filepath"
)

const CONFIG_PATH = "linode-inventory.ini"

type Configuration struct {
ApiKey string `gcfg:"api-key"`
DisplayGroup string `gcfg:"display-group"`
ApiKey string `gcfg:"api-key"`
DisplayGroup string `gcfg:"display-group"`
}

func getConfig() (*Configuration, error) {
// first check directory where the executable is located
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
return nil, err
}
path := dir + "/" + CONFIG_PATH
if _, err := os.Stat(path); os.IsNotExist(err) {
// this happens with using `go run main.go`
// fallback to PWD. This is usefull when using `go run`
path = CONFIG_PATH
}

var config struct {
Linode Configuration
}

err = gcfg.ReadFileInto(&config, path)
if err != nil {
return nil, err
Expand All @@ -40,19 +42,33 @@ func getConfig() (*Configuration, error) {
return &config.Linode, nil
}

type sortedLinodes []*api.Linode

func (self sortedLinodes) Len() int {
return len(self)
}
func (self sortedLinodes) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
}
func (self sortedLinodes) Less(i, j int) bool {
return self[i].Label < self[j].Label
}

func printLinodes(linodes api.Linodes) {
grouped := make(map[string][]*api.Linode)
grouped := make(map[string]sortedLinodes)
for _, linode := range linodes {
grouped[linode.DisplayGroup] = append(grouped[linode.DisplayGroup], linode)
}
for displayGroup, linodes := range grouped {
sort.Sort(linodes)
fmt.Printf("[%s]\n\n", ansi.Color(displayGroup, "green"))
for _, linode := range linodes {
labelColor := "magenta"
if linode.Status != 1 {
labelColor = "blue"
}
fmt.Printf(" * %-25s\tRunning=%v, Ram=%d, LinodeId=%d\n", ansi.Color(linode.Label, labelColor), linode.Status == 1, linode.Ram, linode.Id)
linode.SortIps()
for _, ip := range linode.Ips {
var ipType string
if ip.Public == 1 {
Expand Down Expand Up @@ -99,13 +115,14 @@ func main() {
// empty hash
fmt.Fprint(os.Stdout, "{}")
default:
fmt.Errorf("Unrecognized flag: %v\nUse --list or --host\n", os.Args[1])
fmt.Errorf("Unrecognized flag: %v\nAvailable flags: --list or --host\n", os.Args[1])
}
} else {
// non-ansible case, just print linodes
// optionally using first arg as display group filter
if len(os.Args) > 1 {
linodes = linodes.FilterByDisplayGroup(os.Args[1])
}
// just use info for non-ansible case
}
printLinodes(linodes)
}
}

0 comments on commit add9e94

Please sign in to comment.