gce-inventory

0.2.0 • Public • Published

gce-inventory

A flexible module for creating Ansible dynamic inventories from Google Compute Engine resources.

Change Log

v0.2.0 2015-11-29 - added group_vars capability.

v0.1.0 2015-11-28 - published initial npm package.

Rationale

Since we operate an environment that spans multiple clouds (GCE, AWS, Rackspace) we wanted to be able to dictate the composition of hostvars and groups when generating our dynamic inventories. Doing so greatly reduces the overhead maintaining our Ansible plays because we have reduced the coupling with the specific cloud provider.

What do we mean by coupling? Ansible's gce.py module and its ec2.py module are both opinionated about both hostvars and groups. Notably, both of these markup the inventory in a way that is not meaningful to our automation; a prefix of ec2_ and gce_ on variables is cumbersome and requires unnecessary logic in our plays deal with the prefixed variables.

Our own approach enables us to declaritively compose hostvars and groups into just the information our automation cares about.

Work in Progress

You should know this module is a work in progress - the development of our custom AWS and Rackspace modules will likely cause an iteration or two on this one.

Install

This module is written in node.js and uses node's ES6 features; you must have node v4.0+ installed on your system to use this module. Download and install instructions can be found on the nodejs.org downloads page.

npm install -g gce-inventory

Once installed, you'll have a gce-inventory command available on the machine.

Required Options

The gce-inventory command line must be able to find an options file in order to query the Google Compute Engine APIs. You can either set an environement variable GCE_OPTIONS to the file's full path, or name the file .gce-options and put it in your project's working directory or one of the parent directories.

Options files must be formatted as YAML.

The minimal options file identifies your project and credentials:

.gce-options:

---
projectId: test
keyFileName: /home/myname/TEST-xxxxxxxxxx.json
  • projectId - the name of your project, you can find this on the google console's dashboard
  • keyFileName - the fully qualified path to a service account file

Use

With the required options in place, you can run the inventory directly from a terminal in order to test it out and see what data will be provided to Ansible:

gce-inventory --list

The default output might look something like this (pretty-printed and elided):

{
  "_meta": {
    "hostvars": {
      "gc-ctrl-server-02": {
        "zone": "us-central1-c",
        "name": "gc-ctrl-server-02",
        "ansible_ssh_host": "xx.xx.xx.xx"
      },
      "gc-hafs-server-01": {
        "zone": "us-central1-b",
        "name": "gc-hafs-server-01",
        "ansible_ssh_host": "xx.xx.xx.xx"
      },
      "...",
      "gc-app-server-member-mm7n": {
        "zone": "us-central1-f",
        "name": "gc-app-server-member-mm7n",
        "ansible_ssh_host": "xx.xx.xx.xx"
      }
    }
  }
}

Using with Ansible

In order to use the gce-inventory command with ansible, you must create an executable shell script somewhere within your project and pass that script to the ansible command line.

For example, create a new shell script and place it in the inventory directory under your project:

├<ansible-root>
| └─ inventory
|    └─ gce.sh

gce.sh's is simple; it forwards all parameters to the installed gce-inventory command:

inventory/gce.sh:

#!/bin/sh 
 
gce-inventory $@

Remember to make the script executable!

chmod +x inventory/gce.sh

After you've got the script ready, run ansible's setup module:

ansible --private-key=~/.ssh/google_compute_engine --become all -i inventory -m setup

For those of you not familiar with ansible's command line:

--private-key=~/.ssh/google_compute_engine refers to my ssh key authorized for the machines in my inventory. You'll probably have to modify that part of the command.

--become tells ansible to sudo when executing commands on the hosts.

all tells ansible which hosts to run the module on.

-i inventory tells ansible to use the inventory located in the inventory directory. If you have other static or dynamic inventories in the inventory directory, those will be run too! You may also refer to the script directly, such as -i inventory/gce.sh.

-m setup tells ansible to run the setup module. Ansible's setup module interrogates the host and constructs lots of useful host variables; it is a good module to use to see if things are working right.

NOTE: You also must supply required options when running this command.

Hostvars

The minimal options file generates an unremarkable list of hosts; chances are good you'll need additional hostvars to drive your Ansible playbooks and plays. You can specify additional hostvars by adding a hostvars section to your options file.

---
projectId: test
keyFileName: /home/myname/TEST-xxxxxxxxxx.json
 
hostvars:
  - status
  internalIP: /networkInterfaces/0/networkIP
  externalIP: /networkInterfaces/0/accessConfigs/0/natIP
  machine_type: /machineType
    captureRe: '[^/]+(?=/$|$)'
 

The hostvar options are a list of property descriptors. There are a few different ways of specifying property descriptors:

  • simple-descriptor: names a property to be copied from the source object to hostvars (status in the example)
  • pointer-descriptor: names a property and indicates the JSON-Pointer used to extract the value from the source object (internalIP and externalIP in the example)
  • transform-descriptor: same as either name-descriptor or pointer-descriptor but additionally indicates a transform used on the extracted value before assignment to hostvars

The output produced by these options might look something like this (pretty-printed and some items elided):

{
  "_meta": {
    "hostvars": {
      "gc-ctrl-server-02": {
        "zone": "us-central1-c",
        "name": "gc-ctrl-server-02",
        "status": "RUNNING",
        "internalIP": "xx.xx.xx.xx",
        "externalIP": "xx.xx.xx.xx",
        "machine_type": "n1-standard-2",
        "ansible_ssh_host": "xx.xx.xx.xx"
      },
      "...",
      "gc-hafs-server-01": {
        "zone": "us-central1-b",
        "name": "gc-hafs-server-01",
        "status": "TERMINATED",
        "internalIP": "xx.xx.xx.xx",
        "machine_type": "n1-standard-1",
        "ansible_ssh_host": "xx.xx.xx.xx"
      },
      "...",
      "gc-app-server-member-mm7n": {
        "zone": "us-central1-f",
        "name": "gc-app-server-member-mm7n",
        "status": "RUNNING",
        "internalIP": "xx.xx.xx.xx",
        "externalIP": "146.148.72.192",
        "machine_type": "n1-standard-4",
        "ansible_ssh_host": "146.148.72.192"
      },
      "gc-cd-master-00": {
        "zone": "us-central1-f",
        "name": "gc-cd-master-00",
        "status": "RUNNING",
        "internalIP": "xx.xx.xx.xx",
        "externalIP": "xx.xx.xx.xx",
        "machine_type": "n1-standard-2",
        "ansible_ssh_host": "xx.xx.xx.xx"
      }
    }
  }
}

Note that while we specified how status, internalIP, externalIP and machine_type were derived; zone, name and ansible_ssh_host were added automatically.

Groups

In Ansible, group membership can indicate the plays that get applied to a particular host. gce-inventory enables you to fully specify how groups are composed.

The groups element in the options file lists the properties of hostvars that indicate group membership:

---
projectId: test
keyFileName: /home/myname/TEST-xxxxxxxxxx.json
 
hostvars:
  - status
  internalIP: /networkInterfaces/0/networkIP
  externalIP: /networkInterfaces/0/accessConfigs/0/natIP
  machine_type: /machineType
    captureRe: '[^/]+(?=/$|$)'
 
groups:
  - zone
  - machine_type
  - status

The output produced by these options might look something like this (pretty-printed and some items elided):

{
  "_meta": {
    "hostvars": {
      "gc-ctrl-server-02": {
        "zone": "us-central1-c",
        "name": "gc-ctrl-server-02",
        "status": "RUNNING",
        "internalIP": "xx.xx.xx.xx",
        "externalIP": "xx.xx.xx.xx",
        "machine_type": "n1-standard-2",
        "ansible_ssh_host": "xx.xx.xx.xx"
      },
      "...",
      "gc-cd-master-00": {
        "zone": "us-central1-f",
        "name": "gc-cd-master-00",
        "status": "RUNNING",
        "internalIP": "xx.xx.xx.xx",
        "externalIP": "xx.xx.xx.xx",
        "machine_type": "n1-standard-2",
        "ansible_ssh_host": "xx.xx.xx.xx"
      }
    }
  },
  "us-central1-a": [
    "gc-hafs-server-00"
  ],
  "us-central1-b": [
    "gc-ctrl-server-01",
    "gc-hafs-server-01"
  ],
  "us-central1-c": [
    "gc-ctrl-server-02",
    "gc-hafs-server-02"
  ],
  "us-central1-f": [
    "gc-app-server-member-jorf",
    "gc-app-server-member-ml53",
    "gc-app-server-member-mm7n",
    "gc-cd-master-00",
    "gc-ctrl-server-00",
    "gc-hafs-server-03"
  ],
  "n1-standard-1": [
    "gc-hafs-server-00",
    "gc-hafs-server-01",
    "gc-hafs-server-02",
    "gc-hafs-server-03"
  ],
  "n1-standard-2": [
    "gc-cd-master-00",
    "gc-ctrl-server-00",
    "gc-ctrl-server-01",
    "gc-ctrl-server-02"
  ],
  "n1-standard-4": [
    "gc-app-server-member-jorf",
    "gc-app-server-member-ml53",
    "gc-app-server-member-mm7n"
  ],
  "RUNNING": [
    "gc-app-server-member-jorf",
    "gc-app-server-member-ml53",
    "gc-app-server-member-mm7n",
    "gc-cd-master-00",
    "gc-ctrl-server-00",
    "gc-ctrl-server-01",
    "gc-ctrl-server-02",
    "gc-hafs-server-00",
    "gc-hafs-server-02",
    "gc-hafs-server-03"
  ],
  "TERMINATED": [
    "gc-hafs-server-01"
  ]
}
 

Now that's more interesting output!

Normalizing Group Names

If you work with multiple cloud providers, you may want more control over group names. For instance the group names in the above output are GCE specific. There are a couple of ways you can control/transform the group names in the output:

  • Specify a transform when constructing the hostvars,
  • Specify a transform when materializing the groups.

Here is an example that transforms hostvars using a transform-descriptor:

---
projectId: test
keyFileName: /home/myname/TEST-xxxxxxxxxx.json
 
hostvars:
  status:
    transform: lowercase
 

Now the group names will reflect the lower case status (elided):

{
  "...": "...",
  "running": [
    "gc-app-server-member-jorf",
    "gc-app-server-member-ml53",
    "gc-app-server-member-mm7n",
    "gc-cd-master-00",
    "gc-ctrl-server-00",
    "gc-ctrl-server-01",
    "gc-ctrl-server-02",
    "gc-hafs-server-00",
    "gc-hafs-server-02",
    "gc-hafs-server-03"
  ],
  "terminated": [
    "gc-hafs-server-01"
  ]
}

We can go further and add some more meaningful info to the group name:

---
projectId: test
keyFileName: /home/myname/TEST-xxxxxxxxxx.json
 
hostvars:
  status:
    transform: lowercase
 
groups:
  status:
    prepend: 'status-'

Now the group names are pretty explicit about their purpose/meaning (elided):

{
  "...": "...",
  "status-running": [
    "gc-app-server-member-jorf",
    "gc-app-server-member-ml53",
    "gc-app-server-member-mm7n",
    "gc-cd-master-00",
    "gc-ctrl-server-00",
    "gc-ctrl-server-01",
    "gc-ctrl-server-02",
    "gc-hafs-server-00",
    "gc-hafs-server-02",
    "gc-hafs-server-03"
  ],
  "status-terminated": [
    "gc-hafs-server-01"
  ]
}

Group Vars

There are times when you'll need to set variables on a whole group of hosts. To accomplish this, use group_vars in your options file.

The following options file uses hostvars to get the host's tags, uses groups to group on tags, and adds group_vars to the tag-coreos group:

---
projectId: test
keyFileName: /home/myname/TEST-xxxxxxxxxx.json
 
hostvars:
  tags: /tags/items
 
groups:
  tags:
    prepend: 'tag-'
 
group_vars:
  tag-coreos:
    ansible_ssh_user: core
    ansible_python_interpreter: "PATH=/home/core/bin:$PATH python"
 

Provided we have our CoreOS boxes tagged with coreos, we now have a way to indicate some CoreOS specific stuff for ansible.

We use this particular strategy; you can find more info about using ansible with CoreOS on the CoreOS blog and in this handy github repo.

Debug Mode

Running gce-inventory in debug mode can help troubleshoot the options and the processing pipeline. It also enables you to see the raw JSON structure returned from the google API and can help you compose your JSON Pointers and transforms.

DEBUG=gce* gce-inventory --list

Also Useful

gce-inventory doesn't pretty print the JSON that it outputs. You should install a command line json tool:

npm install -g json

Then we simply pipe output to get it pretty printed:

DEBUG=gce* gce-inventory --list | json

License

This software is covered by the accompanying MIT style license.

Package Sidebar

Install

npm i gce-inventory

Weekly Downloads

1

Version

0.2.0

License

MIT

Last publish

Collaborators

  • cerebralkungfu