summaryrefslogtreecommitdiffstats
path: root/roles/lib_utils/library
diff options
context:
space:
mode:
Diffstat (limited to 'roles/lib_utils/library')
-rw-r--r--roles/lib_utils/library/kubeclient_ca.py88
-rw-r--r--roles/lib_utils/library/modify_yaml.py117
-rw-r--r--roles/lib_utils/library/os_firewall_manage_iptables.py283
-rw-r--r--roles/lib_utils/library/rpm_q.py72
4 files changed, 560 insertions, 0 deletions
diff --git a/roles/lib_utils/library/kubeclient_ca.py b/roles/lib_utils/library/kubeclient_ca.py
new file mode 100644
index 000000000..a89a5574f
--- /dev/null
+++ b/roles/lib_utils/library/kubeclient_ca.py
@@ -0,0 +1,88 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+''' kubeclient_ca ansible module '''
+
+import base64
+import yaml
+from ansible.module_utils.basic import AnsibleModule
+
+
+DOCUMENTATION = '''
+---
+module: kubeclient_ca
+short_description: Modify kubeclient certificate-authority-data
+author: Andrew Butcher
+requirements: [ ]
+'''
+EXAMPLES = '''
+- kubeclient_ca:
+ client_path: /etc/origin/master/admin.kubeconfig
+ ca_path: /etc/origin/master/ca-bundle.crt
+
+- slurp:
+ src: /etc/origin/master/ca-bundle.crt
+ register: ca_data
+- kubeclient_ca:
+ client_path: /etc/origin/master/admin.kubeconfig
+ ca_data: "{{ ca_data.content }}"
+'''
+
+
+def main():
+ ''' Modify kubeconfig located at `client_path`, setting the
+ certificate authority data to specified `ca_data` or contents of
+ `ca_path`.
+ '''
+
+ module = AnsibleModule( # noqa: F405
+ argument_spec=dict(
+ client_path=dict(required=True),
+ ca_data=dict(required=False, default=None),
+ ca_path=dict(required=False, default=None),
+ backup=dict(required=False, default=True, type='bool'),
+ ),
+ supports_check_mode=True,
+ mutually_exclusive=[['ca_data', 'ca_path']],
+ required_one_of=[['ca_data', 'ca_path']]
+ )
+
+ client_path = module.params['client_path']
+ ca_data = module.params['ca_data']
+ ca_path = module.params['ca_path']
+ backup = module.params['backup']
+
+ try:
+ with open(client_path) as client_config_file:
+ client_config_data = yaml.safe_load(client_config_file.read())
+
+ if ca_data is None:
+ with open(ca_path) as ca_file:
+ ca_data = base64.standard_b64encode(ca_file.read())
+
+ changes = []
+ # Naively update the CA information for each cluster in the
+ # kubeconfig.
+ for cluster in client_config_data['clusters']:
+ if cluster['cluster']['certificate-authority-data'] != ca_data:
+ cluster['cluster']['certificate-authority-data'] = ca_data
+ changes.append(cluster['name'])
+
+ if not module.check_mode:
+ if len(changes) > 0 and backup:
+ module.backup_local(client_path)
+
+ with open(client_path, 'w') as client_config_file:
+ client_config_string = yaml.dump(client_config_data, default_flow_style=False)
+ client_config_string = client_config_string.replace('\'\'', '""')
+ client_config_file.write(client_config_string)
+
+ return module.exit_json(changed=(len(changes) > 0))
+
+ # ignore broad-except error to avoid stack trace to ansible user
+ # pylint: disable=broad-except
+ except Exception as error:
+ return module.fail_json(msg=str(error))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/roles/lib_utils/library/modify_yaml.py b/roles/lib_utils/library/modify_yaml.py
new file mode 100644
index 000000000..9b8f9ba33
--- /dev/null
+++ b/roles/lib_utils/library/modify_yaml.py
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+''' modify_yaml ansible module '''
+
+import yaml
+
+# ignore pylint errors related to the module_utils import
+# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import
+from ansible.module_utils.basic import * # noqa: F402,F403
+
+
+DOCUMENTATION = '''
+---
+module: modify_yaml
+short_description: Modify yaml key value pairs
+author: Andrew Butcher
+requirements: [ ]
+'''
+EXAMPLES = '''
+- modify_yaml:
+ dest: /etc/origin/master/master-config.yaml
+ yaml_key: 'kubernetesMasterConfig.masterCount'
+ yaml_value: 2
+'''
+
+
+def set_key(yaml_data, yaml_key, yaml_value):
+ ''' Updates a parsed yaml structure setting a key to a value.
+
+ :param yaml_data: yaml structure to modify.
+ :type yaml_data: dict
+ :param yaml_key: Key to modify.
+ :type yaml_key: mixed
+ :param yaml_value: Value use for yaml_key.
+ :type yaml_value: mixed
+ :returns: Changes to the yaml_data structure
+ :rtype: dict(tuple())
+ '''
+ changes = []
+ ptr = yaml_data
+ final_key = yaml_key.split('.')[-1]
+ for key in yaml_key.split('.'):
+ # Key isn't present and we're not on the final key. Set to empty dictionary.
+ if key not in ptr and key != final_key:
+ ptr[key] = {}
+ ptr = ptr[key]
+ # Current key is the final key. Update value.
+ elif key == final_key:
+ if (key in ptr and module.safe_eval(ptr[key]) != yaml_value) or (key not in ptr): # noqa: F405
+ ptr[key] = yaml_value
+ changes.append((yaml_key, yaml_value))
+ else:
+ # Next value is None and we're not on the final key.
+ # Turn value into an empty dictionary.
+ if ptr[key] is None and key != final_key:
+ ptr[key] = {}
+ ptr = ptr[key]
+ return changes
+
+
+def main():
+ ''' Modify key (supplied in jinja2 dot notation) in yaml file, setting
+ the key to the desired value.
+ '''
+
+ # disabling pylint errors for global-variable-undefined and invalid-name
+ # for 'global module' usage, since it is required to use ansible_facts
+ # pylint: disable=global-variable-undefined, invalid-name,
+ # redefined-outer-name
+ global module
+
+ module = AnsibleModule( # noqa: F405
+ argument_spec=dict(
+ dest=dict(required=True),
+ yaml_key=dict(required=True),
+ yaml_value=dict(required=True),
+ backup=dict(required=False, default=True, type='bool'),
+ ),
+ supports_check_mode=True,
+ )
+
+ dest = module.params['dest']
+ yaml_key = module.params['yaml_key']
+ yaml_value = module.safe_eval(module.params['yaml_value'])
+ backup = module.params['backup']
+
+ # Represent null values as an empty string.
+ # pylint: disable=missing-docstring, unused-argument
+ def none_representer(dumper, data):
+ return yaml.ScalarNode(tag=u'tag:yaml.org,2002:null', value=u'')
+
+ yaml.add_representer(type(None), none_representer)
+
+ try:
+ with open(dest) as yaml_file:
+ yaml_data = yaml.safe_load(yaml_file.read())
+
+ changes = set_key(yaml_data, yaml_key, yaml_value)
+
+ if len(changes) > 0:
+ if backup:
+ module.backup_local(dest)
+ with open(dest, 'w') as yaml_file:
+ yaml_string = yaml.dump(yaml_data, default_flow_style=False)
+ yaml_string = yaml_string.replace('\'\'', '""')
+ yaml_file.write(yaml_string)
+
+ return module.exit_json(changed=(len(changes) > 0), changes=changes)
+
+ # ignore broad-except error to avoid stack trace to ansible user
+ # pylint: disable=broad-except
+ except Exception as error:
+ return module.fail_json(msg=str(error))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/roles/lib_utils/library/os_firewall_manage_iptables.py b/roles/lib_utils/library/os_firewall_manage_iptables.py
new file mode 100644
index 000000000..aeee3ede8
--- /dev/null
+++ b/roles/lib_utils/library/os_firewall_manage_iptables.py
@@ -0,0 +1,283 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# pylint: disable=fixme, missing-docstring
+import subprocess
+
+DOCUMENTATION = '''
+---
+module: os_firewall_manage_iptables
+short_description: This module manages iptables rules for a given chain
+author: Jason DeTiberus
+requirements: [ ]
+'''
+EXAMPLES = '''
+'''
+
+
+class IpTablesError(Exception):
+ def __init__(self, msg, cmd, exit_code, output):
+ super(IpTablesError, self).__init__(msg)
+ self.msg = msg
+ self.cmd = cmd
+ self.exit_code = exit_code
+ self.output = output
+
+
+class IpTablesAddRuleError(IpTablesError):
+ pass
+
+
+class IpTablesRemoveRuleError(IpTablesError):
+ def __init__(self, chain, msg, cmd, exit_code, output): # pylint: disable=too-many-arguments, line-too-long, redefined-outer-name
+ super(IpTablesRemoveRuleError, self).__init__(msg, cmd, exit_code,
+ output)
+ self.chain = chain
+
+
+class IpTablesSaveError(IpTablesError):
+ pass
+
+
+class IpTablesCreateChainError(IpTablesError):
+ def __init__(self, chain, msg, cmd, exit_code, output): # pylint: disable=too-many-arguments, line-too-long, redefined-outer-name
+ super(IpTablesCreateChainError, self).__init__(msg, cmd, exit_code,
+ output)
+ self.chain = chain
+
+
+class IpTablesCreateJumpRuleError(IpTablesError):
+ def __init__(self, chain, msg, cmd, exit_code, output): # pylint: disable=too-many-arguments, line-too-long, redefined-outer-name
+ super(IpTablesCreateJumpRuleError, self).__init__(msg, cmd, exit_code,
+ output)
+ self.chain = chain
+
+
+# TODO: implement rollbacks for any events that were successful and an
+# exception was thrown later. For example, when the chain is created
+# successfully, but the add/remove rule fails.
+class IpTablesManager(object): # pylint: disable=too-many-instance-attributes
+ def __init__(self, module):
+ self.module = module
+ self.ip_version = module.params['ip_version']
+ self.check_mode = module.check_mode
+ self.chain = module.params['chain']
+ self.create_jump_rule = module.params['create_jump_rule']
+ self.jump_rule_chain = module.params['jump_rule_chain']
+ self.cmd = self.gen_cmd()
+ self.save_cmd = self.gen_save_cmd()
+ self.output = []
+ self.changed = False
+
+ def save(self):
+ try:
+ self.output.append(subprocess.check_output(self.save_cmd, stderr=subprocess.STDOUT))
+ except subprocess.CalledProcessError as ex:
+ raise IpTablesSaveError(
+ msg="Failed to save iptables rules",
+ cmd=ex.cmd, exit_code=ex.returncode, output=ex.output)
+
+ def verify_chain(self):
+ if not self.chain_exists():
+ self.create_chain()
+ if self.create_jump_rule and not self.jump_rule_exists():
+ self.create_jump()
+
+ def add_rule(self, port, proto):
+ rule = self.gen_rule(port, proto)
+ if not self.rule_exists(rule):
+ self.verify_chain()
+
+ if self.check_mode:
+ self.changed = True
+ self.output.append("Create rule for %s %s" % (proto, port))
+ else:
+ cmd = self.cmd + ['-A'] + rule
+ try:
+ self.output.append(subprocess.check_output(cmd))
+ self.changed = True
+ self.save()
+ except subprocess.CalledProcessError as ex:
+ raise IpTablesCreateChainError(
+ chain=self.chain,
+ msg="Failed to create rule for "
+ "%s %s" % (proto, port),
+ cmd=ex.cmd, exit_code=ex.returncode,
+ output=ex.output)
+
+ def remove_rule(self, port, proto):
+ rule = self.gen_rule(port, proto)
+ if self.rule_exists(rule):
+ if self.check_mode:
+ self.changed = True
+ self.output.append("Remove rule for %s %s" % (proto, port))
+ else:
+ cmd = self.cmd + ['-D'] + rule
+ try:
+ self.output.append(subprocess.check_output(cmd))
+ self.changed = True
+ self.save()
+ except subprocess.CalledProcessError as ex:
+ raise IpTablesRemoveRuleError(
+ chain=self.chain,
+ msg="Failed to remove rule for %s %s" % (proto, port),
+ cmd=ex.cmd, exit_code=ex.returncode, output=ex.output)
+
+ def rule_exists(self, rule):
+ check_cmd = self.cmd + ['-C'] + rule
+ return True if subprocess.call(check_cmd) == 0 else False
+
+ @staticmethod
+ def port_as_argument(port):
+ if isinstance(port, int):
+ return str(port)
+ if isinstance(port, basestring): # noqa: F405
+ return port.replace('-', ":")
+ return port
+
+ def gen_rule(self, port, proto):
+ return [self.chain, '-p', proto, '-m', 'state', '--state', 'NEW',
+ '-m', proto, '--dport', IpTablesManager.port_as_argument(port), '-j', 'ACCEPT']
+
+ def create_jump(self):
+ if self.check_mode:
+ self.changed = True
+ self.output.append("Create jump rule for chain %s" % self.chain)
+ else:
+ try:
+ cmd = self.cmd + ['-L', self.jump_rule_chain, '--line-numbers']
+ output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+
+ # break the input rules into rows and columns
+ input_rules = [s.split() for s in to_native(output).split('\n')]
+
+ # Find the last numbered rule
+ last_rule_num = None
+ last_rule_target = None
+ for rule in input_rules[:-1]:
+ if rule:
+ try:
+ last_rule_num = int(rule[0])
+ except ValueError:
+ continue
+ last_rule_target = rule[1]
+
+ # Naively assume that if the last row is a REJECT or DROP rule,
+ # then we can insert our rule right before it, otherwise we
+ # assume that we can just append the rule.
+ if (last_rule_num and last_rule_target and last_rule_target in ['REJECT', 'DROP']):
+ # insert rule
+ cmd = self.cmd + ['-I', self.jump_rule_chain,
+ str(last_rule_num)]
+ else:
+ # append rule
+ cmd = self.cmd + ['-A', self.jump_rule_chain]
+ cmd += ['-j', self.chain]
+ output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+ self.changed = True
+ self.output.append(output)
+ self.save()
+ except subprocess.CalledProcessError as ex:
+ if '--line-numbers' in ex.cmd:
+ raise IpTablesCreateJumpRuleError(
+ chain=self.chain,
+ msg=("Failed to query existing " +
+ self.jump_rule_chain +
+ " rules to determine jump rule location"),
+ cmd=ex.cmd, exit_code=ex.returncode,
+ output=ex.output)
+ else:
+ raise IpTablesCreateJumpRuleError(
+ chain=self.chain,
+ msg=("Failed to create jump rule for chain " +
+ self.chain),
+ cmd=ex.cmd, exit_code=ex.returncode,
+ output=ex.output)
+
+ def create_chain(self):
+ if self.check_mode:
+ self.changed = True
+ self.output.append("Create chain %s" % self.chain)
+ else:
+ try:
+ cmd = self.cmd + ['-N', self.chain]
+ self.output.append(subprocess.check_output(cmd, stderr=subprocess.STDOUT))
+ self.changed = True
+ self.output.append("Successfully created chain %s" %
+ self.chain)
+ self.save()
+ except subprocess.CalledProcessError as ex:
+ raise IpTablesCreateChainError(
+ chain=self.chain,
+ msg="Failed to create chain: %s" % self.chain,
+ cmd=ex.cmd, exit_code=ex.returncode, output=ex.output
+ )
+
+ def jump_rule_exists(self):
+ cmd = self.cmd + ['-C', self.jump_rule_chain, '-j', self.chain]
+ return True if subprocess.call(cmd) == 0 else False
+
+ def chain_exists(self):
+ cmd = self.cmd + ['-L', self.chain]
+ return True if subprocess.call(cmd) == 0 else False
+
+ def gen_cmd(self):
+ cmd = 'iptables' if self.ip_version == 'ipv4' else 'ip6tables'
+ # Include -w (wait for xtables lock) in default arguments.
+ default_args = ['-w']
+ return ["/usr/sbin/%s" % cmd] + default_args
+
+ def gen_save_cmd(self): # pylint: disable=no-self-use
+ return ['/usr/libexec/iptables/iptables.init', 'save']
+
+
+def main():
+ module = AnsibleModule( # noqa: F405
+ argument_spec=dict(
+ name=dict(required=True),
+ action=dict(required=True, choices=['add', 'remove',
+ 'verify_chain']),
+ chain=dict(required=False, default='OS_FIREWALL_ALLOW'),
+ create_jump_rule=dict(required=False, type='bool', default=True),
+ jump_rule_chain=dict(required=False, default='INPUT'),
+ protocol=dict(required=False, choices=['tcp', 'udp']),
+ port=dict(required=False, type='str'),
+ ip_version=dict(required=False, default='ipv4',
+ choices=['ipv4', 'ipv6']),
+ ),
+ supports_check_mode=True
+ )
+
+ action = module.params['action']
+ protocol = module.params['protocol']
+ port = module.params['port']
+
+ if action in ['add', 'remove']:
+ if not protocol:
+ error = "protocol is required when action is %s" % action
+ module.fail_json(msg=error)
+ if not port:
+ error = "port is required when action is %s" % action
+ module.fail_json(msg=error)
+
+ iptables_manager = IpTablesManager(module)
+
+ try:
+ if action == 'add':
+ iptables_manager.add_rule(port, protocol)
+ elif action == 'remove':
+ iptables_manager.remove_rule(port, protocol)
+ elif action == 'verify_chain':
+ iptables_manager.verify_chain()
+ except IpTablesError as ex:
+ module.fail_json(msg=ex.msg)
+
+ return module.exit_json(changed=iptables_manager.changed,
+ output=iptables_manager.output)
+
+
+# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, wrong-import-position
+# import module snippets
+from ansible.module_utils.basic import * # noqa: F403,E402
+from ansible.module_utils._text import to_native # noqa: E402
+if __name__ == '__main__':
+ main()
diff --git a/roles/lib_utils/library/rpm_q.py b/roles/lib_utils/library/rpm_q.py
new file mode 100644
index 000000000..3dec50fc2
--- /dev/null
+++ b/roles/lib_utils/library/rpm_q.py
@@ -0,0 +1,72 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2015, Tobias Florek <tob@butter.sh>
+# Licensed under the terms of the MIT License
+"""
+An ansible module to query the RPM database. For use, when yum/dnf are not
+available.
+"""
+
+# pylint: disable=redefined-builtin,wildcard-import,unused-wildcard-import
+from ansible.module_utils.basic import * # noqa: F403
+
+DOCUMENTATION = """
+---
+module: rpm_q
+short_description: Query the RPM database
+author: Tobias Florek
+options:
+ name:
+ description:
+ - The name of the package to query
+ required: true
+ state:
+ description:
+ - Whether the package is supposed to be installed or not
+ choices: [present, absent]
+ default: present
+"""
+
+EXAMPLES = """
+- rpm_q: name=ansible state=present
+- rpm_q: name=ansible state=absent
+"""
+
+RPM_BINARY = '/bin/rpm'
+
+
+def main():
+ """
+ Checks rpm -q for the named package and returns the installed packages
+ or None if not installed.
+ """
+ module = AnsibleModule( # noqa: F405
+ argument_spec=dict(
+ name=dict(required=True),
+ state=dict(default='present', choices=['present', 'absent'])
+ ),
+ supports_check_mode=True
+ )
+
+ name = module.params['name']
+ state = module.params['state']
+
+ # pylint: disable=invalid-name
+ rc, out, err = module.run_command([RPM_BINARY, '-q', name])
+
+ installed = out.rstrip('\n').split('\n')
+
+ if rc != 0:
+ if state == 'present':
+ module.fail_json(msg="%s is not installed" % name, stdout=out, stderr=err, rc=rc)
+ else:
+ module.exit_json(changed=False)
+ elif state == 'present':
+ module.exit_json(changed=False, installed_versions=installed)
+ else:
+ module.fail_json(msg="%s is installed", installed_versions=installed)
+
+
+if __name__ == '__main__':
+ main()