aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2016-02-21 21:32:00 +0100
committerGuido Günther <agx@sigxcpu.org>2016-02-24 21:41:04 +0100
commitb84f40c7da7db68a97285023df2253d1e36d0771 (patch)
tree3e09e8cba26af2137dad990cc1aa6410acf44554
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--README.md15
-rw-r--r--example.json21
-rw-r--r--example.yml26
-rw-r--r--foremanhost.py250
5 files changed, 313 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b25c15b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*~
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9ba4477
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+A simple module to create hosts in [the Foreman][] via [Ansible][]
+
+Usage:
+
+ ansible-playbook --module-path . -c local -i localhost, example.yml
+
+The file example.yml has an example. You can augment this with a
+custom json template for parameters that aren't configurable (yet).
+
+Simple example:
+
+ - foremanhost: name=foo hostgroup=bar state=present api_user=foreman api_password=changeme api_url: https://127.0.0.1
+
+[the Foreman]: http://theforeman.org
+[Ansible]: http://ansible.com \ No newline at end of file
diff --git a/example.json b/example.json
new file mode 100644
index 0000000..abca893
--- /dev/null
+++ b/example.json
@@ -0,0 +1,21 @@
+{
+ "host": {
+ "provision_method": "image",
+ "build": true,
+ "enabled": true,
+ "managed": true,
+ "subnet_id": 1,
+ "location_id": 1,
+ "compute_resource_id": 1,
+ "image_id": 1,
+ "compute_attributes": {
+ "start": "1"
+ }
+ },
+ "interfaces": {
+ "0": {
+ "primary": true,
+ "provision": true
+ }
+ }
+}
diff --git a/example.yml b/example.yml
new file mode 100644
index 0000000..7f7e36e
--- /dev/null
+++ b/example.yml
@@ -0,0 +1,26 @@
+---
+- hosts: localhost
+ tasks:
+ - name: Create a host in Foreman
+ foremanhost:
+ name: foobar
+ hostgroup: Medium/jenkins/blue
+ state: present
+ json: "{{ lookup('template', 'example.json') }}"
+ api_user: foreman
+ api_password: changeme
+ api_url: https://127.0.0.1
+ ssl_verify: False
+ register: result
+
+ - debug:
+ msg: Got "{{ result }}"
+
+ - name: Delete a host in Foreman
+ foremanhost:
+ name: foobar.example.com
+ state: absent
+ api_user: foreman
+ api_password: changeme
+ api_url: https://127.0.0.1
+ ssl_verify: False
diff --git a/foremanhost.py b/foremanhost.py
new file mode 100644
index 0000000..e492b8a
--- /dev/null
+++ b/foremanhost.py
@@ -0,0 +1,250 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Foreman management features
+
+Copyright 2016 Guido Günther <agx@sigxcpu.org>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import os
+import copy
+
+DOCUMENTATION = '''
+---
+module: foremanhost
+short_description: Manages machines in the foreman
+description:
+ - Manages virtual machines in the I(Foreman).
+options:
+ name:
+ description:
+ - name of the machine being managed.
+ required: true
+ hostgroup:
+ description:
+ - put the machine to the given hostgroup
+ required: false
+ default: null
+ api_url:
+ description:
+ - foreman connection url
+ required: true
+ api_user:
+ description:
+ - foreman connection user
+ required: true
+ api_password:
+ description:
+ - foreman connection password (can also be passwed via
+ ANSIBLE_FOREMAN_PW)
+ required: false
+ state:
+ description:
+ - state of the machine (e.g.'present')
+ required: true
+ ssl_verify:
+ description:
+ - Wether to verify SSL certs of the Foreman API
+ required: false
+ json:
+ description:
+ - additional JSON used to define the machine
+ required: false
+ default: {}
+author:
+ - "Guido Günther"
+'''
+
+EXAMPLES = '''
+# a playbook task line:
+- foreman: name=foobar state=present
+
+# a playbook example of defining and launching machine in the Foreman
+tasks:
+ - foremanhost:
+ name: foobar
+ state: present
+ json: "{{ lookup('template', 'example.json') }}"
+ api_user: foreman
+ api_password: admin
+ api_url: http://localhost:3000/
+'''
+
+try:
+ import requests
+except ImportError:
+ HAS_REQUESTE = False
+else:
+ from requests.auth import HTTPBasicAuth
+ HAS_REQUESTS = True
+
+FOREMAN_FAILED = 1
+FOREMAN_SUCCESS = 0
+
+
+def merge_json(name, hostgroup_id, customjson):
+ if customjson:
+ customdata = json.loads(customjson)
+ ret = copy.deepcopy(customdata)
+ if 'host' not in ret:
+ ret['host'] = {}
+ ret['host']['name'] = name
+ if hostgroup_id is not None:
+ ret['host']['hostgroup_id'] = hostgroup_id
+ return ret
+
+
+# Post Data to Foreman
+def do_post(url, data, params):
+ data = None if data is None else json.dumps(data)
+ ret = requests.post(url,
+ data=data,
+ **params)
+ ret.raise_for_status()
+ return dict(status=ret.status_code, text=ret.text)
+
+
+def do_get(url, params):
+ ret = requests.get(url, **params)
+ ret.raise_for_status()
+ return dict(status=ret.status_code, text=ret.text)
+
+
+def do_delete(url, data, params):
+
+ ret = requests.delete(url, **params)
+ ret.raise_for_status()
+ return dict(status=ret.status_code, text=ret.text)
+
+
+def is_exists(e):
+ """
+ Check if the error returned indicates an already existing entity
+ """
+ if e.response.status_code != 422:
+ return False
+ err = json.loads(e.response.text)
+ try:
+ err_msg = err["error"]["errors"]
+ # Be careful to avoid IndexError so we can rethrow
+ # requests exceptoin
+ if ("name" in err_msg("name") and
+ err_msg["name"] == [u'has already been taken']):
+ return True
+ except IndexError:
+ return False
+ return False
+
+
+def is_absent(e):
+ """
+ Check if the error returned indicates an missing entity
+ """
+ if e.response.status_code == 404:
+ return True
+ return False
+
+
+def item_to_id(base_url, field, name, params):
+ url = base_url + "?search=%s=\"%s\"" % (field, name)
+ ret = do_get(url, params)
+ results = json.loads(ret['text'])['results']
+ if results == []:
+ return None
+ if len(results) > 1:
+ raise ValueError("Found more than item for '%s'" % name)
+ return "%s" % results[0]['id']
+
+
+def core(module):
+ name = module.params.get('name', None)
+ hostgroup = module.params.get('hostgroup', None)
+ state = module.params.get('state', 'present')
+ customjson = module.params.get('json', None)
+ api_url = module.params.get('api_url', None)
+ api_user = module.params.get('api_user', None)
+ api_pw = module.params.get('api_password', os.getenv("ANSIBLE_FOREMAN_PW"))
+ ssl_verify = module.params.get('ssl_verify', True)
+ ret = {}
+ hostgroup_id = None
+
+ host_url = "%s/api/v2/hosts" % api_url
+ params = {'headers': {'Content-Type': 'application/json'},
+ 'auth': HTTPBasicAuth(api_user, api_pw),
+ 'verify': ssl_verify,
+ }
+
+ if state == 'present':
+ if hostgroup:
+ hostgroup_url = "%s/api/v2/hostgroups" % api_url
+ hostgroup_id = item_to_id(hostgroup_url, 'title', hostgroup, params)
+ if not hostgroup_id:
+ raise ValueError("Hostgroup '%s' not found for '%s'" % (hostgroup, name))
+ fulljson = merge_json(name, hostgroup_id, customjson)
+ try:
+ ret = do_post(host_url, fulljson, params)
+ ret['changed'] = True
+ except requests.exceptions.HTTPError as e:
+ if is_exists(e):
+ ret['changed'] = False
+ else:
+ raise
+ elif state == 'absent':
+ host_id = item_to_id(host_url, 'name', name, params)
+ if host_id is None:
+ ret['changed'] = False
+ return FOREMAN_SUCCESS, ret
+ try:
+ url = os.path.join(host_url, host_id)
+ ret = do_delete(url, None, params)
+ ret['changed'] = True
+ except requests.exceptions.HTTPError as e:
+ if is_absent(e):
+ ret['changed'] = False
+ else:
+ raise
+ else:
+ raise ValueError("Unknown state %s" % state)
+
+ return FOREMAN_SUCCESS, ret
+
+
+def main():
+ module = AnsibleModule(argument_spec=dict(
+ name=dict(required=True),
+ hostgroup=dict(),
+ json=dict(),
+ state=dict(default='present', choices=['present', 'absent']),
+ api_url=dict(required=True),
+ api_user=dict(required=True),
+ api_password=dict(),
+ ssl_verify=dict(),
+ ))
+
+ if not HAS_REQUESTS:
+ module.fail_json(
+ msg='The `requests` module is not importable. Check the requirements.'
+ )
+
+ rc = FOREMAN_FAILED
+ try:
+ rc, result = core(module)
+ except Exception as e:
+ module.fail_json(msg=str(e))
+
+ if rc != 0: # something went wrong emit the msg
+ module.fail_json(rc=rc, msg=result)
+ else:
+ module.exit_json(**result)
+
+
+# import module snippets
+from ansible.module_utils.basic import *
+main()