Created
October 14, 2016 09:31
-
-
Save jriguera/4c52c57fe43ed04704d3a4e34d6d5ec8 to your computer and use it in GitHub Desktop.
Ansibe cf organizations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
""" | |
Program to create an ansible inventory from all the deployments, jobs and | |
instances managed by a BOSH Director. | |
# This program 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. | |
# | |
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. | |
""" | |
# Python 2 and 3 compatibility | |
from __future__ import unicode_literals, print_function | |
import sys | |
import json | |
import base64 | |
import requests | |
from requests.packages.urllib3.exceptions import InsecureRequestWarning | |
requests.packages.urllib3.disable_warnings(InsecureRequestWarning) | |
from ansible.module_utils.basic import AnsibleModule | |
__program__ = "cf_org" | |
__version__ = "0.1.0" | |
__author__ = "Jose Riguera" | |
__year__ = "2016" | |
__email__ = "<[email protected]>" | |
__license__ = "MIT" | |
DOCUMENTATION = ''' | |
--- | |
module: cf_org | |
short_description: Manage Cloud Foundry Orgs | |
description: | |
- Manage Cloud Foundry Orgs | |
author: "Jose Riguera, [email protected]" | |
options: | |
state: | |
description: | |
- Desired state of the org | |
required: false | |
default: present | |
choices: [present, absent] | |
name: | |
description: | |
- Name of the org | |
required: true | |
default: null | |
aliases: [id] | |
admin_user: | |
description: | |
- Administrator username/email | |
required: true | |
default: null | |
admin_password: | |
description: | |
- Administrator password | |
required: true | |
default: null | |
api_url: | |
description: | |
- URL of api end point | |
required: true | |
quota: | |
description: | |
- Name of quota to associate with the org | |
required: false | |
default: default | |
validate_certs: | |
description: | |
- Validate SSL certs. Validation will fail with self-signed certificates. | |
required: false | |
default: false | |
force: | |
description: | |
- Force deletion of system org and recursive entities in an org | |
required: false | |
default: false | |
''' | |
EXAMPLES = ''' | |
# Create org with default quota | |
- cf_org: state=present name=test admin_user=admin admin_password=abc123 api_url=http://cf.example.com | |
# Create org specifying quota/change quota of existing org | |
- cf_org: state=present name=test admin_user=admin admin_password=abc123 quota=runaway api_url=http://cf.example.com | |
# Delete org | |
- cf_org: state=absent name=test admin_user=admin admin_password=abc123 api_url=http://cf.example.com | |
''' | |
RETURN = ''' | |
dest: | |
description: destination file/path | |
returned: success | |
type: string | |
sample: "/path/to/file.txt" | |
src: | |
description: source file used for the copy on the target machine | |
returned: changed | |
type: string | |
sample: "/home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source" | |
md5sum: | |
description: md5 checksum of the file after running copy | |
returned: when supported | |
type: string | |
sample: "2a5aeecc61dc98c4d780b14b330e3282" | |
... | |
''' | |
class CF(object): | |
user_agent = "ansible-cloudfoundry" | |
info_url = '/v2/info' | |
auth_token_url = '/oauth/token' | |
quotas_url = '/v2/quota_definitions' | |
organizations_url = '/v2/organizations' | |
organization_space_url = '/v2/organizations/%s/spaces' | |
organization_summary_url = '/v2/organizations/%s/summary' | |
organization_services_url = '/v2/organizations/%s/services' | |
organization_space_quota_definitions_url = '/v2/organizations/%s/space_quota_definitions' | |
apps_space_url = '/v2/spaces/%s/apps' | |
spaces_summary_url = '/v2/spaces/%s/summary' | |
app_service_bindings = '/v2/apps/%s/service_bindings' | |
def __init__(self, api_url, username='', password='', ca_cert=None): | |
self.session = requests.Session() | |
self.session.headers.update({ | |
"Accept": "application/json", | |
"Content-Type": "application/json", | |
"User-Agent": self.user_agent} | |
) | |
self.session.verify = True if ca_cert else False | |
self.session.cert = ca_cert if ca_cert else None | |
self.api_url = api_url | |
self.username = str(username) | |
self.password = str(password) | |
def info(self): | |
url = self.api_url + self.info_url | |
r = self.session.get(url) | |
return r.json() | |
def login(self, username='', password=''): | |
auth = None | |
if username: | |
self.username = str(username) | |
if password: | |
self.password = str(password) | |
if self.username: | |
url = self.info()['token_endpoint'] + self.auth_token_url | |
headers = { | |
'Authorization': "Basic %s" % base64.b64encode("%s:%s" % ('cf', '')), | |
'Content-Type': "application/x-www-form-urlencoded" | |
} | |
params = { | |
'username': self.username, | |
'password': self.password, | |
'client_id': 'cf', | |
'grant_type': 'password', | |
'response_type': 'token' | |
} | |
resp = self.session.post(url, params=params, headers=headers) | |
if resp.status_code == requests.codes.ok: | |
auth = resp.json() | |
self.session.headers.update({ | |
'Authorization': ("%s %s" % (auth['token_type'], auth['access_token'])) | |
}) | |
return auth | |
def request(self, method, url, params=None, http_headers=None, data=None): | |
if http_headers: | |
headers = dict(self.session.headers) | |
headers.update(http_headers) | |
else: | |
headers = self.session.headers | |
req = requests.Request(method, url, data=data, headers=headers, params=params).prepare() | |
res = self.session.send(req) | |
# len(res.content): | |
try: | |
response = res.json() | |
if response: | |
if ('error_code' in response and ( | |
response['error_code'] == 'CF-InvalidAuthToken' or | |
response['error_code'] == 'CF-NotAuthenticated')): | |
if self.login(): | |
res = self.session.send(req) | |
response = res.json() | |
except: | |
response = {} | |
return response, res.status_code | |
def get_org(self, name): | |
url = self.api_url + self.organizations_url | |
params = {'q': "name:%s" % str(name)} | |
response, rcode = self.request('GET', url, params) | |
num = int(response['total_results']) | |
if num == 0 or rcode != 200: | |
return None | |
else: | |
return response['resources'][0] | |
def delete_org(self, guid, async=False, recursive=False): | |
url = self.api_url + self.organizations_url + '/' + guid | |
params = { | |
'async': str(async).lower(), | |
'recursive': str(recursive).lower() | |
} | |
response, rcode = self.request('DELETE', url, params) | |
if rcode != 204: | |
return False | |
return True | |
def create_org(self, name, quota_guid=None): | |
url = self.api_url + self.organizations_url | |
data = {'name': name } | |
if quota_guid: | |
data['quota_definition_guid'] = str(quota_guid) | |
response, rcode = self.request('POST', url, None, None, json.dumps(data)) | |
if rcode != 201: | |
return None | |
return response | |
def update_org(self, guid, name, quota_guid=None): | |
url = self.api_url + self.organizations_url + '/' + guid | |
data = {'name': name } | |
if quota_guid: | |
data['quota_definition_guid'] = str(quota_guid) | |
response, rcode = self.request('PUT', url, None, None, json.dumps(data)) | |
if rcode != 201: | |
return None | |
return response | |
def get_quota(self, name): | |
url = self.api_url + self.quotas_url | |
params = {'q': "name:%s" % str(name)} | |
response, rcode = self.request('GET', url, params) | |
num = int(response['total_results']) | |
if num == 0 or rcode != 200: | |
return None | |
else: | |
return response['resources'][0] | |
def delete_quota(self, guid, async=False): | |
url = self.api_url + self.quotas_url + '/' + guid | |
params = { | |
'async': str(async).lower() | |
} | |
response, rcode = self.request('DELETE', url, params) | |
if rcode != 204: | |
return False | |
return True | |
def create_quota(self, name, non_basic_services_allowed, total_services, | |
total_routes, memory_limit, instance_memory_limit, | |
total_service_keys=-1, total_reserved_route_ports=-1, | |
total_private_domains=-1, app_instance_limit=-1): | |
url = self.api_url + self.quotas_url | |
data = { | |
'name': str(name), | |
"non_basic_services_allowed": str(non_basic_services_allowed).lower(), | |
"total_services": str(total_services), | |
"total_routes": str(total_routes), | |
"memory_limit": str(memory_limit), | |
"instance_memory_limit": str(instance_memory_limit), | |
"total_service_keys": str(total_service_keys), | |
"total_reserved_route_ports": str(total_reserved_route_ports), | |
"total_private_domains": str(total_private_domains), | |
"app_instance_limit": str(app_instance_limit) | |
} | |
response, rcode = self.request('POST', url, None, None, json.dumps(data)) | |
if rcode != 201: | |
return None | |
return response | |
def update_quota(self, guid, name, non_basic_services_allowed, total_services, | |
total_routes, memory_limit, instance_memory_limit, | |
total_service_keys=-1, total_reserved_route_ports=-1, | |
total_private_domains=-1, app_instance_limit=-1): | |
url = self.api_url + self.quotas_url + '/' + guid | |
data = { | |
'name': str(name), | |
"non_basic_services_allowed": str(non_basic_services_allowed).lower(), | |
"total_services": str(total_services), | |
"total_routes": str(total_routes), | |
"memory_limit": str(memory_limit), | |
"instance_memory_limit": str(instance_memory_limit), | |
"total_service_keys": str(total_service_keys), | |
"total_reserved_route_ports": str(total_reserved_route_ports), | |
"total_private_domains": str(total_private_domains), | |
"app_instance_limit": str(app_instance_limit) | |
} | |
response, rcode = self.request('PUT', url, None, None, json.dumps(data)) | |
if rcode != 201: | |
return None | |
return response | |
class CF_Org(): | |
def __init__(self, module): | |
self.module = module | |
admin_user = self.module.params['admin_user'] | |
admin_password = self.module.params['admin_password'] | |
api_url = self.module.params['api_url'] | |
self.cf = CF(api_url, admin_user, admin_password) | |
self.name = self.module.params['name'] | |
self.cf.login() | |
def run(self): | |
state = self.module.params['state'] | |
if state == 'present': | |
result = self.present() | |
elif state == 'absent': | |
result = self.absent() | |
else: | |
self.module.fail_json(msg='Invalid state: %s' % state) | |
self.module.exit_json(**result) | |
def absent(self, async=False, forbidden=['pivotal']): | |
force = self.module.params['force'] | |
if self.name in forbidden and not force: | |
self.module.fail_json(msg="Cannot delete a system org") | |
changed = False | |
org = self.cf.get_org(self.name) | |
if org: | |
org_guid = org['metadata']['guid'] | |
changed = True | |
if not self.module.check_mode: | |
if not self.cf.delete_org(org_guid, async, force): | |
self.module.fail_json(msg='Cannot delete org') | |
result = { | |
'changed': changed, | |
'msg': "CF org %s deleted" % self.name | |
} | |
return result | |
def present(self): | |
changed = False | |
quota_name = self.module.params['quota'] | |
quota = self.cf.get_quota(quota_name) | |
if not quota: | |
self.module.fail_json(msg="Quota %s not found" % quota_name) | |
quota_guid = quota['metadata']['guid'] | |
org = self.cf.get_org(self.name) | |
if not org: | |
changed = True | |
if not self.module.check_mode: | |
org = self.cf.create_org(self.name, quota_guid) | |
if not org: | |
self.module.fail_json(msg='Cannot create org') | |
msg = "CF org %s created" % self.name | |
else: | |
guid = org['metadata']['guid'] | |
if org['entity']['quota_definition_guid'] != quota_guid: | |
changed = True | |
if not self.module.check_mode: | |
org = self.cf.update_org(guid, self.name, quota_guid) | |
if not org: | |
self.module.fail_json(msg='Cannot update org') | |
msg = "CF org %s updated" % self.name | |
else: | |
msg = "CF org %s not updated" % self.name | |
result = { | |
'changed': changed, | |
'msg': msg, | |
'data': org | |
} | |
return result | |
def main(): | |
module = AnsibleModule( | |
argument_spec = dict( | |
state = dict(default='present', type='str', choices=['present', 'absent']), | |
name = dict(required=True, type='str', aliases=['id']), | |
admin_user = dict(required=True, type='str'), | |
admin_password = dict(required=True, type='str', no_log=True), | |
api_url = dict(required=True, type='str'), | |
quota = dict(default='default', type='str'), | |
validate_certs = dict(default=False, type='bool'), | |
force = dict(default=False, type='bool'), | |
), | |
supports_check_mode = True, | |
) | |
cf = CF_Org(module) | |
cf.run() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment