diff options
Diffstat (limited to 'filter_plugins')
-rw-r--r-- | filter_plugins/oo_filters.py | 273 | ||||
-rw-r--r-- | filter_plugins/openshift_master.py | 33 | ||||
-rw-r--r-- | filter_plugins/openshift_node.py | 43 |
3 files changed, 295 insertions, 54 deletions
diff --git a/filter_plugins/oo_filters.py b/filter_plugins/oo_filters.py index cd67b69a5..e7409bf22 100644 --- a/filter_plugins/oo_filters.py +++ b/filter_plugins/oo_filters.py @@ -6,10 +6,13 @@ Custom filters for use in openshift-ansible """ from ansible import errors +from collections import Mapping +from distutils.version import LooseVersion from operator import itemgetter import OpenSSL.crypto import os import pdb +import pkg_resources import re import json import yaml @@ -57,6 +60,55 @@ class FilterModule(object): return [item for sublist in data for item in sublist] @staticmethod + def oo_merge_dicts(first_dict, second_dict): + """ Merge two dictionaries where second_dict values take precedence. + Ex: first_dict={'a': 1, 'b': 2} + second_dict={'b': 3, 'c': 4} + returns {'a': 1, 'b': 3, 'c': 4} + """ + if not isinstance(first_dict, dict) or not isinstance(second_dict, dict): + raise errors.AnsibleFilterError("|failed expects to merge two dicts") + merged = first_dict.copy() + merged.update(second_dict) + return merged + + @staticmethod + def oo_merge_hostvars(hostvars, variables, inventory_hostname): + """ Merge host and play variables. + + When ansible version is greater than or equal to 2.0.0, + merge hostvars[inventory_hostname] with variables (ansible vars) + otherwise merge hostvars with hostvars['inventory_hostname']. + + Ex: hostvars={'master1.example.com': {'openshift_variable': '3'}, + 'openshift_other_variable': '7'} + variables={'openshift_other_variable': '6'} + inventory_hostname='master1.example.com' + returns {'openshift_variable': '3', 'openshift_other_variable': '7'} + + hostvars=<ansible.vars.hostvars.HostVars object> (Mapping) + variables={'openshift_other_variable': '6'} + inventory_hostname='master1.example.com' + returns {'openshift_variable': '3', 'openshift_other_variable': '6'} + """ + if not isinstance(hostvars, Mapping): + raise errors.AnsibleFilterError("|failed expects hostvars is dictionary or object") + if not isinstance(variables, dict): + raise errors.AnsibleFilterError("|failed expects variables is a dictionary") + if not isinstance(inventory_hostname, basestring): + raise errors.AnsibleFilterError("|failed expects inventory_hostname is a string") + # pylint: disable=no-member + ansible_version = pkg_resources.get_distribution("ansible").version + merged_hostvars = {} + if LooseVersion(ansible_version) >= LooseVersion('2.0.0'): + merged_hostvars = FilterModule.oo_merge_dicts(hostvars[inventory_hostname], + variables) + else: + merged_hostvars = FilterModule.oo_merge_dicts(hostvars[inventory_hostname], + hostvars) + return merged_hostvars + + @staticmethod def oo_collect(data, attribute=None, filters=None): """ This takes a list of dict and collects all attributes specified into a list. If filter is specified then we will include all items that @@ -115,14 +167,14 @@ class FilterModule(object): returns [1, 3] """ - if not isinstance(data, dict): - raise errors.AnsibleFilterError("|failed expects to filter on a dict") + if not isinstance(data, Mapping): + raise errors.AnsibleFilterError("|failed expects to filter on a dict or object") if not isinstance(keys, list): raise errors.AnsibleFilterError("|failed expects first param is a list") # Gather up the values for the list of keys passed in - retval = [data[key] for key in keys if data.has_key(key)] + retval = [data[key] for key in keys if key in data] return retval @@ -246,8 +298,11 @@ class FilterModule(object): @staticmethod def oo_split(string, separator=','): - """ This splits the input string into a list + """ This splits the input string into a list. If the input string is + already a list we will return it as is. """ + if isinstance(string, list): + return string return string.split(separator) @staticmethod @@ -283,7 +338,86 @@ class FilterModule(object): raise errors.AnsibleFilterError("|failed expects filter_attr is a str or unicode") # Gather up the values for the list of keys passed in - return [x for x in data if x.has_key(filter_attr) and x[filter_attr]] + return [x for x in data if filter_attr in x and x[filter_attr]] + + @staticmethod + def oo_oc_nodes_matching_selector(nodes, selector): + """ Filters a list of nodes by selector. + + Examples: + nodes = [{"kind": "Node", "metadata": {"name": "node1.example.com", + "labels": {"kubernetes.io/hostname": "node1.example.com", + "color": "green"}}}, + {"kind": "Node", "metadata": {"name": "node2.example.com", + "labels": {"kubernetes.io/hostname": "node2.example.com", + "color": "red"}}}] + selector = 'color=green' + returns = ['node1.example.com'] + + nodes = [{"kind": "Node", "metadata": {"name": "node1.example.com", + "labels": {"kubernetes.io/hostname": "node1.example.com", + "color": "green"}}}, + {"kind": "Node", "metadata": {"name": "node2.example.com", + "labels": {"kubernetes.io/hostname": "node2.example.com", + "color": "red"}}}] + selector = 'color=green,color=red' + returns = ['node1.example.com','node2.example.com'] + + Args: + nodes (list[dict]): list of node definitions + selector (str): "label=value" node selector to filter `nodes` by + Returns: + list[str]: nodes filtered by selector + """ + if not isinstance(nodes, list): + raise errors.AnsibleFilterError("failed expects nodes to be a list, got {0}".format(type(nodes))) + if not isinstance(selector, basestring): + raise errors.AnsibleFilterError("failed expects selector to be a string") + if not re.match('.*=.*', selector): + raise errors.AnsibleFilterError("failed selector does not match \"label=value\" format") + node_lists = [] + for node_selector in ''.join(selector.split()).split(','): + label = node_selector.split('=')[0] + value = node_selector.split('=')[1] + node_lists.append(FilterModule.oo_oc_nodes_with_label(nodes, label, value)) + nodes = set(node_lists[0]) + for node_list in node_lists[1:]: + nodes.intersection_update(node_list) + return list(nodes) + + @staticmethod + def oo_oc_nodes_with_label(nodes, label, value): + """ Filters a list of nodes by label, value. + + Examples: + nodes = [{"kind": "Node", "metadata": {"name": "node1.example.com", + "labels": {"kubernetes.io/hostname": "node1.example.com", + "color": "green"}}}, + {"kind": "Node", "metadata": {"name": "node2.example.com", + "labels": {"kubernetes.io/hostname": "node2.example.com", + "color": "red"}}}] + label = 'color' + value = 'green' + returns = ['node1.example.com'] + Args: + nodes (list[dict]): list of node definitions + label (str): label to filter `nodes` by + value (str): value of `label` to filter `nodes` by + Returns: + list[str]: nodes filtered by selector + """ + if not isinstance(nodes, list): + raise errors.AnsibleFilterError("failed expects nodes to be a list") + if not isinstance(label, basestring): + raise errors.AnsibleFilterError("failed expects label to be a string") + if not isinstance(value, basestring): + raise errors.AnsibleFilterError("failed expects value to be a string") + matching_nodes = [] + for node in nodes: + if label in node['metadata']['labels']: + if node['metadata']['labels'][label] == value: + matching_nodes.append(node['metadata']['name']) + return matching_nodes @staticmethod def oo_nodes_with_label(nodes, label, value=None): @@ -558,7 +692,9 @@ class FilterModule(object): @staticmethod def oo_openshift_env(hostvars): - ''' Return facts which begin with "openshift_" + ''' Return facts which begin with "openshift_" and translate + legacy facts to their openshift_env counterparts. + Ex: hostvars = {'openshift_fact': 42, 'theyre_taking_the_hobbits_to': 'isengard'} returns = {'openshift_fact': 42} @@ -571,6 +707,11 @@ class FilterModule(object): for key in hostvars: if regex.match(key): facts[key] = hostvars[key] + + migrations = {'openshift_router_selector': 'openshift_hosted_router_selector'} + for old_fact, new_fact in migrations.iteritems(): + if old_fact in facts and new_fact not in facts: + facts[new_fact] = facts[old_fact] return facts @staticmethod @@ -588,36 +729,54 @@ class FilterModule(object): if persistent_volumes == None: persistent_volumes = [] - for component in hostvars['openshift']['hosted']: - kind = hostvars['openshift']['hosted'][component]['storage']['kind'] - create_pv = hostvars['openshift']['hosted'][component]['storage']['create_pv'] - if kind != None and create_pv: - if kind == 'nfs': - host = hostvars['openshift']['hosted'][component]['storage']['host'] - if host == None: - if len(groups['oo_nfs_to_config']) > 0: - host = groups['oo_nfs_to_config'][0] + if 'hosted' in hostvars['openshift']: + for component in hostvars['openshift']['hosted']: + if 'storage' in hostvars['openshift']['hosted'][component]: + params = hostvars['openshift']['hosted'][component]['storage'] + kind = params['kind'] + create_pv = params['create_pv'] + if kind != None and create_pv: + if kind == 'nfs': + host = params['host'] + if host == None: + if len(groups['oo_nfs_to_config']) > 0: + host = groups['oo_nfs_to_config'][0] + else: + raise errors.AnsibleFilterError("|failed no storage host detected") + directory = params['nfs']['directory'] + volume = params['volume']['name'] + path = directory + '/' + volume + size = params['volume']['size'] + access_modes = params['access_modes'] + persistent_volume = dict( + name="{0}-volume".format(volume), + capacity=size, + access_modes=access_modes, + storage=dict( + nfs=dict( + server=host, + path=path))) + persistent_volumes.append(persistent_volume) + elif kind == 'openstack': + volume = params['volume']['name'] + size = params['volume']['size'] + access_modes = params['access_modes'] + filesystem = params['openstack']['filesystem'] + volume_id = params['openstack']['volumeID'] + persistent_volume = dict( + name="{0}-volume".format(volume), + capacity=size, + access_modes=access_modes, + storage=dict( + cinder=dict( + fsType=filesystem, + volumeID=volume_id))) + persistent_volumes.append(persistent_volume) else: - raise errors.AnsibleFilterError("|failed no storage host detected") - directory = hostvars['openshift']['hosted'][component]['storage']['nfs']['directory'] - volume = hostvars['openshift']['hosted'][component]['storage']['volume']['name'] - path = directory + '/' + volume - size = hostvars['openshift']['hosted'][component]['storage']['volume']['size'] - access_modes = hostvars['openshift']['hosted'][component]['storage']['access_modes'] - persistent_volume = dict( - name="{0}-volume".format(volume), - capacity=size, - access_modes=access_modes, - storage=dict( - nfs=dict( - server=host, - path=path))) - persistent_volumes.append(persistent_volume) - else: - msg = "|failed invalid storage kind '{0}' for component '{1}'".format( - kind, - component) - raise errors.AnsibleFilterError(msg) + msg = "|failed invalid storage kind '{0}' for component '{1}'".format( + kind, + component) + raise errors.AnsibleFilterError(msg) return persistent_volumes @staticmethod @@ -632,18 +791,20 @@ class FilterModule(object): if persistent_volume_claims == None: persistent_volume_claims = [] - for component in hostvars['openshift']['hosted']: - kind = hostvars['openshift']['hosted'][component]['storage']['kind'] - create_pv = hostvars['openshift']['hosted'][component]['storage']['create_pv'] - if kind != None and create_pv: - volume = hostvars['openshift']['hosted'][component]['storage']['volume']['name'] - size = hostvars['openshift']['hosted'][component]['storage']['volume']['size'] - access_modes = hostvars['openshift']['hosted'][component]['storage']['access_modes'] - persistent_volume_claim = dict( - name="{0}-claim".format(volume), - capacity=size, - access_modes=access_modes) - persistent_volume_claims.append(persistent_volume_claim) + if 'hosted' in hostvars['openshift']: + for component in hostvars['openshift']['hosted']: + if 'storage' in hostvars['openshift']['hosted'][component]: + kind = hostvars['openshift']['hosted'][component]['storage']['kind'] + create_pv = hostvars['openshift']['hosted'][component]['storage']['create_pv'] + if kind != None and create_pv: + volume = hostvars['openshift']['hosted'][component]['storage']['volume']['name'] + size = hostvars['openshift']['hosted'][component]['storage']['volume']['size'] + access_modes = hostvars['openshift']['hosted'][component]['storage']['access_modes'] + persistent_volume_claim = dict( + name="{0}-claim".format(volume), + capacity=size, + access_modes=access_modes) + persistent_volume_claims.append(persistent_volume_claim) return persistent_volume_claims @staticmethod @@ -711,18 +872,24 @@ class FilterModule(object): return retval @staticmethod - def oo_image_tag_to_rpm_version(version): + def oo_image_tag_to_rpm_version(version, include_dash=False): """ Convert an image tag string to an RPM version if necessary Empty strings and strings that are already in rpm version format - are ignored. + are ignored. Also remove non semantic version components. Ex. v3.2.0.10 -> -3.2.0.10 + v1.2.0-rc1 -> -1.2.0 """ if not isinstance(version, basestring): raise errors.AnsibleFilterError("|failed expects a string or unicode") - + # TODO: Do we need to make this actually convert v1.2.0-rc1 into 1.2.0-0.rc1 + # We'd need to be really strict about how we build the RPM Version+Release if version.startswith("v"): - version = "-" + version.replace("v", "") + version = version.replace("v", "") + version = version.split('-')[0] + + if include_dash: + version = "-" + version return version @@ -755,4 +922,8 @@ class FilterModule(object): "oo_pods_match_component": self.oo_pods_match_component, "oo_get_hosts_from_hostvars": self.oo_get_hosts_from_hostvars, "oo_image_tag_to_rpm_version": self.oo_image_tag_to_rpm_version, + "oo_merge_dicts": self.oo_merge_dicts, + "oo_oc_nodes_matching_selector": self.oo_oc_nodes_matching_selector, + "oo_oc_nodes_with_label": self.oo_oc_nodes_with_label, + "oo_merge_hostvars": self.oo_merge_hostvars, } diff --git a/filter_plugins/openshift_master.py b/filter_plugins/openshift_master.py index d0fb98ec3..bb2f5ba7a 100644 --- a/filter_plugins/openshift_master.py +++ b/filter_plugins/openshift_master.py @@ -9,8 +9,14 @@ import sys import yaml from ansible import errors -from ansible.runner.filter_plugins.core import bool as ansible_bool +# pylint: disable=no-name-in-module,import-error +try: + # ansible-2.0 + from ansible.runner.filter_plugins.core import bool as ansible_bool +except ImportError: + # ansible-1.9.x + from ansible.plugins.filter.core import bool as ansible_bool class IdentityProviderBase(object): """ IdentityProviderBase @@ -57,7 +63,7 @@ class IdentityProviderBase(object): mapping_method = None for key in mm_keys: if key in self._idp: - mapping_method = self._idp[key] + mapping_method = self._idp.pop(key) if mapping_method is None: mapping_method = self.get_default('mappingMethod') self.mapping_method = mapping_method @@ -527,9 +533,30 @@ class FilterModule(object): 'openshift-master.kubeconfig'] return certs + @staticmethod + def oo_htpasswd_users_from_file(file_contents): + ''' return a dictionary of htpasswd users from htpasswd file contents ''' + htpasswd_entries = {} + if not isinstance(file_contents, basestring): + raise errors.AnsibleFilterError("failed, expects to filter on a string") + for line in file_contents.splitlines(): + user = None + passwd = None + if len(line) == 0: + continue + if ':' in line: + user, passwd = line.split(':', 1) + + if user is None or len(user) == 0 or passwd is None or len(passwd) == 0: + error_msg = "failed, expects each line to be a colon separated string representing the user and passwd" + raise errors.AnsibleFilterError(error_msg) + htpasswd_entries[user] = passwd + return htpasswd_entries + def filters(self): ''' returns a mapping of filters to methods ''' return {"translate_idps": self.translate_idps, "validate_pcs_cluster": self.validate_pcs_cluster, - "certificates_to_synchronize": self.certificates_to_synchronize} + "certificates_to_synchronize": self.certificates_to_synchronize, + "oo_htpasswd_users_from_file": self.oo_htpasswd_users_from_file} diff --git a/filter_plugins/openshift_node.py b/filter_plugins/openshift_node.py new file mode 100644 index 000000000..22670cf79 --- /dev/null +++ b/filter_plugins/openshift_node.py @@ -0,0 +1,43 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vim: expandtab:tabstop=4:shiftwidth=4 +''' +Custom filters for use in openshift-node +''' +from ansible import errors + +class FilterModule(object): + ''' Custom ansible filters for use by openshift_node role''' + + @staticmethod + def get_dns_ip(openshift_dns_ip, hostvars): + ''' Navigates the complicated logic of when to set dnsIP + + In all situations if they've set openshift_dns_ip use that + For 1.0/3.0 installs we use the openshift_master_cluster_vip, openshift_node_first_master_ip, else None + For 1.1/3.1 installs we use openshift_master_cluster_vip, else None (product will use kube svc ip) + For 1.2/3.2+ installs we set to the node's default interface ip + ''' + + if not issubclass(type(hostvars), dict): + raise errors.AnsibleFilterError("|failed expects hostvars is a dict") + + # We always use what they've specified if they've specified a value + if openshift_dns_ip != None: + return openshift_dns_ip + + if bool(hostvars['openshift']['common']['use_dnsmasq']): + return hostvars['ansible_default_ipv4']['address'] + elif bool(hostvars['openshift']['common']['version_gte_3_1_or_1_1']): + if 'openshift_master_cluster_vip' in hostvars: + return hostvars['openshift_master_cluster_vip'] + else: + if 'openshift_master_cluster_vip' in hostvars: + return hostvars['openshift_master_cluster_vip'] + elif 'openshift_node_first_master_ip' in hostvars: + return hostvars['openshift_node_first_master_ip'] + return None + + def filters(self): + ''' returns a mapping of filters to methods ''' + return {'get_dns_ip': self.get_dns_ip} |