diff options
Diffstat (limited to 'filter_plugins')
-rw-r--r-- | filter_plugins/oo_filters.py | 95 | ||||
-rw-r--r-- | filter_plugins/openshift_master.py | 469 |
2 files changed, 550 insertions, 14 deletions
diff --git a/filter_plugins/oo_filters.py b/filter_plugins/oo_filters.py index 9a17913c4..1a854f637 100644 --- a/filter_plugins/oo_filters.py +++ b/filter_plugins/oo_filters.py @@ -191,7 +191,11 @@ class FilterModule(object): { 'root': { 'volume_size': 10, 'device_type': 'gp2', 'iops': 500 - } + }, + 'docker': + { 'volume_size': 40, 'device_type': 'gp2', + 'iops': 500, 'ephemeral': 'true' + } }, 'node': { 'root': @@ -216,7 +220,7 @@ class FilterModule(object): root_vol['delete_on_termination'] = True if root_vol['device_type'] != 'io1': root_vol.pop('iops', None) - if host_type == 'node': + if host_type in ['master', 'node'] and 'docker' in data[host_type]: docker_vol = data[host_type]['docker'] docker_vol['device_name'] = '/dev/xvdb' docker_vol['delete_on_termination'] = True @@ -227,7 +231,7 @@ class FilterModule(object): docker_vol.pop('delete_on_termination', None) docker_vol['ephemeral'] = 'ephemeral0' return [root_vol, docker_vol] - elif host_type == 'etcd': + elif host_type == 'etcd' and 'etcd' in data[host_type]: etcd_vol = data[host_type]['etcd'] etcd_vol['device_name'] = '/dev/xvdb' etcd_vol['delete_on_termination'] = True @@ -346,27 +350,27 @@ class FilterModule(object): @staticmethod # pylint: disable=too-many-branches - def oo_parse_certificate_names(certificates, data_dir, internal_hostnames): + def oo_parse_named_certificates(certificates, named_certs_dir, internal_hostnames): ''' Parses names from list of certificate hashes. - Ex: certificates = [{ "certfile": "/etc/origin/master/custom1.crt", - "keyfile": "/etc/origin/master/custom1.key" }, + Ex: certificates = [{ "certfile": "/root/custom1.crt", + "keyfile": "/root/custom1.key" }, { "certfile": "custom2.crt", "keyfile": "custom2.key" }] - returns [{ "certfile": "/etc/origin/master/custom1.crt", - "keyfile": "/etc/origin/master/custom1.key", + returns [{ "certfile": "/etc/origin/master/named_certificates/custom1.crt", + "keyfile": "/etc/origin/master/named_certificates/custom1.key", "names": [ "public-master-host.com", "other-master-host.com" ] }, - { "certfile": "/etc/origin/master/custom2.crt", - "keyfile": "/etc/origin/master/custom2.key", + { "certfile": "/etc/origin/master/named_certificates/custom2.crt", + "keyfile": "/etc/origin/master/named_certificates/custom2.key", "names": [ "some-hostname.com" ] }] ''' if not issubclass(type(certificates), list): raise errors.AnsibleFilterError("|failed expects certificates is a list") - if not issubclass(type(data_dir), unicode): - raise errors.AnsibleFilterError("|failed expects data_dir is unicode") + if not issubclass(type(named_certs_dir), unicode): + raise errors.AnsibleFilterError("|failed expects named_certs_dir is unicode") if not issubclass(type(internal_hostnames), list): raise errors.AnsibleFilterError("|failed expects internal_hostnames is list") @@ -399,8 +403,70 @@ class FilterModule(object): raise errors.AnsibleFilterError(("|failed to parse certificate '%s' or " % certificate['certfile'] + "detected a collision with internal hostname, please specify " + "certificate names in host inventory")) + + for certificate in certificates: + # Update paths for configuration + certificate['certfile'] = os.path.join(named_certs_dir, os.path.basename(certificate['certfile'])) + certificate['keyfile'] = os.path.join(named_certs_dir, os.path.basename(certificate['keyfile'])) return certificates + @staticmethod + def oo_pretty_print_cluster(data): + ''' Read a subset of hostvars and build a summary of the cluster + in the following layout: + +"c_id": { + "master": [ + { "name": "c_id-master-12345", "public IP": "172.16.0.1", "private IP": "192.168.0.1", "subtype": "default" }] + "node": [ + { "name": "c_id-node-infra-23456", "public IP": "172.16.0.2", "private IP": "192.168.0.2", "subtype": "infra" }, + { "name": "c_id-node-compute-23456", "public IP": "172.16.0.3", "private IP": "192.168.0.3", "subtype": "compute" }, + ... + ]} + ''' + + def _get_tag_value(tags, key): + ''' Extract values of a map implemented as a set. + Ex: tags = { 'tag_foo_value1', 'tag_bar_value2', 'tag_baz_value3' } + key = 'bar' + returns 'value2' + ''' + for tag in tags: + # Skip tag_env-host-type to avoid ambiguity with tag_env + if tag[:17] == 'tag_env-host-type': + continue + if tag[:len(key)+4] == 'tag_' + key: + return tag[len(key)+5:] + raise KeyError(key) + + def _add_host(clusters, + env, + host_type, + sub_host_type, + host): + ''' Add a new host in the clusters data structure ''' + if env not in clusters: + clusters[env] = {} + if host_type not in clusters[env]: + clusters[env][host_type] = {} + if sub_host_type not in clusters[env][host_type]: + clusters[env][host_type][sub_host_type] = [] + clusters[env][host_type][sub_host_type].append(host) + + clusters = {} + for host in data: + try: + _add_host(clusters=clusters, + env=_get_tag_value(host['group_names'], 'env'), + host_type=_get_tag_value(host['group_names'], 'host-type'), + sub_host_type=_get_tag_value(host['group_names'], 'sub-host-type'), + host={'name': host['inventory_hostname'], + 'public IP': host['ansible_ssh_host'], + 'private IP': host['ansible_default_ipv4']['address']}) + except KeyError: + pass + return clusters + def filters(self): ''' returns a mapping of filters to methods ''' return { @@ -417,6 +483,7 @@ class FilterModule(object): "oo_split": self.oo_split, "oo_filter_list": self.oo_filter_list, "oo_parse_heat_stack_outputs": self.oo_parse_heat_stack_outputs, - "oo_parse_certificate_names": self.oo_parse_certificate_names, - "oo_haproxy_backend_masters": self.oo_haproxy_backend_masters + "oo_parse_named_certificates": self.oo_parse_named_certificates, + "oo_haproxy_backend_masters": self.oo_haproxy_backend_masters, + "oo_pretty_print_cluster": self.oo_pretty_print_cluster } diff --git a/filter_plugins/openshift_master.py b/filter_plugins/openshift_master.py new file mode 100644 index 000000000..f12017967 --- /dev/null +++ b/filter_plugins/openshift_master.py @@ -0,0 +1,469 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vim: expandtab:tabstop=4:shiftwidth=4 +''' +Custom filters for use in openshift-master +''' +import copy +import sys +import yaml + +from ansible import errors +from ansible.runner.filter_plugins.core import bool as ansible_bool + + +class IdentityProviderBase(object): + """ IdentityProviderBase + + Attributes: + name (str): Identity provider Name + login (bool): Is this identity provider a login provider? + challenge (bool): Is this identity provider a challenge provider? + provider (dict): Provider specific config + _idp (dict): internal copy of the IDP dict passed in + _required (list): List of lists of strings for required attributes + _optional (list): List of lists of strings for optional attributes + _allow_additional (bool): Does this provider support attributes + not in _required and _optional + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + # disabling this check since the number of instance attributes are + # necessary for this class + # pylint: disable=too-many-instance-attributes + def __init__(self, api_version, idp): + if api_version not in ['v1']: + raise errors.AnsibleFilterError("|failed api version {0} unknown".format(api_version)) + + self._idp = copy.deepcopy(idp) + + if 'name' not in self._idp: + raise errors.AnsibleFilterError("|failed identity provider missing a name") + + if 'kind' not in self._idp: + raise errors.AnsibleFilterError("|failed identity provider missing a kind") + + self.name = self._idp.pop('name') + self.login = ansible_bool(self._idp.pop('login', False)) + self.challenge = ansible_bool(self._idp.pop('challenge', False)) + self.provider = dict(apiVersion=api_version, kind=self._idp.pop('kind')) + + self._required = [['mappingMethod', 'mapping_method']] + self._optional = [] + self._allow_additional = True + + @staticmethod + def validate_idp_list(idp_list): + ''' validates a list of idps ''' + login_providers = [x.name for x in idp_list if x.login] + if len(login_providers) > 1: + raise errors.AnsibleFilterError("|failed multiple providers are " + "not allowed for login. login " + "providers: {0}".format(', '.join(login_providers))) + + names = [x.name for x in idp_list] + if len(set(names)) != len(names): + raise errors.AnsibleFilterError("|failed more than one provider configured with the same name") + + for idp in idp_list: + idp.validate() + + def validate(self): + ''' validate an instance of this idp class ''' + valid_mapping_methods = ['add', 'claim', 'generate', 'lookup'] + if self.provider['mappingMethod'] not in valid_mapping_methods: + raise errors.AnsibleFilterError("|failed unkown mapping method " + "for provider {0}".format(self.__class__.__name__)) + + @staticmethod + def get_default(key): + ''' get a default value for a given key ''' + if key == 'mappingMethod': + return 'claim' + else: + return None + + def set_provider_item(self, items, required=False): + ''' set a provider item based on the list of item names provided. ''' + for item in items: + provider_key = items[0] + if item in self._idp: + self.provider[provider_key] = self._idp.pop(item) + break + else: + default = self.get_default(provider_key) + if default is not None: + self.provider[provider_key] = default + elif required: + raise errors.AnsibleFilterError("|failed provider {0} missing " + "required key {1}".format(self.__class__.__name__, provider_key)) + + def set_provider_items(self): + ''' set the provider items for this idp ''' + for items in self._required: + self.set_provider_item(items, True) + for items in self._optional: + self.set_provider_item(items) + if self._allow_additional: + for key in self._idp.keys(): + self.set_provider_item([key]) + else: + if len(self._idp) > 0: + raise errors.AnsibleFilterError("|failed provider {0} " + "contains unknown keys " + "{1}".format(self.__class__.__name__, ', '.join(self._idp.keys()))) + + def to_dict(self): + ''' translate this idp to a dictionary ''' + return dict(name=self.name, challenge=self.challenge, + login=self.login, provider=self.provider) + + +class LDAPPasswordIdentityProvider(IdentityProviderBase): + """ LDAPPasswordIdentityProvider + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderBase.__init__(self, api_version, idp) + self._allow_additional = False + self._required += [['attributes'], ['url'], ['insecure']] + self._optional += [['ca'], + ['bindDN', 'bind_dn'], + ['bindPassword', 'bind_password']] + + self._idp['insecure'] = ansible_bool(self._idp.pop('insecure', False)) + + if 'attributes' in self._idp and 'preferred_username' in self._idp['attributes']: + pref_user = self._idp['attributes'].pop('preferred_username') + self._idp['attributes']['preferredUsername'] = pref_user + + def validate(self): + ''' validate this idp instance ''' + IdentityProviderBase.validate(self) + if not isinstance(self.provider['attributes'], dict): + raise errors.AnsibleFilterError("|failed attributes for provider " + "{0} must be a dictionary".format(self.__class__.__name__)) + + attrs = ['id', 'email', 'name', 'preferredUsername'] + for attr in attrs: + if attr in self.provider['attributes'] and not isinstance(self.provider['attributes'][attr], list): + raise errors.AnsibleFilterError("|failed {0} attribute for " + "provider {1} must be a list".format(attr, self.__class__.__name__)) + + unknown_attrs = set(self.provider['attributes'].keys()) - set(attrs) + if len(unknown_attrs) > 0: + raise errors.AnsibleFilterError("|failed provider {0} has unknown " + "attributes: {1}".format(self.__class__.__name__, ', '.join(unknown_attrs))) + + +class KeystonePasswordIdentityProvider(IdentityProviderBase): + """ KeystoneIdentityProvider + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderBase.__init__(self, api_version, idp) + self._allow_additional = False + self._required += [['url'], ['domainName', 'domain_name']] + self._optional += [['ca'], ['certFile', 'cert_file'], ['keyFile', 'key_file']] + + +class RequestHeaderIdentityProvider(IdentityProviderBase): + """ RequestHeaderIdentityProvider + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderBase.__init__(self, api_version, idp) + self._allow_additional = False + self._required += [['headers']] + self._optional += [['challengeURL', 'challenge_url'], + ['loginURL', 'login_url'], + ['clientCA', 'client_ca']] + + def validate(self): + ''' validate this idp instance ''' + IdentityProviderBase.validate(self) + if not isinstance(self.provider['headers'], list): + raise errors.AnsibleFilterError("|failed headers for provider {0} " + "must be a list".format(self.__class__.__name__)) + + +class AllowAllPasswordIdentityProvider(IdentityProviderBase): + """ AllowAllPasswordIdentityProvider + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderBase.__init__(self, api_version, idp) + self._allow_additional = False + + +class DenyAllPasswordIdentityProvider(IdentityProviderBase): + """ DenyAllPasswordIdentityProvider + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderBase.__init__(self, api_version, idp) + self._allow_additional = False + + +class HTPasswdPasswordIdentityProvider(IdentityProviderBase): + """ HTPasswdPasswordIdentity + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderBase.__init__(self, api_version, idp) + self._allow_additional = False + self._required += [['file', 'filename', 'fileName', 'file_name']] + + @staticmethod + def get_default(key): + if key == 'file': + return '/etc/origin/htpasswd' + else: + return IdentityProviderBase.get_default(key) + + +class BasicAuthPasswordIdentityProvider(IdentityProviderBase): + """ BasicAuthPasswordIdentityProvider + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderBase.__init__(self, api_version, idp) + self._allow_additional = False + self._required += [['url']] + self._optional += [['ca'], ['certFile', 'cert_file'], ['keyFile', 'key_file']] + + +class IdentityProviderOauthBase(IdentityProviderBase): + """ IdentityProviderOauthBase + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderBase.__init__(self, api_version, idp) + self._allow_additional = False + self._required += [['clientID', 'client_id'], ['clientSecret', 'client_secret']] + + def validate(self): + ''' validate this idp instance ''' + IdentityProviderBase.validate(self) + if self.challenge: + raise errors.AnsibleFilterError("|failed provider {0} does not " + "allow challenge authentication".format(self.__class__.__name__)) + + +class OpenIDIdentityProvider(IdentityProviderOauthBase): + """ OpenIDIdentityProvider + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderOauthBase.__init__(self, api_version, idp) + self._required += [['claims'], ['urls']] + self._optional += [['ca'], + ['extraScopes'], + ['extraAuthorizeParameters']] + if 'claims' in self._idp and 'preferred_username' in self._idp['claims']: + pref_user = self._idp['claims'].pop('preferred_username') + self._idp['claims']['preferredUsername'] = pref_user + if 'urls' in self._idp and 'user_info' in self._idp['urls']: + user_info = self._idp['urls'].pop('user_info') + self._idp['urls']['userInfo'] = user_info + if 'extra_scopes' in self._idp: + self._idp['extraScopes'] = self._idp.pop('extra_scopes') + if 'extra_authorize_parameters' in self._idp: + self._idp['extraAuthorizeParameters'] = self._idp.pop('extra_authorize_parameters') + + if 'extraAuthorizeParameters' in self._idp: + if 'include_granted_scopes' in self._idp['extraAuthorizeParameters']: + val = ansible_bool(self._idp['extraAuthorizeParameters'].pop('include_granted_scopes')) + self._idp['extraAuthorizeParameters']['include_granted_scopes'] = val + + + def validate(self): + ''' validate this idp instance ''' + IdentityProviderOauthBase.validate(self) + if not isinstance(self.provider['claims'], dict): + raise errors.AnsibleFilterError("|failed claims for provider {0} " + "must be a dictionary".format(self.__class__.__name__)) + + if 'extraScopes' not in self.provider['extraScopes'] and not isinstance(self.provider['extraScopes'], list): + raise errors.AnsibleFilterError("|failed extraScopes for provider " + "{0} must be a list".format(self.__class__.__name__)) + if ('extraAuthorizeParameters' not in self.provider['extraAuthorizeParameters'] + and not isinstance(self.provider['extraAuthorizeParameters'], dict)): + raise errors.AnsibleFilterError("|failed extraAuthorizeParameters " + "for provider {0} must be a dictionary".format(self.__class__.__name__)) + + required_claims = ['id'] + optional_claims = ['email', 'name', 'preferredUsername'] + all_claims = required_claims + optional_claims + + for claim in required_claims: + if claim in required_claims and claim not in self.provider['claims']: + raise errors.AnsibleFilterError("|failed {0} claim missing " + "for provider {1}".format(claim, self.__class__.__name__)) + + for claim in all_claims: + if claim in self.provider['claims'] and not isinstance(self.provider['claims'][claim], list): + raise errors.AnsibleFilterError("|failed {0} claims for " + "provider {1} must be a list".format(claim, self.__class__.__name__)) + + unknown_claims = set(self.provider['claims'].keys()) - set(all_claims) + if len(unknown_claims) > 0: + raise errors.AnsibleFilterError("|failed provider {0} has unknown " + "claims: {1}".format(self.__class__.__name__, ', '.join(unknown_claims))) + + if not isinstance(self.provider['urls'], dict): + raise errors.AnsibleFilterError("|failed urls for provider {0} " + "must be a dictionary".format(self.__class__.__name__)) + + required_urls = ['authorize', 'token'] + optional_urls = ['userInfo'] + all_urls = required_urls + optional_urls + + for url in required_urls: + if url not in self.provider['urls']: + raise errors.AnsibleFilterError("|failed {0} url missing for " + "provider {1}".format(url, self.__class__.__name__)) + + unknown_urls = set(self.provider['urls'].keys()) - set(all_urls) + if len(unknown_urls) > 0: + raise errors.AnsibleFilterError("|failed provider {0} has unknown " + "urls: {1}".format(self.__class__.__name__, ', '.join(unknown_urls))) + + +class GoogleIdentityProvider(IdentityProviderOauthBase): + """ GoogleIdentityProvider + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + def __init__(self, api_version, idp): + IdentityProviderOauthBase.__init__(self, api_version, idp) + self._optional += [['hostedDomain', 'hosted_domain']] + + +class GitHubIdentityProvider(IdentityProviderOauthBase): + """ GitHubIdentityProvider + + Attributes: + + Args: + api_version(str): OpenShift config version + idp (dict): idp config dict + + Raises: + AnsibleFilterError: + """ + pass + + +class FilterModule(object): + ''' Custom ansible filters for use by the openshift_master role''' + + @staticmethod + def translate_idps(idps, api_version): + ''' Translates a list of dictionaries into a valid identityProviders config ''' + idp_list = [] + + if not isinstance(idps, list): + raise errors.AnsibleFilterError("|failed expects to filter on a list of identity providers") + for idp in idps: + if not isinstance(idp, dict): + raise errors.AnsibleFilterError("|failed identity providers must be a list of dictionaries") + + cur_module = sys.modules[__name__] + idp_class = getattr(cur_module, idp['kind'], None) + idp_inst = idp_class(api_version, idp) if idp_class is not None else IdentityProviderBase(api_version, idp) + idp_inst.set_provider_items() + idp_list.append(idp_inst) + + + IdentityProviderBase.validate_idp_list(idp_list) + return yaml.safe_dump([idp.to_dict() for idp in idp_list], default_flow_style=False) + + + def filters(self): + ''' returns a mapping of filters to methods ''' + return {"translate_idps": self.translate_idps} |