#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Daniel Korn # (c) 2017, Yaacov Zamir # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = { 'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community' } DOCUMENTATION = ''' module: manageiq_provider short_description: Management of provider in ManageIQ. extends_documentation_fragment: manageiq version_added: '2.4' author: Daniel Korn (@dkorn) description: - The manageiq_provider module supports adding, updating, and deleting provider in ManageIQ. options: state: description: - absent - provider should not exist, present - provider should be present. required: False choices: ['absent', 'present'] default: 'present' name: description: The provider's name. required: true type: description: The provider's type. required: true choices: ['Openshift', 'Amazon'] zone: description: The ManageIQ zone name that will manage the provider. required: false default: 'default' provider_region: description: The provider region name to connect to (e.g. AWS region for Amazon). required: false default: null provider: required: false description: Default endpoint connection information, required if state is true. default: null suboptions: hostname: description: The provider's api hostname. required: true port: description: The provider's api port. required: false userid: required: false default: null description: Provider's api endpoint authentication userid. defaults to None. password: required: false default: null description: Provider's api endpoint authentication password. defaults to None. auth_key: required: false default: null description: Provider's api endpoint authentication bearer token. defaults to None. verify_ssl: required: false default: true description: Whether SSL certificates should be verified for HTTPS requests (deprecated). defaults to True. security_protocol: required: false default: None choices: ['ssl-with-validation','ssl-with-validation-custom-ca','ssl-without-validation'] description: How SSL certificates should be used for HTTPS requests. defaults to None. certificate_authority: required: false default: null description: The CA bundle string with custom certificates. defaults to None. metrics: required: false description: Metrics endpoint connection information. default: null suboptions: hostname: description: The provider's api hostname. required: true port: description: The provider's api port. required: false userid: required: false default: null description: Provider's api endpoint authentication userid. defaults to None. password: required: false default: null description: Provider's api endpoint authentication password. defaults to None. auth_key: required: false default: null description: Provider's api endpoint authentication bearer token. defaults to None. verify_ssl: required: false default: true description: Whether SSL certificates should be verified for HTTPS requests (deprecated). defaults to True. security_protocol: required: false default: None choices: ['ssl-with-validation','ssl-with-validation-custom-ca','ssl-without-validation'] description: How SSL certificates should be used for HTTPS requests. defaults to None. certificate_authority: required: false default: null description: The CA bundle string with custom certificates. defaults to None. alerts: required: false description: Alerts endpoint connection information. default: null suboptions: hostname: description: The provider's api hostname. required: true port: description: The provider's api port. required: false userid: required: false default: null description: Provider's api endpoint authentication userid. defaults to None. password: required: false default: null description: Provider's api endpoint authentication password. defaults to None. auth_key: required: false default: null description: Provider's api endpoint authentication bearer token. defaults to None. verify_ssl: required: false default: true description: Whether SSL certificates should be verified for HTTPS requests (deprecated). defaults to True. security_protocol: required: false default: None choices: ['ssl-with-validation','ssl-with-validation-custom-ca','ssl-without-validation'] description: How SSL certificates should be used for HTTPS requests. defaults to None. certificate_authority: required: false default: null description: The CA bundle string with custom certificates. defaults to None. ''' EXAMPLES = ''' - hosts: 127.0.0.1 tasks: - name: Create a new provider in ManageIQ ('Hawkular' metrics) manageiq_provider: name: 'EngLab' type: 'OpenShift' state: 'present' provider: auth_key: 'topSecret' hostname: 'example.com' port: 8443 verify_ssl: true security_protocol: 'ssl-with-validation-custom-ca' certificate_authority: | -----BEGIN CERTIFICATE----- FAKECERTsdKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVu c2hpZnQtc2lnbmVyQDE1MDMzMjAxMTkwHhcNMTcwODIxMTI1NTE5WhcNMjIwODIw MTI1NTIwWjAmMSQwIgYDVQQDDBtvcGVuc2hpZnQtc2lnbmVyQDE1MDMzMjAxMTkw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUDnL2tQ2xf/zO7F7hmZ4S ZuwKENdI4IYuWSxye4i3hPhKg6eKPzGzmDNWkIMDOrDAj1EgVSNPtPwsOL8OWvJm AaTjr070D7ZGWWnrrDrWEClBx9Rx/6JAM38RT8Pu7c1hXBm0J81KufSLLYiZ/gOw Znks5v5RUSGcAXvLkBJeATbsbh6fKX0RgQ3fFTvqQaE/r8LxcTN1uehPX1g5AaRa z/SNDHaFtQlE3XcqAAukyMn4N5kdNcuwF3GlQ+tJnJv8SstPkfQcZbTMUQ7I2KpJ ajXnMxmBhV5fCN4rb0QUNCrk2/B+EUMBY4MnxIakqNxnN1kvgI7FBbFgrHUe6QvJ AgMBAAGjIzAhMA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG SIb3DQEBCwUAA4IBAQAYRV57LUsqznSLZHA77o9+0fQetIE115DYP7wea42PODJI QJ+JETEfoCr0+YOMAbVmznP9GH5cMTKEWHExcIpbMBU7nMZp6A3htcJgF2fgPzOA aTUtzkuVCSrV//mbbYVxoFOc6sR3Br0wBs5+5iz3dBSt7xmgpMzZvqsQl655i051 gGSTIY3z5EJmBZBjwuTjal9mMoPGA4eoTPqlITJDHQ2bdCV2oDbc7zqupGrUfZFA qzgieEyGzdCSRwjr1/PibA3bpwHyhD9CGD0PRVVTLhw6h6L5kuN1jA20OfzWxf/o XUsdmRaWiF+l4s6Dcd56SuRp5SGNa2+vP9Of/FX5 -----END CERTIFICATE----- metrics: auth_key: 'topSecret' role: 'hawkular' hostname: 'example.com' port: 443 verify_ssl: true security_protocol: 'ssl-with-validation-custom-ca' certificate_authority: | -----BEGIN CERTIFICATE----- FAKECERTsdKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVu c2hpZnQtc2lnbmVyQDE1MDMzMjAxMTkwHhcNMTcwODIxMTI1NTE5WhcNMjIwODIw MTI1NTIwWjAmMSQwIgYDVQQDDBtvcGVuc2hpZnQtc2lnbmVyQDE1MDMzMjAxMTkw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUDnL2tQ2xf/zO7F7hmZ4S ZuwKENdI4IYuWSxye4i3hPhKg6eKPzGzmDNWkIMDOrDAj1EgVSNPtPwsOL8OWvJm AaTjr070D7ZGWWnrrDrWEClBx9Rx/6JAM38RT8Pu7c1hXBm0J81KufSLLYiZ/gOw Znks5v5RUSGcAXvLkBJeATbsbh6fKX0RgQ3fFTvqQaE/r8LxcTN1uehPX1g5AaRa z/SNDHaFtQlE3XcqAAukyMn4N5kdNcuwF3GlQ+tJnJv8SstPkfQcZbTMUQ7I2KpJ ajXnMxmBhV5fCN4rb0QUNCrk2/B+EUMBY4MnxIakqNxnN1kvgI7FBbFgrHUe6QvJ AgMBAAGjIzAhMA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG SIb3DQEBCwUAA4IBAQAYRV57LUsqznSLZHA77o9+0fQetIE115DYP7wea42PODJI QJ+JETEfoCr0+YOMAbVmznP9GH5cMTKEWHExcIpbMBU7nMZp6A3htcJgF2fgPzOA aTUtzkuVCSrV//mbbYVxoFOc6sR3Br0wBs5+5iz3dBSt7xmgpMzZvqsQl655i051 gGSTIY3z5EJmBZBjwuTjal9mMoPGA4eoTPqlITJDHQ2bdCV2oDbc7zqupGrUfZFA qzgieEyGzdCSRwjr1/PibA3bpwHyhD9CGD0PRVVTLhw6h6L5kuN1jA20OfzWxf/o XUsdmRaWiF+l4s6Dcd56SuRp5SGNa2+vP9Of/FX5 -----END CERTIFICATE----- manageiq_connection: url: 'https://127.0.0.1' username: 'admin' password: 'password' verify_ssl: true - hosts: 127.0.0.1 tasks: - name: Update an existing provider named 'EngLab' (defaults to 'Prometheus' metrics) manageiq_provider: name: 'EngLab' type: 'Openshift' state: 'present' provider: auth_key: 'topSecret' hostname: 'next.example.com' port: 8443 verify_ssl: true security_protocol: 'ssl-with-validation-custom-ca' certificate_authority: | -----BEGIN CERTIFICATE----- FAKECERTsdKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVu c2hpZnQtc2lnbmVyQDE1MDMzMjAxMTkwHhcNMTcwODIxMTI1NTE5WhcNMjIwODIw MTI1NTIwWjAmMSQwIgYDVQQDDBtvcGVuc2hpZnQtc2lnbmVyQDE1MDMzMjAxMTkw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUDnL2tQ2xf/zO7F7hmZ4S ZuwKENdI4IYuWSxye4i3hPhKg6eKPzGzmDNWkIMDOrDAj1EgVSNPtPwsOL8OWvJm AaTjr070D7ZGWWnrrDrWEClBx9Rx/6JAM38RT8Pu7c1hXBm0J81KufSLLYiZ/gOw Znks5v5RUSGcAXvLkBJeATbsbh6fKX0RgQ3fFTvqQaE/r8LxcTN1uehPX1g5AaRa z/SNDHaFtQlE3XcqAAukyMn4N5kdNcuwF3GlQ+tJnJv8SstPkfQcZbTMUQ7I2KpJ ajXnMxmBhV5fCN4rb0QUNCrk2/B+EUMBY4MnxIakqNxnN1kvgI7FBbFgrHUe6QvJ AgMBAAGjIzAhMA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG SIb3DQEBCwUAA4IBAQAYRV57LUsqznSLZHA77o9+0fQetIE115DYP7wea42PODJI QJ+JETEfoCr0+YOMAbVmznP9GH5cMTKEWHExcIpbMBU7nMZp6A3htcJgF2fgPzOA aTUtzkuVCSrV//mbbYVxoFOc6sR3Br0wBs5+5iz3dBSt7xmgpMzZvqsQl655i051 gGSTIY3z5EJmBZBjwuTjal9mMoPGA4eoTPqlITJDHQ2bdCV2oDbc7zqupGrUfZFA qzgieEyGzdCSRwjr1/PibA3bpwHyhD9CGD0PRVVTLhw6h6L5kuN1jA20OfzWxf/o XUsdmRaWiF+l4s6Dcd56SuRp5SGNa2+vP9Of/FX5 -----END CERTIFICATE----- metrics: auth_key: 'topSecret' hostname: 'next.example.com' port: 443 verify_ssl: true security_protocol: 'ssl-with-validation-custom-ca' certificate_authority: | -----BEGIN CERTIFICATE----- FAKECERTsdKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVu c2hpZnQtc2lnbmVyQDE1MDMzMjAxMTkwHhcNMTcwODIxMTI1NTE5WhcNMjIwODIw MTI1NTIwWjAmMSQwIgYDVQQDDBtvcGVuc2hpZnQtc2lnbmVyQDE1MDMzMjAxMTkw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUDnL2tQ2xf/zO7F7hmZ4S ZuwKENdI4IYuWSxye4i3hPhKg6eKPzGzmDNWkIMDOrDAj1EgVSNPtPwsOL8OWvJm AaTjr070D7ZGWWnrrDrWEClBx9Rx/6JAM38RT8Pu7c1hXBm0J81KufSLLYiZ/gOw Znks5v5RUSGcAXvLkBJeATbsbh6fKX0RgQ3fFTvqQaE/r8LxcTN1uehPX1g5AaRa z/SNDHaFtQlE3XcqAAukyMn4N5kdNcuwF3GlQ+tJnJv8SstPkfQcZbTMUQ7I2KpJ ajXnMxmBhV5fCN4rb0QUNCrk2/B+EUMBY4MnxIakqNxnN1kvgI7FBbFgrHUe6QvJ AgMBAAGjIzAhMA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG SIb3DQEBCwUAA4IBAQAYRV57LUsqznSLZHA77o9+0fQetIE115DYP7wea42PODJI QJ+JETEfoCr0+YOMAbVmznP9GH5cMTKEWHExcIpbMBU7nMZp6A3htcJgF2fgPzOA aTUtzkuVCSrV//mbbYVxoFOc6sR3Br0wBs5+5iz3dBSt7xmgpMzZvqsQl655i051 gGSTIY3z5EJmBZBjwuTjal9mMoPGA4eoTPqlITJDHQ2bdCV2oDbc7zqupGrUfZFA qzgieEyGzdCSRwjr1/PibA3bpwHyhD9CGD0PRVVTLhw6h6L5kuN1jA20OfzWxf/o XUsdmRaWiF+l4s6Dcd56SuRp5SGNa2+vP9Of/FX5 -----END CERTIFICATE----- manageiq_connection: url: 'https://127.0.0.1' username: 'admin' password: 'password' verify_ssl: true - hosts: 127.0.0.1 tasks: - name: Delete a provider in ManageIQ manageiq_provider: name: 'EngLab' type: 'Openshift' state: 'absent' manageiq_connection: url: 'https://127.0.0.1' username: 'admin' password: 'password' verify_ssl: true - hosts: 127.0.0.1 tasks: - name: Create a new Amazon provider in ManageIQ using token authentication manageiq_provider: name: 'EngAmazon' type: 'Amazon' state: 'present' provider: hostname: 'amazon.example.com' userid: 'hello' password: 'world' manageiq_connection: url: 'https://127.0.0.1' token: 'VeryLongToken' verify_ssl: true ''' RETURN = ''' ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.manageiq import ManageIQ, manageiq_argument_spec def supported_providers(): return dict( Openshift=dict( class_name='ManageIQ::Providers::Openshift::ContainerManager', authtype='bearer', default_role='default', metrics_role='prometheus', alerts_role='prometheus_alerts', ), Amazon=dict( class_name='ManageIQ::Providers::Amazon::CloudManager', ), ) def endpoint_list_spec(): return dict( provider=dict(type='dict', options=endpoint_argument_spec()), metrics=dict(type='dict', options=endpoint_argument_spec()), alerts=dict(type='dict', options=endpoint_argument_spec()), ) def endpoint_argument_spec(): return dict( role=dict(), hostname=dict(required=True), port=dict(type='int'), verify_ssl=dict(default=True, type='bool'), certificate_authority=dict(), security_protocol=dict( choices=[ 'ssl-with-validation', 'ssl-with-validation-custom-ca', 'ssl-without-validation', ], ), userid=dict(), password=dict(no_log=True), auth_key=dict(no_log=True), ) def delete_nulls(h): """ Remove null entries from a hash Returns: a hash without nulls """ if isinstance(h, list): return map(delete_nulls, h) if isinstance(h, dict): return dict((k, delete_nulls(v)) for k, v in h.items() if v is not None) return h class ManageIQProvider(object): """ Object to execute provider management operations in manageiq. """ def __init__(self, manageiq): self.manageiq = manageiq self.module = self.manageiq.module self.api_url = self.manageiq.api_url self.client = self.manageiq.client def class_name_to_type(self, class_name): """ Convert class_name to type Returns: the type """ out = [k for k, v in supported_providers().items() if v['class_name'] == class_name] if len(out) == 1: return out[0] return None def zone_id(self, name): """ Search for zone id by zone name. Returns: the zone id, or send a module Fail signal if zone not found. """ zone = self.manageiq.find_collection_resource_by('zones', name=name) if not zone: # zone doesn't exist self.module.fail_json( msg="zone %s does not exist in manageiq" % (name)) return zone['id'] def provider(self, name): """ Search for provider object by name. Returns: the provider, or None if provider not found. """ return self.manageiq.find_collection_resource_by('providers', name=name) def build_connection_configurations(self, provider_type, endpoints): """ Build "connection_configurations" objects from requested endpoints provided by user Returns: the user requested provider endpoints list """ connection_configurations = [] endpoint_keys = endpoint_list_spec().keys() provider_defaults = supported_providers().get(provider_type, {}) # get endpoint defaults endpoint = endpoints.get('provider') default_auth_key = endpoint.get('auth_key') # build a connection_configuration object for each endpoint for endpoint_key in endpoint_keys: endpoint = endpoints.get(endpoint_key) if endpoint: # get role and authtype role = endpoint.get('role') or provider_defaults.get(endpoint_key + '_role', 'default') if role == 'default': authtype = provider_defaults.get('authtype', role) else: authtype = role # set a connection_configuration connection_configurations.append({ 'endpoint': { 'role': role, 'hostname': endpoint.get('hostname'), 'port': endpoint.get('port'), 'verify_ssl': [0, 1][endpoint.get('verify_ssl', True)], 'security_protocol': endpoint.get('security_protocol'), 'certificate_authority': endpoint.get('certificate_authority'), }, 'authentication': { 'authtype': authtype, 'userid': endpoint.get('userid'), 'password': endpoint.get('password'), 'auth_key': endpoint.get('auth_key', default_auth_key), } }) return connection_configurations def delete_provider(self, provider): """ Deletes a provider from manageiq. Returns: a short message describing the operation executed. """ try: url = '%s/providers/%s' % (self.api_url, provider['id']) result = self.client.post(url, action='delete') except Exception as e: self.module.fail_json(msg="failed to delete provider %s: %s" % (provider['name'], str(e))) return dict(changed=True, msg=result['message']) def edit_provider(self, provider, name, provider_type, endpoints, zone_id, provider_region): """ Edit a user from manageiq. Returns: a short message describing the operation executed. """ url = '%s/providers/%s' % (self.api_url, provider['id']) resource = dict( name=name, zone={'id': zone_id}, provider_region=provider_region, connection_configurations=endpoints, ) # NOTE: we do not check for diff's between requested and current # provider, we always submit endpoints with password or auth_keys, # since we can not compare with current password or auth_key, # every edit request is sent to ManageIQ API without compareing # it to current state. # clean nulls, we do not send nulls to the api resource = delete_nulls(resource) # try to update provider try: result = self.client.post(url, action='edit', resource=resource) except Exception as e: self.module.fail_json(msg="failed to update provider %s: %s" % (provider['name'], str(e))) return dict( changed=True, msg="successfully updated the provider %s: %s" % (provider['name'], result)) def create_provider(self, name, provider_type, endpoints, zone_id, provider_region): """ Creates the user in manageiq. Returns: the created user id, name, created_on timestamp, updated_on timestamp, userid and current_group_id. """ # clean nulls, we do not send nulls to the api endpoints = delete_nulls(endpoints) # try to create a new provider try: url = '%s/providers' % (self.api_url) result = self.client.post( url, name=name, type=supported_providers()[provider_type]['class_name'], zone={'id': zone_id}, provider_region=provider_region, connection_configurations=endpoints, ) except Exception as e: self.module.fail_json(msg="failed to create provider %s: %s" % (name, str(e))) return dict( changed=True, msg="successfully created the provider %s: %s" % (name, result['results'])) def main(): zone_id = None endpoints = [] argument_spec = dict( manageiq_connection=dict(required=True, type='dict', options=manageiq_argument_spec()), state=dict(choices=['absent', 'present'], default='present'), name=dict(required=True), zone=dict(default='default'), provider_region=dict(), type=dict(choices=supported_providers().keys()), ) # add the endpoint arguments to the arguments argument_spec.update(endpoint_list_spec()) module = AnsibleModule( argument_spec=argument_spec, required_if=[ ('state', 'present', ['provider'])], ) name = module.params['name'] zone_name = module.params['zone'] provider_type = module.params['type'] raw_endpoints = module.params provider_region = module.params['provider_region'] state = module.params['state'] manageiq = ManageIQ(module) manageiq_provider = ManageIQProvider(manageiq) provider = manageiq_provider.provider(name) # provider should not exist if state == "absent": # if we have a provider, delete it if provider: res_args = manageiq_provider.delete_provider(provider) # if we do not have a provider, nothing to do else: res_args = dict( changed=False, msg="provider %s: does not exist in manageiq" % (name)) # provider should exist if state == "present": # get data user did not explicitly give if zone_name: zone_id = manageiq_provider.zone_id(zone_name) # if we do not have a provider_type, use the current provider_type if provider and not provider_type: provider_type = manageiq_provider.class_name_to_type(provider['type']) # check supported_providers types if not provider_type: manageiq_provider.module.fail_json( msg="missing required argument: provider_type") # check supported_providers types if provider_type not in supported_providers().keys(): manageiq_provider.module.fail_json( msg="provider_type %s is not supported" % (provider_type)) # build "connection_configurations" objects from user requsted endpoints # "provider" is a required endpoint, if we have it, we have endpoints if raw_endpoints.get("provider"): endpoints = manageiq_provider.build_connection_configurations(provider_type, raw_endpoints) # if we have a provider, edit it if provider: res_args = manageiq_provider.edit_provider(provider, name, provider_type, endpoints, zone_id, provider_region) # if we do not have a provider, create it else: res_args = manageiq_provider.create_provider(name, provider_type, endpoints, zone_id, provider_region) module.exit_json(**res_args) if __name__ == "__main__": main()