boundary_meter 7.95 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Ansible module to add boundary meters.

(c) 2013, curtis <curtis@serverascode.com>

This file is part of Ansible

Ansible is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Ansible is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
"""

import json
import datetime
import base64
28
import os
29 30 31 32 33 34 35 36 37 38 39

DOCUMENTATION = '''

module: boundary_meter
short_description: Manage boundary meters
description:
    - This module manages boundary meters
version_added: "1.3"
author: curtis@serverascode.com
requirements:
    - Boundary API access
40 41
    - bprobe is required to send data, but not to register a meter
    - Python urllib2
42 43 44 45 46 47 48 49 50 51
options:
    name:
        description:
            - meter name
        required: true
    state:
        description:
            - Whether to create or remove the client from boundary
        required: false
        default: true
52
        choices: ["present", "absent"]
53 54 55 56 57 58 59 60
    apiid:
        description:
            - Organizations boundary API ID
        required: true
    apikey:
        description:
            - Organizations boundary API KEY
        required: true
61 62 63 64 65 66 67 68
    validate_certs:
        description:
            - If C(no), SSL certificates will not be validated. This should only be used
              on personally controlled sites using self-signed certificates.
        required: false
        default: 'yes'
        choices: ['yes', 'no']
        version_added: 1.5.1
69 70

notes:
71
    - This module does not yet support boundary tags.
72 73 74 75 76

'''

EXAMPLES='''
- name: Create meter
77
  boundary_meter: apiid=AAAAAA api_key=BBBBBB state=present name={{ inventory_hostname }}"
78 79

- name: Delete meter
80
  boundary_meter: apiid=AAAAAA api_key=BBBBBB state=absent name={{ inventory_hostname }}"
81 82 83

'''

84 85
api_host = "api.boundary.com"
config_directory = "/etc/bprobe"
86

87 88 89
# "resource" like thing or apikey?
def auth_encode(apikey):
    auth = base64.standard_b64encode(apikey)
90 91 92
    auth.replace("\n", "")
    return auth
    
93
def build_url(name, apiid, action, meter_id=None, cert_type=None):
94
    if action == "create":
95
        return 'https://%s/%s/meters' % (api_host, apiid)
96
    elif action == "search":
97
        return "https://%s/%s/meters?name=%s" % (api_host, apiid, name)
98
    elif action == "certificates":
99
        return "https://%s/%s/meters/%s/%s.pem" % (api_host, apiid, meter_id, cert_type)
100
    elif action == "tags":
101
        return  "https://%s/%s/meters/%s/tags" % (api_host, apiid, meter_id)
102
    elif action == "delete":
103
        return  "https://%s/%s/meters/%s" % (api_host, apiid, meter_id)
104

105
def http_request(module, name, apiid, apikey, action, data=None, meter_id=None, cert_type=None):
106
    
107 108
    if meter_id is None:
        url = build_url(name, apiid, action)
109
    else:
110 111
        if cert_type is None:
            url = build_url(name, apiid, action, meter_id)
112
        else:
113
            url = build_url(name, apiid, action, meter_id, cert_type)
114

115 116 117 118 119
    headers = dict()
    headers["Authorization"] = "Basic %s" % auth_encode(apikey)
    headers["Content-Type"] = "application/json"

    return fetch_url(module, url, data=data, headers=headers)
120

121
def create_meter(module, name, apiid, apikey):
122

123
    meters = search_meter(module, name, apiid, apikey)
124 125 126

    if len(meters) > 0:
        # If the meter already exists, do nothing
127
        module.exit_json(status="Meter " + name + " already exists",changed=False)
128 129
    else:
        # If it doesn't exist, create it
130
        body = '{"name":"' + name + '"}'
131
        response, info = http_request(module, name, apiid, apikey, data=body, action="create")
132

133
        if info['status'] != 200:
134 135
            module.fail_json(msg="Failed to connect to api host to create meter")

136 137 138 139 140 141 142
        # If the config directory doesn't exist, create it
        if not os.path.exists(config_directory):
            try:
                os.makedirs(config_directory)
            except:
                module.fail_json("Could not create " + config_directory)

143 144 145 146 147 148

        # Download both cert files from the api host
        types = ['key', 'cert']
        for cert_type in types:
            try:
                # If we can't open the file it's not there, so we should download it
149
                cert_file = open('%s/%s.pem' % (config_directory,cert_type))
150 151
            except IOError:
                # Now download the file...
152
                rc = download_request(module, name, apiid, apikey, cert_type)
153 154 155
                if rc == False:
                    module.fail_json("Download request for " + cert_type + ".pem failed")

156
        return 0, "Meter " + name + " created"
157

158
def search_meter(module, name, apiid, apikey):
159

160
    response, info = http_request(module, name, apiid, apikey, action="search")
161

162
    if info['status'] != 200:
163
        module.fail_json("Failed to connect to api host to search for meter")
164 165

    # Return meters
166
    return json.loads(response.read())
167

168
def get_meter_id(module, name, apiid, apikey):
169
    # In order to delete the meter we need its id
170
    meters = search_meter(module, name, apiid, apikey)
171 172 173 174 175 176

    if len(meters) > 0:
        return meters[0]['id']
    else:
        return None

177
def delete_meter(module, name, apiid, apikey):
178

179
    meter_id = get_meter_id(module, name, apiid, apikey)
180 181 182 183

    if meter_id is None:
        return 1, "Meter does not exist, so can't delete it"
    else:
184 185 186
        response, info = http_request(module, name, apiid, apikey, action, meter_id)
        if info['status'] != 200:
            module.fail_json("Failed to delete meter")
187 188 189 190 191

        # Each new meter gets a new key.pem and ca.pem file, so they should be deleted
        types = ['cert', 'key']
        for cert_type in types:
            try:
192
                cert_file = '%s/%s.pem' % (config_directory,cert_type)
193
                os.remove(cert_file)
194
            except OSError, e:  
195
                module.fail_json("Failed to remove " + cert_type + ".pem file")
196

197
    return 0, "Meter " + name + " deleted"
198

199
def download_request(module, name, apiid, apikey, cert_type):
200

201
    meter_id = get_meter_id(module, name, apiid, apikey)
202 203 204

    if meter_id is not None:
        action = "certificates"
205 206
        response, info = http_request(module, name, apiid, apikey, action, meter_id, cert_type)
        if info['status'] != 200:
207
            module.fail_json("Failed to connect to api host to download certificate")
208 209 210

        if result:
            try:
211
                cert_file_path = '%s/%s.pem' % (config_directory,cert_type)
212
                body = response.read()
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
                cert_file = open(cert_file_path, 'w')
                cert_file.write(body)
                cert_file.close
                os.chmod(cert_file_path, 0o600)
            except: 
                module.fail_json("Could not write to certificate file")

        return True
    else:
        module.fail_json("Could not get meter id")

def main():

    module = AnsibleModule(
        argument_spec=dict(
228
        state=dict(required=True, choices=['present', 'absent']),
229 230 231
        name=dict(required=False),
        apikey=dict(required=True),
        apiid=dict(required=True),
232
        validate_certs = dict(default='yes', type='bool'),
233 234 235 236
        )
    )

    state = module.params['state']
237
    name= module.params['name']
238 239
    apikey = module.params['api_key']
    apiid = module.params['api_id']
240 241

    if state == "present":
242
        (rc, result) = create_meter(module, name, apiid, apikey)
243

244 245
    if state == "absent":
        (rc, result) = delete_meter(module, name, apiid, apikey)
246 247 248 249 250 251

    if rc != 0:
        module.fail_json(msg=result)

    module.exit_json(status=result,changed=True)

252 253
# import module snippets
from ansible.module_utils.basic import *
254
from ansible.module_utils.urls import *
255
main()
Michael DeHaan committed
256