Skip to content

Commit

Permalink
Merge pull request #1 from mvdkleijn/develop
Browse files Browse the repository at this point in the history
Initial version
  • Loading branch information
mvdkleijn authored May 11, 2017
2 parents 11686f9 + 1eb8240 commit 2433c39
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CHANGELOG.md merge=union
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 2.0.0

Reworked some of the code, added extra features and made compatibility changes
so it works for MAAS API 2.0.

Dropped support for MAAS API 1.0 as MAAS itself no longer supports it.

# 0.1.0

Original version written around August 28th, 2015 for MAAS API version 1.0 by
Paul Stevens <mailto:[email protected]>
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
# ansible-maas
Ansible Dynamic Inventory Script for Ubuntu MAAS 2.0 API

An Ansible Dynamic Inventory Script for the Ubuntu MAAS 2.0 API.

Primarily it will be called by Ansible itself with the `--list` or `--host`
arguments, but additional arguments were implemented for administrators to take
advantage of if desired.

## Usage

- Replace Ansible's hosts file in the correct directory, e.g. `/etc/ansible/hosts`
- Set environment variables `MAAS_API_URL` and `MAAS_API_KEY`
- Enjoy!
189 changes: 189 additions & 0 deletions ansible-maas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#!/usr/bin/env python

"""
Ansible Dynamic Inventory Script for Ubuntu MAAS.
This script fetches hosts data for Ansible from Ubuntu MAAS, using Tags to
identify groups and roles. It is expected that the script will be copied to
Tower within the new Inventory Scripts dialog offered within the interface,
where it will be passed the `--list` argument to invoke the dynamic inventory
process.
It is also possible to run as a standalone script or a replacement for the
Ansible `hosts` file.
See https://docs.ubuntu.com/maas/2.1/en/api for API details
:copyright: Internet Solutions (Pty) Ltd, 2015
:author: Paul Stevens <mailto:[email protected]>
:copyright: Martijn van der kleijn, 2017
:author: Martijn van der Kleijn <mailto:[email protected]>
:license: Released under the Apache 2.0 License. See LICENSE for details.
:version: 2.0.0
:date: 11 May 2017
"""

import argparse
import json
import os
import re
import sys
import uuid
import pickle

import oauth.oauth as oauth
import requests

class Inventory:
"""Provide several convenience methods to retrieve information from MAAS API."""

def __init__(self):
"""Check for precense of mandatory environment variables and route commands."""
self.supported = '2.0'
self.apikeydocs = 'https://docs.ubuntu.com/maas/2.1/en/manage-cli#log-in-(required)'

self.maas = os.environ.get("MAAS_API_URL", None)
if not self.maas:
sys.exit("MAAS_API_URL environment variable not found. Set this to http<s>://<HOSTNAME or IP>/MAAS/api/{}".format(self.supported))
self.token = os.environ.get("MAAS_API_KEY", None)
if not self.token:
sys.exit("MAAS_API_KEY environment variable not found. See {} for getting a MAAS API KEY".format(self.apikeydocs))
self.args = None

# Parse command line arguments
self.cli_handler()

if self.args.list:
print json.dumps(self.inventory(), sort_keys=True, indent=2)
elif self.args.host:
print json.dumps(self.host(), sort_keys=True, indent=2)
elif self.args.nodes:
print json.dumps(self.nodes(), sort_keys=True, indent=2)
elif self.args.tags:
print json.dumps(self.tags(), sort_keys=True, indent=2)
elif self.args.tag:
print json.dumps(self.tag(), sort_keys=True, indent=2)
elif self.args.supported:
print self.supported()
else:
sys.exit(1)

def supported(self):
"""Display MAAS API version supported by this tool."""
return self.supported

def auth(self):
"""Split the user's API key from MAAS into its component parts (Maas UI > Account > MAAS Keys)."""
(consumer_key, key, secret) = self.token.split(':')
# Format an OAuth header
resource_token_string = "oauth_token_secret={}&oauth_token={}".format(secret, key)
resource_token = oauth.OAuthToken.from_string(resource_token_string)
consumer_token = oauth.OAuthConsumer(consumer_key, "")
oauth_request = oauth.OAuthRequest.from_consumer_and_token(
consumer_token, token=resource_token, http_url=self.maas,
parameters={'auth_nonce': uuid.uuid4().get_hex()})
oauth_request.sign_request(
oauth.OAuthSignatureMethod_PLAINTEXT(), consumer_token, resource_token)
headers = oauth_request.to_header()
headers['Accept'] = 'application/json'
return headers

def host(self):
"""Return data on a single host/node."""
host = {}
headers = self.auth()

url = "{}/nodes/{}/".format(self.maas.rstrip(), self.args.host)
request = requests.get(url, headers=headers)
return json.loads(request.text)

def tags(self):
"""Fetch a simple list of available tags from MAAS."""
headers = self.auth()

url = "{}/tags/".format(self.maas.rstrip())
request = requests.get(url, headers=headers)
response = json.loads(request.text)
tag_list = [item["name"] for item in response]
return tag_list

def tag(self):
"""Fetch detailed information on a particular tag from MAAS."""
headers = self.auth()

url = "{}/tags/{}/?op=machines".format(self.maas.rstrip(), self.args.tag)
request = requests.get(url, headers=headers)
return json.loads(request.text)

def inventory(self):
"""Look up hosts by tag(s) and return a dict that Ansible will understand as an inventory."""
tags = self.tags()
ansible = {}

for tag in tags:
headers = self.auth()
url = "{}/tags/{}/?op=machines".format(self.maas.rstrip(), tag)
request = requests.get(url, headers=headers)
response = json.loads(request.text)
group_name = tag
hosts = []
for server in response:
hosts.append(server['fqdn'])
ansible[group_name] = {
"hosts": hosts,
"vars": {}
}
# PS 2015-09-03: Create metadata block for Ansible's Dynamic Inventory
# The below code gets a dump of ALL nodes in MAAS and then builds out a _meta JSON attribute.
# node_dump = self.nodes()
# nodes = {
# '_meta': {
# 'hostvars': {}
# }
# }
#
# for node in node_dump:
# if not node['tag_names']:
# pass
# else:
# nodes['_meta']['hostvars'][node['hostname']] = {
# 'mac_address': node['macaddress_set'][0]['mac_address'],
# 'system_id': node['system_id'],
# 'power_type': node['power_type'],
# 'os': node['osystem'],
# 'os_release': node['distro_series']
# }

# Need to merge ansible and nodes dict()s as a shallow copy, or Ansible shits itself and throws an error
result = ansible.copy()
# result.update(nodes)
return result

def nodes(self):
"""Return a list of nodes from the MAAS API."""
headers = self.auth()
url = "%s/nodes/" % self.maas.rstrip()
request = requests.get(url, headers=headers)
response = json.loads(request.text)
return response

def cli_handler(self):
"""Manage command line options and arguments."""
parser = argparse.ArgumentParser(description='Dynamically produce an Ansible inventory from Ubuntu MAAS.', add_help=False)
parser.add_argument('-l', '--list', action='store_true', help='List instances by tag. (default)')
parser.add_argument('-h', '--host', action='store', help='Get variables relating to a specific instance.')
parser.add_argument('-n', '--nodes', action='store_true', help='List all nodes registered under MAAS.')
parser.add_argument('-t', '--tags', action='store_true', help='List all tags registered under MAAS.')
parser.add_argument('--tag', action='store', help='Get details for a specific tag registered under MAAS.')
parser.add_argument('-s', '--supported', action='store_true', help='List which MAAS API version are supported.')
parser.add_argument('--help', action='help', help='Show this help message and exit.')

# Be kind and print help when no arguments given.
if len(sys.argv)==1:
parser.print_help()
sys.exit(1)

self.args = parser.parse_args()

if __name__ == "__main__":
Inventory()

0 comments on commit 2433c39

Please sign in to comment.