diff options
Diffstat (limited to 'roles/openshift_register_nodes')
-rwxr-xr-x | roles/openshift_register_nodes/library/kubernetes_register_node.py | 413 | ||||
-rw-r--r-- | roles/openshift_register_nodes/tasks/main.yml | 3 |
2 files changed, 321 insertions, 95 deletions
diff --git a/roles/openshift_register_nodes/library/kubernetes_register_node.py b/roles/openshift_register_nodes/library/kubernetes_register_node.py index 1ec977716..afa9eb27d 100755 --- a/roles/openshift_register_nodes/library/kubernetes_register_node.py +++ b/roles/openshift_register_nodes/library/kubernetes_register_node.py @@ -1,12 +1,21 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # vim: expandtab:tabstop=4:shiftwidth=4 +# +# disable pylint checks +# temporarily disabled until items can be addressed: +# fixme - until all TODO comments have been addressed +# permanently disabled unless someone wants to refactor the object model: +# too-few-public-methods +# no-self-use +# too-many-arguments +# too-many-locals +# too-many-branches +# pylint:disable=fixme, too-many-arguments, no-self-use +# pylint:disable=too-many-locals, too-many-branches, too-few-public-methods +"""Ansible module to register a kubernetes node to the cluster""" import os -import multiprocessing -import socket -from subprocess import check_output, Popen -from decimal import * DOCUMENTATION = ''' --- @@ -93,73 +102,170 @@ EXAMPLES = ''' class ClientConfigException(Exception): + """Client Configuration Exception""" pass -class ClientConfig: +class ClientConfig(object): + """ Representation of a client config + + Attributes: + config (dict): dictionary representing the client configuration + + Args: + client_opts (list of str): client options to use + module (AnsibleModule): + + Raises: + ClientConfigException: + """ def __init__(self, client_opts, module): kubectl = module.params['kubectl_cmd'] - _, output, error = module.run_command(kubectl + ["config", "view", "-o", "json"] + client_opts, check_rc = True) + _, output, _ = module.run_command((kubectl + + ["config", "view", "-o", "json"] + + client_opts), check_rc=True) self.config = json.loads(output) if not (bool(self.config['clusters']) or bool(self.config['contexts']) or bool(self.config['current-context']) or bool(self.config['users'])): - raise ClientConfigException(msg="Client config missing required " \ - "values", - output=output) + raise ClientConfigException( + "Client config missing required values: %s" % output + ) def current_context(self): + """ Gets the current context for the client config + + Returns: + str: The current context as set in the config + """ return self.config['current-context'] def section_has_value(self, section_name, value): + """ Test if specified section contains a value + + Args: + section_name (str): config section to test + value (str): value to test if present + Returns: + bool: True if successful, false otherwise + """ section = self.config[section_name] if isinstance(section, dict): return value in section else: val = next((item for item in section - if item['name'] == value), None) + if item['name'] == value), None) return val is not None def has_context(self, context): + """ Test if specified context exists in config + + Args: + context (str): value to test if present + Returns: + bool: True if successful, false otherwise + """ return self.section_has_value('contexts', context) def has_user(self, user): + """ Test if specified user exists in config + + Args: + context (str): value to test if present + Returns: + bool: True if successful, false otherwise + """ return self.section_has_value('users', user) def has_cluster(self, cluster): + """ Test if specified cluster exists in config + + Args: + context (str): value to test if present + Returns: + bool: True if successful, false otherwise + """ return self.section_has_value('clusters', cluster) def get_value_for_context(self, context, attribute): + """ Get the value of attribute in context + + Args: + context (str): context to search + attribute (str): attribute wanted + Returns: + str: The value for attribute in context + """ contexts = self.config['contexts'] if isinstance(contexts, dict): return contexts[context][attribute] else: return next((c['context'][attribute] for c in contexts - if c['name'] == context), None) + if c['name'] == context), None) def get_user_for_context(self, context): + """ Get the user attribute in context + + Args: + context (str): context to search + Returns: + str: The value for the attribute in context + """ return self.get_value_for_context(context, 'user') def get_cluster_for_context(self, context): + """ Get the cluster attribute in context + + Args: + context (str): context to search + Returns: + str: The value for the attribute in context + """ return self.get_value_for_context(context, 'cluster') def get_namespace_for_context(self, context): + """ Get the namespace attribute in context + + Args: + context (str): context to search + Returns: + str: The value for the attribute in context + """ return self.get_value_for_context(context, 'namespace') -class Util: +class Util(object): + """Utility methods""" @staticmethod def remove_empty_elements(mapping): + """ Recursively removes empty elements from a dict + + Args: + mapping (dict): dict to remove empty attributes from + Returns: + dict: A copy of the dict with empty elements removed + """ if isinstance(mapping, dict): - m = mapping.copy() + copy = mapping.copy() for key, val in mapping.iteritems(): if not val: - del m[key] - return m + del copy[key] + return copy else: return mapping -class NodeResources: +class NodeResources(object): + """ Kubernetes Node Resources + + Attributes: + resources (dict): A dictionary representing the node resources + + Args: + version (str): kubernetes api version + cpu (str): string representation of the cpu resources for the node + memory (str): string representation of the memory resources for the + node + """ def __init__(self, version, cpu=None, memory=None): if version == 'v1beta1': self.resources = dict(capacity=dict()) @@ -167,10 +273,31 @@ class NodeResources: self.resources['capacity']['memory'] = memory def get_resources(self): + """ Get the dict representing the node resources + + Returns: + dict: representation of the node resources with any empty + elements removed + """ return Util.remove_empty_elements(self.resources) -class NodeSpec: - def __init__(self, version, cpu=None, memory=None, cidr=None, externalID=None): +class NodeSpec(object): + """ Kubernetes Node Spec + + Attributes: + spec (dict): A dictionary representing the node resources + + Args: + version (str): kubernetes api version + cpu (str): string representation of the cpu resources for the node + memory (str): string representation of the memory resources for the + node + cidr (str): string representation of the cidr block available for + the node + externalID (str): The external id of the node + """ + def __init__(self, version, cpu=None, memory=None, cidr=None, + externalID=None): if version == 'v1beta3': self.spec = dict(podCIDR=cidr, externalID=externalID, capacity=dict()) @@ -178,67 +305,128 @@ class NodeSpec: self.spec['capacity']['memory'] = memory def get_spec(self): + """ Get the dict representing the node spec + + Returns: + dict: representation of the node spec with any empty elements + removed + """ return Util.remove_empty_elements(self.spec) -class NodeStatus: - def addAddresses(self, addressType, addresses): - addressList = [] +class NodeStatus(object): + """ Kubernetes Node Status + + Attributes: + status (dict): A dictionary representing the node status + + Args: + version (str): kubernetes api version + externalIPs (list, optional): externalIPs for the node + internalIPs (list, optional): internalIPs for the node + hostnames (list, optional): hostnames for the node + """ + def add_addresses(self, address_type, addresses): + """ Adds addresses of the specified type + + Args: + address_type (str): address type + addresses (list): addresses to add + """ + address_list = [] for address in addresses: - addressList.append(dict(type=addressType, address=address)) - return addressList + address_list.append(dict(type=address_type, address=address)) + return address_list - def __init__(self, version, externalIPs = [], internalIPs = [], - hostnames = []): + def __init__(self, version, externalIPs=None, internalIPs=None, + hostnames=None): if version == 'v1beta3': - self.status = dict(addresses = addAddresses('ExternalIP', - externalIPs) + - addAddresses('InternalIP', - internalIPs) + - addAddresses('Hostname', - hostnames)) + addresses = [] + if externalIPs is not None: + addresses += self.add_addresses('ExternalIP', externalIPs) + if internalIPs is not None: + addresses += self.add_addresses('InternalIP', internalIPs) + if hostnames is not None: + addresses += self.add_addresses('Hostname', hostnames) + + self.status = dict(addresses=addresses) def get_status(self): + """ Get the dict representing the node status + + Returns: + dict: representation of the node status with any empty elements + removed + """ return Util.remove_empty_elements(self.status) -class Node: - def __init__(self, module, client_opts, version='v1beta1', name=None, - hostIP = None, hostnames=[], externalIPs=[], internalIPs=[], - cpu=None, memory=None, labels=dict(), annotations=dict(), - podCIDR=None, externalID=None): +class Node(object): + """ Kubernetes Node + + Attributes: + status (dict): A dictionary representing the node + + Args: + module (AnsibleModule): + client_opts (list): client connection options + version (str, optional): kubernetes api version + node_name (str, optional): name for node + hostIP (str, optional): node host ip + hostnames (list, optional): hostnames for the node + externalIPs (list, optional): externalIPs for the node + internalIPs (list, optional): internalIPs for the node + cpu (str, optional): cpu resources for the node + memory (str, optional): memory resources for the node + labels (list, optional): labels for the node + annotations (list, optional): annotations for the node + podCIDR (list, optional): cidr block to use for pods + externalID (str, optional): external id of the node + """ + def __init__(self, module, client_opts, version='v1beta1', node_name=None, + hostIP=None, hostnames=None, externalIPs=None, + internalIPs=None, cpu=None, memory=None, labels=None, + annotations=None, podCIDR=None, externalID=None): self.module = module self.client_opts = client_opts if version == 'v1beta1': - self.node = dict(id = name, - kind = 'Node', - apiVersion = version, - hostIP = hostIP, - resources = NodeResources(version, cpu, memory), - cidr = podCIDR, - labels = labels, - annotations = annotations, - externalID = externalID - ) + self.node = dict(id=node_name, + kind='Node', + apiVersion=version, + hostIP=hostIP, + resources=NodeResources(version, cpu, memory), + cidr=podCIDR, + labels=labels, + annotations=annotations, + externalID=externalID) elif version == 'v1beta3': - metadata = dict(name = name, - labels = labels, - annotations = annotations - ) - self.node = dict(kind = 'Node', - apiVersion = version, - metadata = metadata, - spec = NodeSpec(version, cpu, memory, podCIDR, - externalID), - status = NodeStatus(version, externalIPs, - internalIPs, hostnames), - ) + metadata = dict(name=node_name, + labels=labels, + annotations=annotations) + self.node = dict(kind='Node', + apiVersion=version, + metadata=metadata, + spec=NodeSpec(version, cpu, memory, podCIDR, + externalID), + status=NodeStatus(version, externalIPs, + internalIPs, hostnames)) def get_name(self): + """ Get the name for the node + + Returns: + str: node name + """ if self.node['apiVersion'] == 'v1beta1': return self.node['id'] elif self.node['apiVersion'] == 'v1beta3': return self.node['name'] def get_node(self): + """ Get the dict representing the node + + Returns: + dict: representation of the node with any empty elements + removed + """ node = self.node.copy() if self.node['apiVersion'] == 'v1beta1': node['resources'] = self.node['resources'].get_resources() @@ -248,54 +436,82 @@ class Node: return Util.remove_empty_elements(node) def exists(self): + """ Tests if the node already exists + + Returns: + bool: True if node exists, otherwise False + """ kubectl = self.module.params['kubectl_cmd'] - _, output, error = self.module.run_command(kubectl + ["get", "nodes"] + self.client_opts, check_rc = True) + _, output, _ = self.module.run_command((kubectl + ["get", "nodes"] + + self.client_opts), + check_rc=True) if re.search(self.module.params['name'], output, re.MULTILINE): return True return False def create(self): + """ Creates the node + + Returns: + bool: True if node creation successful + """ kubectl = self.module.params['kubectl_cmd'] cmd = kubectl + self.client_opts + ['create', '-f', '-'] - rc, output, error = self.module.run_command(cmd, - data=self.module.jsonify(self.get_node())) - if rc != 0: + exit_code, output, error = self.module.run_command( + cmd, data=self.module.jsonify(self.get_node()) + ) + if exit_code != 0: if re.search("minion \"%s\" already exists" % self.get_name(), error): - self.module.exit_json(changed=False, - msg="node definition already exists", - node=self.get_node()) + self.module.exit_json(msg="node definition already exists", + changed=False, node=self.get_node()) else: - self.module.fail_json(msg="Node creation failed.", rc=rc, - output=output, error=error, - node=self.get_node()) + self.module.fail_json(msg="Node creation failed.", + exit_code=exit_code, + output=output, error=error, + node=self.get_node()) else: return True def main(): + """ main """ module = AnsibleModule( - argument_spec = dict( - name = dict(required = True, type = 'str'), - host_ip = dict(type = 'str'), - hostnames = dict(type = 'list', default = []), - external_ips = dict(type = 'list', default = []), - internal_ips = dict(type = 'list', default = []), - api_version = dict(type = 'str', default = 'v1beta1', # TODO: after kube rebase, we can default to v1beta3 - choices = ['v1beta1', 'v1beta3']), - cpu = dict(type = 'str'), - memory = dict(type = 'str'), - labels = dict(type = 'dict', default = {}), # TODO: needs documented - annotations = dict(type = 'dict', default = {}), # TODO: needs documented - pod_cidr = dict(type = 'str'), # TODO: needs documented - external_id = dict(type = 'str'), # TODO: needs documented - client_config = dict(type = 'str'), # TODO: needs documented - client_cluster = dict(type = 'str', default = 'master'), # TODO: needs documented - client_context = dict(type = 'str', default = 'default'), # TODO: needs documented - client_namespace = dict(type = 'str', default = 'default'), # TODO: needs documented - client_user = dict(type = 'str', default = 'system:openshift-client'), # TODO: needs documented - kubectl_cmd = dict(type = 'list', default = ['kubectl']) # TODO: needs documented + argument_spec=dict( + name=dict(required=True, type='str'), + host_ip=dict(type='str'), + hostnames=dict(type='list', default=[]), + external_ips=dict(type='list', default=[]), + internal_ips=dict(type='list', default=[]), + api_version=dict(type='str', default='v1beta1', + choices=['v1beta1', 'v1beta3']), + cpu=dict(type='str'), + memory=dict(type='str'), + # TODO: needs documented + labels=dict(type='dict', default={}), + # TODO: needs documented + annotations=dict(type='dict', default={}), + # TODO: needs documented + pod_cidr=dict(type='str'), + # TODO: needs documented + external_id=dict(type='str'), + # TODO: needs documented + client_config=dict(type='str'), + # TODO: needs documented + client_cluster=dict(type='str', default='master'), + # TODO: needs documented + client_context=dict(type='str', default='default'), + # TODO: needs documented + client_namespace=dict(type='str', default='default'), + # TODO: needs documented + client_user=dict(type='str', default='system:openshift-client'), + # TODO: needs documented + kubectl_cmd=dict(type='list', default=['kubectl']), + # TODO: needs documented + kubeconfig_flag=dict(type='str'), + # TODO: needs documented + default_client_config=dict(type='str') ), - mutually_exclusive = [ + mutually_exclusive=[ ['host_ip', 'external_ips'], ['host_ip', 'internal_ips'], ['host_ip', 'hostnames'], @@ -303,7 +519,10 @@ def main(): supports_check_mode=True ) - user_has_client_config = os.path.exists(os.path.expanduser('~/.kube/.kubeconfig')) + client_config = '~/.kube/.kubeconfig' + if 'default_client_config' in module.params: + client_config = module.params['default_client_config'] + user_has_client_config = os.path.exists(os.path.expanduser(client_config)) if not (user_has_client_config or module.params['client_config']): module.fail_json(msg="Could not locate client configuration, " "client_config must be specified if " @@ -311,12 +530,17 @@ def main(): client_opts = [] if module.params['client_config']: - client_opts.append("--kubeconfig=%s" % module.params['client_config']) + kubeconfig_flag = '--kubeconfig' + if 'kubeconfig_flag' in module.params: + kubeconfig_flag = module.params['kubeconfig_flag'] + client_opts.append(kubeconfig_flag + '=' + + os.path.expanduser(module.params['client_config'])) try: config = ClientConfig(client_opts, module) - except ClientConfigException as e: - module.fail_json(msg="Failed to get client configuration", exception=e) + except ClientConfigException as ex: + module.fail_json(msg="Failed to get client configuration", + exception=str(ex)) client_context = module.params['client_context'] if config.has_context(client_context): @@ -369,7 +593,8 @@ def main(): module.fail_json(msg="Unknown error creating node", node=node.get_node()) - +# ignore pylint errors related to the module_utils import +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import # import module snippets from ansible.module_utils.basic import * if __name__ == '__main__': diff --git a/roles/openshift_register_nodes/tasks/main.yml b/roles/openshift_register_nodes/tasks/main.yml index 85bead16d..d4d72d126 100644 --- a/roles/openshift_register_nodes/tasks/main.yml +++ b/roles/openshift_register_nodes/tasks/main.yml @@ -39,7 +39,8 @@ - name: Register unregistered nodes kubernetes_register_node: - kubectl_cmd: ['openshift', 'kube'] + kubectl_cmd: ['osc'] + default_client_config: '~/.config/openshift/.config' name: "{{ item.openshift.common.hostname }}" api_version: "{{ openshift_kube_api_version }}" cpu: "{{ item.openshift.node.resources_cpu | default(None) }}" |