diff options
Diffstat (limited to 'roles')
123 files changed, 5016 insertions, 983 deletions
diff --git a/roles/docker/defaults/main.yml b/roles/docker/defaults/main.yml new file mode 100644 index 000000000..1b26af0dd --- /dev/null +++ b/roles/docker/defaults/main.yml @@ -0,0 +1,2 @@ +--- +docker_version: ''
\ No newline at end of file diff --git a/roles/docker/tasks/main.yml b/roles/docker/tasks/main.yml index 9709c5014..9cf949d65 100644 --- a/roles/docker/tasks/main.yml +++ b/roles/docker/tasks/main.yml @@ -1,32 +1,29 @@ --- # tasks file for docker -# Avoid docker 1.9 when installing origin < 1.2 or OSE < 3.2 on RHEL/Centos and -# See: https://bugzilla.redhat.com/show_bug.cgi?id=1304038 +- name: Get current installed version if docker_version is specified + command: "{{ repoquery_cmd }} --installed --qf '%{version}' docker" + when: not openshift.common.is_atomic | bool and docker_version != '' + register: docker_version_result + changed_when: false -- name: Default to latest docker for 1.2/3.2 or Fedora - set_fact: - docker_version: '' - when: openshift.common.version_gte_3_2_or_1_2 | bool or ansible_distribution == 'Fedora' +- name: Downgrade docker if necessary + command: "{{ ansible_pkg_mgr }} downgrade -y docker-{{ docker_version }}" + register: docker_downgrade_result + when: not docker_version_result | skipped and docker_version_result | default('0.0', True) | version_compare(docker_version, 'gt') -- name: Gather latest version of docker - shell: > - yum list available -e 0 -q "docker" 2>&1 | tail -n +2 | awk '{ print $2 }' | sort -r | tr '\n' ' ' | tail -n 1 - register: latest_docker - when: not openshift.common.version_gte_3_2_or_1_2 | bool and ansible_distribution != 'Fedora' - -- name: Check if Docker 1.9 is the latest - set_fact: - docker19_is_latest: "{{ True if '1.9' in latest_docker.stdout else False }}" - when: not openshift.common.version_gte_3_2_or_1_2 | bool and ansible_distribution != 'Fedora' +- name: Install docker + action: "{{ ansible_pkg_mgr }} name=docker{{ '-' + docker_version if docker_version != '' else '' }} state=present" + when: not openshift.common.is_atomic | bool and not docker_downgrade_result | changed -- set_fact: - docker_version: "{{ '-1.8.2' if docker19_is_latest | bool else ''}}" - when: not openshift.common.version_gte_3_2_or_1_2 | bool and ansible_distribution != 'Fedora' +- stat: path=/etc/sysconfig/docker + register: docker_check + when: docker_downgrade_result | changed -- name: Install docker - action: "{{ ansible_pkg_mgr }} name=docker{{ docker_version }} state=present" - when: not openshift.common.is_atomic | bool +- name: Remove deferred deletion for downgrades from 1.9 + command: > + sed -i 's/--storage-opt dm.use_deferred_deletion=true//' /etc/sysconfig/docker-storage + when: docker_downgrade_result | changed and docker_check.stat.exists | bool and docker_version_result | default('0.0', True) | version_compare('1.9', '>=') and docker_version | version_compare('1.9', '<') - name: enable and start the docker service service: diff --git a/roles/docker/vars/main.yml b/roles/docker/vars/main.yml index 162487545..606cdb9b9 100644 --- a/roles/docker/vars/main.yml +++ b/roles/docker/vars/main.yml @@ -1,3 +1,3 @@ --- - +repoquery_cmd: "{{ 'dnf repoquery --latest-limit 1 -d 0' if ansible_pkg_mgr == 'dnf' else 'repoquery' }}" udevw_udevd_dir: /etc/systemd/system/systemd-udevd.service.d diff --git a/roles/docker_storage_setup/tasks/main.yml b/roles/docker_storage_setup/tasks/main.yml index 8af9bbe1e..21c80e36e 100755 --- a/roles/docker_storage_setup/tasks/main.yml +++ b/roles/docker_storage_setup/tasks/main.yml @@ -2,7 +2,7 @@ - name: Ensure docker is installed action: "{{ ansible_pkg_mgr }} name={{ item }} state=present" with_items: - - docker + - docker-1.8.2 # Docker doesn't seem to start cleanly the first time run # when loopback (not directlvm) is configured. Putting in an diff --git a/roles/etcd/tasks/main.yml b/roles/etcd/tasks/main.yml index 064544b03..afec6b30b 100644 --- a/roles/etcd/tasks/main.yml +++ b/roles/etcd/tasks/main.yml @@ -39,7 +39,8 @@ - name: Check for etcd service presence command: systemctl show etcd.service register: etcd_show - + changed_when: false + - name: Mask system etcd when containerized when: openshift.common.is_containerized | bool and 'LoadState=not-found' not in etcd_show.stdout command: systemctl mask etcd diff --git a/roles/etcd/templates/etcd.docker.service b/roles/etcd/templates/etcd.docker.service index defc070bd..b8dbefa64 100644 --- a/roles/etcd/templates/etcd.docker.service +++ b/roles/etcd/templates/etcd.docker.service @@ -13,4 +13,4 @@ SyslogIdentifier=etcd_container Restart=always [Install] -WantedBy=multi-user.target +WantedBy=docker.service diff --git a/roles/lib_openshift_api/build/ansible/edit.py b/roles/lib_openshift_api/build/ansible/edit.py new file mode 100644 index 000000000..943fa47a6 --- /dev/null +++ b/roles/lib_openshift_api/build/ansible/edit.py @@ -0,0 +1,84 @@ +# pylint: skip-file + +def main(): + ''' + ansible oc module for services + ''' + + module = AnsibleModule( + argument_spec=dict( + kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), + state=dict(default='present', type='str', + choices=['present']), + debug=dict(default=False, type='bool'), + namespace=dict(default='default', type='str'), + name=dict(default=None, required=True, type='str'), + kind=dict(required=True, + type='str', + choices=['dc', 'deploymentconfig', + 'svc', 'service', + 'scc', 'securitycontextconstraints', + 'ns', 'namespace', 'project', 'projects', + 'is', 'imagestream', + 'istag', 'imagestreamtag', + 'bc', 'buildconfig', + 'routes', + 'node', + 'secret', + ]), + file_name=dict(default=None, type='str'), + file_format=dict(default='yaml', type='str'), + content=dict(default=None, required=True, type='dict'), + force=dict(default=False, type='bool'), + ), + supports_check_mode=True, + ) + ocedit = Edit(module.params['kind'], + module.params['namespace'], + module.params['name'], + kubeconfig=module.params['kubeconfig'], + verbose=module.params['debug']) + + state = module.params['state'] + + api_rval = ocedit.get() + + ######## + # Create + ######## + if not Utils.exists(api_rval['results'], module.params['name']): + module.fail_json(msg=api_rval) + + ######## + # Update + ######## + api_rval = ocedit.update(module.params['file_name'], + module.params['content'], + module.params['force'], + module.params['file_format']) + + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + if api_rval.has_key('updated') and not api_rval['updated']: + module.exit_json(changed=False, results=api_rval, state="present") + + # return the created object + api_rval = ocedit.get() + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + module.exit_json(changed=True, results=api_rval, state="present") + + module.exit_json(failed=True, + changed=False, + results='Unknown state passed. %s' % state, + state="unknown") + +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled +# import module snippets. This are required +from ansible.module_utils.basic import * + +main() diff --git a/roles/lib_openshift_api/build/ansible/obj.py b/roles/lib_openshift_api/build/ansible/obj.py new file mode 100644 index 000000000..a14ac0e43 --- /dev/null +++ b/roles/lib_openshift_api/build/ansible/obj.py @@ -0,0 +1,139 @@ +# pylint: skip-file + +# pylint: disable=too-many-branches +def main(): + ''' + ansible oc module for services + ''' + + module = AnsibleModule( + argument_spec=dict( + kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), + state=dict(default='present', type='str', + choices=['present', 'absent', 'list']), + debug=dict(default=False, type='bool'), + namespace=dict(default='default', type='str'), + name=dict(default=None, type='str'), + files=dict(default=None, type='list'), + kind=dict(required=True, + type='str', + choices=['dc', 'deploymentconfig', + 'svc', 'service', + 'scc', 'securitycontextconstraints', + 'ns', 'namespace', 'project', 'projects', + 'is', 'imagestream', + 'istag', 'imagestreamtag', + 'bc', 'buildconfig', + 'routes', + 'node', + 'secret', + ]), + delete_after=dict(default=False, type='bool'), + content=dict(default=None, type='dict'), + force=dict(default=False, type='bool'), + ), + mutually_exclusive=[["content", "files"]], + + supports_check_mode=True, + ) + ocobj = OCObject(module.params['kind'], + module.params['namespace'], + module.params['name'], + kubeconfig=module.params['kubeconfig'], + verbose=module.params['debug']) + + state = module.params['state'] + + api_rval = ocobj.get() + + ##### + # Get + ##### + if state == 'list': + module.exit_json(changed=False, results=api_rval['results'], state="list") + + if not module.params['name']: + module.fail_json(msg='Please specify a name when state is absent|present.') + ######## + # Delete + ######## + if state == 'absent': + if not Utils.exists(api_rval['results'], module.params['name']): + module.exit_json(changed=False, state="absent") + + if module.check_mode: + module.exit_json(change=False, msg='Would have performed a delete.') + + api_rval = ocobj.delete() + module.exit_json(changed=True, results=api_rval, state="absent") + + if state == 'present': + ######## + # Create + ######## + if not Utils.exists(api_rval['results'], module.params['name']): + + if module.check_mode: + module.exit_json(change=False, msg='Would have performed a create.') + + # Create it here + api_rval = ocobj.create(module.params['files'], module.params['content']) + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + # return the created object + api_rval = ocobj.get() + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + # Remove files + if module.params['files'] and module.params['delete_after']: + Utils.cleanup(module.params['files']) + + module.exit_json(changed=True, results=api_rval, state="present") + + ######## + # Update + ######## + # if a file path is passed, use it. + update = ocobj.needs_update(module.params['files'], module.params['content']) + if not isinstance(update, bool): + module.fail_json(msg=update) + + # No changes + if not update: + if module.params['files'] and module.params['delete_after']: + Utils.cleanup(module.params['files']) + + module.exit_json(changed=False, results=api_rval['results'][0], state="present") + + if module.check_mode: + module.exit_json(change=False, msg='Would have performed an update.') + + api_rval = ocobj.update(module.params['files'], + module.params['content'], + module.params['force']) + + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + # return the created object + api_rval = ocobj.get() + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + module.exit_json(changed=True, results=api_rval, state="present") + + module.exit_json(failed=True, + changed=False, + results='Unknown state passed. %s' % state, + state="unknown") + +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled +# import module snippets. This are required +from ansible.module_utils.basic import * + +main() diff --git a/roles/lib_openshift_api/build/ansible/secret.py b/roles/lib_openshift_api/build/ansible/secret.py new file mode 100644 index 000000000..8df7bbc64 --- /dev/null +++ b/roles/lib_openshift_api/build/ansible/secret.py @@ -0,0 +1,121 @@ +# pylint: skip-file + +# pylint: disable=too-many-branches +def main(): + ''' + ansible oc module for secrets + ''' + + module = AnsibleModule( + argument_spec=dict( + kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), + state=dict(default='present', type='str', + choices=['present', 'absent', 'list']), + debug=dict(default=False, type='bool'), + namespace=dict(default='default', type='str'), + name=dict(default=None, type='str'), + files=dict(default=None, type='list'), + delete_after=dict(default=False, type='bool'), + contents=dict(default=None, type='list'), + force=dict(default=False, type='bool'), + ), + mutually_exclusive=[["contents", "files"]], + + supports_check_mode=True, + ) + occmd = Secret(module.params['namespace'], + module.params['name'], + kubeconfig=module.params['kubeconfig'], + verbose=module.params['debug']) + + state = module.params['state'] + + api_rval = occmd.get() + + ##### + # Get + ##### + if state == 'list': + module.exit_json(changed=False, results=api_rval['results'], state="list") + + if not module.params['name']: + module.fail_json(msg='Please specify a name when state is absent|present.') + ######## + # Delete + ######## + if state == 'absent': + if not Utils.exists(api_rval['results'], module.params['name']): + module.exit_json(changed=False, state="absent") + + if module.check_mode: + module.exit_json(change=False, msg='Would have performed a delete.') + + api_rval = occmd.delete() + module.exit_json(changed=True, results=api_rval, state="absent") + + + if state == 'present': + if module.params['files']: + files = module.params['files'] + elif module.params['contents']: + files = Utils.create_files_from_contents(module.params['contents']) + else: + module.fail_json(msg='Either specify files or contents.') + + ######## + # Create + ######## + if not Utils.exists(api_rval['results'], module.params['name']): + + if module.check_mode: + module.exit_json(change=False, msg='Would have performed a create.') + + api_rval = occmd.create(module.params['files'], module.params['contents']) + + # Remove files + if files and module.params['delete_after']: + Utils.cleanup(files) + + module.exit_json(changed=True, results=api_rval, state="present") + + ######## + # Update + ######## + secret = occmd.prep_secret(module.params['files'], module.params['contents']) + + if secret['returncode'] != 0: + module.fail_json(msg=secret) + + if Utils.check_def_equal(secret['results'], api_rval['results'][0]): + + # Remove files + if files and module.params['delete_after']: + Utils.cleanup(files) + + module.exit_json(changed=False, results=secret['results'], state="present") + + if module.check_mode: + module.exit_json(change=False, msg='Would have performed an update.') + + api_rval = occmd.update(files, force=module.params['force']) + + # Remove files + if secret and module.params['delete_after']: + Utils.cleanup(files) + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + + module.exit_json(changed=True, results=api_rval, state="present") + + module.exit_json(failed=True, + changed=False, + results='Unknown state passed. %s' % state, + state="unknown") + +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled +# import module snippets. This are required +from ansible.module_utils.basic import * + +main() diff --git a/roles/lib_openshift_api/build/generate.py b/roles/lib_openshift_api/build/generate.py new file mode 100755 index 000000000..cf3f61d2c --- /dev/null +++ b/roles/lib_openshift_api/build/generate.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +''' + Generate the openshift-ansible/roles/lib_openshift_cli/library/ modules. +''' + +import os + +# pylint: disable=anomalous-backslash-in-string +GEN_STR = "#!/usr/bin/env python\n" + \ + "# ___ ___ _ _ ___ ___ _ _____ ___ ___\n" + \ + "# / __| __| \| | __| _ \ /_\_ _| __| \\\n" + \ + "# | (_ | _|| .` | _|| / / _ \| | | _|| |) |\n" + \ + "# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____\n" + \ + "# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _|\n" + \ + "# | |) | (_) | | .` | (_) || | | _|| |) | | | |\n" + \ + "# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_|\n" + +OPENSHIFT_ANSIBLE_PATH = os.path.dirname(os.path.realpath(__file__)) + + +FILES = {'oc_obj.py': ['src/base.py', + '../../lib_yaml_editor/build/src/yedit.py', + 'src/obj.py', + 'ansible/obj.py', + ], + 'oc_secret.py': ['src/base.py', + '../../lib_yaml_editor/build/src/yedit.py', + 'src/secret.py', + 'ansible/secret.py', + ], + 'oc_edit.py': ['src/base.py', + '../../lib_yaml_editor/build/src/yedit.py', + 'src/edit.py', + 'ansible/edit.py', + ], + } + + +def main(): + ''' combine the necessary files to create the ansible module ''' + library = os.path.join(OPENSHIFT_ANSIBLE_PATH, '..', 'library/') + for fname, parts in FILES.items(): + with open(os.path.join(library, fname), 'w') as afd: + afd.seek(0) + afd.write(GEN_STR) + for fpart in parts: + with open(os.path.join(OPENSHIFT_ANSIBLE_PATH, fpart)) as pfd: + # first line is pylint disable so skip it + for idx, line in enumerate(pfd): + if idx == 0 and 'skip-file' in line: + continue + + afd.write(line) + + +if __name__ == '__main__': + main() + + diff --git a/roles/lib_openshift_api/build/src/base.py b/roles/lib_openshift_api/build/src/base.py new file mode 100644 index 000000000..66831c4e2 --- /dev/null +++ b/roles/lib_openshift_api/build/src/base.py @@ -0,0 +1,273 @@ +# pylint: skip-file +''' + OpenShiftCLI class that wraps the oc commands in a subprocess +''' + +import atexit +import json +import os +import shutil +import subprocess +import re + +import yaml +# This is here because of a bug that causes yaml +# to incorrectly handle timezone info on timestamps +def timestamp_constructor(_, node): + '''return timestamps as strings''' + return str(node.value) +yaml.add_constructor(u'tag:yaml.org,2002:timestamp', timestamp_constructor) + +# pylint: disable=too-few-public-methods +class OpenShiftCLI(object): + ''' Class to wrap the oc command line tools ''' + def __init__(self, + namespace, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + self.namespace = namespace + self.verbose = verbose + self.kubeconfig = kubeconfig + + # Pylint allows only 5 arguments to be passed. + # pylint: disable=too-many-arguments + def _replace_content(self, resource, rname, content, force=False): + ''' replace the current object with the content ''' + res = self._get(resource, rname) + if not res['results']: + return res + + fname = '/tmp/%s' % rname + yed = Yedit(fname, res['results'][0]) + changes = [] + for key, value in content.items(): + changes.append(yed.put(key, value)) + + if any([not change[0] for change in changes]): + return {'returncode': 0, 'updated': False} + + yed.write() + + atexit.register(Utils.cleanup, [fname]) + + return self._replace(fname, force) + + def _replace(self, fname, force=False): + '''return all pods ''' + cmd = ['-n', self.namespace, 'replace', '-f', fname] + if force: + cmd.append('--force') + return self.oc_cmd(cmd) + + def _create(self, fname): + '''return all pods ''' + return self.oc_cmd(['create', '-f', fname, '-n', self.namespace]) + + def _delete(self, resource, rname): + '''return all pods ''' + return self.oc_cmd(['delete', resource, rname, '-n', self.namespace]) + + def _get(self, resource, rname=None): + '''return a secret by name ''' + cmd = ['get', resource, '-o', 'json', '-n', self.namespace] + if rname: + cmd.append(rname) + + rval = self.oc_cmd(cmd, output=True) + + # Ensure results are retuned in an array + if rval.has_key('items'): + rval['results'] = rval['items'] + elif not isinstance(rval['results'], list): + rval['results'] = [rval['results']] + + return rval + + def oc_cmd(self, cmd, output=False): + '''Base command for oc ''' + #cmds = ['/usr/bin/oc', '--config', self.kubeconfig] + cmds = ['/usr/bin/oc'] + cmds.extend(cmd) + + rval = {} + results = '' + err = None + + if self.verbose: + print ' '.join(cmds) + + proc = subprocess.Popen(cmds, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env={'KUBECONFIG': self.kubeconfig}) + + proc.wait() + stdout = proc.stdout.read() + stderr = proc.stderr.read() + + rval = {"returncode": proc.returncode, + "results": results, + } + + if proc.returncode == 0: + if output: + try: + rval['results'] = json.loads(stdout) + except ValueError as err: + if "No JSON object could be decoded" in err.message: + err = err.message + + if self.verbose: + print stdout + print stderr + print + + if err: + rval.update({"err": err, + "stderr": stderr, + "stdout": stdout, + "cmd": cmds + }) + + else: + rval.update({"stderr": stderr, + "stdout": stdout, + "results": {}, + }) + + return rval + +class Utils(object): + ''' utilities for openshiftcli modules ''' + @staticmethod + def create_file(rname, data, ftype=None): + ''' create a file in tmp with name and contents''' + path = os.path.join('/tmp', rname) + with open(path, 'w') as fds: + if ftype == 'yaml': + fds.write(yaml.safe_dump(data, default_flow_style=False)) + + elif ftype == 'json': + fds.write(json.dumps(data)) + else: + fds.write(data) + + # Register cleanup when module is done + atexit.register(Utils.cleanup, [path]) + return path + + @staticmethod + def create_files_from_contents(data): + '''Turn an array of dict: filename, content into a files array''' + files = [] + + for sfile in data: + path = Utils.create_file(sfile['path'], sfile['content']) + files.append(path) + + return files + + @staticmethod + def cleanup(files): + '''Clean up on exit ''' + for sfile in files: + if os.path.exists(sfile): + if os.path.isdir(sfile): + shutil.rmtree(sfile) + elif os.path.isfile(sfile): + os.remove(sfile) + + + @staticmethod + def exists(results, _name): + ''' Check to see if the results include the name ''' + if not results: + return False + + + if Utils.find_result(results, _name): + return True + + return False + + @staticmethod + def find_result(results, _name): + ''' Find the specified result by name''' + rval = None + for result in results: + if result.has_key('metadata') and result['metadata']['name'] == _name: + rval = result + break + + return rval + + @staticmethod + def get_resource_file(sfile, sfile_type='yaml'): + ''' return the service file ''' + contents = None + with open(sfile) as sfd: + contents = sfd.read() + + if sfile_type == 'yaml': + contents = yaml.safe_load(contents) + elif sfile_type == 'json': + contents = json.loads(contents) + + return contents + + # Disabling too-many-branches. This is a yaml dictionary comparison function + # pylint: disable=too-many-branches,too-many-return-statements + @staticmethod + def check_def_equal(user_def, result_def, debug=False): + ''' Given a user defined definition, compare it with the results given back by our query. ''' + + # Currently these values are autogenerated and we do not need to check them + skip = ['metadata', 'status'] + + for key, value in result_def.items(): + if key in skip: + continue + + # Both are lists + if isinstance(value, list): + if not isinstance(user_def[key], list): + return False + + # lists should be identical + if value != user_def[key]: + return False + + # recurse on a dictionary + elif isinstance(value, dict): + if not isinstance(user_def[key], dict): + if debug: + print "dict returned false not instance of dict" + return False + + # before passing ensure keys match + api_values = set(value.keys()) - set(skip) + user_values = set(user_def[key].keys()) - set(skip) + if api_values != user_values: + if debug: + print api_values + print user_values + print "keys are not equal in dict" + return False + + result = Utils.check_def_equal(user_def[key], value, debug=debug) + if not result: + if debug: + print "dict returned false" + return False + + # Verify each key, value pair is the same + else: + if not user_def.has_key(key) or value != user_def[key]: + if debug: + print "value not equal; user_def does not have key" + print value + print user_def[key] + return False + + return True diff --git a/roles/lib_openshift_api/build/src/edit.py b/roles/lib_openshift_api/build/src/edit.py new file mode 100644 index 000000000..7020ace47 --- /dev/null +++ b/roles/lib_openshift_api/build/src/edit.py @@ -0,0 +1,49 @@ +# pylint: skip-file + +class Edit(OpenShiftCLI): + ''' Class to wrap the oc command line tools + ''' + # pylint: disable=too-many-arguments + def __init__(self, + kind, + namespace, + resource_name=None, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + super(Edit, self).__init__(namespace, kubeconfig) + self.namespace = namespace + self.kind = kind + self.name = resource_name + self.kubeconfig = kubeconfig + self.verbose = verbose + + def get(self): + '''return a secret by name ''' + return self._get(self.kind, self.name) + + def update(self, file_name, content, force=False, content_type='yaml'): + '''run update ''' + if file_name: + if content_type == 'yaml': + data = yaml.load(open(file_name)) + elif content_type == 'json': + data = json.loads(open(file_name).read()) + + changes = [] + yed = Yedit(file_name, data) + for key, value in content.items(): + changes.append(yed.put(key, value)) + + if any([not change[0] for change in changes]): + return {'returncode': 0, 'updated': False} + + yed.write() + + atexit.register(Utils.cleanup, [file_name]) + + return self._replace(file_name, force=force) + + return self._replace_content(self.kind, self.name, content, force=force) + + diff --git a/roles/lib_openshift_api/build/src/obj.py b/roles/lib_openshift_api/build/src/obj.py new file mode 100644 index 000000000..a3ad4b3c4 --- /dev/null +++ b/roles/lib_openshift_api/build/src/obj.py @@ -0,0 +1,78 @@ +# pylint: skip-file + +class OCObject(OpenShiftCLI): + ''' Class to wrap the oc command line tools ''' + + # pylint allows 5. we need 6 + # pylint: disable=too-many-arguments + def __init__(self, + kind, + namespace, + rname=None, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + super(OCObject, self).__init__(namespace, kubeconfig) + self.kind = kind + self.namespace = namespace + self.name = rname + self.kubeconfig = kubeconfig + self.verbose = verbose + + def get(self): + '''return a deploymentconfig by name ''' + return self._get(self.kind, rname=self.name) + + def delete(self): + '''return all pods ''' + return self._delete(self.kind, self.name) + + def create(self, files=None, content=None): + '''Create a deploymentconfig ''' + if files: + return self._create(files[0]) + + return self._create(Utils.create_files_from_contents(content)) + + + # pylint: disable=too-many-function-args + def update(self, files=None, content=None, force=False): + '''run update dc + + This receives a list of file names and takes the first filename and calls replace. + ''' + if files: + return self._replace(files[0], force) + + return self.update_content(content, force) + + def update_content(self, content, force=False): + '''update the dc with the content''' + return self._replace_content(self.kind, self.name, content, force=force) + + def needs_update(self, files=None, content=None, content_type='yaml'): + ''' check to see if we need to update ''' + objects = self.get() + if objects['returncode'] != 0: + return objects + + # pylint: disable=no-member + data = None + if files: + data = Utils.get_resource_file(files[0], content_type) + + # if equal then no need. So not equal is True + return not Utils.check_def_equal(data, objects['results'][0], True) + else: + data = content + + for key, value in data.items(): + if key == 'metadata': + continue + if not objects['results'][0].has_key(key): + return True + if value != objects['results'][0][key]: + return True + + return False + diff --git a/roles/lib_openshift_api/build/src/secret.py b/roles/lib_openshift_api/build/src/secret.py new file mode 100644 index 000000000..af61dfa01 --- /dev/null +++ b/roles/lib_openshift_api/build/src/secret.py @@ -0,0 +1,68 @@ +# pylint: skip-file + +class Secret(OpenShiftCLI): + ''' Class to wrap the oc command line tools + ''' + def __init__(self, + namespace, + secret_name=None, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + super(Secret, self).__init__(namespace, kubeconfig) + self.namespace = namespace + self.name = secret_name + self.kubeconfig = kubeconfig + self.verbose = verbose + + def get(self): + '''return a secret by name ''' + return self._get('secrets', self.name) + + def delete(self): + '''delete a secret by name''' + return self._delete('secrets', self.name) + + def create(self, files=None, contents=None): + '''Create a secret ''' + if not files: + files = Utils.create_files_from_contents(contents) + + secrets = ["%s=%s" % (os.path.basename(sfile), sfile) for sfile in files] + cmd = ['-n%s' % self.namespace, 'secrets', 'new', self.name] + cmd.extend(secrets) + + return self.oc_cmd(cmd) + + def update(self, files, force=False): + '''run update secret + + This receives a list of file names and converts it into a secret. + The secret is then written to disk and passed into the `oc replace` command. + ''' + secret = self.prep_secret(files) + if secret['returncode'] != 0: + return secret + + sfile_path = '/tmp/%s' % self.name + with open(sfile_path, 'w') as sfd: + sfd.write(json.dumps(secret['results'])) + + atexit.register(Utils.cleanup, [sfile_path]) + + return self._replace(sfile_path, force=force) + + def prep_secret(self, files=None, contents=None): + ''' return what the secret would look like if created + This is accomplished by passing -ojson. This will most likely change in the future + ''' + if not files: + files = Utils.create_files_from_contents(contents) + + secrets = ["%s=%s" % (os.path.basename(sfile), sfile) for sfile in files] + cmd = ['-ojson', '-n%s' % self.namespace, 'secrets', 'new', self.name] + cmd.extend(secrets) + + return self.oc_cmd(cmd, output=True) + + diff --git a/roles/lib_openshift_api/build/test/README b/roles/lib_openshift_api/build/test/README new file mode 100644 index 000000000..af9f05b3d --- /dev/null +++ b/roles/lib_openshift_api/build/test/README @@ -0,0 +1,5 @@ +After generate.py has run, the ansible modules will be placed under ../../../openshift-ansible/roles/lib_openshift_api/library. + + +To run the tests you need to run them like this: +./services.yml -M ../../library diff --git a/roles/lib_openshift_api/build/test/deploymentconfig.yml b/roles/lib_openshift_api/build/test/deploymentconfig.yml new file mode 100755 index 000000000..d041ab22a --- /dev/null +++ b/roles/lib_openshift_api/build/test/deploymentconfig.yml @@ -0,0 +1,120 @@ +#!/usr/bin/ansible-playbook +--- +- hosts: "oo_clusterid_mwoodson:&oo_version_3:&oo_master_primary" + gather_facts: no + user: root + + post_tasks: + - copy: + dest: "/tmp/{{ item }}" + src: "files/{{ item }}" + with_items: + - dc.yml + + - name: list dc + oc_obj: + kind: dc + state: list + namespace: default + name: router + register: dcout + + - debug: + var: dcout + + - name: absent dc + oc_obj: + kind: dc + state: absent + namespace: default + name: router + register: dcout + + - debug: + var: dcout + + - name: present dc + oc_obj: + kind: dc + state: present + namespace: default + name: router + files: + - /tmp/dc.yml + register: dcout + + - debug: + var: dcout + + - name: dump router + oc_obj: + kind: dc + state: list + name: router + register: routerout + + - name: write router file + copy: + dest: /tmp/dc-mod.json + content: "{{ routerout.results[0] }}" + + - command: cat /tmp/dc-mod.json + register: catout + + - debug: + msg: "{{ catout }}" + + - command: "sed -i 's/: 80/: 81/g' /tmp/dc-mod.json" + register: catout + + - name: present dc update + oc_obj: + kind: dc + state: present + namespace: default + name: router + files: + - /tmp/dc-mod.json + delete_after: True + register: dcout + + - debug: + var: dcout + + - include_vars: "files/dc-mod.yml" + + - name: absent dc + oc_obj: + kind: dc + state: absent + namespace: default + name: router + register: dcout + + - debug: + var: dcout + + - name: present dc + oc_obj: + kind: dc + state: present + namespace: default + name: router + files: + - /tmp/dc.yml + delete_after: True + register: dcout + + - name: present dc + oc_obj: + kind: dc + state: present + namespace: default + name: router + content: "{{ dc }}" + delete_after: True + register: dcout + + - debug: + var: dcout + diff --git a/roles/lib_openshift_api/build/test/edit.yml b/roles/lib_openshift_api/build/test/edit.yml new file mode 100755 index 000000000..9aa01303a --- /dev/null +++ b/roles/lib_openshift_api/build/test/edit.yml @@ -0,0 +1,53 @@ +#!/usr/bin/ansible-playbook +--- +- hosts: "oo_clusterid_mwoodson:&oo_version_3:&oo_master_primary" + gather_facts: no + user: root + + post_tasks: + - copy: + dest: "/tmp/{{ item }}" + src: "files/{{ item }}" + with_items: + - dc.yml + + - name: present dc + oc_edit: + kind: dc + namespace: default + name: router + content: + spec.template.spec.containers[0].ports[0].containerPort: 80 + spec.template.spec.containers[0].ports[0].hostPort: 80 + register: dcout + + - debug: + var: dcout + + - name: present dc + oc_edit: + kind: dc + namespace: default + name: router + content: + spec.template.spec.containers[0].ports[0].containerPort: 81 + spec.template.spec.containers[0].ports[0].hostPort: 81 + file_format: yaml + register: dcout + + - debug: + var: dcout + + - name: present dc + oc_edit: + kind: dc + namespace: default + name: router + content: + spec.template.spec.containers[0].ports[0].containerPort: 80 + spec.template.spec.containers[0].ports[0].hostPort: 80 + file_format: yaml + register: dcout + + - debug: + var: dcout diff --git a/roles/lib_openshift_api/build/test/files/config.yml b/roles/lib_openshift_api/build/test/files/config.yml new file mode 100644 index 000000000..c544c6fd4 --- /dev/null +++ b/roles/lib_openshift_api/build/test/files/config.yml @@ -0,0 +1 @@ +value: True diff --git a/roles/lib_openshift_api/build/test/files/dc-mod.yml b/roles/lib_openshift_api/build/test/files/dc-mod.yml new file mode 100644 index 000000000..6c700d6c7 --- /dev/null +++ b/roles/lib_openshift_api/build/test/files/dc-mod.yml @@ -0,0 +1,124 @@ +dc: + path: + dc-mod.yml + content: + apiVersion: v1 + kind: DeploymentConfig + metadata: + labels: + router: router + name: router + namespace: default + resourceVersion: "84016" + selfLink: /oapi/v1/namespaces/default/deploymentconfigs/router + uid: 48f8b9d9-ed42-11e5-9903-0a9a9d4e7f2b + spec: + replicas: 2 + selector: + router: router + strategy: + resources: {} + rollingParams: + intervalSeconds: 1 + maxSurge: 0 + maxUnavailable: 25% + timeoutSeconds: 600 + updatePercent: -25 + updatePeriodSeconds: 1 + type: Rolling + template: + metadata: + creationTimestamp: null + labels: + router: router + spec: + containers: + - env: + - name: DEFAULT_CERTIFICATE + - name: OPENSHIFT_CA_DATA + value: | + -----BEGIN CERTIFICATE----- + MIIC5jCCAdCgAwIBAgIBATALBgkqhkiG9w0BAQswJjEkMCIGA1UEAwwbb3BlbnNo + -----END CERTIFICATE----- + - name: OPENSHIFT_CERT_DATA + value: | + -----BEGIN CERTIFICATE----- + MIIDDTCCAfegAwIBAgIBCDALBgkqhkiG9w0BAQswJjEkMCIGA1UEAwwbb3BlbnNo + -----END CERTIFICATE----- + - name: OPENSHIFT_INSECURE + value: "false" + - name: OPENSHIFT_KEY_DATA + value: | + -----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEA2lf49DrPHfCdCORcnIbmDVrx8yos7trjWdBvuledijyslRVR + -----END RSA PRIVATE KEY----- + - name: OPENSHIFT_MASTER + value: https://internal.api.mwoodson.openshift.com + - name: ROUTER_EXTERNAL_HOST_HOSTNAME + - name: ROUTER_EXTERNAL_HOST_HTTPS_VSERVER + - name: ROUTER_EXTERNAL_HOST_HTTP_VSERVER + - name: ROUTER_EXTERNAL_HOST_INSECURE + value: "false" + - name: ROUTER_EXTERNAL_HOST_PARTITION_PATH + - name: ROUTER_EXTERNAL_HOST_PASSWORD + - name: ROUTER_EXTERNAL_HOST_PRIVKEY + value: /etc/secret-volume/router.pem + - name: ROUTER_EXTERNAL_HOST_USERNAME + - name: ROUTER_SERVICE_NAME + value: router + - name: ROUTER_SERVICE_NAMESPACE + value: default + - name: STATS_PASSWORD + value: ugCk6YBm4q + - name: STATS_PORT + value: "1936" + - name: STATS_USERNAME + value: admin + image: openshift3/ose-haproxy-router:v3.1.1.6 + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + host: localhost + path: /healthz + port: 1936 + scheme: HTTP + initialDelaySeconds: 10 + timeoutSeconds: 1 + name: router + ports: + - containerPort: 81 + hostPort: 81 + protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: TCP + - containerPort: 1936 + hostPort: 1936 + name: stats + protocol: TCP + readinessProbe: + httpGet: + host: localhost + path: /healthz + port: 1937 + scheme: HTTP + timeoutSeconds: 1 + resources: {} + terminationMessagePath: /dev/termination-log + dnsPolicy: ClusterFirst + hostNetwork: true + nodeSelector: + type: infra + restartPolicy: Always + securityContext: {} + serviceAccount: router + serviceAccountName: router + terminationGracePeriodSeconds: 30 + triggers: + - type: ConfigChange + status: + details: + causes: + - type: ConfigChange + latestVersion: 1 + diff --git a/roles/lib_openshift_api/build/test/files/dc.yml b/roles/lib_openshift_api/build/test/files/dc.yml new file mode 100644 index 000000000..24f690ef4 --- /dev/null +++ b/roles/lib_openshift_api/build/test/files/dc.yml @@ -0,0 +1,120 @@ +apiVersion: v1 +kind: DeploymentConfig +metadata: + creationTimestamp: 2016-04-01T15:23:29Z + labels: + router: router + name: router + namespace: default + resourceVersion: "1338477" + selfLink: /oapi/v1/namespaces/default/deploymentconfigs/router + uid: b00c7eba-f81d-11e5-809b-0a581f893e3f +spec: + replicas: 2 + selector: + router: router + strategy: + resources: {} + rollingParams: + intervalSeconds: 1 + maxSurge: 0 + maxUnavailable: 25% + timeoutSeconds: 600 + updatePercent: -25 + updatePeriodSeconds: 1 + type: Rolling + template: + metadata: + creationTimestamp: null + labels: + router: router + spec: + containers: + - env: + - name: DEFAULT_CERTIFICATE + - name: OPENSHIFT_CA_DATA + value: | + -----BEGIN CERTIFICATE----- + MIIC5jCCAdCgAwIBAgIBATALBgkqhkiG9w0BAQswJjEkMCIGA1UEAwwbb3BlbnNo + -----END CERTIFICATE----- + - name: OPENSHIFT_CERT_DATA + value: | + -----BEGIN CERTIFICATE----- + MIIDDTCCAfegAwIBAgIBCDALBgkqhkiG9w0BAQswJjEkMCIGA1UEAwwbb3BlbnNo + -----END CERTIFICATE----- + - name: OPENSHIFT_INSECURE + value: "false" + - name: OPENSHIFT_KEY_DATA + value: | + -----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEA2lf49DrPHfCdCORcnIbmDVrx8yos7trjWdBvuledijyslRVR + -----END RSA PRIVATE KEY----- + - name: OPENSHIFT_MASTER + value: https://internal.api.mwoodson.openshift.com + - name: ROUTER_EXTERNAL_HOST_HOSTNAME + - name: ROUTER_EXTERNAL_HOST_HTTPS_VSERVER + - name: ROUTER_EXTERNAL_HOST_HTTP_VSERVER + - name: ROUTER_EXTERNAL_HOST_INSECURE + value: "false" + - name: ROUTER_EXTERNAL_HOST_PARTITION_PATH + - name: ROUTER_EXTERNAL_HOST_PASSWORD + - name: ROUTER_EXTERNAL_HOST_PRIVKEY + value: /etc/secret-volume/router.pem + - name: ROUTER_EXTERNAL_HOST_USERNAME + - name: ROUTER_SERVICE_NAME + value: router + - name: ROUTER_SERVICE_NAMESPACE + value: default + - name: STATS_PASSWORD + value: ugCk6YBm4q + - name: STATS_PORT + value: "1936" + - name: STATS_USERNAME + value: admin + image: openshift3/ose-haproxy-router:v3.1.1.6 + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + host: localhost + path: /healthz + port: 1936 + scheme: HTTP + initialDelaySeconds: 10 + timeoutSeconds: 1 + name: router + ports: + - containerPort: 80 + hostPort: 80 + protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: TCP + - containerPort: 1936 + hostPort: 1936 + name: stats + protocol: TCP + readinessProbe: + httpGet: + host: localhost + path: /healthz + port: 1936 + scheme: HTTP + timeoutSeconds: 1 + resources: {} + terminationMessagePath: /dev/termination-log + dnsPolicy: ClusterFirst + hostNetwork: true + nodeSelector: + type: infra + restartPolicy: Always + securityContext: {} + serviceAccount: router + serviceAccountName: router + terminationGracePeriodSeconds: 30 + triggers: + - type: ConfigChange +status: + details: + causes: + - type: ConfigChange + latestVersion: 12 diff --git a/roles/lib_openshift_api/build/test/files/passwords.yml b/roles/lib_openshift_api/build/test/files/passwords.yml new file mode 100644 index 000000000..fadbf1d85 --- /dev/null +++ b/roles/lib_openshift_api/build/test/files/passwords.yml @@ -0,0 +1,4 @@ +test1 +test2 +test3 +test4 diff --git a/roles/lib_openshift_api/build/test/files/router-mod.json b/roles/lib_openshift_api/build/test/files/router-mod.json new file mode 100644 index 000000000..45e2e7c8d --- /dev/null +++ b/roles/lib_openshift_api/build/test/files/router-mod.json @@ -0,0 +1,30 @@ +{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "router", + "namespace": "default", + "labels": { + "router": "router" + } + }, + "spec": { + "ports": [ + { + "name": "81-tcp", + "protocol": "TCP", + "port": 81, + "targetPort": 81 + } + ], + "selector": { + "router": "router" + }, + "type": "ClusterIP", + "sessionAffinity": "None" + }, + "status": { + "loadBalancer": {} + } +} + diff --git a/roles/lib_openshift_api/build/test/files/router.json b/roles/lib_openshift_api/build/test/files/router.json new file mode 100644 index 000000000..cad3c6f53 --- /dev/null +++ b/roles/lib_openshift_api/build/test/files/router.json @@ -0,0 +1,29 @@ +{ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "labels": { + "router": "router" + }, + "name": "router", + "namespace": "default" + }, + "spec": { + "ports": [ + { + "name": "80-tcp", + "port": 80, + "protocol": "TCP", + "targetPort": 80 + } + ], + "selector": { + "router": "router" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } +} diff --git a/roles/lib_openshift_api/build/test/roles b/roles/lib_openshift_api/build/test/roles new file mode 120000 index 000000000..ae82aa9bb --- /dev/null +++ b/roles/lib_openshift_api/build/test/roles @@ -0,0 +1 @@ +../../../../roles/
\ No newline at end of file diff --git a/roles/lib_openshift_api/build/test/secrets.yml b/roles/lib_openshift_api/build/test/secrets.yml new file mode 100755 index 000000000..dddc05c4d --- /dev/null +++ b/roles/lib_openshift_api/build/test/secrets.yml @@ -0,0 +1,81 @@ +#!/usr/bin/ansible-playbook +--- +- hosts: "oo_clusterid_mwoodson:&oo_version_3:&oo_master_primary" + gather_facts: no + user: root + + post_tasks: + - copy: + dest: "/tmp/{{ item }}" + src: "files/{{ item }}" + with_items: + - config.yml + - passwords.yml + + - name: list secrets + oc_secret: + state: list + namespace: default + name: kenny + register: secret_out + + - debug: + var: secret_out + + - name: absent secrets + oc_secret: + state: absent + namespace: default + name: kenny + register: secret_out + + - debug: + var: secret_out + + - name: present secrets + oc_secret: + state: present + namespace: default + name: kenny + files: + - /tmp/config.yml + - /tmp/passwords.yml + delete_after: True + register: secret_out + + - debug: + var: secret_out + + - name: present secrets + oc_secret: + state: present + namespace: default + name: kenny + contents: + - path: config.yml + content: "value: True\n" + - path: passwords.yml + content: "test1\ntest2\ntest3\ntest4\n" + delete_after: True + register: secret_out + + - debug: + var: secret_out + + - name: present secrets update + oc_secret: + state: present + namespace: default + name: kenny + contents: + - path: config.yml + content: "value: True\n" + - path: passwords.yml + content: "test1\ntest2\ntest3\ntest4\ntest5\n" + delete_after: True + force: True + register: secret_out + + - debug: + var: secret_out + diff --git a/roles/lib_openshift_api/build/test/services.yml b/roles/lib_openshift_api/build/test/services.yml new file mode 100755 index 000000000..a32e8d012 --- /dev/null +++ b/roles/lib_openshift_api/build/test/services.yml @@ -0,0 +1,133 @@ +#!/usr/bin/ansible-playbook +--- +- hosts: "oo_clusterid_mwoodson:&oo_master_primary" + gather_facts: no + user: root + + roles: + - roles/lib_yaml_editor + + tasks: + - copy: + dest: "/tmp/{{ item }}" + src: "files/{{ item }}" + with_items: + - router.json + - router-mod.json + + - name: list services + oc_obj: + kind: service + state: list + namespace: default + name: router + register: service_out + + - debug: + var: service_out.results + + - name: absent service + oc_obj: + kind: service + state: absent + namespace: default + name: router + register: service_out + + - debug: + var: service_out + + - name: present service create + oc_obj: + kind: service + state: present + namespace: default + name: router + files: + - /tmp/router.json + delete_after: True + register: service_out + + - debug: + var: service_out + + - name: dump router + oc_obj: + kind: service + state: list + name: router + namespace: default + register: routerout + + - name: write router file + copy: + dest: /tmp/router-mod.json + content: "{{ routerout.results[0] }}" + + - command: cat /tmp/router-mod.json + register: catout + + - debug: + msg: "{{ catout }}" + + - command: "sed -i 's/80-tcp/81-tcp/g' /tmp/router-mod.json" + register: catout + + - name: present service replace + oc_obj: + kind: service + state: present + namespace: default + name: router + files: + - /tmp/router-mod.json + #delete_after: True + register: service_out + + - debug: + var: service_out + + - name: list services + oc_obj: + kind: service + state: list + namespace: default + name: router + register: service_out + + - debug: + var: service_out.results + + - set_fact: + new_service: "{{ service_out.results[0] }}" + + - yedit: + src: /tmp/routeryedit + content: "{{ new_service }}" + key: spec.ports + value: + - name: 80-tcp + port: 80 + protocol: TCP + targetPort: 80 + + - yedit: + src: /tmp/routeryedit + state: list + register: yeditout + + - debug: + var: yeditout + + - name: present service replace + oc_obj: + kind: service + state: present + namespace: default + name: router + content: "{{ yeditout.results }}" + delete_after: True + register: service_out + + - debug: + var: service_out diff --git a/roles/lib_openshift_api/library/oc_edit.py b/roles/lib_openshift_api/library/oc_edit.py new file mode 100644 index 000000000..44e77331d --- /dev/null +++ b/roles/lib_openshift_api/library/oc_edit.py @@ -0,0 +1,619 @@ +#!/usr/bin/env python +# ___ ___ _ _ ___ ___ _ _____ ___ ___ +# / __| __| \| | __| _ \ /_\_ _| __| \ +# | (_ | _|| .` | _|| / / _ \| | | _|| |) | +# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____ +# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _| +# | |) | (_) | | .` | (_) || | | _|| |) | | | | +# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_| +''' + OpenShiftCLI class that wraps the oc commands in a subprocess +''' + +import atexit +import json +import os +import shutil +import subprocess +import re + +import yaml +# This is here because of a bug that causes yaml +# to incorrectly handle timezone info on timestamps +def timestamp_constructor(_, node): + '''return timestamps as strings''' + return str(node.value) +yaml.add_constructor(u'tag:yaml.org,2002:timestamp', timestamp_constructor) + +# pylint: disable=too-few-public-methods +class OpenShiftCLI(object): + ''' Class to wrap the oc command line tools ''' + def __init__(self, + namespace, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + self.namespace = namespace + self.verbose = verbose + self.kubeconfig = kubeconfig + + # Pylint allows only 5 arguments to be passed. + # pylint: disable=too-many-arguments + def _replace_content(self, resource, rname, content, force=False): + ''' replace the current object with the content ''' + res = self._get(resource, rname) + if not res['results']: + return res + + fname = '/tmp/%s' % rname + yed = Yedit(fname, res['results'][0]) + changes = [] + for key, value in content.items(): + changes.append(yed.put(key, value)) + + if any([not change[0] for change in changes]): + return {'returncode': 0, 'updated': False} + + yed.write() + + atexit.register(Utils.cleanup, [fname]) + + return self._replace(fname, force) + + def _replace(self, fname, force=False): + '''return all pods ''' + cmd = ['-n', self.namespace, 'replace', '-f', fname] + if force: + cmd.append('--force') + return self.oc_cmd(cmd) + + def _create(self, fname): + '''return all pods ''' + return self.oc_cmd(['create', '-f', fname, '-n', self.namespace]) + + def _delete(self, resource, rname): + '''return all pods ''' + return self.oc_cmd(['delete', resource, rname, '-n', self.namespace]) + + def _get(self, resource, rname=None): + '''return a secret by name ''' + cmd = ['get', resource, '-o', 'json', '-n', self.namespace] + if rname: + cmd.append(rname) + + rval = self.oc_cmd(cmd, output=True) + + # Ensure results are retuned in an array + if rval.has_key('items'): + rval['results'] = rval['items'] + elif not isinstance(rval['results'], list): + rval['results'] = [rval['results']] + + return rval + + def oc_cmd(self, cmd, output=False): + '''Base command for oc ''' + #cmds = ['/usr/bin/oc', '--config', self.kubeconfig] + cmds = ['/usr/bin/oc'] + cmds.extend(cmd) + + rval = {} + results = '' + err = None + + if self.verbose: + print ' '.join(cmds) + + proc = subprocess.Popen(cmds, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env={'KUBECONFIG': self.kubeconfig}) + + proc.wait() + stdout = proc.stdout.read() + stderr = proc.stderr.read() + + rval = {"returncode": proc.returncode, + "results": results, + } + + if proc.returncode == 0: + if output: + try: + rval['results'] = json.loads(stdout) + except ValueError as err: + if "No JSON object could be decoded" in err.message: + err = err.message + + if self.verbose: + print stdout + print stderr + print + + if err: + rval.update({"err": err, + "stderr": stderr, + "stdout": stdout, + "cmd": cmds + }) + + else: + rval.update({"stderr": stderr, + "stdout": stdout, + "results": {}, + }) + + return rval + +class Utils(object): + ''' utilities for openshiftcli modules ''' + @staticmethod + def create_file(rname, data, ftype=None): + ''' create a file in tmp with name and contents''' + path = os.path.join('/tmp', rname) + with open(path, 'w') as fds: + if ftype == 'yaml': + fds.write(yaml.safe_dump(data, default_flow_style=False)) + + elif ftype == 'json': + fds.write(json.dumps(data)) + else: + fds.write(data) + + # Register cleanup when module is done + atexit.register(Utils.cleanup, [path]) + return path + + @staticmethod + def create_files_from_contents(data): + '''Turn an array of dict: filename, content into a files array''' + files = [] + + for sfile in data: + path = Utils.create_file(sfile['path'], sfile['content']) + files.append(path) + + return files + + @staticmethod + def cleanup(files): + '''Clean up on exit ''' + for sfile in files: + if os.path.exists(sfile): + if os.path.isdir(sfile): + shutil.rmtree(sfile) + elif os.path.isfile(sfile): + os.remove(sfile) + + + @staticmethod + def exists(results, _name): + ''' Check to see if the results include the name ''' + if not results: + return False + + + if Utils.find_result(results, _name): + return True + + return False + + @staticmethod + def find_result(results, _name): + ''' Find the specified result by name''' + rval = None + for result in results: + if result.has_key('metadata') and result['metadata']['name'] == _name: + rval = result + break + + return rval + + @staticmethod + def get_resource_file(sfile, sfile_type='yaml'): + ''' return the service file ''' + contents = None + with open(sfile) as sfd: + contents = sfd.read() + + if sfile_type == 'yaml': + contents = yaml.safe_load(contents) + elif sfile_type == 'json': + contents = json.loads(contents) + + return contents + + # Disabling too-many-branches. This is a yaml dictionary comparison function + # pylint: disable=too-many-branches,too-many-return-statements + @staticmethod + def check_def_equal(user_def, result_def, debug=False): + ''' Given a user defined definition, compare it with the results given back by our query. ''' + + # Currently these values are autogenerated and we do not need to check them + skip = ['metadata', 'status'] + + for key, value in result_def.items(): + if key in skip: + continue + + # Both are lists + if isinstance(value, list): + if not isinstance(user_def[key], list): + return False + + # lists should be identical + if value != user_def[key]: + return False + + # recurse on a dictionary + elif isinstance(value, dict): + if not isinstance(user_def[key], dict): + if debug: + print "dict returned false not instance of dict" + return False + + # before passing ensure keys match + api_values = set(value.keys()) - set(skip) + user_values = set(user_def[key].keys()) - set(skip) + if api_values != user_values: + if debug: + print api_values + print user_values + print "keys are not equal in dict" + return False + + result = Utils.check_def_equal(user_def[key], value, debug=debug) + if not result: + if debug: + print "dict returned false" + return False + + # Verify each key, value pair is the same + else: + if not user_def.has_key(key) or value != user_def[key]: + if debug: + print "value not equal; user_def does not have key" + print value + print user_def[key] + return False + + return True + +class YeditException(Exception): + ''' Exception class for Yedit ''' + pass + +class Yedit(object): + ''' Class to modify yaml files ''' + re_valid_key = r"(((\[-?\d+\])|([a-zA-Z-./]+)).?)+$" + re_key = r"(?:\[(-?\d+)\])|([a-zA-Z-./]+)" + + def __init__(self, filename=None, content=None, content_type='yaml'): + self.content = content + self.filename = filename + self.__yaml_dict = content + self.content_type = content_type + if self.filename and not self.content: + self.load(content_type=self.content_type) + + @property + def yaml_dict(self): + ''' getter method for yaml_dict ''' + return self.__yaml_dict + + @yaml_dict.setter + def yaml_dict(self, value): + ''' setter method for yaml_dict ''' + self.__yaml_dict = value + + @staticmethod + def remove_entry(data, key): + ''' remove data at location key ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + # process last index for remove + # expected list entry + if key_indexes[-1][0]: + if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + del data[int(key_indexes[-1][0])] + return True + + # expected dict entry + elif key_indexes[-1][1]: + if isinstance(data, dict): + del data[key_indexes[-1][1]] + return True + + @staticmethod + def add_entry(data, key, item=None): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a.b + return c + ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + curr_data = data + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key: + if isinstance(data, dict) and data.has_key(dict_key): + data = data[dict_key] + continue + + data[dict_key] = {} + data = data[dict_key] + + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + # process last index for add + # expected list entry + if key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + data[int(key_indexes[-1][0])] = item + + # expected dict entry + elif key_indexes[-1][1] and isinstance(data, dict): + data[key_indexes[-1][1]] = item + + return curr_data + + @staticmethod + def get_entry(data, key): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a.b + return c + ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + return data + + def write(self): + ''' write to file ''' + if not self.filename: + raise YeditException('Please specify a filename.') + + with open(self.filename, 'w') as yfd: + yfd.write(yaml.safe_dump(self.yaml_dict, default_flow_style=False)) + + def read(self): + ''' write to file ''' + # check if it exists + if not self.exists(): + return None + + contents = None + with open(self.filename) as yfd: + contents = yfd.read() + + return contents + + def exists(self): + ''' return whether file exists ''' + if os.path.exists(self.filename): + return True + + return False + + def load(self, content_type='yaml'): + ''' return yaml file ''' + contents = self.read() + + if not contents: + return None + + # check if it is yaml + try: + if content_type == 'yaml': + self.yaml_dict = yaml.load(contents) + elif content_type == 'json': + self.yaml_dict = json.loads(contents) + except yaml.YAMLError as _: + # Error loading yaml or json + return None + + return self.yaml_dict + + def get(self, key): + ''' get a specified key''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + + return entry + + def delete(self, key): + ''' remove key from a dict''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + if not entry: + return (False, self.yaml_dict) + + result = Yedit.remove_entry(self.yaml_dict, key) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) + + def put(self, key, value): + ''' put key, value into a dict ''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + + if entry == value: + return (False, self.yaml_dict) + + result = Yedit.add_entry(self.yaml_dict, key, value) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) + + def create(self, key, value): + ''' create a yaml file ''' + if not self.exists(): + self.yaml_dict = {key: value} + return (True, self.yaml_dict) + + return (False, self.yaml_dict) + +class Edit(OpenShiftCLI): + ''' Class to wrap the oc command line tools + ''' + # pylint: disable=too-many-arguments + def __init__(self, + kind, + namespace, + resource_name=None, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + super(Edit, self).__init__(namespace, kubeconfig) + self.namespace = namespace + self.kind = kind + self.name = resource_name + self.kubeconfig = kubeconfig + self.verbose = verbose + + def get(self): + '''return a secret by name ''' + return self._get(self.kind, self.name) + + def update(self, file_name, content, force=False, content_type='yaml'): + '''run update ''' + if file_name: + if content_type == 'yaml': + data = yaml.load(open(file_name)) + elif content_type == 'json': + data = json.loads(open(file_name).read()) + + changes = [] + yed = Yedit(file_name, data) + for key, value in content.items(): + changes.append(yed.put(key, value)) + + if any([not change[0] for change in changes]): + return {'returncode': 0, 'updated': False} + + yed.write() + + atexit.register(Utils.cleanup, [file_name]) + + return self._replace(file_name, force=force) + + return self._replace_content(self.kind, self.name, content, force=force) + + + +def main(): + ''' + ansible oc module for services + ''' + + module = AnsibleModule( + argument_spec=dict( + kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), + state=dict(default='present', type='str', + choices=['present']), + debug=dict(default=False, type='bool'), + namespace=dict(default='default', type='str'), + name=dict(default=None, required=True, type='str'), + kind=dict(required=True, + type='str', + choices=['dc', 'deploymentconfig', + 'svc', 'service', + 'scc', 'securitycontextconstraints', + 'ns', 'namespace', 'project', 'projects', + 'is', 'imagestream', + 'istag', 'imagestreamtag', + 'bc', 'buildconfig', + 'routes', + 'node', + 'secret', + ]), + file_name=dict(default=None, type='str'), + file_format=dict(default='yaml', type='str'), + content=dict(default=None, required=True, type='dict'), + force=dict(default=False, type='bool'), + ), + supports_check_mode=True, + ) + ocedit = Edit(module.params['kind'], + module.params['namespace'], + module.params['name'], + kubeconfig=module.params['kubeconfig'], + verbose=module.params['debug']) + + state = module.params['state'] + + api_rval = ocedit.get() + + ######## + # Create + ######## + if not Utils.exists(api_rval['results'], module.params['name']): + module.fail_json(msg=api_rval) + + ######## + # Update + ######## + api_rval = ocedit.update(module.params['file_name'], + module.params['content'], + module.params['force'], + module.params['file_format']) + + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + if api_rval.has_key('updated') and not api_rval['updated']: + module.exit_json(changed=False, results=api_rval, state="present") + + # return the created object + api_rval = ocedit.get() + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + module.exit_json(changed=True, results=api_rval, state="present") + + module.exit_json(failed=True, + changed=False, + results='Unknown state passed. %s' % state, + state="unknown") + +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled +# import module snippets. This are required +from ansible.module_utils.basic import * + +main() diff --git a/roles/lib_openshift_api/library/oc_obj.py b/roles/lib_openshift_api/library/oc_obj.py new file mode 100644 index 000000000..c058072e3 --- /dev/null +++ b/roles/lib_openshift_api/library/oc_obj.py @@ -0,0 +1,703 @@ +#!/usr/bin/env python +# ___ ___ _ _ ___ ___ _ _____ ___ ___ +# / __| __| \| | __| _ \ /_\_ _| __| \ +# | (_ | _|| .` | _|| / / _ \| | | _|| |) | +# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____ +# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _| +# | |) | (_) | | .` | (_) || | | _|| |) | | | | +# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_| +''' + OpenShiftCLI class that wraps the oc commands in a subprocess +''' + +import atexit +import json +import os +import shutil +import subprocess +import re + +import yaml +# This is here because of a bug that causes yaml +# to incorrectly handle timezone info on timestamps +def timestamp_constructor(_, node): + '''return timestamps as strings''' + return str(node.value) +yaml.add_constructor(u'tag:yaml.org,2002:timestamp', timestamp_constructor) + +# pylint: disable=too-few-public-methods +class OpenShiftCLI(object): + ''' Class to wrap the oc command line tools ''' + def __init__(self, + namespace, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + self.namespace = namespace + self.verbose = verbose + self.kubeconfig = kubeconfig + + # Pylint allows only 5 arguments to be passed. + # pylint: disable=too-many-arguments + def _replace_content(self, resource, rname, content, force=False): + ''' replace the current object with the content ''' + res = self._get(resource, rname) + if not res['results']: + return res + + fname = '/tmp/%s' % rname + yed = Yedit(fname, res['results'][0]) + changes = [] + for key, value in content.items(): + changes.append(yed.put(key, value)) + + if any([not change[0] for change in changes]): + return {'returncode': 0, 'updated': False} + + yed.write() + + atexit.register(Utils.cleanup, [fname]) + + return self._replace(fname, force) + + def _replace(self, fname, force=False): + '''return all pods ''' + cmd = ['-n', self.namespace, 'replace', '-f', fname] + if force: + cmd.append('--force') + return self.oc_cmd(cmd) + + def _create(self, fname): + '''return all pods ''' + return self.oc_cmd(['create', '-f', fname, '-n', self.namespace]) + + def _delete(self, resource, rname): + '''return all pods ''' + return self.oc_cmd(['delete', resource, rname, '-n', self.namespace]) + + def _get(self, resource, rname=None): + '''return a secret by name ''' + cmd = ['get', resource, '-o', 'json', '-n', self.namespace] + if rname: + cmd.append(rname) + + rval = self.oc_cmd(cmd, output=True) + + # Ensure results are retuned in an array + if rval.has_key('items'): + rval['results'] = rval['items'] + elif not isinstance(rval['results'], list): + rval['results'] = [rval['results']] + + return rval + + def oc_cmd(self, cmd, output=False): + '''Base command for oc ''' + #cmds = ['/usr/bin/oc', '--config', self.kubeconfig] + cmds = ['/usr/bin/oc'] + cmds.extend(cmd) + + rval = {} + results = '' + err = None + + if self.verbose: + print ' '.join(cmds) + + proc = subprocess.Popen(cmds, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env={'KUBECONFIG': self.kubeconfig}) + + proc.wait() + stdout = proc.stdout.read() + stderr = proc.stderr.read() + + rval = {"returncode": proc.returncode, + "results": results, + } + + if proc.returncode == 0: + if output: + try: + rval['results'] = json.loads(stdout) + except ValueError as err: + if "No JSON object could be decoded" in err.message: + err = err.message + + if self.verbose: + print stdout + print stderr + print + + if err: + rval.update({"err": err, + "stderr": stderr, + "stdout": stdout, + "cmd": cmds + }) + + else: + rval.update({"stderr": stderr, + "stdout": stdout, + "results": {}, + }) + + return rval + +class Utils(object): + ''' utilities for openshiftcli modules ''' + @staticmethod + def create_file(rname, data, ftype=None): + ''' create a file in tmp with name and contents''' + path = os.path.join('/tmp', rname) + with open(path, 'w') as fds: + if ftype == 'yaml': + fds.write(yaml.safe_dump(data, default_flow_style=False)) + + elif ftype == 'json': + fds.write(json.dumps(data)) + else: + fds.write(data) + + # Register cleanup when module is done + atexit.register(Utils.cleanup, [path]) + return path + + @staticmethod + def create_files_from_contents(data): + '''Turn an array of dict: filename, content into a files array''' + files = [] + + for sfile in data: + path = Utils.create_file(sfile['path'], sfile['content']) + files.append(path) + + return files + + @staticmethod + def cleanup(files): + '''Clean up on exit ''' + for sfile in files: + if os.path.exists(sfile): + if os.path.isdir(sfile): + shutil.rmtree(sfile) + elif os.path.isfile(sfile): + os.remove(sfile) + + + @staticmethod + def exists(results, _name): + ''' Check to see if the results include the name ''' + if not results: + return False + + + if Utils.find_result(results, _name): + return True + + return False + + @staticmethod + def find_result(results, _name): + ''' Find the specified result by name''' + rval = None + for result in results: + if result.has_key('metadata') and result['metadata']['name'] == _name: + rval = result + break + + return rval + + @staticmethod + def get_resource_file(sfile, sfile_type='yaml'): + ''' return the service file ''' + contents = None + with open(sfile) as sfd: + contents = sfd.read() + + if sfile_type == 'yaml': + contents = yaml.safe_load(contents) + elif sfile_type == 'json': + contents = json.loads(contents) + + return contents + + # Disabling too-many-branches. This is a yaml dictionary comparison function + # pylint: disable=too-many-branches,too-many-return-statements + @staticmethod + def check_def_equal(user_def, result_def, debug=False): + ''' Given a user defined definition, compare it with the results given back by our query. ''' + + # Currently these values are autogenerated and we do not need to check them + skip = ['metadata', 'status'] + + for key, value in result_def.items(): + if key in skip: + continue + + # Both are lists + if isinstance(value, list): + if not isinstance(user_def[key], list): + return False + + # lists should be identical + if value != user_def[key]: + return False + + # recurse on a dictionary + elif isinstance(value, dict): + if not isinstance(user_def[key], dict): + if debug: + print "dict returned false not instance of dict" + return False + + # before passing ensure keys match + api_values = set(value.keys()) - set(skip) + user_values = set(user_def[key].keys()) - set(skip) + if api_values != user_values: + if debug: + print api_values + print user_values + print "keys are not equal in dict" + return False + + result = Utils.check_def_equal(user_def[key], value, debug=debug) + if not result: + if debug: + print "dict returned false" + return False + + # Verify each key, value pair is the same + else: + if not user_def.has_key(key) or value != user_def[key]: + if debug: + print "value not equal; user_def does not have key" + print value + print user_def[key] + return False + + return True + +class YeditException(Exception): + ''' Exception class for Yedit ''' + pass + +class Yedit(object): + ''' Class to modify yaml files ''' + re_valid_key = r"(((\[-?\d+\])|([a-zA-Z-./]+)).?)+$" + re_key = r"(?:\[(-?\d+)\])|([a-zA-Z-./]+)" + + def __init__(self, filename=None, content=None, content_type='yaml'): + self.content = content + self.filename = filename + self.__yaml_dict = content + self.content_type = content_type + if self.filename and not self.content: + self.load(content_type=self.content_type) + + @property + def yaml_dict(self): + ''' getter method for yaml_dict ''' + return self.__yaml_dict + + @yaml_dict.setter + def yaml_dict(self, value): + ''' setter method for yaml_dict ''' + self.__yaml_dict = value + + @staticmethod + def remove_entry(data, key): + ''' remove data at location key ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + # process last index for remove + # expected list entry + if key_indexes[-1][0]: + if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + del data[int(key_indexes[-1][0])] + return True + + # expected dict entry + elif key_indexes[-1][1]: + if isinstance(data, dict): + del data[key_indexes[-1][1]] + return True + + @staticmethod + def add_entry(data, key, item=None): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a.b + return c + ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + curr_data = data + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key: + if isinstance(data, dict) and data.has_key(dict_key): + data = data[dict_key] + continue + + data[dict_key] = {} + data = data[dict_key] + + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + # process last index for add + # expected list entry + if key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + data[int(key_indexes[-1][0])] = item + + # expected dict entry + elif key_indexes[-1][1] and isinstance(data, dict): + data[key_indexes[-1][1]] = item + + return curr_data + + @staticmethod + def get_entry(data, key): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a.b + return c + ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + return data + + def write(self): + ''' write to file ''' + if not self.filename: + raise YeditException('Please specify a filename.') + + with open(self.filename, 'w') as yfd: + yfd.write(yaml.safe_dump(self.yaml_dict, default_flow_style=False)) + + def read(self): + ''' write to file ''' + # check if it exists + if not self.exists(): + return None + + contents = None + with open(self.filename) as yfd: + contents = yfd.read() + + return contents + + def exists(self): + ''' return whether file exists ''' + if os.path.exists(self.filename): + return True + + return False + + def load(self, content_type='yaml'): + ''' return yaml file ''' + contents = self.read() + + if not contents: + return None + + # check if it is yaml + try: + if content_type == 'yaml': + self.yaml_dict = yaml.load(contents) + elif content_type == 'json': + self.yaml_dict = json.loads(contents) + except yaml.YAMLError as _: + # Error loading yaml or json + return None + + return self.yaml_dict + + def get(self, key): + ''' get a specified key''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + + return entry + + def delete(self, key): + ''' remove key from a dict''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + if not entry: + return (False, self.yaml_dict) + + result = Yedit.remove_entry(self.yaml_dict, key) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) + + def put(self, key, value): + ''' put key, value into a dict ''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + + if entry == value: + return (False, self.yaml_dict) + + result = Yedit.add_entry(self.yaml_dict, key, value) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) + + def create(self, key, value): + ''' create a yaml file ''' + if not self.exists(): + self.yaml_dict = {key: value} + return (True, self.yaml_dict) + + return (False, self.yaml_dict) + +class OCObject(OpenShiftCLI): + ''' Class to wrap the oc command line tools ''' + + # pylint allows 5. we need 6 + # pylint: disable=too-many-arguments + def __init__(self, + kind, + namespace, + rname=None, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + super(OCObject, self).__init__(namespace, kubeconfig) + self.kind = kind + self.namespace = namespace + self.name = rname + self.kubeconfig = kubeconfig + self.verbose = verbose + + def get(self): + '''return a deploymentconfig by name ''' + return self._get(self.kind, rname=self.name) + + def delete(self): + '''return all pods ''' + return self._delete(self.kind, self.name) + + def create(self, files=None, content=None): + '''Create a deploymentconfig ''' + if files: + return self._create(files[0]) + + return self._create(Utils.create_files_from_contents(content)) + + + # pylint: disable=too-many-function-args + def update(self, files=None, content=None, force=False): + '''run update dc + + This receives a list of file names and takes the first filename and calls replace. + ''' + if files: + return self._replace(files[0], force) + + return self.update_content(content, force) + + def update_content(self, content, force=False): + '''update the dc with the content''' + return self._replace_content(self.kind, self.name, content, force=force) + + def needs_update(self, files=None, content=None, content_type='yaml'): + ''' check to see if we need to update ''' + objects = self.get() + if objects['returncode'] != 0: + return objects + + # pylint: disable=no-member + data = None + if files: + data = Utils.get_resource_file(files[0], content_type) + + # if equal then no need. So not equal is True + return not Utils.check_def_equal(data, objects['results'][0], True) + else: + data = content + + for key, value in data.items(): + if key == 'metadata': + continue + if not objects['results'][0].has_key(key): + return True + if value != objects['results'][0][key]: + return True + + return False + + +# pylint: disable=too-many-branches +def main(): + ''' + ansible oc module for services + ''' + + module = AnsibleModule( + argument_spec=dict( + kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), + state=dict(default='present', type='str', + choices=['present', 'absent', 'list']), + debug=dict(default=False, type='bool'), + namespace=dict(default='default', type='str'), + name=dict(default=None, type='str'), + files=dict(default=None, type='list'), + kind=dict(required=True, + type='str', + choices=['dc', 'deploymentconfig', + 'svc', 'service', + 'scc', 'securitycontextconstraints', + 'ns', 'namespace', 'project', 'projects', + 'is', 'imagestream', + 'istag', 'imagestreamtag', + 'bc', 'buildconfig', + 'routes', + 'node', + 'secret', + ]), + delete_after=dict(default=False, type='bool'), + content=dict(default=None, type='dict'), + force=dict(default=False, type='bool'), + ), + mutually_exclusive=[["content", "files"]], + + supports_check_mode=True, + ) + ocobj = OCObject(module.params['kind'], + module.params['namespace'], + module.params['name'], + kubeconfig=module.params['kubeconfig'], + verbose=module.params['debug']) + + state = module.params['state'] + + api_rval = ocobj.get() + + ##### + # Get + ##### + if state == 'list': + module.exit_json(changed=False, results=api_rval['results'], state="list") + + if not module.params['name']: + module.fail_json(msg='Please specify a name when state is absent|present.') + ######## + # Delete + ######## + if state == 'absent': + if not Utils.exists(api_rval['results'], module.params['name']): + module.exit_json(changed=False, state="absent") + + if module.check_mode: + module.exit_json(change=False, msg='Would have performed a delete.') + + api_rval = ocobj.delete() + module.exit_json(changed=True, results=api_rval, state="absent") + + if state == 'present': + ######## + # Create + ######## + if not Utils.exists(api_rval['results'], module.params['name']): + + if module.check_mode: + module.exit_json(change=False, msg='Would have performed a create.') + + # Create it here + api_rval = ocobj.create(module.params['files'], module.params['content']) + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + # return the created object + api_rval = ocobj.get() + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + # Remove files + if module.params['files'] and module.params['delete_after']: + Utils.cleanup(module.params['files']) + + module.exit_json(changed=True, results=api_rval, state="present") + + ######## + # Update + ######## + # if a file path is passed, use it. + update = ocobj.needs_update(module.params['files'], module.params['content']) + if not isinstance(update, bool): + module.fail_json(msg=update) + + # No changes + if not update: + if module.params['files'] and module.params['delete_after']: + Utils.cleanup(module.params['files']) + + module.exit_json(changed=False, results=api_rval['results'][0], state="present") + + if module.check_mode: + module.exit_json(change=False, msg='Would have performed an update.') + + api_rval = ocobj.update(module.params['files'], + module.params['content'], + module.params['force']) + + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + # return the created object + api_rval = ocobj.get() + + if api_rval['returncode'] != 0: + module.fail_json(msg=api_rval) + + module.exit_json(changed=True, results=api_rval, state="present") + + module.exit_json(failed=True, + changed=False, + results='Unknown state passed. %s' % state, + state="unknown") + +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled +# import module snippets. This are required +from ansible.module_utils.basic import * + +main() diff --git a/roles/lib_openshift_api/library/oc_secret.py b/roles/lib_openshift_api/library/oc_secret.py index d69d490ac..a03022e35 100644 --- a/roles/lib_openshift_api/library/oc_secret.py +++ b/roles/lib_openshift_api/library/oc_secret.py @@ -1,16 +1,30 @@ #!/usr/bin/env python +# ___ ___ _ _ ___ ___ _ _____ ___ ___ +# / __| __| \| | __| _ \ /_\_ _| __| \ +# | (_ | _|| .` | _|| / / _ \| | | _|| |) | +# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____ +# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _| +# | |) | (_) | | .` | (_) || | | _|| |) | | | | +# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_| ''' - OpenShiftCLI class that wraps the oc commands in a subprocess + OpenShiftCLI class that wraps the oc commands in a subprocess ''' + import atexit import json import os import shutil import subprocess +import re + import yaml +# This is here because of a bug that causes yaml +# to incorrectly handle timezone info on timestamps +def timestamp_constructor(_, node): + '''return timestamps as strings''' + return str(node.value) +yaml.add_constructor(u'tag:yaml.org,2002:timestamp', timestamp_constructor) -# The base class is here to share methods. -# Currently there is only 1 but will grow in the future. # pylint: disable=too-few-public-methods class OpenShiftCLI(object): ''' Class to wrap the oc command line tools ''' @@ -23,13 +37,69 @@ class OpenShiftCLI(object): self.verbose = verbose self.kubeconfig = kubeconfig + # Pylint allows only 5 arguments to be passed. + # pylint: disable=too-many-arguments + def _replace_content(self, resource, rname, content, force=False): + ''' replace the current object with the content ''' + res = self._get(resource, rname) + if not res['results']: + return res + + fname = '/tmp/%s' % rname + yed = Yedit(fname, res['results'][0]) + changes = [] + for key, value in content.items(): + changes.append(yed.put(key, value)) + + if any([not change[0] for change in changes]): + return {'returncode': 0, 'updated': False} + + yed.write() + + atexit.register(Utils.cleanup, [fname]) + + return self._replace(fname, force) + + def _replace(self, fname, force=False): + '''return all pods ''' + cmd = ['-n', self.namespace, 'replace', '-f', fname] + if force: + cmd.append('--force') + return self.oc_cmd(cmd) + + def _create(self, fname): + '''return all pods ''' + return self.oc_cmd(['create', '-f', fname, '-n', self.namespace]) + + def _delete(self, resource, rname): + '''return all pods ''' + return self.oc_cmd(['delete', resource, rname, '-n', self.namespace]) + + def _get(self, resource, rname=None): + '''return a secret by name ''' + cmd = ['get', resource, '-o', 'json', '-n', self.namespace] + if rname: + cmd.append(rname) + + rval = self.oc_cmd(cmd, output=True) + + # Ensure results are retuned in an array + if rval.has_key('items'): + rval['results'] = rval['items'] + elif not isinstance(rval['results'], list): + rval['results'] = [rval['results']] + + return rval + def oc_cmd(self, cmd, output=False): '''Base command for oc ''' #cmds = ['/usr/bin/oc', '--config', self.kubeconfig] cmds = ['/usr/bin/oc'] cmds.extend(cmd) + rval = {} results = '' + err = None if self.verbose: print ' '.join(cmds) @@ -38,46 +108,73 @@ class OpenShiftCLI(object): stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={'KUBECONFIG': self.kubeconfig}) + proc.wait() + stdout = proc.stdout.read() + stderr = proc.stderr.read() + + rval = {"returncode": proc.returncode, + "results": results, + } + if proc.returncode == 0: if output: try: - results = json.loads(proc.stdout.read()) + rval['results'] = json.loads(stdout) except ValueError as err: if "No JSON object could be decoded" in err.message: - results = err.message + err = err.message if self.verbose: - print proc.stderr.read() - print results + print stdout + print stderr print - return {"returncode": proc.returncode, "results": results} + if err: + rval.update({"err": err, + "stderr": stderr, + "stdout": stdout, + "cmd": cmds + }) - return {"returncode": proc.returncode, - "stderr": proc.stderr.read(), - "stdout": proc.stdout.read(), - "results": {} - } + else: + rval.update({"stderr": stderr, + "stdout": stdout, + "results": {}, + }) + + return rval class Utils(object): ''' utilities for openshiftcli modules ''' @staticmethod + def create_file(rname, data, ftype=None): + ''' create a file in tmp with name and contents''' + path = os.path.join('/tmp', rname) + with open(path, 'w') as fds: + if ftype == 'yaml': + fds.write(yaml.safe_dump(data, default_flow_style=False)) + + elif ftype == 'json': + fds.write(json.dumps(data)) + else: + fds.write(data) + + # Register cleanup when module is done + atexit.register(Utils.cleanup, [path]) + return path + + @staticmethod def create_files_from_contents(data): '''Turn an array of dict: filename, content into a files array''' files = [] for sfile in data: - path = os.path.join('/tmp', sfile['path']) - with open(path, 'w') as fds: - fds.write(sfile['content']) + path = Utils.create_file(sfile['path'], sfile['content']) files.append(path) - # Register cleanup when module is done - atexit.register(Utils.cleanup, files) return files - @staticmethod def cleanup(files): '''Clean up on exit ''' @@ -106,7 +203,6 @@ class Utils(object): ''' Find the specified result by name''' rval = None for result in results: - #print "%s == %s" % (result['metadata']['name'], name) if result.has_key('metadata') and result['metadata']['name'] == _name: rval = result break @@ -121,7 +217,7 @@ class Utils(object): contents = sfd.read() if sfile_type == 'yaml': - contents = yaml.load(contents) + contents = yaml.safe_load(contents) elif sfile_type == 'json': contents = json.loads(contents) @@ -134,7 +230,7 @@ class Utils(object): ''' Given a user defined definition, compare it with the results given back by our query. ''' # Currently these values are autogenerated and we do not need to check them - skip = ['creationTimestamp', 'selfLink', 'resourceVersion', 'uid', 'namespace'] + skip = ['metadata', 'status'] for key, value in result_def.items(): if key in skip: @@ -166,7 +262,7 @@ class Utils(object): print "keys are not equal in dict" return False - result = Utils.check_def_equal(user_def[key], value) + result = Utils.check_def_equal(user_def[key], value, debug=debug) if not result: if debug: print "dict returned false" @@ -183,6 +279,214 @@ class Utils(object): return True +class YeditException(Exception): + ''' Exception class for Yedit ''' + pass + +class Yedit(object): + ''' Class to modify yaml files ''' + re_valid_key = r"(((\[-?\d+\])|([a-zA-Z-./]+)).?)+$" + re_key = r"(?:\[(-?\d+)\])|([a-zA-Z-./]+)" + + def __init__(self, filename=None, content=None, content_type='yaml'): + self.content = content + self.filename = filename + self.__yaml_dict = content + self.content_type = content_type + if self.filename and not self.content: + self.load(content_type=self.content_type) + + @property + def yaml_dict(self): + ''' getter method for yaml_dict ''' + return self.__yaml_dict + + @yaml_dict.setter + def yaml_dict(self, value): + ''' setter method for yaml_dict ''' + self.__yaml_dict = value + + @staticmethod + def remove_entry(data, key): + ''' remove data at location key ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + # process last index for remove + # expected list entry + if key_indexes[-1][0]: + if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + del data[int(key_indexes[-1][0])] + return True + + # expected dict entry + elif key_indexes[-1][1]: + if isinstance(data, dict): + del data[key_indexes[-1][1]] + return True + + @staticmethod + def add_entry(data, key, item=None): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a.b + return c + ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + curr_data = data + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key: + if isinstance(data, dict) and data.has_key(dict_key): + data = data[dict_key] + continue + + data[dict_key] = {} + data = data[dict_key] + + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + # process last index for add + # expected list entry + if key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + data[int(key_indexes[-1][0])] = item + + # expected dict entry + elif key_indexes[-1][1] and isinstance(data, dict): + data[key_indexes[-1][1]] = item + + return curr_data + + @staticmethod + def get_entry(data, key): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a.b + return c + ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + return data + + def write(self): + ''' write to file ''' + if not self.filename: + raise YeditException('Please specify a filename.') + + with open(self.filename, 'w') as yfd: + yfd.write(yaml.safe_dump(self.yaml_dict, default_flow_style=False)) + + def read(self): + ''' write to file ''' + # check if it exists + if not self.exists(): + return None + + contents = None + with open(self.filename) as yfd: + contents = yfd.read() + + return contents + + def exists(self): + ''' return whether file exists ''' + if os.path.exists(self.filename): + return True + + return False + + def load(self, content_type='yaml'): + ''' return yaml file ''' + contents = self.read() + + if not contents: + return None + + # check if it is yaml + try: + if content_type == 'yaml': + self.yaml_dict = yaml.load(contents) + elif content_type == 'json': + self.yaml_dict = json.loads(contents) + except yaml.YAMLError as _: + # Error loading yaml or json + return None + + return self.yaml_dict + + def get(self, key): + ''' get a specified key''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + + return entry + + def delete(self, key): + ''' remove key from a dict''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + if not entry: + return (False, self.yaml_dict) + + result = Yedit.remove_entry(self.yaml_dict, key) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) + + def put(self, key, value): + ''' put key, value into a dict ''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + + if entry == value: + return (False, self.yaml_dict) + + result = Yedit.add_entry(self.yaml_dict, key, value) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) + + def create(self, key, value): + ''' create a yaml file ''' + if not self.exists(): + self.yaml_dict = {key: value} + return (True, self.yaml_dict) + + return (False, self.yaml_dict) + class Secret(OpenShiftCLI): ''' Class to wrap the oc command line tools ''' @@ -192,34 +496,22 @@ class Secret(OpenShiftCLI): kubeconfig='/etc/origin/master/admin.kubeconfig', verbose=False): ''' Constructor for OpenshiftOC ''' - super(Secret, OpenShiftCLI).__init__(namespace, kubeconfig) + super(Secret, self).__init__(namespace, kubeconfig) self.namespace = namespace self.name = secret_name self.kubeconfig = kubeconfig self.verbose = verbose - def get_secrets(self): + def get(self): '''return a secret by name ''' - cmd = ['get', 'secrets', '-o', 'json', '-n', self.namespace] - if self.name: - cmd.append(self.name) - - rval = self.oc_cmd(cmd, output=True) - - # Ensure results are retuned in an array - if rval.has_key('items'): - rval['results'] = rval['items'] - elif not isinstance(rval['results'], list): - rval['results'] = [rval['results']] + return self._get('secrets', self.name) - return rval + def delete(self): + '''delete a secret by name''' + return self._delete('secrets', self.name) - def delete_secret(self): - '''return all pods ''' - return self.oc_cmd(['delete', 'secrets', self.name, '-n', self.namespace]) - - def secret_new(self, files=None, contents=None): - '''Create a secret with all pods ''' + def create(self, files=None, contents=None): + '''Create a secret ''' if not files: files = Utils.create_files_from_contents(contents) @@ -229,7 +521,7 @@ class Secret(OpenShiftCLI): return self.oc_cmd(cmd) - def update_secret(self, files, force=False): + def update(self, files, force=False): '''run update secret This receives a list of file names and converts it into a secret. @@ -243,13 +535,9 @@ class Secret(OpenShiftCLI): with open(sfile_path, 'w') as sfd: sfd.write(json.dumps(secret['results'])) - cmd = ['replace', '-f', sfile_path] - if force: - cmd = ['replace', '--force', '-f', sfile_path] - atexit.register(Utils.cleanup, [sfile_path]) - return self.oc_cmd(cmd) + return self._replace(sfile_path, force=force) def prep_secret(self, files=None, contents=None): ''' return what the secret would look like if created @@ -296,7 +584,7 @@ def main(): state = module.params['state'] - api_rval = occmd.get_secrets() + api_rval = occmd.get() ##### # Get @@ -316,7 +604,7 @@ def main(): if module.check_mode: module.exit_json(change=False, msg='Would have performed a delete.') - api_rval = occmd.delete_secret() + api_rval = occmd.delete() module.exit_json(changed=True, results=api_rval, state="absent") @@ -336,7 +624,7 @@ def main(): if module.check_mode: module.exit_json(change=False, msg='Would have performed a create.') - api_rval = occmd.secret_new(module.params['files'], module.params['contents']) + api_rval = occmd.create(module.params['files'], module.params['contents']) # Remove files if files and module.params['delete_after']: @@ -363,7 +651,7 @@ def main(): if module.check_mode: module.exit_json(change=False, msg='Would have performed an update.') - api_rval = occmd.update_secret(files, force=module.params['force']) + api_rval = occmd.update(files, force=module.params['force']) # Remove files if secret and module.params['delete_after']: diff --git a/roles/lib_openshift_api/library/oc_service.py b/roles/lib_openshift_api/library/oc_service.py deleted file mode 100644 index 48281f254..000000000 --- a/roles/lib_openshift_api/library/oc_service.py +++ /dev/null @@ -1,356 +0,0 @@ -#!/usr/bin/env python -''' - OpenShiftCLI class that wraps the oc commands in a subprocess -''' -import atexit -import json -import os -import shutil -import subprocess -import yaml - -# The base class is here to share methods. -# Currently there is only 1 but will grow in the future. -# pylint: disable=too-few-public-methods -class OpenShiftCLI(object): - ''' Class to wrap the oc command line tools ''' - def __init__(self, - namespace, - kubeconfig='/etc/origin/master/admin.kubeconfig', - verbose=False): - ''' Constructor for OpenshiftOC ''' - self.namespace = namespace - self.verbose = verbose - self.kubeconfig = kubeconfig - - def oc_cmd(self, cmd, output=False): - '''Base command for oc ''' - #cmds = ['/usr/bin/oc', '--config', self.kubeconfig] - cmds = ['/usr/bin/oc'] - cmds.extend(cmd) - - results = '' - - if self.verbose: - print ' '.join(cmds) - - proc = subprocess.Popen(cmds, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env={'KUBECONFIG': self.kubeconfig}) - proc.wait() - if proc.returncode == 0: - if output: - try: - results = json.loads(proc.stdout.read()) - except ValueError as err: - if "No JSON object could be decoded" in err.message: - results = err.message - - if self.verbose: - print proc.stderr.read() - print results - print - - return {"returncode": proc.returncode, "results": results} - - return {"returncode": proc.returncode, - "stderr": proc.stderr.read(), - "stdout": proc.stdout.read(), - "results": {} - } - -class Utils(object): - ''' utilities for openshiftcli modules ''' - @staticmethod - def create_files_from_contents(data): - '''Turn an array of dict: filename, content into a files array''' - files = [] - - for sfile in data: - path = os.path.join('/tmp', sfile['path']) - with open(path, 'w') as fds: - fds.write(sfile['content']) - files.append(path) - - # Register cleanup when module is done - atexit.register(Utils.cleanup, files) - return files - - - @staticmethod - def cleanup(files): - '''Clean up on exit ''' - for sfile in files: - if os.path.exists(sfile): - if os.path.isdir(sfile): - shutil.rmtree(sfile) - elif os.path.isfile(sfile): - os.remove(sfile) - - - @staticmethod - def exists(results, _name): - ''' Check to see if the results include the name ''' - if not results: - return False - - - if Utils.find_result(results, _name): - return True - - return False - - @staticmethod - def find_result(results, _name): - ''' Find the specified result by name''' - rval = None - for result in results: - #print "%s == %s" % (result['metadata']['name'], name) - if result.has_key('metadata') and result['metadata']['name'] == _name: - rval = result - break - - return rval - - @staticmethod - def get_resource_file(sfile, sfile_type='yaml'): - ''' return the service file ''' - contents = None - with open(sfile) as sfd: - contents = sfd.read() - - if sfile_type == 'yaml': - contents = yaml.load(contents) - elif sfile_type == 'json': - contents = json.loads(contents) - - return contents - - # Disabling too-many-branches. This is a yaml dictionary comparison function - # pylint: disable=too-many-branches,too-many-return-statements - @staticmethod - def check_def_equal(user_def, result_def, debug=False): - ''' Given a user defined definition, compare it with the results given back by our query. ''' - - # Currently these values are autogenerated and we do not need to check them - skip = ['creationTimestamp', 'selfLink', 'resourceVersion', 'uid', 'namespace'] - - for key, value in result_def.items(): - if key in skip: - continue - - # Both are lists - if isinstance(value, list): - if not isinstance(user_def[key], list): - return False - - # lists should be identical - if value != user_def[key]: - return False - - # recurse on a dictionary - elif isinstance(value, dict): - if not isinstance(user_def[key], dict): - if debug: - print "dict returned false not instance of dict" - return False - - # before passing ensure keys match - api_values = set(value.keys()) - set(skip) - user_values = set(user_def[key].keys()) - set(skip) - if api_values != user_values: - if debug: - print api_values - print user_values - print "keys are not equal in dict" - return False - - result = Utils.check_def_equal(user_def[key], value) - if not result: - if debug: - print "dict returned false" - return False - - # Verify each key, value pair is the same - else: - if not user_def.has_key(key) or value != user_def[key]: - if debug: - print "value not equal; user_def does not have key" - print value - print user_def[key] - return False - - return True - -class Service(OpenShiftCLI): - ''' Class to wrap the oc command line tools - ''' - def __init__(self, - namespace, - service_name=None, - kubeconfig='/etc/origin/master/admin.kubeconfig', - verbose=False): - ''' Constructor for OpenshiftOC ''' - super(Service, OpenShiftCLI).__init__(namespace, kubeconfig) - self.namespace = namespace - self.name = service_name - self.verbose = verbose - self.kubeconfig = kubeconfig - - def create_service(self, sfile): - ''' create the service ''' - return self.oc_cmd(['create', '-f', sfile]) - - def get_services(self): - '''return a secret by name ''' - cmd = ['get', 'services', '-o', 'json', '-n', self.namespace] - if self.name: - cmd.append(self.name) - - rval = self.oc_cmd(cmd, output=True) - - # Ensure results are retuned in an array - if rval.has_key('items'): - rval['results'] = rval['items'] - elif not isinstance(rval['results'], list): - rval['results'] = [rval['results']] - - return rval - - def delete_service(self): - '''return all pods ''' - return self.oc_cmd(['delete', 'service', self.name, '-n', self.namespace]) - - def update_service(self, sfile, force=False): - '''run update service - - This receives a list of file names and converts it into a secret. - The secret is then written to disk and passed into the `oc replace` command. - ''' - - cmd = ['replace', '-f', sfile] - if force: - cmd = ['replace', '--force', '-f', sfile] - - atexit.register(Utils.cleanup, [sfile]) - - return self.oc_cmd(cmd) - - -# pylint: disable=too-many-branches -def main(): - ''' - ansible oc module for services - ''' - - module = AnsibleModule( - argument_spec=dict( - kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), - state=dict(default='present', type='str', - choices=['present', 'absent', 'list']), - debug=dict(default=False, type='bool'), - namespace=dict(default='default', type='str'), - name=dict(default=None, type='str'), - service_file=dict(default=None, type='str'), - service_file_type=dict(default=None, type='str'), - delete_after=dict(default=False, type='bool'), - contents=dict(default=None, type='list'), - force=dict(default=False, type='bool'), - ), - mutually_exclusive=[["contents", "service_file"]], - - supports_check_mode=True, - ) - occmd = Service(module.params['namespace'], - module.params['name'], - kubeconfig=module.params['kubeconfig'], - verbose=module.params['debug']) - - state = module.params['state'] - - api_rval = occmd.get_services() - - ##### - # Get - ##### - if state == 'list': - module.exit_json(changed=False, results=api_rval['results'], state="list") - - if not module.params['name']: - module.fail_json(msg='Please specify a name when state is absent|present.') - ######## - # Delete - ######## - if state == 'absent': - if not Utils.exists(api_rval['results'], module.params['name']): - module.exit_json(changed=False, state="absent") - - if module.check_mode: - module.exit_json(change=False, msg='Would have performed a delete.') - - api_rval = occmd.delete_service() - module.exit_json(changed=True, results=api_rval, state="absent") - - - if state == 'present': - if module.params['service_file']: - sfile = module.params['service_file'] - elif module.params['contents']: - sfile = Utils.create_files_from_contents(module.params['contents']) - else: - module.fail_json(msg='Either specify files or contents.') - - ######## - # Create - ######## - if not Utils.exists(api_rval['results'], module.params['name']): - - if module.check_mode: - module.exit_json(change=False, msg='Would have performed a create.') - - api_rval = occmd.create_service(sfile) - - # Remove files - if sfile and module.params['delete_after']: - Utils.cleanup([sfile]) - - module.exit_json(changed=True, results=api_rval, state="present") - - ######## - # Update - ######## - sfile_contents = Utils.get_resource_file(sfile, module.params['service_file_type']) - if Utils.check_def_equal(sfile_contents, api_rval['results'][0]): - - # Remove files - if module.params['service_file'] and module.params['delete_after']: - Utils.cleanup([sfile]) - - module.exit_json(changed=False, results=api_rval['results'][0], state="present") - - if module.check_mode: - module.exit_json(change=False, msg='Would have performed an update.') - - api_rval = occmd.update_service(sfile, force=module.params['force']) - - # Remove files - if sfile and module.params['delete_after']: - Utils.cleanup([sfile]) - - if api_rval['returncode'] != 0: - module.fail_json(msg=api_rval) - - - module.exit_json(changed=True, results=api_rval, state="present") - - module.exit_json(failed=True, - changed=False, - results='Unknown state passed. %s' % state, - state="unknown") - -# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled -# import module snippets. This are required -from ansible.module_utils.basic import * - -main() diff --git a/roles/lib_yaml_editor/build/ansible/yedit.py b/roles/lib_yaml_editor/build/ansible/yedit.py new file mode 100644 index 000000000..dab3d6347 --- /dev/null +++ b/roles/lib_yaml_editor/build/ansible/yedit.py @@ -0,0 +1,69 @@ +#pylint: skip-file + +def main(): + ''' + ansible oc module for secrets + ''' + + module = AnsibleModule( + argument_spec=dict( + state=dict(default='present', type='str', + choices=['present', 'absent', 'list']), + debug=dict(default=False, type='bool'), + src=dict(default=None, type='str'), + content=dict(default=None, type='dict'), + key=dict(default=None, type='str'), + value=dict(default=None, type='str'), + value_format=dict(default='yaml', choices=['yaml', 'json'], type='str'), + ), + #mutually_exclusive=[["src", "content"]], + + supports_check_mode=True, + ) + state = module.params['state'] + + yamlfile = Yedit(module.params['src'], module.params['content']) + + rval = yamlfile.load() + if not rval and state != 'present': + module.fail_json(msg='Error opening file [%s]. Verify that the' + \ + ' file exists, that it is has correct permissions, and is valid yaml.') + + if state == 'list': + module.exit_json(changed=False, results=rval, state="list") + + if state == 'absent': + rval = yamlfile.delete(module.params['key']) + module.exit_json(changed=rval[0], results=rval[1], state="absent") + + if state == 'present': + + if module.params['value_format'] == 'yaml': + value = yaml.load(module.params['value']) + elif module.params['value_format'] == 'json': + value = json.loads(module.params['value']) + + if rval: + rval = yamlfile.put(module.params['key'], value) + if rval[0]: + yamlfile.write() + module.exit_json(changed=rval[0], results=rval[1], state="present") + + if not module.params['content']: + rval = yamlfile.create(module.params['key'], value) + else: + rval = yamlfile.load() + yamlfile.write() + + module.exit_json(changed=rval[0], results=rval[1], state="present") + + module.exit_json(failed=True, + changed=False, + results='Unknown state passed. %s' % state, + state="unknown") + +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled +# import module snippets. This are required +from ansible.module_utils.basic import * + +main() diff --git a/roles/lib_yaml_editor/build/generate.py b/roles/lib_yaml_editor/build/generate.py new file mode 100755 index 000000000..312e4d0ee --- /dev/null +++ b/roles/lib_yaml_editor/build/generate.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +''' + Generate the openshift-ansible/roles/lib_openshift_cli/library/ modules. +''' + +import os + +# pylint: disable=anomalous-backslash-in-string +GEN_STR = "#!/usr/bin/env python\n" + \ + "# ___ ___ _ _ ___ ___ _ _____ ___ ___\n" + \ + "# / __| __| \| | __| _ \ /_\_ _| __| \\\n" + \ + "# | (_ | _|| .` | _|| / / _ \| | | _|| |) |\n" + \ + "# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____\n" + \ + "# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _|\n" + \ + "# | |) | (_) | | .` | (_) || | | _|| |) | | | |\n" + \ + "# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_|\n" + +OPENSHIFT_ANSIBLE_PATH = os.path.dirname(os.path.realpath(__file__)) + +FILES = {'yedit.py': ['src/base.py', 'src/yedit.py', 'ansible/yedit.py'], + } + +def main(): + ''' combine the necessary files to create the ansible module ''' + library = os.path.join(OPENSHIFT_ANSIBLE_PATH, '..', 'library/') + for fname, parts in FILES.items(): + with open(os.path.join(library, fname), 'w') as afd: + afd.seek(0) + afd.write(GEN_STR) + for fpart in parts: + with open(os.path.join(OPENSHIFT_ANSIBLE_PATH, fpart)) as pfd: + # first line is pylint disable so skip it + for idx, line in enumerate(pfd): + if idx == 0 and 'skip-file' in line: + continue + + afd.write(line) + + +if __name__ == '__main__': + main() + + diff --git a/roles/lib_yaml_editor/build/src/base.py b/roles/lib_yaml_editor/build/src/base.py new file mode 100644 index 000000000..9e43d45dc --- /dev/null +++ b/roles/lib_yaml_editor/build/src/base.py @@ -0,0 +1,17 @@ +# pylint: skip-file + +''' +module for managing yaml files +''' + +import os +import re + +import yaml +# This is here because of a bug that causes yaml +# to incorrectly handle timezone info on timestamps +def timestamp_constructor(_, node): + ''' return timestamps as strings''' + return str(node.value) +yaml.add_constructor(u'tag:yaml.org,2002:timestamp', timestamp_constructor) + diff --git a/roles/lib_yaml_editor/build/src/yedit.py b/roles/lib_yaml_editor/build/src/yedit.py new file mode 100644 index 000000000..642733914 --- /dev/null +++ b/roles/lib_yaml_editor/build/src/yedit.py @@ -0,0 +1,209 @@ +# pylint: skip-file + +class YeditException(Exception): + ''' Exception class for Yedit ''' + pass + +class Yedit(object): + ''' Class to modify yaml files ''' + re_valid_key = r"(((\[-?\d+\])|([a-zA-Z-./]+)).?)+$" + re_key = r"(?:\[(-?\d+)\])|([a-zA-Z-./]+)" + + def __init__(self, filename=None, content=None, content_type='yaml'): + self.content = content + self.filename = filename + self.__yaml_dict = content + self.content_type = content_type + if self.filename and not self.content: + self.load(content_type=self.content_type) + + @property + def yaml_dict(self): + ''' getter method for yaml_dict ''' + return self.__yaml_dict + + @yaml_dict.setter + def yaml_dict(self, value): + ''' setter method for yaml_dict ''' + self.__yaml_dict = value + + @staticmethod + def remove_entry(data, key): + ''' remove data at location key ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + # process last index for remove + # expected list entry + if key_indexes[-1][0]: + if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + del data[int(key_indexes[-1][0])] + return True + + # expected dict entry + elif key_indexes[-1][1]: + if isinstance(data, dict): + del data[key_indexes[-1][1]] + return True + + @staticmethod + def add_entry(data, key, item=None): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a.b + return c + ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + curr_data = data + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key: + if isinstance(data, dict) and data.has_key(dict_key): + data = data[dict_key] + continue + + data[dict_key] = {} + data = data[dict_key] + + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + # process last index for add + # expected list entry + if key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + data[int(key_indexes[-1][0])] = item + + # expected dict entry + elif key_indexes[-1][1] and isinstance(data, dict): + data[key_indexes[-1][1]] = item + + return curr_data + + @staticmethod + def get_entry(data, key): + ''' Get an item from a dictionary with key notation a.b.c + d = {'a': {'b': 'c'}}} + key = a.b + return c + ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + return data + + def write(self): + ''' write to file ''' + if not self.filename: + raise YeditException('Please specify a filename.') + + with open(self.filename, 'w') as yfd: + yfd.write(yaml.safe_dump(self.yaml_dict, default_flow_style=False)) + + def read(self): + ''' write to file ''' + # check if it exists + if not self.exists(): + return None + + contents = None + with open(self.filename) as yfd: + contents = yfd.read() + + return contents + + def exists(self): + ''' return whether file exists ''' + if os.path.exists(self.filename): + return True + + return False + + def load(self, content_type='yaml'): + ''' return yaml file ''' + contents = self.read() + + if not contents: + return None + + # check if it is yaml + try: + if content_type == 'yaml': + self.yaml_dict = yaml.load(contents) + elif content_type == 'json': + self.yaml_dict = json.loads(contents) + except yaml.YAMLError as _: + # Error loading yaml or json + return None + + return self.yaml_dict + + def get(self, key): + ''' get a specified key''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + + return entry + + def delete(self, key): + ''' remove key from a dict''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + if not entry: + return (False, self.yaml_dict) + + result = Yedit.remove_entry(self.yaml_dict, key) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) + + def put(self, key, value): + ''' put key, value into a dict ''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + + if entry == value: + return (False, self.yaml_dict) + + result = Yedit.add_entry(self.yaml_dict, key, value) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) + + def create(self, key, value): + ''' create a yaml file ''' + if not self.exists(): + self.yaml_dict = {key: value} + return (True, self.yaml_dict) + + return (False, self.yaml_dict) diff --git a/roles/lib_yaml_editor/build/test/foo.yml b/roles/lib_yaml_editor/build/test/foo.yml new file mode 100644 index 000000000..20e9ff3fe --- /dev/null +++ b/roles/lib_yaml_editor/build/test/foo.yml @@ -0,0 +1 @@ +foo: bar diff --git a/roles/lib_yaml_editor/build/test/test.yaml b/roles/lib_yaml_editor/build/test/test.yaml new file mode 100755 index 000000000..ac9c37565 --- /dev/null +++ b/roles/lib_yaml_editor/build/test/test.yaml @@ -0,0 +1,15 @@ +#!/usr/bin/ansible-playbook +--- +- hosts: localhost + gather_facts: no + tasks: + - yedit: + src: /home/kwoodson/git/openshift-ansible/roles/lib_yaml_editor/build/test/foo.yml + key: foo + value: barplus + state: present + register: output + + - debug: + msg: "{{ output }}" + diff --git a/roles/lib_yaml_editor/library/yedit.py b/roles/lib_yaml_editor/library/yedit.py index b75aff109..b7ae45b31 100644 --- a/roles/lib_yaml_editor/library/yedit.py +++ b/roles/lib_yaml_editor/library/yedit.py @@ -1,10 +1,27 @@ #!/usr/bin/env python +# ___ ___ _ _ ___ ___ _ _____ ___ ___ +# / __| __| \| | __| _ \ /_\_ _| __| \ +# | (_ | _|| .` | _|| / / _ \| | | _|| |) | +# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____ +# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _| +# | |) | (_) | | .` | (_) || | | _|| |) | | | | +# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_| + ''' -module for openshift cloud secrets +module for managing yaml files ''' import os +import re + import yaml +# This is here because of a bug that causes yaml +# to incorrectly handle timezone info on timestamps +def timestamp_constructor(_, node): + ''' return timestamps as strings''' + return str(node.value) +yaml.add_constructor(u'tag:yaml.org,2002:timestamp', timestamp_constructor) + class YeditException(Exception): ''' Exception class for Yedit ''' @@ -12,11 +29,16 @@ class YeditException(Exception): class Yedit(object): ''' Class to modify yaml files ''' + re_valid_key = r"(((\[-?\d+\])|([a-zA-Z-./]+)).?)+$" + re_key = r"(?:\[(-?\d+)\])|([a-zA-Z-./]+)" - def __init__(self, filename): + def __init__(self, filename=None, content=None, content_type='yaml'): + self.content = content self.filename = filename - self.__yaml_dict = None - self.get() + self.__yaml_dict = content + self.content_type = content_type + if self.filename and not self.content: + self.load(content_type=self.content_type) @property def yaml_dict(self): @@ -29,63 +51,99 @@ class Yedit(object): self.__yaml_dict = value @staticmethod - def remove_entry(data, keys): - ''' remove an item from a dictionary with key notation a.b.c - d = {'a': {'b': 'c'}}} - keys = a.b - item = c - ''' - if "." in keys: - key, rest = keys.split(".", 1) - if key in data.keys(): - Yedit.remove_entry(data[key], rest) - else: - del data[keys] + def remove_entry(data, key): + ''' remove data at location key ''' + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] + else: + return None + + # process last index for remove + # expected list entry + if key_indexes[-1][0]: + if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + del data[int(key_indexes[-1][0])] + return True + + # expected dict entry + elif key_indexes[-1][1]: + if isinstance(data, dict): + del data[key_indexes[-1][1]] + return True @staticmethod - def add_entry(data, keys, item): - ''' Add an item to a dictionary with key notation a.b.c + def add_entry(data, key, item=None): + ''' Get an item from a dictionary with key notation a.b.c d = {'a': {'b': 'c'}}} - keys = a.b - item = c + key = a.b + return c ''' - if "." in keys: - key, rest = keys.split(".", 1) - if key not in data: - data[key] = {} + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + + curr_data = data - if not isinstance(data, dict): - raise YeditException('Invalid add_entry called on a [%s] of type [%s].' % (data, type(data))) + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes[:-1]: + if dict_key: + if isinstance(data, dict) and data.has_key(dict_key): + data = data[dict_key] + continue + + data[dict_key] = {} + data = data[dict_key] + + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] else: - Yedit.add_entry(data[key], rest, item) + return None - else: - data[keys] = item + # process last index for add + # expected list entry + if key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: + data[int(key_indexes[-1][0])] = item + + # expected dict entry + elif key_indexes[-1][1] and isinstance(data, dict): + data[key_indexes[-1][1]] = item + return curr_data @staticmethod - def get_entry(data, keys): + def get_entry(data, key): ''' Get an item from a dictionary with key notation a.b.c d = {'a': {'b': 'c'}}} - keys = a.b + key = a.b return c ''' - if keys and "." in keys: - key, rest = keys.split(".", 1) - if not isinstance(data[key], dict): - raise YeditException('Invalid get_entry called on a [%s] of type [%s].' % (data, type(data))) + if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))): + return None + key_indexes = re.findall(Yedit.re_key, key) + for arr_ind, dict_key in key_indexes: + if dict_key and isinstance(data, dict): + data = data.get(dict_key, None) + elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1: + data = data[int(arr_ind)] else: - return Yedit.get_entry(data[key], rest) - - else: - return data.get(keys, None) + return None + return data def write(self): ''' write to file ''' + if not self.filename: + raise YeditException('Please specify a filename.') + with open(self.filename, 'w') as yfd: - yfd.write(yaml.dump(self.yaml_dict, default_flow_style=False)) + yfd.write(yaml.safe_dump(self.yaml_dict, default_flow_style=False)) def read(self): ''' write to file ''' @@ -105,7 +163,8 @@ class Yedit(object): return True return False - def get(self): + + def load(self, content_type='yaml'): ''' return yaml file ''' contents = self.read() @@ -114,15 +173,27 @@ class Yedit(object): # check if it is yaml try: - self.yaml_dict = yaml.load(contents) + if content_type == 'yaml': + self.yaml_dict = yaml.load(contents) + elif content_type == 'json': + self.yaml_dict = json.loads(contents) except yaml.YAMLError as _: - # Error loading yaml + # Error loading yaml or json return None return self.yaml_dict + def get(self, key): + ''' get a specified key''' + try: + entry = Yedit.get_entry(self.yaml_dict, key) + except KeyError as _: + entry = None + + return entry + def delete(self, key): - ''' put key, value into a yaml file ''' + ''' remove key from a dict''' try: entry = Yedit.get_entry(self.yaml_dict, key) except KeyError as _: @@ -130,12 +201,14 @@ class Yedit(object): if not entry: return (False, self.yaml_dict) - Yedit.remove_entry(self.yaml_dict, key) - self.write() - return (True, self.get()) + result = Yedit.remove_entry(self.yaml_dict, key) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) def put(self, key, value): - ''' put key, value into a yaml file ''' + ''' put key, value into a dict ''' try: entry = Yedit.get_entry(self.yaml_dict, key) except KeyError as _: @@ -144,19 +217,19 @@ class Yedit(object): if entry == value: return (False, self.yaml_dict) - Yedit.add_entry(self.yaml_dict, key, value) - self.write() - return (True, self.get()) + result = Yedit.add_entry(self.yaml_dict, key, value) + if not result: + return (False, self.yaml_dict) + + return (True, self.yaml_dict) def create(self, key, value): - ''' create the file ''' + ''' create a yaml file ''' if not self.exists(): self.yaml_dict = {key: value} - self.write() - return (True, self.get()) - - return (False, self.get()) + return (True, self.yaml_dict) + return (False, self.yaml_dict) def main(): ''' @@ -168,24 +241,24 @@ def main(): state=dict(default='present', type='str', choices=['present', 'absent', 'list']), debug=dict(default=False, type='bool'), - src=dict(default=None, type='str'), + content=dict(default=None, type='dict'), key=dict(default=None, type='str'), value=dict(default=None, type='str'), value_format=dict(default='yaml', choices=['yaml', 'json'], type='str'), ), - mutually_exclusive=[["contents", "files"]], + #mutually_exclusive=[["src", "content"]], supports_check_mode=True, ) state = module.params['state'] - yamlfile = Yedit(module.params['src']) + yamlfile = Yedit(module.params['src'], module.params['content']) - rval = yamlfile.get() + rval = yamlfile.load() if not rval and state != 'present': module.fail_json(msg='Error opening file [%s]. Verify that the' + \ - ' file exists and that it is has correct permissions.') + ' file exists, that it is has correct permissions, and is valid yaml.') if state == 'list': module.exit_json(changed=False, results=rval, state="list") @@ -203,9 +276,16 @@ def main(): if rval: rval = yamlfile.put(module.params['key'], value) + if rval[0]: + yamlfile.write() module.exit_json(changed=rval[0], results=rval[1], state="present") - rval = yamlfile.create(module.params['key'], value) + if not module.params['content']: + rval = yamlfile.create(module.params['key'], value) + else: + rval = yamlfile.load() + yamlfile.write() + module.exit_json(changed=rval[0], results=rval[1], state="present") module.exit_json(failed=True, diff --git a/roles/openshift_cli/meta/main.yml b/roles/openshift_cli/meta/main.yml index 62de120c6..223cb768d 100644 --- a/roles/openshift_cli/meta/main.yml +++ b/roles/openshift_cli/meta/main.yml @@ -12,6 +12,6 @@ galaxy_info: categories: - cloud dependencies: -- role: openshift_common - role: openshift_docker - when: openshift.common.is_containerized | bool +- role: openshift_common +- role: openshift_cli_facts diff --git a/roles/openshift_cli/tasks/main.yml b/roles/openshift_cli/tasks/main.yml index e82903b81..bfa60e5b0 100644 --- a/roles/openshift_cli/tasks/main.yml +++ b/roles/openshift_cli/tasks/main.yml @@ -1,10 +1,4 @@ --- -# TODO: move this to a new 'cli' role -- openshift_facts: - role: common - local_facts: - cli_image: "{{ osm_image | default(None) }}" - - name: Install clients action: "{{ ansible_pkg_mgr }} name={{ openshift.common.service_type }}-clients state=present" when: not openshift.common.is_containerized | bool diff --git a/roles/openshift_cli_facts/meta/main.yml b/roles/openshift_cli_facts/meta/main.yml new file mode 100644 index 000000000..59acde215 --- /dev/null +++ b/roles/openshift_cli_facts/meta/main.yml @@ -0,0 +1,15 @@ +--- +galaxy_info: + author: Jason DeTiberus + description: OpenShift CLI Facts + company: Red Hat, Inc. + license: Apache License, Version 2.0 + min_ansible_version: 1.9 + platforms: + - name: EL + versions: + - 7 + categories: + - cloud +dependencies: +- role: openshift_facts diff --git a/roles/openshift_cli_facts/tasks/main.yml b/roles/openshift_cli_facts/tasks/main.yml new file mode 100644 index 000000000..dd1ed8965 --- /dev/null +++ b/roles/openshift_cli_facts/tasks/main.yml @@ -0,0 +1,6 @@ +--- +# TODO: move this to a new 'cli' role +- openshift_facts: + role: common + local_facts: + cli_image: "{{ osm_image | default(None) }}" diff --git a/roles/openshift_cloud_provider/defaults/main.yml b/roles/openshift_cloud_provider/defaults/main.yml deleted file mode 100644 index 6c7403232..000000000 --- a/roles/openshift_cloud_provider/defaults/main.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -ocp_os_auth_url: "{{ lookup('env', 'OS_AUTH_URL') }}" -ocp_os_username: "{{ lookup('env', 'OS_USERNAME') }}" -ocp_os_password: "{{ lookup('env', 'OS_PASSWORD') }}" -ocp_os_tenant_id: "{{ lookup('env', 'OS_TENANT_ID') }}" -ocp_os_tenant_name: "{{ lookup('env', 'OS_TENANT_NAME') }}" -ocp_os_region: "{{ lookup('env', 'OS_REGION_NAME') }}" diff --git a/roles/openshift_cloud_provider/meta/main.yml b/roles/openshift_cloud_provider/meta/main.yml new file mode 100644 index 000000000..8ab95bf5a --- /dev/null +++ b/roles/openshift_cloud_provider/meta/main.yml @@ -0,0 +1,15 @@ +--- +galaxy_info: + author: Sylvain Baubeau, Andrew Butcher + description: OpenShift Cloud Provider + company: Red Hat, Inc. + license: Apache License, Version 2.0 + min_ansible_version: 1.9 + platforms: + - name: EL + versions: + - 7 + categories: + - cloud +dependencies: +- role: openshift_facts diff --git a/roles/openshift_cloud_provider/tasks/aws.yml b/roles/openshift_cloud_provider/tasks/aws.yml new file mode 100644 index 000000000..bf2abcbf5 --- /dev/null +++ b/roles/openshift_cloud_provider/tasks/aws.yml @@ -0,0 +1,6 @@ +- name: Create cloud config + ini_file: + dest: "{{ openshift.common.config_base }}/cloudprovider/aws.conf" + section: Global + option: Zone + value: "{{ openshift.provider.zone }}" diff --git a/roles/openshift_cloud_provider/tasks/main.yml b/roles/openshift_cloud_provider/tasks/main.yml index e14f944e8..471fd686b 100644 --- a/roles/openshift_cloud_provider/tasks/main.yml +++ b/roles/openshift_cloud_provider/tasks/main.yml @@ -1,3 +1,24 @@ --- +- name: Set cloud provider facts + openshift_facts: + role: cloudprovider + openshift_env: "{{ item | oo_openshift_env }}" + openshift_env_structures: + - 'openshift.cloudprovider.aws.*' + - 'openshift.cloudprovider.openstack.*' + no_log: true + with_items: + - "{{ hostvars[inventory_hostname] }}" + - "{{ hostvars }}" + +- name: Create cloudprovider config dir + file: + path: "{{ openshift.common.config_base }}/cloudprovider" + state: directory + when: has_cloudprovider | bool + - include: openstack.yml - when: "openshift_cloud_provider is defined and openshift_cloud_provider == 'openstack' and 'provider' in openshift and openshift.provider.name == 'openstack'" + when: cloudprovider_is_openstack | bool + +- include: aws.yml + when: cloudprovider_is_aws | bool diff --git a/roles/openshift_cloud_provider/tasks/openstack.yml b/roles/openshift_cloud_provider/tasks/openstack.yml index a56f1891a..c501121e5 100644 --- a/roles/openshift_cloud_provider/tasks/openstack.yml +++ b/roles/openshift_cloud_provider/tasks/openstack.yml @@ -1,9 +1,10 @@ +--- - fail: msg: "The Openstack integration requires OpenShift Enterprise 3.2 or Origin 1.2." when: not openshift.common.version_gte_3_2_or_1_2 | bool -- name: Create /etc/cloud.conf +- name: Create cloud config template: - dest: /etc/cloud.conf - src: openstack/cloud.conf.j2 - when: ocp_os_auth_url and ocp_os_username and ocp_os_password and (ocp_os_tenant_id or ocp_os_tenant_name)
\ No newline at end of file + dest: "{{ openshift.common.config_base }}/cloudprovider/openstack.conf" + src: openstack.conf.j2 + when: "'auth_url' in openshift.cloudprovider.openstack and 'username' in openshift.cloudprovider.openstack and 'password' in openshift.cloudprovider.openstack and ('tenant_id' in openshift.cloudprovider.openstack or 'tenant_name' in openshift.cloudprovider.openstack)" diff --git a/roles/openshift_cloud_provider/templates/openstack.conf.j2 b/roles/openshift_cloud_provider/templates/openstack.conf.j2 new file mode 100644 index 000000000..1b70edc16 --- /dev/null +++ b/roles/openshift_cloud_provider/templates/openstack.conf.j2 @@ -0,0 +1,17 @@ +[Global] +auth-url = {{ openshift.cloudprovider.openstack.auth_url }} +username = {{ openshift.cloudprovider.openstack.username }} +password = {{ openshift.cloudprovider.openstack.password }} +{% if 'tenant_id' in openshift.cloudprovider.openstack %} +tenant-id = {{ openshift.cloudprovider.openstack.tenant_id }} +{% else %} +tenant-name = {{ openshift.cloudprovider.openstack.tenant_name }} +{% endif %} +{% if 'region' in openshift.cloudprovider.openstack %} +region = {{ openshift.cloudprovider.openstack.region }} +{% endif %} +{% if 'lb_subnet_id' in openshift.cloudprovider.openstack %} ++ ++[LoadBalancer] ++subnet-id = {{ openshift.cloudprovider.openstack.lb_subnet_id }} ++{% endif %} diff --git a/roles/openshift_cloud_provider/templates/openstack/cloud.conf.j2 b/roles/openshift_cloud_provider/templates/openstack/cloud.conf.j2 deleted file mode 100644 index 388f3a735..000000000 --- a/roles/openshift_cloud_provider/templates/openstack/cloud.conf.j2 +++ /dev/null @@ -1,17 +0,0 @@ -[Global] -auth-url = {{ ocp_os_auth_url }} -username = {{ ocp_os_username }} -password = {{ ocp_os_password }} -{% if ocp_os_tenant_id %} -tenant-id = {{ ocp_os_tenant_id }} -{% else %} -tenant-name = {{ ocp_os_tenant_name }} -{% endif %} -{% if ocp_os_region %} -region = {{ ocp_os_region }} -{% endif %} -{% if ocp_os_lb_subnet_id is defined %} -+ -+[LoadBalancer] -+subnet-id = {{ ocp_os_lb_subnet_id }} -+{% endif %}
\ No newline at end of file diff --git a/roles/openshift_cloud_provider/vars/main.yml b/roles/openshift_cloud_provider/vars/main.yml new file mode 100644 index 000000000..c608e9b54 --- /dev/null +++ b/roles/openshift_cloud_provider/vars/main.yml @@ -0,0 +1,4 @@ +--- +has_cloudprovider: "{{ 'cloudprovider' in openshift and 'kind' in openshift.cloudprovider and openshift.cloudprovider.kind != None }}" +cloudprovider_is_aws: "{{ has_cloudprovider | bool and openshift.cloudprovider.kind == 'aws' }}" +cloudprovider_is_openstack: "{{ has_cloudprovider | bool and openshift.cloudprovider.kind == 'openstack' }}" diff --git a/roles/openshift_common/meta/main.yml b/roles/openshift_common/meta/main.yml index d879db0aa..02150406d 100644 --- a/roles/openshift_common/meta/main.yml +++ b/roles/openshift_common/meta/main.yml @@ -12,7 +12,6 @@ galaxy_info: categories: - cloud dependencies: -- { role: os_firewall } -- { role: openshift_facts } -- { role: openshift_repos } -- { role: openshift_cloud_provider } +- role: os_firewall +- role: openshift_facts +- role: openshift_repos diff --git a/roles/openshift_common/tasks/main.yml b/roles/openshift_common/tasks/main.yml index 541efe27a..b6074ff64 100644 --- a/roles/openshift_common/tasks/main.yml +++ b/roles/openshift_common/tasks/main.yml @@ -28,8 +28,12 @@ use_manageiq: "{{ openshift_use_manageiq | default(None) }}" data_dir: "{{ openshift_data_dir | default(None) }}" +# Using oo_image_tag_to_rpm_version here is a workaround for how +# openshift_version is set. That value is computed based on either RPM +# versions or image tags. openshift_common's usage requires that it be a RPM +# version and openshift_cli expects it to be an image tag. - name: Install the base package for versioning - action: "{{ ansible_pkg_mgr }} name={{ openshift.common.service_type }}{{ openshift_version | default('') }} state=present" + action: "{{ ansible_pkg_mgr }} name={{ openshift.common.service_type }}{{ openshift_version | default('') | oo_image_tag_to_rpm_version }} state=present" when: not openshift.common.is_containerized | bool # This invocation also updates the version facts which are necessary diff --git a/roles/openshift_docker/meta/main.yml b/roles/openshift_docker/meta/main.yml index 60efd4e45..d98f953ea 100644 --- a/roles/openshift_docker/meta/main.yml +++ b/roles/openshift_docker/meta/main.yml @@ -12,5 +12,6 @@ galaxy_info: categories: - cloud dependencies: +- role: openshift_repos - role: openshift_docker_facts - role: docker diff --git a/roles/openshift_docker_facts/tasks/main.yml b/roles/openshift_docker_facts/tasks/main.yml index 26b46aa94..1848619e0 100644 --- a/roles/openshift_docker_facts/tasks/main.yml +++ b/roles/openshift_docker_facts/tasks/main.yml @@ -37,3 +37,19 @@ - set_fact: docker_options: "{{ openshift.docker.options | default(omit) }}" when: not openshift.docker.hosted_registry_insecure | default(False) | bool + +# Avoid docker 1.9 when installing origin < 1.2 or OSE < 3.2 on RHEL/Centos and +# See: https://bugzilla.redhat.com/show_bug.cgi?id=1304038 +- name: Gather common package version + command: > + {{ repoquery_cmd }} --qf '%{version}' "{{ openshift.common.service_type}}" + register: common_version + failed_when: false + changed_when: false + when: not openshift.common.is_atomic | bool + +- name: Set docker version to be installed + set_fact: + docker_version: "{{ '1.8.2' }}" + when: " ( common_version.stdout | default('0.0', True) | version_compare('3.2','<') and openshift.common.service_type == 'atomic-openshift' ) or + ( common_version.stdout | default('0.0', True) | version_compare('1.2','<') and openshift.common.service_type == 'origin' )" diff --git a/roles/openshift_docker_facts/vars/main.yml b/roles/openshift_docker_facts/vars/main.yml new file mode 100644 index 000000000..f7ad1b329 --- /dev/null +++ b/roles/openshift_docker_facts/vars/main.yml @@ -0,0 +1,2 @@ +--- +repoquery_cmd: "{{ 'dnf repoquery --latest-limit 1 -d 0' if ansible_pkg_mgr == 'dnf' else 'repoquery' }}" diff --git a/roles/openshift_examples/files/examples/v1.1/db-templates/README.md b/roles/openshift_examples/files/examples/v1.1/db-templates/README.md index b39abf8b9..609f4dec9 100644 --- a/roles/openshift_examples/files/examples/v1.1/db-templates/README.md +++ b/roles/openshift_examples/files/examples/v1.1/db-templates/README.md @@ -9,7 +9,7 @@ the Web Console or the CLI. The examples can also be tweaked to create new templates. -## Ephemeral x Persistent +## Ephemeral vs Persistent For each supported database, there are two template files. diff --git a/roles/openshift_examples/files/examples/v1.1/db-templates/mongodb-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.1/db-templates/mongodb-ephemeral-template.json index fe9effc19..227c8d30e 100644 --- a/roles/openshift_examples/files/examples/v1.1/db-templates/mongodb-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.1/db-templates/mongodb-ephemeral-template.json @@ -61,7 +61,7 @@ "from": { "kind": "ImageStreamTag", "name": "mongodb:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -163,44 +163,50 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "mongodb", "required": true }, { "name": "MONGODB_USER", - "displayName": "MongoDB user", - "description": "Username for MongoDB user that will be used for accessing the database", + "displayName": "MongoDB User", + "description": "Username for MongoDB user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "MONGODB_PASSWORD", - "displayName": "MongoDB password", - "description": "Password for the MongoDB user", + "displayName": "MongoDB Password", + "description": "Password for the MongoDB user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "MONGODB_DATABASE", - "displayName": "MongoDB database name", - "description": "Name of the MongoDB database accessed", + "displayName": "MongoDB Database Name", + "description": "Name of the MongoDB database accessed.", "value": "sampledb", "required": true }, { "name": "MONGODB_ADMIN_PASSWORD", - "displayName": "MongoDB admin password", - "description": "Password for the database admin user", + "displayName": "MongoDB Admin Password", + "description": "Password for the database admin user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true diff --git a/roles/openshift_examples/files/examples/v1.1/db-templates/mongodb-persistent-template.json b/roles/openshift_examples/files/examples/v1.1/db-templates/mongodb-persistent-template.json index ab37e7a3a..672eaaa09 100644 --- a/roles/openshift_examples/files/examples/v1.1/db-templates/mongodb-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.1/db-templates/mongodb-persistent-template.json @@ -78,7 +78,7 @@ "from": { "kind": "ImageStreamTag", "name": "mongodb:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -180,52 +180,58 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "mongodb", "required": true }, { "name": "MONGODB_USER", - "displayName": "MongoDB user", - "description": "Username for MongoDB user that will be used for accessing the database", + "displayName": "MongoDB User", + "description": "Username for MongoDB user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "MONGODB_PASSWORD", - "displayName": "MongoDB password", - "description": "Password for the MongoDB user", + "displayName": "MongoDB Password", + "description": "Password for the MongoDB user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "MONGODB_DATABASE", - "displayName": "MongoDB database name", - "description": "Name of the MongoDB database accessed", + "displayName": "MongoDB Database Name", + "description": "Name of the MongoDB database accessed.", "value": "sampledb", "required": true }, { "name": "MONGODB_ADMIN_PASSWORD", - "displayName": "MongoDB admin password", - "description": "Password for the database admin user", + "displayName": "MongoDB Admin Password", + "description": "Password for the database admin user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "VOLUME_CAPACITY", - "displayName": "Volume capacity", - "description": "Volume space available for data, e.g. 512Mi, 2Gi", + "displayName": "Volume Capacity", + "description": "Volume space available for data, e.g. 512Mi, 2Gi.", "value": "512Mi", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.1/db-templates/mysql-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.1/db-templates/mysql-ephemeral-template.json index 2c28db746..f4c118052 100644 --- a/roles/openshift_examples/files/examples/v1.1/db-templates/mysql-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.1/db-templates/mysql-ephemeral-template.json @@ -61,7 +61,7 @@ "from": { "kind": "ImageStreamTag", "name": "mysql:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -97,7 +97,7 @@ "initialDelaySeconds": 5, "exec": { "command": [ "/bin/sh", "-i", "-c", - "MYSQL_PWD='$MYSQL_PASSWORD' mysql -h 127.0.0.1 -u $MYSQL_USER -D $MYSQL_DATABASE -e 'SELECT 1'"] + "MYSQL_PWD=\"$MYSQL_PASSWORD\" mysql -h 127.0.0.1 -u $MYSQL_USER -D $MYSQL_DATABASE -e 'SELECT 1'"] } }, "livenessProbe": { @@ -160,37 +160,43 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "mysql", "required": true }, { "name": "MYSQL_USER", - "displayName": "MySQL user", - "description": "Username for MySQL user that will be used for accessing the database", + "displayName": "MySQL User", + "description": "Username for MySQL user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "MYSQL_PASSWORD", - "displayName": "MySQL password", - "description": "Password for the MySQL user", + "displayName": "MySQL Password", + "description": "Password for the MySQL user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "MYSQL_DATABASE", - "displayName": "MySQL database name", - "description": "Name of the MySQL database accessed", + "displayName": "MySQL Database Name", + "description": "Name of the MySQL database accessed.", "value": "sampledb", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.1/db-templates/mysql-persistent-template.json b/roles/openshift_examples/files/examples/v1.1/db-templates/mysql-persistent-template.json index 94199b6fe..d94262dde 100644 --- a/roles/openshift_examples/files/examples/v1.1/db-templates/mysql-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.1/db-templates/mysql-persistent-template.json @@ -78,7 +78,7 @@ "from": { "kind": "ImageStreamTag", "name": "mysql:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -114,7 +114,7 @@ "initialDelaySeconds": 5, "exec": { "command": [ "/bin/sh", "-i", "-c", - "MYSQL_PWD='$MYSQL_PASSWORD' mysql -h 127.0.0.1 -u $MYSQL_USER -D $MYSQL_DATABASE -e 'SELECT 1'"] + "MYSQL_PWD=\"$MYSQL_PASSWORD\" mysql -h 127.0.0.1 -u $MYSQL_USER -D $MYSQL_DATABASE -e 'SELECT 1'"] } }, "livenessProbe": { @@ -177,44 +177,50 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "mysql", "required": true }, { "name": "MYSQL_USER", - "displayName": "MySQL user", - "description": "Username for MySQL user that will be used for accessing the database", + "displayName": "MySQL User", + "description": "Username for MySQL user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "MYSQL_PASSWORD", - "displayName": "MySQL password", - "description": "Password for the MySQL user", + "displayName": "MySQL Password", + "description": "Password for the MySQL user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "MYSQL_DATABASE", - "displayName": "MySQL database name", - "description": "Name of the MySQL database accessed", + "displayName": "MySQL Database Name", + "description": "Name of the MySQL database accessed.", "value": "sampledb", "required": true }, { "name": "VOLUME_CAPACITY", - "displayName": "Volume capacity", - "description": "Volume space available for data, e.g. 512Mi, 2Gi", + "displayName": "Volume Capacity", + "description": "Volume space available for data, e.g. 512Mi, 2Gi.", "value": "512Mi", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.1/db-templates/postgresql-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.1/db-templates/postgresql-ephemeral-template.json index da548c591..c14f3c3df 100644 --- a/roles/openshift_examples/files/examples/v1.1/db-templates/postgresql-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.1/db-templates/postgresql-ephemeral-template.json @@ -61,7 +61,7 @@ "from": { "kind": "ImageStreamTag", "name": "postgresql:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -159,37 +159,43 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "postgresql", "required": true }, { "name": "POSTGRESQL_USER", - "displayName": "PostgreSQL user", - "description": "Username for PostgreSQL user that will be used for accessing the database", + "displayName": "PostgreSQL User", + "description": "Username for PostgreSQL user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "POSTGRESQL_PASSWORD", - "displayName": "PostgreSQL password", - "description": "Password for the PostgreSQL user", + "displayName": "PostgreSQL Password", + "description": "Password for the PostgreSQL user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "POSTGRESQL_DATABASE", - "displayName": "PostgreSQL database name", - "description": "Name of the PostgreSQL database accessed", + "displayName": "PostgreSQL Database Name", + "description": "Name of the PostgreSQL database accessed.", "value": "sampledb", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.1/db-templates/postgresql-persistent-template.json b/roles/openshift_examples/files/examples/v1.1/db-templates/postgresql-persistent-template.json index df8a34a9e..5713411ad 100644 --- a/roles/openshift_examples/files/examples/v1.1/db-templates/postgresql-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.1/db-templates/postgresql-persistent-template.json @@ -78,7 +78,7 @@ "from": { "kind": "ImageStreamTag", "name": "postgresql:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -176,44 +176,50 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "postgresql", "required": true }, { "name": "POSTGRESQL_USER", - "displayName": "PostgreSQL user", - "description": "Username for PostgreSQL user that will be used for accessing the database", + "displayName": "PostgreSQL User", + "description": "Username for PostgreSQL user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "POSTGRESQL_PASSWORD", - "displayName": "PostgreSQL password", - "description": "Password for the PostgreSQL user", + "displayName": "PostgreSQL Password", + "description": "Password for the PostgreSQL user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "POSTGRESQL_DATABASE", - "displayName": "PostgreSQL database name", - "description": "Name of the PostgreSQL database accessed", + "displayName": "PostgreSQL Database Name", + "description": "Name of the PostgreSQL database accessed.", "value": "sampledb", "required": true }, { "name": "VOLUME_CAPACITY", - "displayName": "Volume capacity", - "description": "Volume space available for data, e.g. 512Mi, 2Gi", + "displayName": "Volume Capacity", + "description": "Volume space available for data, e.g. 512Mi, 2Gi.", "value": "512Mi", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/cakephp-mysql.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/cakephp-mysql.json index 6e6f4f096..922e5bed8 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/cakephp-mysql.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/cakephp-mysql.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "php:5.6" } } @@ -282,7 +282,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "mysql:5.6" } } @@ -361,6 +361,12 @@ "value": "cakephp-mysql-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the CakePHP container can use.", diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/cakephp.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/cakephp.json index 21e29ae30..780faec55 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/cakephp.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/cakephp.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "php:5.6" } } @@ -236,6 +236,12 @@ "value": "cakephp-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the container can use.", diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/dancer-mysql.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/dancer-mysql.json index 20d9ac2b2..c0fc02ae4 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/dancer-mysql.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/dancer-mysql.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "perl:5.20" } } @@ -256,7 +256,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "mysql:5.6" } } @@ -335,6 +335,12 @@ "value": "dancer-mysql-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the Perl Dancer container can use.", diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/dancer.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/dancer.json index 17a114cbb..1ea5a21a0 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/dancer.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/dancer.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "perl:5.20" } } @@ -204,6 +204,12 @@ "value": "dancer-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the container can use.", diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/django-postgresql.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/django-postgresql.json index 063591a8f..844201e7c 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/django-postgresql.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/django-postgresql.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "python:3.4" } } @@ -266,7 +266,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "postgresql:9.4" } } @@ -345,6 +345,12 @@ "value": "django-psql-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the Django container can use.", diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/django.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/django.json index a36678ba6..38ef694f8 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/django.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/django.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "python:3.4" } } @@ -231,6 +231,12 @@ "value": "django-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the container can use.", diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/jenkins-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/jenkins-ephemeral-template.json index bbe6713ff..e464b5971 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/jenkins-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/jenkins-ephemeral-template.json @@ -78,7 +78,7 @@ "from": { "kind": "ImageStreamTag", "name": "jenkins:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -163,20 +163,26 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "JENKINS_SERVICE_NAME", - "displayName": "Jenkins service name", - "description": "The name of the OpenShift Service exposed for the Jenkins container", + "displayName": "Jenkins Service Name", + "description": "The name of the OpenShift Service exposed for the Jenkins container.", "value": "jenkins" }, { "name": "JENKINS_PASSWORD", - "displayName": "Jenkins password", - "description": "Password for the Jenkins user", + "displayName": "Jenkins Password", + "description": "Password for the Jenkins user.", "generate": "expression", "value": "password" } diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/jenkins-persistent-template.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/jenkins-persistent-template.json index d98e729d4..6c143fc70 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/jenkins-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/jenkins-persistent-template.json @@ -95,7 +95,7 @@ "from": { "kind": "ImageStreamTag", "name": "jenkins:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -180,27 +180,33 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "JENKINS_SERVICE_NAME", - "displayName": "Jenkins service name", - "description": "The name of the OpenShift Service exposed for the Jenkins container", + "displayName": "Jenkins Service Name", + "description": "The name of the OpenShift Service exposed for the Jenkins container.", "value": "jenkins" }, { "name": "JENKINS_PASSWORD", - "displayName": "Jenkins password", - "description": "Password for the Jenkins user", + "displayName": "Jenkins Password", + "description": "Password for the Jenkins user.", "generate": "expression", "value": "password" }, { "name": "VOLUME_CAPACITY", - "displayName": "Volume capacity", - "description": "Volume space available for data, e.g. 512Mi, 2Gi", + "displayName": "Volume Capacity", + "description": "Volume space available for data, e.g. 512Mi, 2Gi.", "value": "512Mi", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/nodejs-mongodb.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/nodejs-mongodb.json index e352b15f3..3298ef40c 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/nodejs-mongodb.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/nodejs-mongodb.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "nodejs:0.10" } } @@ -261,7 +261,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "mongodb:2.6" } } @@ -344,6 +344,12 @@ "value": "nodejs-mongodb-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the Node.js container can use.", diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/nodejs.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/nodejs.json index da16ec157..82df67c4e 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/nodejs.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/nodejs.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "nodejs:0.10" } } @@ -231,6 +231,12 @@ "value": "nodejs-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the container can use.", diff --git a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/rails-postgresql.json b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/rails-postgresql.json index 99b6513f2..6292cf3e7 100644 --- a/roles/openshift_examples/files/examples/v1.1/quickstart-templates/rails-postgresql.json +++ b/roles/openshift_examples/files/examples/v1.1/quickstart-templates/rails-postgresql.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "ruby:2.2" } } @@ -293,7 +293,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "postgresql:9.4" } } @@ -380,6 +380,12 @@ "value": "rails-postgresql-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the Rails container can use.", diff --git a/roles/openshift_examples/files/examples/v1.2/db-templates/README.md b/roles/openshift_examples/files/examples/v1.2/db-templates/README.md index b39abf8b9..609f4dec9 100644 --- a/roles/openshift_examples/files/examples/v1.2/db-templates/README.md +++ b/roles/openshift_examples/files/examples/v1.2/db-templates/README.md @@ -9,7 +9,7 @@ the Web Console or the CLI. The examples can also be tweaked to create new templates. -## Ephemeral x Persistent +## Ephemeral vs Persistent For each supported database, there are two template files. diff --git a/roles/openshift_examples/files/examples/v1.2/db-templates/mongodb-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.2/db-templates/mongodb-ephemeral-template.json index fe9effc19..227c8d30e 100644 --- a/roles/openshift_examples/files/examples/v1.2/db-templates/mongodb-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.2/db-templates/mongodb-ephemeral-template.json @@ -61,7 +61,7 @@ "from": { "kind": "ImageStreamTag", "name": "mongodb:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -163,44 +163,50 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "mongodb", "required": true }, { "name": "MONGODB_USER", - "displayName": "MongoDB user", - "description": "Username for MongoDB user that will be used for accessing the database", + "displayName": "MongoDB User", + "description": "Username for MongoDB user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "MONGODB_PASSWORD", - "displayName": "MongoDB password", - "description": "Password for the MongoDB user", + "displayName": "MongoDB Password", + "description": "Password for the MongoDB user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "MONGODB_DATABASE", - "displayName": "MongoDB database name", - "description": "Name of the MongoDB database accessed", + "displayName": "MongoDB Database Name", + "description": "Name of the MongoDB database accessed.", "value": "sampledb", "required": true }, { "name": "MONGODB_ADMIN_PASSWORD", - "displayName": "MongoDB admin password", - "description": "Password for the database admin user", + "displayName": "MongoDB Admin Password", + "description": "Password for the database admin user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true diff --git a/roles/openshift_examples/files/examples/v1.2/db-templates/mongodb-persistent-template.json b/roles/openshift_examples/files/examples/v1.2/db-templates/mongodb-persistent-template.json index ab37e7a3a..672eaaa09 100644 --- a/roles/openshift_examples/files/examples/v1.2/db-templates/mongodb-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.2/db-templates/mongodb-persistent-template.json @@ -78,7 +78,7 @@ "from": { "kind": "ImageStreamTag", "name": "mongodb:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -180,52 +180,58 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "mongodb", "required": true }, { "name": "MONGODB_USER", - "displayName": "MongoDB user", - "description": "Username for MongoDB user that will be used for accessing the database", + "displayName": "MongoDB User", + "description": "Username for MongoDB user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "MONGODB_PASSWORD", - "displayName": "MongoDB password", - "description": "Password for the MongoDB user", + "displayName": "MongoDB Password", + "description": "Password for the MongoDB user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "MONGODB_DATABASE", - "displayName": "MongoDB database name", - "description": "Name of the MongoDB database accessed", + "displayName": "MongoDB Database Name", + "description": "Name of the MongoDB database accessed.", "value": "sampledb", "required": true }, { "name": "MONGODB_ADMIN_PASSWORD", - "displayName": "MongoDB admin password", - "description": "Password for the database admin user", + "displayName": "MongoDB Admin Password", + "description": "Password for the database admin user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "VOLUME_CAPACITY", - "displayName": "Volume capacity", - "description": "Volume space available for data, e.g. 512Mi, 2Gi", + "displayName": "Volume Capacity", + "description": "Volume space available for data, e.g. 512Mi, 2Gi.", "value": "512Mi", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.2/db-templates/mysql-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.2/db-templates/mysql-ephemeral-template.json index 2c28db746..f4c118052 100644 --- a/roles/openshift_examples/files/examples/v1.2/db-templates/mysql-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.2/db-templates/mysql-ephemeral-template.json @@ -61,7 +61,7 @@ "from": { "kind": "ImageStreamTag", "name": "mysql:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -97,7 +97,7 @@ "initialDelaySeconds": 5, "exec": { "command": [ "/bin/sh", "-i", "-c", - "MYSQL_PWD='$MYSQL_PASSWORD' mysql -h 127.0.0.1 -u $MYSQL_USER -D $MYSQL_DATABASE -e 'SELECT 1'"] + "MYSQL_PWD=\"$MYSQL_PASSWORD\" mysql -h 127.0.0.1 -u $MYSQL_USER -D $MYSQL_DATABASE -e 'SELECT 1'"] } }, "livenessProbe": { @@ -160,37 +160,43 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "mysql", "required": true }, { "name": "MYSQL_USER", - "displayName": "MySQL user", - "description": "Username for MySQL user that will be used for accessing the database", + "displayName": "MySQL User", + "description": "Username for MySQL user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "MYSQL_PASSWORD", - "displayName": "MySQL password", - "description": "Password for the MySQL user", + "displayName": "MySQL Password", + "description": "Password for the MySQL user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "MYSQL_DATABASE", - "displayName": "MySQL database name", - "description": "Name of the MySQL database accessed", + "displayName": "MySQL Database Name", + "description": "Name of the MySQL database accessed.", "value": "sampledb", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.2/db-templates/mysql-persistent-template.json b/roles/openshift_examples/files/examples/v1.2/db-templates/mysql-persistent-template.json index 94199b6fe..d94262dde 100644 --- a/roles/openshift_examples/files/examples/v1.2/db-templates/mysql-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.2/db-templates/mysql-persistent-template.json @@ -78,7 +78,7 @@ "from": { "kind": "ImageStreamTag", "name": "mysql:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -114,7 +114,7 @@ "initialDelaySeconds": 5, "exec": { "command": [ "/bin/sh", "-i", "-c", - "MYSQL_PWD='$MYSQL_PASSWORD' mysql -h 127.0.0.1 -u $MYSQL_USER -D $MYSQL_DATABASE -e 'SELECT 1'"] + "MYSQL_PWD=\"$MYSQL_PASSWORD\" mysql -h 127.0.0.1 -u $MYSQL_USER -D $MYSQL_DATABASE -e 'SELECT 1'"] } }, "livenessProbe": { @@ -177,44 +177,50 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "mysql", "required": true }, { "name": "MYSQL_USER", - "displayName": "MySQL user", - "description": "Username for MySQL user that will be used for accessing the database", + "displayName": "MySQL User", + "description": "Username for MySQL user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "MYSQL_PASSWORD", - "displayName": "MySQL password", - "description": "Password for the MySQL user", + "displayName": "MySQL Password", + "description": "Password for the MySQL user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "MYSQL_DATABASE", - "displayName": "MySQL database name", - "description": "Name of the MySQL database accessed", + "displayName": "MySQL Database Name", + "description": "Name of the MySQL database accessed.", "value": "sampledb", "required": true }, { "name": "VOLUME_CAPACITY", - "displayName": "Volume capacity", - "description": "Volume space available for data, e.g. 512Mi, 2Gi", + "displayName": "Volume Capacity", + "description": "Volume space available for data, e.g. 512Mi, 2Gi.", "value": "512Mi", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.2/db-templates/postgresql-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.2/db-templates/postgresql-ephemeral-template.json index da548c591..c14f3c3df 100644 --- a/roles/openshift_examples/files/examples/v1.2/db-templates/postgresql-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.2/db-templates/postgresql-ephemeral-template.json @@ -61,7 +61,7 @@ "from": { "kind": "ImageStreamTag", "name": "postgresql:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -159,37 +159,43 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "postgresql", "required": true }, { "name": "POSTGRESQL_USER", - "displayName": "PostgreSQL user", - "description": "Username for PostgreSQL user that will be used for accessing the database", + "displayName": "PostgreSQL User", + "description": "Username for PostgreSQL user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "POSTGRESQL_PASSWORD", - "displayName": "PostgreSQL password", - "description": "Password for the PostgreSQL user", + "displayName": "PostgreSQL Password", + "description": "Password for the PostgreSQL user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "POSTGRESQL_DATABASE", - "displayName": "PostgreSQL database name", - "description": "Name of the PostgreSQL database accessed", + "displayName": "PostgreSQL Database Name", + "description": "Name of the PostgreSQL database accessed.", "value": "sampledb", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.2/db-templates/postgresql-persistent-template.json b/roles/openshift_examples/files/examples/v1.2/db-templates/postgresql-persistent-template.json index df8a34a9e..5713411ad 100644 --- a/roles/openshift_examples/files/examples/v1.2/db-templates/postgresql-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.2/db-templates/postgresql-persistent-template.json @@ -78,7 +78,7 @@ "from": { "kind": "ImageStreamTag", "name": "postgresql:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -176,44 +176,50 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "DATABASE_SERVICE_NAME", - "displayName": "Database service name", - "description": "The name of the OpenShift Service exposed for the database", + "displayName": "Database Service Name", + "description": "The name of the OpenShift Service exposed for the database.", "value": "postgresql", "required": true }, { "name": "POSTGRESQL_USER", - "displayName": "PostgreSQL user", - "description": "Username for PostgreSQL user that will be used for accessing the database", + "displayName": "PostgreSQL User", + "description": "Username for PostgreSQL user that will be used for accessing the database.", "generate": "expression", "from": "user[A-Z0-9]{3}", "required": true }, { "name": "POSTGRESQL_PASSWORD", - "displayName": "PostgreSQL password", - "description": "Password for the PostgreSQL user", + "displayName": "PostgreSQL Password", + "description": "Password for the PostgreSQL user.", "generate": "expression", "from": "[a-zA-Z0-9]{16}", "required": true }, { "name": "POSTGRESQL_DATABASE", - "displayName": "PostgreSQL database name", - "description": "Name of the PostgreSQL database accessed", + "displayName": "PostgreSQL Database Name", + "description": "Name of the PostgreSQL database accessed.", "value": "sampledb", "required": true }, { "name": "VOLUME_CAPACITY", - "displayName": "Volume capacity", - "description": "Volume space available for data, e.g. 512Mi, 2Gi", + "displayName": "Volume Capacity", + "description": "Volume space available for data, e.g. 512Mi, 2Gi.", "value": "512Mi", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/cakephp-mysql.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/cakephp-mysql.json index 6e6f4f096..922e5bed8 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/cakephp-mysql.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/cakephp-mysql.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "php:5.6" } } @@ -282,7 +282,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "mysql:5.6" } } @@ -361,6 +361,12 @@ "value": "cakephp-mysql-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the CakePHP container can use.", diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/cakephp.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/cakephp.json index 21e29ae30..780faec55 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/cakephp.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/cakephp.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "php:5.6" } } @@ -236,6 +236,12 @@ "value": "cakephp-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the container can use.", diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/dancer-mysql.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/dancer-mysql.json index 20d9ac2b2..c0fc02ae4 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/dancer-mysql.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/dancer-mysql.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "perl:5.20" } } @@ -256,7 +256,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "mysql:5.6" } } @@ -335,6 +335,12 @@ "value": "dancer-mysql-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the Perl Dancer container can use.", diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/dancer.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/dancer.json index 17a114cbb..1ea5a21a0 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/dancer.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/dancer.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "perl:5.20" } } @@ -204,6 +204,12 @@ "value": "dancer-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the container can use.", diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/django-postgresql.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/django-postgresql.json index 063591a8f..844201e7c 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/django-postgresql.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/django-postgresql.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "python:3.4" } } @@ -266,7 +266,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "postgresql:9.4" } } @@ -345,6 +345,12 @@ "value": "django-psql-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the Django container can use.", diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/django.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/django.json index a36678ba6..38ef694f8 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/django.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/django.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "python:3.4" } } @@ -231,6 +231,12 @@ "value": "django-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the container can use.", diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/jenkins-ephemeral-template.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/jenkins-ephemeral-template.json index bbe6713ff..e464b5971 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/jenkins-ephemeral-template.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/jenkins-ephemeral-template.json @@ -78,7 +78,7 @@ "from": { "kind": "ImageStreamTag", "name": "jenkins:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -163,20 +163,26 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "JENKINS_SERVICE_NAME", - "displayName": "Jenkins service name", - "description": "The name of the OpenShift Service exposed for the Jenkins container", + "displayName": "Jenkins Service Name", + "description": "The name of the OpenShift Service exposed for the Jenkins container.", "value": "jenkins" }, { "name": "JENKINS_PASSWORD", - "displayName": "Jenkins password", - "description": "Password for the Jenkins user", + "displayName": "Jenkins Password", + "description": "Password for the Jenkins user.", "generate": "expression", "value": "password" } diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/jenkins-persistent-template.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/jenkins-persistent-template.json index d98e729d4..6c143fc70 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/jenkins-persistent-template.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/jenkins-persistent-template.json @@ -95,7 +95,7 @@ "from": { "kind": "ImageStreamTag", "name": "jenkins:latest", - "namespace": "openshift" + "namespace": "${NAMESPACE}" }, "lastTriggeredImage": "" } @@ -180,27 +180,33 @@ "parameters": [ { "name": "MEMORY_LIMIT", - "displayName": "Memory limit", - "description": "Maximum amount of memory the container can use", + "displayName": "Memory Limit", + "description": "Maximum amount of memory the container can use.", "value": "512Mi" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "JENKINS_SERVICE_NAME", - "displayName": "Jenkins service name", - "description": "The name of the OpenShift Service exposed for the Jenkins container", + "displayName": "Jenkins Service Name", + "description": "The name of the OpenShift Service exposed for the Jenkins container.", "value": "jenkins" }, { "name": "JENKINS_PASSWORD", - "displayName": "Jenkins password", - "description": "Password for the Jenkins user", + "displayName": "Jenkins Password", + "description": "Password for the Jenkins user.", "generate": "expression", "value": "password" }, { "name": "VOLUME_CAPACITY", - "displayName": "Volume capacity", - "description": "Volume space available for data, e.g. 512Mi, 2Gi", + "displayName": "Volume Capacity", + "description": "Volume space available for data, e.g. 512Mi, 2Gi.", "value": "512Mi", "required": true } diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/nodejs-mongodb.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/nodejs-mongodb.json index e352b15f3..3298ef40c 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/nodejs-mongodb.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/nodejs-mongodb.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "nodejs:0.10" } } @@ -261,7 +261,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "mongodb:2.6" } } @@ -344,6 +344,12 @@ "value": "nodejs-mongodb-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the Node.js container can use.", diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/nodejs.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/nodejs.json index da16ec157..82df67c4e 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/nodejs.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/nodejs.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "nodejs:0.10" } } @@ -231,6 +231,12 @@ "value": "nodejs-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the container can use.", diff --git a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/rails-postgresql.json b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/rails-postgresql.json index 99b6513f2..6292cf3e7 100644 --- a/roles/openshift_examples/files/examples/v1.2/quickstart-templates/rails-postgresql.json +++ b/roles/openshift_examples/files/examples/v1.2/quickstart-templates/rails-postgresql.json @@ -82,7 +82,7 @@ "sourceStrategy": { "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "ruby:2.2" } } @@ -293,7 +293,7 @@ ], "from": { "kind": "ImageStreamTag", - "namespace": "openshift", + "namespace": "${NAMESPACE}", "name": "postgresql:9.4" } } @@ -380,6 +380,12 @@ "value": "rails-postgresql-example" }, { + "name": "NAMESPACE", + "displayName": "Namespace", + "description": "The OpenShift Namespace where the ImageStream resides.", + "value": "openshift" + }, + { "name": "MEMORY_LIMIT", "displayName": "Memory Limit", "description": "Maximum amount of memory the Rails container can use.", diff --git a/roles/openshift_facts/library/openshift_facts.py b/roles/openshift_facts/library/openshift_facts.py index 30e29787a..0d31d4ddf 100755 --- a/roles/openshift_facts/library/openshift_facts.py +++ b/roles/openshift_facts/library/openshift_facts.py @@ -26,6 +26,8 @@ from distutils.util import strtobool from distutils.version import LooseVersion import struct import socket +from dbus import SystemBus, Interface +from dbus.exceptions import DBusException def migrate_docker_facts(facts): @@ -332,14 +334,10 @@ def normalize_provider_facts(provider, metadata): facts = dict(name=provider, metadata=metadata, network=dict(interfaces=[], ipv6_enabled=False)) - if os.path.exists('/etc/cloud.conf'): - for arg in ('api_server_args', 'controller_args', 'kubelet_args'): - facts[arg] = {'cloud-provider': [provider], - 'cloud-config': ['/etc/cloud.conf']} if provider == 'gce': facts = normalize_gce_facts(metadata, facts) - elif provider == 'ec2': + elif provider == 'aws': facts = normalize_aws_facts(metadata, facts) elif provider == 'openstack': facts = normalize_openstack_facts(metadata, facts) @@ -749,8 +747,9 @@ def set_version_facts_if_unset(facts): """ if 'common' in facts: deployment_type = facts['common']['deployment_type'] - facts['common']['version'] = version = get_openshift_version(facts) + version = get_openshift_version(facts) if version is not None: + facts['common']['version'] = version if deployment_type == 'origin': version_gte_3_1_or_1_1 = LooseVersion(version) >= LooseVersion('1.1.0') version_gte_3_1_1_or_1_1_1 = LooseVersion(version) >= LooseVersion('1.1.1') @@ -918,6 +917,101 @@ def get_current_config(facts): return current_config +def build_kubelet_args(facts): + """ Build node kubelet_args """ + cloud_cfg_path = os.path.join(facts['common']['config_base'], + 'cloudprovider') + if 'node' in facts: + kubelet_args = {} + if 'cloudprovider' in facts: + if facts['cloudprovider']['kind'] == 'aws': + kubelet_args['cloud-provider'] = ['aws'] + kubelet_args['cloud-config'] = [cloud_cfg_path + '/aws.conf'] + if facts['cloudprovider']['kind'] == 'openstack': + kubelet_args['cloud-provider'] = ['openstack'] + kubelet_args['cloud-config'] = [cloud_cfg_path + '/openstack.conf'] + if kubelet_args != {}: + facts = merge_facts({'node': {'kubelet_args': kubelet_args}}, facts, [], []) + return facts + +def build_controller_args(facts): + """ Build master controller_args """ + cloud_cfg_path = os.path.join(facts['common']['config_base'], + 'cloudprovider') + if 'master' in facts: + controller_args = {} + if 'cloudprovider' in facts: + if facts['cloudprovider']['kind'] == 'aws': + controller_args['cloud-provider'] = ['aws'] + controller_args['cloud-config'] = [cloud_cfg_path + '/aws.conf'] + if facts['cloudprovider']['kind'] == 'openstack': + controller_args['cloud-provider'] = ['openstack'] + controller_args['cloud-config'] = [cloud_cfg_path + '/openstack.conf'] + if controller_args != {}: + facts = merge_facts({'master': {'controller_args': controller_args}}, facts, [], []) + return facts + +def build_api_server_args(facts): + """ Build master api_server_args """ + cloud_cfg_path = os.path.join(facts['common']['config_base'], + 'cloudprovider') + if 'master' in facts: + api_server_args = {} + if 'cloudprovider' in facts: + if facts['cloudprovider']['kind'] == 'aws': + api_server_args['cloud-provider'] = ['aws'] + api_server_args['cloud-config'] = [cloud_cfg_path + '/aws.conf'] + if facts['cloudprovider']['kind'] == 'openstack': + api_server_args['cloud-provider'] = ['openstack'] + api_server_args['cloud-config'] = [cloud_cfg_path + '/openstack.conf'] + if api_server_args != {}: + facts = merge_facts({'master': {'api_server_args': api_server_args}}, facts, [], []) + return facts + +def is_service_running(service): + """ Queries systemd through dbus to see if the service is running """ + service_running = False + bus = SystemBus() + systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') + manager = Interface(systemd, dbus_interface='org.freedesktop.systemd1.Manager') + try: + service_unit = service if service.endswith('.service') else manager.GetUnit('{0}.service'.format(service)) + service_proxy = bus.get_object('org.freedesktop.systemd1', str(service_unit)) + service_properties = Interface(service_proxy, dbus_interface='org.freedesktop.DBus.Properties') + service_load_state = service_properties.Get('org.freedesktop.systemd1.Unit', 'LoadState') + service_active_state = service_properties.Get('org.freedesktop.systemd1.Unit', 'ActiveState') + if service_load_state == 'loaded' and service_active_state == 'active': + service_running = True + except DBusException: + pass + + return service_running + +def get_version_output(binary, version_cmd): + """ runs and returns the version output for a command """ + cmd = [] + for item in (binary, version_cmd): + if isinstance(item, list): + cmd.extend(item) + else: + cmd.append(item) + + if os.path.isfile(cmd[0]): + _, output, _ = module.run_command(cmd) + return output + +def get_docker_version_info(): + """ Parses and returns the docker version info """ + result = None + if is_service_running('docker'): + version_info = yaml.safe_load(get_version_output('/usr/bin/docker', 'version')) + if 'Server' in version_info: + result = { + 'api_version': version_info['Server']['API version'], + 'version': version_info['Server']['Version'] + } + return result + def get_openshift_version(facts, cli_image=None): """ Get current version of openshift on the host @@ -959,9 +1053,10 @@ def get_openshift_version(facts, cli_image=None): if version is None and cli_image is not None: # Assume we haven't installed the environment yet and we need - # to query the latest image - exit_code, output, _ = module.run_command(['docker', 'run', '--rm', cli_image, 'version']) - version = parse_openshift_version(output) + # to query the latest image, but only if docker is installed + if 'docker' in facts and 'version' in facts['docker']: + exit_code, output, _ = module.run_command(['docker', 'run', '--rm', cli_image, 'version']) + version = parse_openshift_version(output) return version @@ -1084,31 +1179,6 @@ def merge_facts(orig, new, additive_facts_to_overwrite, protected_facts_to_overw facts[key] = copy.deepcopy(new[key]) return facts - -def merge_provider_facts(facts): - """ Recursively merge provider facts dicts - - Args: - facts (dict): existing facts - Returns: - dict: the facts dict updated with the provider config - """ - if 'provider' not in facts: - return facts - if 'master' in facts: - for arg in ('api_server_args', 'controller_args'): - facts['master'][arg] = merge_facts( - facts['provider'].get(arg, {}), - facts['master'].get(arg, {}), - [], []) - if 'node' in facts: - facts['node']['kubelet_args'] = merge_facts( - facts['provider'].get('kubelet_args', {}), - facts['node'].get('kubelet_args', {}), - [], []) - return facts - - def save_local_facts(filename, facts): """ Save local facts @@ -1167,6 +1237,7 @@ def safe_get_bool(fact): """ return bool(strtobool(str(fact))) +# pylint: disable=too-many-statements def set_container_facts_if_unset(facts): """ Set containerized facts. @@ -1183,24 +1254,44 @@ def set_container_facts_if_unset(facts): node_image = 'openshift3/node' ovs_image = 'openshift3/openvswitch' etcd_image = 'registry.access.redhat.com/rhel7/etcd' + pod_image = 'openshift3/ose-pod' + router_image = 'openshift3/ose-haproxy-router' + registry_image = 'openshift3/ose-docker-registry' + deployer_image = 'openshift3/ose-deployer' elif deployment_type == 'atomic-enterprise': master_image = 'aep3_beta/aep' cli_image = master_image node_image = 'aep3_beta/node' ovs_image = 'aep3_beta/openvswitch' etcd_image = 'registry.access.redhat.com/rhel7/etcd' + pod_image = 'aep3_beta/aep-pod' + router_image = 'aep3_beta/aep-haproxy-router' + registry_image = 'aep3_beta/aep-docker-registry' + deployer_image = 'aep3_beta/aep-deployer' else: master_image = 'openshift/origin' cli_image = master_image node_image = 'openshift/node' ovs_image = 'openshift/openvswitch' etcd_image = 'registry.access.redhat.com/rhel7/etcd' + pod_image = 'openshift/origin-pod' + router_image = 'openshift/origin-haproxy-router' + registry_image = 'openshift/origin-docker-registry' + deployer_image = 'openshift/origin-deployer' facts['common']['is_atomic'] = os.path.isfile('/run/ostree-booted') if 'is_containerized' not in facts['common']: facts['common']['is_containerized'] = facts['common']['is_atomic'] if 'cli_image' not in facts['common']: facts['common']['cli_image'] = cli_image + if 'pod_image' not in facts['common']: + facts['common']['pod_image'] = pod_image + if 'router_image' not in facts['common']: + facts['common']['router_image'] = router_image + if 'registry_image' not in facts['common']: + facts['common']['registry_image'] = registry_image + if 'deployer_image' not in facts['common']: + facts['common']['deployer_image'] = deployer_image if 'etcd' in facts and 'etcd_image' not in facts['etcd']: facts['etcd']['etcd_image'] = etcd_image if 'master' in facts and 'master_image' not in facts['master']: @@ -1214,8 +1305,10 @@ def set_container_facts_if_unset(facts): if safe_get_bool(facts['common']['is_containerized']): facts['common']['admin_binary'] = '/usr/local/bin/oadm' facts['common']['client_binary'] = '/usr/local/bin/oc' - base_version = get_openshift_version(facts, cli_image).split('-')[0] - facts['common']['image_tag'] = "v" + base_version + openshift_version = get_openshift_version(facts, cli_image) + if openshift_version is not None: + base_version = openshift_version.split('-')[0] + facts['common']['image_tag'] = "v" + base_version return facts @@ -1281,13 +1374,20 @@ class OpenShiftFacts(object): Raises: OpenShiftFactsUnsupportedRoleError: """ - known_roles = ['common', 'master', 'node', 'etcd', 'hosted', 'docker'] + known_roles = ['cloudprovider', + 'common', + 'docker', + 'etcd', + 'hosted', + 'master', + 'node'] # Disabling too-many-arguments, this should be cleaned up as a TODO item. # pylint: disable=too-many-arguments def __init__(self, role, filename, local_facts, additive_facts_to_overwrite=None, openshift_env=None, + openshift_env_structures=None, protected_facts_to_overwrite=None): self.changed = False self.filename = filename @@ -1300,12 +1400,14 @@ class OpenShiftFacts(object): self.facts = self.generate_facts(local_facts, additive_facts_to_overwrite, openshift_env, + openshift_env_structures, protected_facts_to_overwrite) def generate_facts(self, local_facts, additive_facts_to_overwrite, openshift_env, + openshift_env_structures, protected_facts_to_overwrite): """ Generate facts @@ -1322,6 +1424,7 @@ class OpenShiftFacts(object): local_facts = self.init_local_facts(local_facts, additive_facts_to_overwrite, openshift_env, + openshift_env_structures, protected_facts_to_overwrite) roles = local_facts.keys() @@ -1338,7 +1441,6 @@ class OpenShiftFacts(object): local_facts, additive_facts_to_overwrite, protected_facts_to_overwrite) - facts = merge_provider_facts(facts) facts['current_config'] = get_current_config(facts) facts = set_url_facts_if_unset(facts) facts = set_project_cfg_facts_if_unset(facts) @@ -1350,11 +1452,14 @@ class OpenShiftFacts(object): facts = set_identity_providers_if_unset(facts) facts = set_sdn_facts_if_unset(facts, self.system_facts) facts = set_deployment_facts_if_unset(facts) + facts = set_container_facts_if_unset(facts) + facts = build_kubelet_args(facts) + facts = build_controller_args(facts) + facts = build_api_server_args(facts) facts = set_version_facts_if_unset(facts) facts = set_manageiq_facts_if_unset(facts) facts = set_aggregate_facts(facts) facts = set_etcd_facts_if_unset(facts) - facts = set_container_facts_if_unset(facts) if not safe_get_bool(facts['common']['is_containerized']): facts = set_installed_variant_rpm_facts(facts) return dict(openshift=facts) @@ -1387,6 +1492,19 @@ class OpenShiftFacts(object): debug_level=2) if 'master' in roles: + scheduler_predicates = [ + {"name": "MatchNodeSelector"}, + {"name": "PodFitsResources"}, + {"name": "PodFitsPorts"}, + {"name": "NoDiskConflict"}, + {"name": "Region", "argument": {"serviceAffinity" : {"labels" : ["region"]}}} + ] + scheduler_priorities = [ + {"name": "LeastRequestedPriority", "weight": 1}, + {"name": "SelectorSpreadPriority", "weight": 1}, + {"name": "Zone", "weight" : 2, "argument": {"serviceAntiAffinity" : {"label": "zone"}}} + ] + defaults['master'] = dict(api_use_ssl=True, api_port='8443', controllers_port='8444', console_use_ssl=True, @@ -1402,7 +1520,9 @@ class OpenShiftFacts(object): session_secrets_file='', access_token_max_seconds=86400, auth_token_max_seconds=500, - oauth_grant_method='auto') + oauth_grant_method='auto', + scheduler_predicates=scheduler_predicates, + scheduler_priorities=scheduler_priorities) if 'node' in roles: defaults['node'] = dict(labels={}, annotations={}, @@ -1411,7 +1531,15 @@ class OpenShiftFacts(object): set_node_ip=False) if 'docker' in roles: - defaults['docker'] = dict(disable_push_dockerhub=False) + docker = dict(disable_push_dockerhub=False) + version_info = get_docker_version_info() + if version_info is not None: + docker['api_version'] = version_info['api_version'] + docker['version'] = version_info['version'] + defaults['docker'] = docker + + if 'cloudprovider' in roles: + defaults['cloudprovider'] = dict(kind=None) defaults['hosted'] = dict( registry=dict( @@ -1431,7 +1559,6 @@ class OpenShiftFacts(object): ) ) - return defaults def guess_host_provider(self): @@ -1467,7 +1594,7 @@ class OpenShiftFacts(object): metadata['instance'].pop('serviceAccounts', None) elif (virt_type == 'xen' and virt_role == 'guest' and re.match(r'.*\.amazon$', product_version)): - provider = 'ec2' + provider = 'aws' metadata_url = 'http://169.254.169.254/latest/meta-data/' metadata = get_provider_metadata(metadata_url) elif re.search(r'OpenStack', product_name): @@ -1509,11 +1636,53 @@ class OpenShiftFacts(object): ) return provider_facts - # Disabling too-many-branches. This should be cleaned up as a TODO item. - #pylint: disable=too-many-branches + @staticmethod + def split_openshift_env_fact_keys(openshift_env_fact, openshift_env_structures): + """ Split openshift_env facts based on openshift_env structures. + + Args: + openshift_env_fact (string): the openshift_env fact to split + ex: 'openshift_cloudprovider_openstack_auth_url' + openshift_env_structures (list): a list of structures to determine fact keys + ex: ['openshift.cloudprovider.openstack.*'] + Returns: + list: a list of keys that represent the fact + ex: ['openshift', 'cloudprovider', 'openstack', 'auth_url'] + """ + # By default, we'll split an openshift_env fact by underscores. + fact_keys = openshift_env_fact.split('_') + + # Determine if any of the provided variable structures match the fact. + matching_structure = None + if openshift_env_structures != None: + for structure in openshift_env_structures: + if re.match(structure, openshift_env_fact): + matching_structure = structure + # Fact didn't match any variable structures so return the default fact keys. + if matching_structure is None: + return fact_keys + + final_keys = [] + structure_keys = matching_structure.split('.') + for structure_key in structure_keys: + # Matched current key. Add to final keys. + if structure_key == fact_keys[structure_keys.index(structure_key)]: + final_keys.append(structure_key) + # Wildcard means we will be taking everything from here to the end of the fact. + elif structure_key == '*': + final_keys.append('_'.join(fact_keys[structure_keys.index(structure_key):])) + # Shouldn't have gotten here, return the fact keys. + else: + return fact_keys + return final_keys + + # Disabling too-many-branches and too-many-locals. + # This should be cleaned up as a TODO item. + #pylint: disable=too-many-branches, too-many-locals def init_local_facts(self, facts=None, additive_facts_to_overwrite=None, openshift_env=None, + openshift_env_structures=None, protected_facts_to_overwrite=None): """ Initialize the local facts @@ -1541,8 +1710,8 @@ class OpenShiftFacts(object): for fact, value in openshift_env.iteritems(): oo_env_facts = dict() current_level = oo_env_facts - keys = fact.split('_')[1:] - if keys[0] != self.role: + keys = self.split_openshift_env_fact_keys(fact, openshift_env_structures)[1:] + if len(keys) > 0 and keys[0] != self.role: continue for key in keys: if key == keys[-1]: @@ -1670,6 +1839,7 @@ def main(): local_facts=dict(default=None, type='dict', required=False), additive_facts_to_overwrite=dict(default=[], type='list', required=False), openshift_env=dict(default={}, type='dict', required=False), + openshift_env_structures=dict(default=[], type='list', required=False), protected_facts_to_overwrite=dict(default=[], type='list', required=False), ), supports_check_mode=True, @@ -1680,6 +1850,7 @@ def main(): local_facts = module.params['local_facts'] additive_facts_to_overwrite = module.params['additive_facts_to_overwrite'] openshift_env = module.params['openshift_env'] + openshift_env_structures = module.params['openshift_env_structures'] protected_facts_to_overwrite = module.params['protected_facts_to_overwrite'] fact_file = '/etc/ansible/facts.d/openshift.fact' @@ -1689,6 +1860,7 @@ def main(): local_facts, additive_facts_to_overwrite, openshift_env, + openshift_env_structures, protected_facts_to_overwrite) file_params = module.params.copy() diff --git a/roles/openshift_facts/tasks/main.yml b/roles/openshift_facts/tasks/main.yml index 50e7e5747..36def57c8 100644 --- a/roles/openshift_facts/tasks/main.yml +++ b/roles/openshift_facts/tasks/main.yml @@ -15,7 +15,7 @@ - set_fact: l_is_atomic: "{{ ostree_output.rc == 0 }}" - set_fact: - l_is_containerized: "{{ l_is_atomic or containerized | default(false) | bool }}" + l_is_containerized: "{{ (l_is_atomic | bool) or (containerized | default(false) | bool) }}" - name: Ensure PyYaml is installed action: "{{ ansible_pkg_mgr }} name=PyYAML state=present" @@ -30,6 +30,6 @@ cluster_id: "{{ openshift_cluster_id | default('default') }}" hostname: "{{ openshift_hostname | default(None) }}" ip: "{{ openshift_ip | default(None) }}" - is_containerized: "{{ containerized | default(None) }}" + is_containerized: "{{ l_is_containerized | default(None) }}" public_hostname: "{{ openshift_public_hostname | default(None) }}" public_ip: "{{ openshift_public_ip | default(None) }}" diff --git a/roles/openshift_hosted_logging/README.md b/roles/openshift_hosted_logging/README.md new file mode 100644 index 000000000..b3f363571 --- /dev/null +++ b/roles/openshift_hosted_logging/README.md @@ -0,0 +1,10 @@ +###Required vars: + +- openshift_hosted_logging_hostname: kibana.example.com +- openshift_hosted_logging_elasticsearch_cluster_size: 1 +- openshift_hosted_logging_master_public_url: https://localhost:8443 + +###Optional vars: +- openshift_hosted_logging_secret_vars: (defaults to nothing=/dev/null) kibana.crt=/etc/origin/master/ca.crt kibana.key=/etc/origin/master/ca.key ca.crt=/etc/origin/master/ca.crt ca.key=/etc/origin/master/ca.key +- openshift_hosted_logging_fluentd_replicas: (defaults to 1) 3 +- openshift_hosted_logging_cleanup: (defaults to no) Set this to 'yes' in order to cleanup logging components instead of deploying. diff --git a/roles/openshift_hosted_logging/files/logging-deployer-sa.yaml b/roles/openshift_hosted_logging/files/logging-deployer-sa.yaml new file mode 100644 index 000000000..334c9402b --- /dev/null +++ b/roles/openshift_hosted_logging/files/logging-deployer-sa.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: logging-deployer +secrets: +- name: logging-deployer diff --git a/roles/openshift_hosted_logging/meta/main.yaml b/roles/openshift_hosted_logging/meta/main.yaml new file mode 100644 index 000000000..b695bde87 --- /dev/null +++ b/roles/openshift_hosted_logging/meta/main.yaml @@ -0,0 +1,3 @@ +--- +dependencies: + - { role: openshift_common } diff --git a/roles/openshift_hosted_logging/tasks/cleanup_logging.yaml b/roles/openshift_hosted_logging/tasks/cleanup_logging.yaml new file mode 100644 index 000000000..8331f0389 --- /dev/null +++ b/roles/openshift_hosted_logging/tasks/cleanup_logging.yaml @@ -0,0 +1,59 @@ +--- + - name: Create temp directory for kubeconfig + command: mktemp -d /tmp/openshift-ansible-XXXXXX + register: mktemp + changed_when: False + + - name: Copy the admin client config(s) + command: > + cp {{ openshift_master_config_dir }}/admin.kubeconfig {{ mktemp.stdout }}/admin.kubeconfig + changed_when: False + + - name: "Checking for logging project" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig get project logging" + register: logging_project + failed_when: "'FAILED' in logging_project.stderr" + + - name: "Changing projects" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig project logging" + + + - name: "Cleanup any previous logging infrastructure" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig delete --ignore-not-found all --selector logging-infra={{ item }}" + with_items: + - kibana + - fluentd + - elasticsearch + ignore_errors: yes + + - name: "Cleanup existing support infrastructure" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig delete --ignore-not-found all,sa,oauthclient --selector logging-infra=support" + ignore_errors: yes + + - name: "Cleanup existing secrets" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig delete secret logging-fluentd logging-elasticsearch logging-es-proxy logging-kibana logging-kibana-proxy logging-kibana-ops-proxy" + ignore_errors: yes + register: clean_result + failed_when: clean_result.rc == 1 and 'not found' not in clean_result.stderr + + - name: "Cleanup existing logging deployers" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig delete pods --all" + + + - name: "Cleanup logging project" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig delete project logging" + + + - name: "Remove deployer template" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig delete template logging-deployer-template -n openshift" + register: delete_ouput + failed_when: delete_ouput.rc == 1 and 'exists' not in delete_ouput.stderr + + + - name: Delete temp directory + file: + name: "{{ mktemp.stdout }}" + state: absent + changed_when: False + + - debug: msg="Success!" diff --git a/roles/openshift_hosted_logging/tasks/deploy_logging.yaml b/roles/openshift_hosted_logging/tasks/deploy_logging.yaml new file mode 100644 index 000000000..d8a5b62a0 --- /dev/null +++ b/roles/openshift_hosted_logging/tasks/deploy_logging.yaml @@ -0,0 +1,105 @@ +--- + - fail: msg="This role requires the following vars to be defined. openshift_hosted_logging_master_public_url, openshift_hosted_logging_hostname, openshift_hosted_logging_elasticsearch_cluster_size" + when: "openshift_hosted_logging_hostname is not defined or + openshift_hosted_logging_elasticsearch_cluster_size is not defined or + openshift_hosted_logging_master_public_url is not defined" + + - name: Create temp directory for kubeconfig + command: mktemp -d /tmp/openshift-ansible-XXXXXX + register: mktemp + changed_when: False + + - name: Copy the admin client config(s) + command: > + cp {{ openshift_master_config_dir }}/admin.kubeconfig {{ mktemp.stdout }}/admin.kubeconfig + changed_when: False + + - name: "Create logging project" + command: {{ openshift.common.admin_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig new-project logging + + - name: "Changing projects" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig project logging" + + - name: "Creating logging deployer secret" + command: " {{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig secrets new logging-deployer {{ openshift_hosted_logging_secret_vars | default('nothing=/dev/null') }}" + register: secret_output + failed_when: "secret_output.rc == 1 and 'exists' not in secret_output.stderr" + + - name: "Copy serviceAccount file" + copy: dest=/tmp/logging-deployer-sa.yaml + src={{role_path}}/files/logging-deployer-sa.yaml + force=yes + + - name: "Create logging-deployer service account" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig create -f /tmp/logging-deployer-sa.yaml" + register: deployer_output + failed_when: "deployer_output.rc == 1 and 'exists' not in deployer_output.stderr" + + - name: "Set permissions for logging-deployer service account" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig policy add-role-to-user edit system:serviceaccount:logging:logging-deployer" + register: permiss_output + failed_when: "permiss_output.rc == 1 and 'exists' not in permiss_output.stderr" + + - name: "Set permissions for fluentd" + command: {{ openshift.common.admin_binary}} policy add-scc-to-user privileged system:serviceaccount:logging:aggregated-logging-fluentd + register: fluentd_output + failed_when: "fluentd_output.rc == 1 and 'exists' not in fluentd_output.stderr" + + - name: "Set additional permissions for fluentd" + command: {{ openshift.common.admin_binary}} policy add-cluster-role-to-user cluster-reader system:serviceaccount:logging:aggregated-logging-fluentd + register: fluentd2_output + failed_when: "fluentd2_output.rc == 1 and 'exists' not in fluentd2_output.stderr" + + - name: "Create deployer template" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig create -f /usr/share/openshift/examples/infrastructure-templates/enterprise/logging-deployer.yaml -n openshift" + register: template_output + failed_when: "template_output.rc == 1 and 'exists' not in template_output.stderr" + + - name: "Process the deployer template" + shell: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig process logging-deployer-template -n openshift -v {{ oc_process_values }} | {{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig create -f -" + + - name: "Wait for image pull and deployer pod" + shell: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig get pods | grep logging-deployer.*Completed" + register: result + until: result.rc == 0 + retries: 15 + delay: 10 + + - name: "Process support template" + shell: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig process logging-support-template | {{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig create -f -" + + - name: "Set insecured registry" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig annotate is --all openshift.io/image.insecureRepository=true --overwrite" + when: "target_registry is defined and insecure_registry == 'true'" + + - name: "Wait for imagestreams to become available" + shell: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig get is | grep logging-fluentd" + register: result + until: result.rc == 0 + failed_when: result.rc == 1 and 'not found' not in result.stderr + retries: 20 + delay: 10 + + - name: "Wait for replication controllers to become available" + shell: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig get rc | grep logging-fluentd-1" + register: result + until: result.rc == 0 + failed_when: result.rc == 1 and 'not found' not in result.stderr + retries: 20 + delay: 10 + + + - name: "Scale fluentd deployment config" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig scale dc/logging-fluentd --replicas={{ fluentd_replicas | default('1') }}" + + + - name: "Scale fluentd replication controller" + command: "{{ openshift.common.client_binary }} --config={{ mktemp.stdout }}/admin.kubeconfig scale rc/logging-fluentd-1 --replicas={{ fluentd_replicas | default('1') }}" + + - debug: msg="Logging components deployed. Note persistant volume for elasticsearch must be setup manually" + + - name: Delete temp directory + file: + name: "{{ mktemp.stdout }}" + state: absent + changed_when: False diff --git a/roles/openshift_hosted_logging/tasks/main.yaml b/roles/openshift_hosted_logging/tasks/main.yaml new file mode 100644 index 000000000..42568597a --- /dev/null +++ b/roles/openshift_hosted_logging/tasks/main.yaml @@ -0,0 +1,8 @@ +--- +- name: Cleanup logging deployment + include: "{{ role_path }}/tasks/cleanup_logging.yaml" + when: openshift_hosted_logging_cleanup | default(false) | bool + +- name: Deploy logging + include: "{{ role_path }}/tasks/deploy_logging.yaml" + when: not openshift_hosted_logging_cleanup | default(false) | bool diff --git a/roles/openshift_hosted_logging/vars/main.yaml b/roles/openshift_hosted_logging/vars/main.yaml new file mode 100644 index 000000000..586c2ab91 --- /dev/null +++ b/roles/openshift_hosted_logging/vars/main.yaml @@ -0,0 +1,6 @@ +kh_kv: "KIBANA_HOSTNAME={{ openshift_hosted_logging_hostname | quote }}" +es_cs_kv: "ES_CLUSTER_SIZE={{ openshift_hosted_logging_elasticsearch_cluster_size | quote }}" +pmu_kv: "PUBLIC_MASTER_URL={{ openshift_hosted_logging_master_public_url | quote }}" +ip_kv: "{{ 'IMAGE_PREFIX=' ~ target_registry | quote if target_registry is defined else '' }}" +oc_process_values: "{{ kh_kv }},{{ es_cs_kv }},{{ pmu_kv }},{{ ip_kv }}" +openshift_master_config_dir: "{{ openshift.common.config_base }}/master" diff --git a/roles/openshift_manageiq/tasks/main.yaml b/roles/openshift_manageiq/tasks/main.yaml index d2ff1b4b7..2a651df65 100644 --- a/roles/openshift_manageiq/tasks/main.yaml +++ b/roles/openshift_manageiq/tasks/main.yaml @@ -18,7 +18,7 @@ failed_when: "'already exists' not in osmiq_create_mi_project.stderr and osmiq_create_mi_project.rc != 0" changed_when: osmiq_create_mi_project.rc == 0 -- name: Create Service Account +- name: Create Admin Service Account shell: > echo {{ manageiq_service_account | to_json | quote }} | {{ openshift.common.client_binary }} create @@ -29,6 +29,17 @@ failed_when: "'already exists' not in osmiq_create_service_account.stderr and osmiq_create_service_account.rc != 0" changed_when: osmiq_create_service_account.rc == 0 +- name: Create Image Inspector Service Account + shell: > + echo {{ manageiq_image_inspector_service_account | to_json | quote }} | + {{ openshift.common.client_binary }} create + -n management-infra + --config={{manage_iq_tmp_conf}} + -f - + register: osmiq_create_service_account + failed_when: "'already exists' not in osmiq_create_service_account.stderr and osmiq_create_service_account.rc != 0" + changed_when: osmiq_create_service_account.rc == 0 + - name: Create Cluster Role shell: > echo {{ manageiq_cluster_role | to_json | quote }} | diff --git a/roles/openshift_manageiq/vars/main.yml b/roles/openshift_manageiq/vars/main.yml index 77e1c304b..69ee2cb4c 100644 --- a/roles/openshift_manageiq/vars/main.yml +++ b/roles/openshift_manageiq/vars/main.yml @@ -15,6 +15,12 @@ manageiq_service_account: metadata: name: management-admin +manageiq_image_inspector_service_account: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: inspector-admin + manage_iq_tmp_conf: /tmp/manageiq_admin.kubeconfig manage_iq_tasks: @@ -22,3 +28,5 @@ manage_iq_tasks: - policy add-role-to-user -n management-infra management-infra-admin -z management-admin - policy add-cluster-role-to-user cluster-reader system:serviceaccount:management-infra:management-admin - policy add-scc-to-user privileged system:serviceaccount:management-infra:management-admin + - policy add-cluster-role-to-user system:image-puller system:serviceaccount:management-infra:inspector-admin + - policy add-scc-to-user privileged system:serviceaccount:management-infra:inspector-admin diff --git a/roles/openshift_master/handlers/main.yml b/roles/openshift_master/handlers/main.yml index e5b9e4977..70c7ef4e4 100644 --- a/roles/openshift_master/handlers/main.yml +++ b/roles/openshift_master/handlers/main.yml @@ -1,17 +1,17 @@ --- - name: restart master service: name={{ openshift.common.service_type }}-master state=restarted - when: (not openshift_master_ha | bool) and (not (master_service_status_changed | default(false) | bool)) + when: (openshift.master.ha is defined and not openshift.master.ha | bool) and (not (master_service_status_changed | default(false) | bool)) notify: Verify API Server - name: restart master api service: name={{ openshift.common.service_type }}-master-api state=restarted - when: (openshift_master_ha | bool) and (not (master_api_service_status_changed | default(false) | bool)) and openshift.master.cluster_method == 'native' + when: (openshift.master.ha is defined and openshift_master_ha | bool) and (not (master_api_service_status_changed | default(false) | bool)) and openshift.master.cluster_method == 'native' notify: Verify API Server - name: restart master controllers service: name={{ openshift.common.service_type }}-master-controllers state=restarted - when: (openshift_master_ha | bool) and (not (master_controllers_service_status_changed | default(false) | bool)) and openshift.master.cluster_method == 'native' + when: (openshift.master.ha is defined and openshift_master_ha | bool) and (not (master_controllers_service_status_changed | default(false) | bool)) and openshift.master.cluster_method == 'native' - name: Verify API Server # Using curl here since the uri module requires python-httplib2 and diff --git a/roles/openshift_master/meta/main.yml b/roles/openshift_master/meta/main.yml index 4eda4a8e2..7ab913eea 100644 --- a/roles/openshift_master/meta/main.yml +++ b/roles/openshift_master/meta/main.yml @@ -12,4 +12,7 @@ galaxy_info: categories: - cloud dependencies: +- role: openshift_docker - role: openshift_cli +- role: openshift_cloud_provider +- role: openshift_master_facts diff --git a/roles/openshift_master/tasks/main.yml b/roles/openshift_master/tasks/main.yml index 9c3d09d09..23994cdcf 100644 --- a/roles/openshift_master/tasks/main.yml +++ b/roles/openshift_master/tasks/main.yml @@ -23,71 +23,6 @@ msg: "Pacemaker based HA is not supported at this time when used with containerized installs" when: openshift_master_ha | bool and openshift_master_cluster_method == "pacemaker" and openshift.common.is_containerized | bool -- name: Set master facts - openshift_facts: - role: master - local_facts: - cluster_method: "{{ openshift_master_cluster_method | default(None) }}" - cluster_hostname: "{{ openshift_master_cluster_hostname | default(None) }}" - cluster_public_hostname: "{{ openshift_master_cluster_public_hostname | default(None) }}" - debug_level: "{{ openshift_master_debug_level | default(openshift.common.debug_level) }}" - api_port: "{{ openshift_master_api_port | default(None) }}" - api_url: "{{ openshift_master_api_url | default(None) }}" - api_use_ssl: "{{ openshift_master_api_use_ssl | default(None) }}" - public_api_url: "{{ openshift_master_public_api_url | default(None) }}" - console_path: "{{ openshift_master_console_path | default(None) }}" - console_port: "{{ openshift_master_console_port | default(None) }}" - console_url: "{{ openshift_master_console_url | default(None) }}" - console_use_ssl: "{{ openshift_master_console_use_ssl | default(None) }}" - public_console_url: "{{ openshift_master_public_console_url | default(None) }}" - logging_public_url: "{{ openshift_master_logging_public_url | default(None) }}" - metrics_public_url: "{{ openshift_master_metrics_public_url | default(None) }}" - logout_url: "{{ openshift_master_logout_url | default(None) }}" - extension_scripts: "{{ openshift_master_extension_scripts | default(None) }}" - extension_stylesheets: "{{ openshift_master_extension_stylesheets | default(None) }}" - extensions: "{{ openshift_master_extensions | default(None) }}" - oauth_template: "{{ openshift_master_oauth_template | default(None) }}" - etcd_hosts: "{{ openshift_master_etcd_hosts | default(None) }}" - etcd_port: "{{ openshift_master_etcd_port | default(None) }}" - etcd_use_ssl: "{{ openshift_master_etcd_use_ssl | default(None) }}" - etcd_urls: "{{ openshift_master_etcd_urls | default(None) }}" - embedded_etcd: "{{ openshift_master_embedded_etcd | default(None) }}" - embedded_kube: "{{ openshift_master_embedded_kube | default(None) }}" - embedded_dns: "{{ openshift_master_embedded_dns | default(None) }}" - dns_port: "{{ openshift_master_dns_port | default(None) }}" - bind_addr: "{{ openshift_master_bind_addr | default(None) }}" - pod_eviction_timeout: "{{ openshift_master_pod_eviction_timeout | default(None) }}" - portal_net: "{{ openshift_master_portal_net | default(None) }}" - session_max_seconds: "{{ openshift_master_session_max_seconds | default(None) }}" - session_name: "{{ openshift_master_session_name | default(None) }}" - session_secrets_file: "{{ openshift_master_session_secrets_file | default(None) }}" - session_auth_secrets: "{{ openshift_master_session_auth_secrets | default(None) }}" - session_encryption_secrets: "{{ openshift_master_session_encryption_secrets | default(None) }}" - access_token_max_seconds: "{{ openshift_master_access_token_max_seconds | default(None) }}" - auth_token_max_seconds: "{{ openshift_master_auth_token_max_seconds | default(None) }}" - identity_providers: "{{ openshift_master_identity_providers | default(None) }}" - registry_url: "{{ oreg_url | default(None) }}" - oauth_grant_method: "{{ openshift_master_oauth_grant_method | default(None) }}" - sdn_cluster_network_cidr: "{{ osm_cluster_network_cidr | default(None) }}" - sdn_host_subnet_length: "{{ osm_host_subnet_length | default(None) }}" - default_subdomain: "{{ openshift_master_default_subdomain | default(osm_default_subdomain) | default(None) }}" - custom_cors_origins: "{{ osm_custom_cors_origins | default(None) }}" - default_node_selector: "{{ osm_default_node_selector | default(None) }}" - project_request_message: "{{ osm_project_request_message | default(None) }}" - project_request_template: "{{ osm_project_request_template | default(None) }}" - mcs_allocator_range: "{{ osm_mcs_allocator_range | default(None) }}" - mcs_labels_per_project: "{{ osm_mcs_labels_per_project | default(None) }}" - uid_allocator_range: "{{ osm_uid_allocator_range | default(None) }}" - router_selector: "{{ openshift_router_selector | default(None) }}" - registry_selector: "{{ openshift_registry_selector | default(None) }}" - api_server_args: "{{ osm_api_server_args | default(None) }}" - controller_args: "{{ osm_controller_args | default(None) }}" - infra_nodes: "{{ openshift_infra_nodes | default(None) }}" - disabled_features: "{{ osm_disabled_features | default(None) }}" - master_count: "{{ openshift_master_count | default(None) }}" - controller_lease_ttl: "{{ osm_controller_lease_ttl | default(None) }}" - master_image: "{{ osm_image | default(None) }}" - - name: Install Master package action: "{{ ansible_pkg_mgr }} name={{ openshift.common.service_type }}-master{{ openshift_version }} state=present" when: not openshift.common.is_containerized | bool @@ -97,13 +32,6 @@ docker pull {{ openshift.master.master_image }}:{{ openshift_version }} when: openshift.common.is_containerized | bool -- name: Install Master docker service file - template: - dest: "/etc/systemd/system/{{ openshift.common.service_type }}-master.service" - src: docker/master.docker.service.j2 - register: install_result - when: openshift.common.is_containerized | bool and not openshift_master_ha | bool - - name: Create openshift.common.data_dir file: path: "{{ openshift.common.data_dir }}" @@ -137,9 +65,9 @@ - restart master controllers - name: Create the scheduler config - template: + copy: + content: "{{ scheduler_config | to_nice_json }}" dest: "{{ openshift_master_scheduler_conf }}" - src: scheduler.json.j2 backup: true notify: - restart master @@ -168,54 +96,8 @@ when: item.kind == 'HTPasswdPasswordIdentityProvider' with_items: openshift.master.identity_providers -- name: Init HA Service Info - set_fact: - ha_suffix: "" - ha_svcdir: "/usr/lib/systemd/system" - -- name: Set HA Service Info for containerized installs - set_fact: - ha_suffix: ".docker" - ha_svcdir: "/etc/systemd/system" - when: openshift.common.is_containerized | bool - -# workaround for missing systemd unit files for controllers/api -- name: Create the systemd unit files - template: - src: "{{ ha_svc_template_path }}/atomic-openshift-master-{{ item }}.service.j2" - dest: "{{ ha_svcdir }}/{{ openshift.common.service_type }}-master-{{ item }}.service" - when: openshift_master_ha | bool and openshift_master_cluster_method == "native" - with_items: - - api - - controllers - register: create_unit_files - -- command: systemctl daemon-reload - when: create_unit_files | changed -# end workaround for missing systemd unit files - -- name: Create the master api service env file - template: - src: "{{ ha_svc_template_path }}/atomic-openshift-master-api.j2" - dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-api - when: openshift_master_ha | bool and openshift_master_cluster_method == "native" - notify: - - restart master api - -- name: Create the master controllers service env file - template: - src: "{{ ha_svc_template_path }}/atomic-openshift-master-controllers.j2" - dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-controllers - when: openshift_master_ha | bool and openshift_master_cluster_method == "native" - notify: - - restart master controllers - -- name: Create the master service env file - template: - src: "atomic-openshift-master.j2" - dest: /etc/sysconfig/{{ openshift.common.service_type }}-master - notify: - - restart master +- name: Install the systemd units + include: systemd_units.yml - name: Create session secrets file template: diff --git a/roles/openshift_master/tasks/systemd_units.yml b/roles/openshift_master/tasks/systemd_units.yml new file mode 100644 index 000000000..a81270bab --- /dev/null +++ b/roles/openshift_master/tasks/systemd_units.yml @@ -0,0 +1,69 @@ +# This file is included both in the openshift_master role and in the upgrade +# playbooks. For that reason the ha_svc variables are use set_fact instead of +# the vars directory on the role. + +- name: Init HA Service Info + set_fact: + containerized_svc_dir: "/usr/lib/systemd/system" + ha_svc_template_path: "native-cluster" + +- name: Set HA Service Info for containerized installs + set_fact: + containerized_svc_dir: "/etc/systemd/system" + ha_svc_template_path: "docker-cluster" + when: openshift.common.is_containerized | bool + +# workaround for missing systemd unit files +- name: Create the systemd unit files + template: + src: "docker/master.docker.service.j2" + dest: "{{ containerized_svc_dir }}/{{ openshift.common.service_type }}-master.service" + when: openshift.common.is_containerized | bool and (openshift.master.ha is not defined or not openshift.master.ha | bool) + register: create_master_unit_file + +- command: systemctl daemon-reload + when: create_master_unit_file | changed + +- name: Create the ha systemd unit files + template: + src: "{{ ha_svc_template_path }}/atomic-openshift-master-{{ item }}.service.j2" + dest: "{{ containerized_svc_dir }}/{{ openshift.common.service_type }}-master-{{ item }}.service" + when: openshift.master.ha is defined and openshift.master.ha | bool and openshift_master_cluster_method == "native" + with_items: + - api + - controllers + register: create_ha_unit_files + +- command: systemctl daemon-reload + when: create_ha_unit_files | changed +# end workaround for missing systemd unit files + +- name: Create the master api service env file + template: + src: "{{ ha_svc_template_path }}/atomic-openshift-master-api.j2" + dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-api + when: openshift.master.ha is defined and openshift.master.ha | bool and openshift_master_cluster_method == "native" + notify: + - restart master api + +- name: Create the master controllers service env file + template: + src: "{{ ha_svc_template_path }}/atomic-openshift-master-controllers.j2" + dest: /etc/sysconfig/{{ openshift.common.service_type }}-master-controllers + when: openshift.master.ha is defined and openshift.master.ha | bool and openshift_master_cluster_method == "native" + notify: + - restart master controllers + +- name: Install Master docker service file + template: + dest: "/etc/systemd/system/{{ openshift.common.service_type }}-master.service" + src: docker/master.docker.service.j2 + register: install_result + when: openshift.common.is_containerized | bool and openshift.master.ha is defined and not openshift.master.ha | bool + +- name: Create the master service env file + template: + src: "atomic-openshift-master.j2" + dest: /etc/sysconfig/{{ openshift.common.service_type }}-master + notify: + - restart master diff --git a/roles/openshift_master/templates/atomic-openshift-master.j2 b/roles/openshift_master/templates/atomic-openshift-master.j2 index c848e0ac2..7f1576682 100644 --- a/roles/openshift_master/templates/atomic-openshift-master.j2 +++ b/roles/openshift_master/templates/atomic-openshift-master.j2 @@ -4,6 +4,11 @@ CONFIG_FILE={{ openshift_master_config_file }} IMAGE_VERSION={{ openshift_version }} {% endif %} +{% if 'cloudprovider' in openshift and 'aws' in openshift.cloudprovider and openshift.cloudprovider.kind == 'aws' and 'access_key' in openshift.cloudprovider.aws and 'secret_key' in openshift.cloudprovider.aws %} +AWS_ACCESS_KEY_ID={{ openshift.cloudprovider.aws.access_key }} +AWS_SECRET_ACCESS_KEY={{ openshift.cloudprovider.aws.secret_key }} +{% endif %} + # Proxy configuration # Origin uses standard HTTP_PROXY environment variables. Be sure to set # NO_PROXY for your master diff --git a/roles/openshift_master/templates/docker-cluster/atomic-openshift-master-api.service.j2 b/roles/openshift_master/templates/docker-cluster/atomic-openshift-master-api.service.j2 index 6a21a04ab..5e6577d95 100644 --- a/roles/openshift_master/templates/docker-cluster/atomic-openshift-master-api.service.j2 +++ b/roles/openshift_master/templates/docker-cluster/atomic-openshift-master-api.service.j2 @@ -22,5 +22,5 @@ SyslogIdentifier={{ openshift.common.service_type }}-master-api Restart=always [Install] -WantedBy=multi-user.target +WantedBy=docker.service WantedBy={{ openshift.common.service_type }}-node.service diff --git a/roles/openshift_master/templates/docker-cluster/atomic-openshift-master-controllers.service.j2 b/roles/openshift_master/templates/docker-cluster/atomic-openshift-master-controllers.service.j2 index 69f68d843..04c84a84a 100644 --- a/roles/openshift_master/templates/docker-cluster/atomic-openshift-master-controllers.service.j2 +++ b/roles/openshift_master/templates/docker-cluster/atomic-openshift-master-controllers.service.j2 @@ -21,4 +21,4 @@ SyslogIdentifier={{ openshift.common.service_type }}-master-controllers Restart=on-failure [Install] -WantedBy=multi-user.target +WantedBy=docker.service diff --git a/roles/openshift_master/templates/docker/master.docker.service.j2 b/roles/openshift_master/templates/docker/master.docker.service.j2 index b714fdeb7..6bd0dcf56 100644 --- a/roles/openshift_master/templates/docker/master.docker.service.j2 +++ b/roles/openshift_master/templates/docker/master.docker.service.j2 @@ -14,4 +14,4 @@ ExecStop=/usr/bin/docker stop {{ openshift.common.service_type }}-master Restart=always [Install] -WantedBy=multi-user.target +WantedBy=docker.service diff --git a/roles/openshift_master/templates/native-cluster/atomic-openshift-master-api.j2 b/roles/openshift_master/templates/native-cluster/atomic-openshift-master-api.j2 index 8e2d927aa..fa2323a2c 100644 --- a/roles/openshift_master/templates/native-cluster/atomic-openshift-master-api.j2 +++ b/roles/openshift_master/templates/native-cluster/atomic-openshift-master-api.j2 @@ -4,6 +4,11 @@ CONFIG_FILE={{ openshift_master_config_file }} IMAGE_VERSION={{ openshift_version }} {% endif %} +{% if 'cloudprovider' in openshift and 'aws' in openshift.cloudprovider and openshift.cloudprovider.kind == 'aws' and 'access_key' in openshift.cloudprovider.aws and 'secret_key' in openshift.cloudprovider.aws %} +AWS_ACCESS_KEY_ID={{ openshift.cloudprovider.aws.access_key }} +AWS_SECRET_ACCESS_KEY={{ openshift.cloudprovider.aws.secret_key }} +{% endif %} + # Proxy configuration # Origin uses standard HTTP_PROXY environment variables. Be sure to set # NO_PROXY for your master diff --git a/roles/openshift_master/templates/native-cluster/atomic-openshift-master-controllers.j2 b/roles/openshift_master/templates/native-cluster/atomic-openshift-master-controllers.j2 index 5c6cb2dcb..632dfbb8a 100644 --- a/roles/openshift_master/templates/native-cluster/atomic-openshift-master-controllers.j2 +++ b/roles/openshift_master/templates/native-cluster/atomic-openshift-master-controllers.j2 @@ -4,6 +4,11 @@ CONFIG_FILE={{ openshift_master_config_file }} IMAGE_VERSION={{ openshift_version }} {% endif %} +{% if 'cloudprovider' in openshift and 'aws' in openshift.cloudprovider and openshift.cloudprovider.kind == 'aws' and 'access_key' in openshift.cloudprovider.aws and 'secret_key' in openshift.cloudprovider.aws %} +AWS_ACCESS_KEY_ID={{ openshift.cloudprovider.aws.access_key }} +AWS_SECRET_ACCESS_KEY={{ openshift.cloudprovider.aws.secret_key }} +{% endif %} + # Proxy configuration # Origin uses standard HTTP_PROXY environment variables. Be sure to set # NO_PROXY for your master diff --git a/roles/openshift_master/templates/scheduler.json.j2 b/roles/openshift_master/templates/scheduler.json.j2 deleted file mode 100644 index cb5f43bb2..000000000 --- a/roles/openshift_master/templates/scheduler.json.j2 +++ /dev/null @@ -1,15 +0,0 @@ -{ - "kind": "Policy", - "apiVersion": "v1", - "predicates": [ - {"name": "MatchNodeSelector"}, - {"name": "PodFitsResources"}, - {"name": "PodFitsPorts"}, - {"name": "NoDiskConflict"}, - {"name": "Region", "argument": {"serviceAffinity" : {"labels" : ["region"]}}} - ],"priorities": [ - {"name": "LeastRequestedPriority", "weight": 1}, - {"name": "ServiceSpreadingPriority", "weight": 1}, - {"name": "Zone", "weight" : 2, "argument": {"serviceAntiAffinity" : {"label": "zone"}}} - ] -} diff --git a/roles/openshift_master/vars/main.yml b/roles/openshift_master/vars/main.yml index 6b5a73238..198f9235d 100644 --- a/roles/openshift_master/vars/main.yml +++ b/roles/openshift_master/vars/main.yml @@ -8,8 +8,11 @@ openshift_master_session_secrets_file: "{{ openshift_master_config_dir }}/sessio openshift_master_policy: "{{ openshift_master_config_dir }}/policy.json" openshift_version: "{{ openshift_pkg_version | default(openshift_image_tag) | default(openshift.common.image_tag) | default('') }}" -ha_svc_template_path: "{{ 'docker-cluster' if openshift.common.is_containerized | bool else 'native-cluster' }}" -ha_svc_svc_dir: "{{ '/etc/systemd/system' if openshift.common.is_containerized | bool else '/usr/lib/systemd/system' }}" +scheduler_config: + kind: Policy + apiVersion: v1 + predicates: "{{ openshift.master.scheduler_predicates }}" + priorities: "{{ openshift.master.scheduler_priorities }}" openshift_master_valid_grant_methods: - auto diff --git a/roles/openshift_master_cluster/tasks/main.yml b/roles/openshift_master_cluster/tasks/main.yml index 40705d357..0543872c9 100644 --- a/roles/openshift_master_cluster/tasks/main.yml +++ b/roles/openshift_master_cluster/tasks/main.yml @@ -3,6 +3,10 @@ msg: "Not possible on atomic hosts for now" when: openshift.common.is_containerized | bool +- fail: + msg: "Pacemaker HA is unsupported on OpenShift Enterprise 3.2 and Origin 1.2" + when: openshift.master.cluster_method == "pacemaker" and openshift.common.version_gte_3_2_or_1_2 | bool + - name: Test if cluster is already configured command: pcs status register: pcs_status diff --git a/roles/openshift_master_facts/meta/main.yml b/roles/openshift_master_facts/meta/main.yml new file mode 100644 index 000000000..9dbf719f8 --- /dev/null +++ b/roles/openshift_master_facts/meta/main.yml @@ -0,0 +1,15 @@ +--- +galaxy_info: + author: Jason DeTiberus + description: OpenShift Master Facts + company: Red Hat, Inc. + license: Apache License, Version 2.0 + min_ansible_version: 1.9 + platforms: + - name: EL + versions: + - 7 + categories: + - cloud +dependencies: +- role: openshift_facts diff --git a/roles/openshift_master_facts/tasks/main.yml b/roles/openshift_master_facts/tasks/main.yml new file mode 100644 index 000000000..2a3e38af4 --- /dev/null +++ b/roles/openshift_master_facts/tasks/main.yml @@ -0,0 +1,67 @@ +--- +- name: Set master facts + openshift_facts: + role: master + local_facts: + cluster_method: "{{ openshift_master_cluster_method | default(None) }}" + cluster_hostname: "{{ openshift_master_cluster_hostname | default(None) }}" + cluster_public_hostname: "{{ openshift_master_cluster_public_hostname | default(None) }}" + debug_level: "{{ openshift_master_debug_level | default(openshift.common.debug_level) }}" + api_port: "{{ openshift_master_api_port | default(None) }}" + api_url: "{{ openshift_master_api_url | default(None) }}" + api_use_ssl: "{{ openshift_master_api_use_ssl | default(None) }}" + public_api_url: "{{ openshift_master_public_api_url | default(None) }}" + console_path: "{{ openshift_master_console_path | default(None) }}" + console_port: "{{ openshift_master_console_port | default(None) }}" + console_url: "{{ openshift_master_console_url | default(None) }}" + console_use_ssl: "{{ openshift_master_console_use_ssl | default(None) }}" + public_console_url: "{{ openshift_master_public_console_url | default(None) }}" + logging_public_url: "{{ openshift_master_logging_public_url | default(None) }}" + metrics_public_url: "{{ openshift_master_metrics_public_url | default(None) }}" + logout_url: "{{ openshift_master_logout_url | default(None) }}" + extension_scripts: "{{ openshift_master_extension_scripts | default(None) }}" + extension_stylesheets: "{{ openshift_master_extension_stylesheets | default(None) }}" + extensions: "{{ openshift_master_extensions | default(None) }}" + oauth_template: "{{ openshift_master_oauth_template | default(None) }}" + etcd_hosts: "{{ openshift_master_etcd_hosts | default(None) }}" + etcd_port: "{{ openshift_master_etcd_port | default(None) }}" + etcd_use_ssl: "{{ openshift_master_etcd_use_ssl | default(None) }}" + etcd_urls: "{{ openshift_master_etcd_urls | default(None) }}" + embedded_etcd: "{{ openshift_master_embedded_etcd | default(None) }}" + embedded_kube: "{{ openshift_master_embedded_kube | default(None) }}" + embedded_dns: "{{ openshift_master_embedded_dns | default(None) }}" + dns_port: "{{ openshift_master_dns_port | default(None) }}" + bind_addr: "{{ openshift_master_bind_addr | default(None) }}" + pod_eviction_timeout: "{{ openshift_master_pod_eviction_timeout | default(None) }}" + portal_net: "{{ openshift_master_portal_net | default(None) }}" + session_max_seconds: "{{ openshift_master_session_max_seconds | default(None) }}" + session_name: "{{ openshift_master_session_name | default(None) }}" + session_secrets_file: "{{ openshift_master_session_secrets_file | default(None) }}" + session_auth_secrets: "{{ openshift_master_session_auth_secrets | default(None) }}" + session_encryption_secrets: "{{ openshift_master_session_encryption_secrets | default(None) }}" + access_token_max_seconds: "{{ openshift_master_access_token_max_seconds | default(None) }}" + auth_token_max_seconds: "{{ openshift_master_auth_token_max_seconds | default(None) }}" + identity_providers: "{{ openshift_master_identity_providers | default(None) }}" + registry_url: "{{ oreg_url | default(None) }}" + oauth_grant_method: "{{ openshift_master_oauth_grant_method | default(None) }}" + sdn_cluster_network_cidr: "{{ osm_cluster_network_cidr | default(None) }}" + sdn_host_subnet_length: "{{ osm_host_subnet_length | default(None) }}" + default_subdomain: "{{ openshift_master_default_subdomain | default(osm_default_subdomain) | default(None) }}" + custom_cors_origins: "{{ osm_custom_cors_origins | default(None) }}" + default_node_selector: "{{ osm_default_node_selector | default(None) }}" + project_request_message: "{{ osm_project_request_message | default(None) }}" + project_request_template: "{{ osm_project_request_template | default(None) }}" + mcs_allocator_range: "{{ osm_mcs_allocator_range | default(None) }}" + mcs_labels_per_project: "{{ osm_mcs_labels_per_project | default(None) }}" + uid_allocator_range: "{{ osm_uid_allocator_range | default(None) }}" + router_selector: "{{ openshift_router_selector | default(None) }}" + registry_selector: "{{ openshift_registry_selector | default(None) }}" + api_server_args: "{{ osm_api_server_args | default(None) }}" + controller_args: "{{ osm_controller_args | default(None) }}" + infra_nodes: "{{ openshift_infra_nodes | default(None) }}" + disabled_features: "{{ osm_disabled_features | default(None) }}" + master_count: "{{ openshift_master_count | default(None) }}" + controller_lease_ttl: "{{ osm_controller_lease_ttl | default(None) }}" + master_image: "{{ osm_image | default(None) }}" + scheduler_predicates: "{{ openshift_master_scheduler_predicates | default(None) }}" + scheduler_priorities: "{{ openshift_master_scheduler_priorities | default(None) }}" diff --git a/roles/openshift_node/meta/main.yml b/roles/openshift_node/meta/main.yml index 702012489..84ba9ac2e 100644 --- a/roles/openshift_node/meta/main.yml +++ b/roles/openshift_node/meta/main.yml @@ -12,5 +12,6 @@ galaxy_info: categories: - cloud dependencies: -- role: openshift_common - role: openshift_docker +- role: openshift_cloud_provider +- role: openshift_common diff --git a/roles/openshift_node/tasks/main.yml b/roles/openshift_node/tasks/main.yml index ca1e26459..80b3e710d 100644 --- a/roles/openshift_node/tasks/main.yml +++ b/roles/openshift_node/tasks/main.yml @@ -53,27 +53,8 @@ docker pull {{ openshift.node.ovs_image }}:{{ openshift_version }} when: openshift.common.is_containerized | bool and openshift.common.use_openshift_sdn | bool -- name: Install Node docker service file - template: - dest: "/etc/systemd/system/{{ openshift.common.service_type }}-node.service" - src: openshift.docker.node.service - register: install_node_result - when: openshift.common.is_containerized | bool - -- name: Create the openvswitch service env file - template: - src: openvswitch.sysconfig.j2 - dest: /etc/sysconfig/openvswitch - when: openshift.common.is_containerized | bool - register: install_ovs_sysconfig - -- name: Install OpenvSwitch docker service file - template: - dest: "/etc/systemd/system/openvswitch.service" - src: openvswitch.docker.service - when: openshift.common.is_containerized | bool and openshift.common.use_openshift_sdn | bool - notify: - - restart openvswitch +- name: Install the systemd units + include: systemd_units.yml - name: Reload systemd units command: systemctl daemon-reload @@ -100,19 +81,18 @@ notify: - restart node -- name: Configure Node settings +- name: Configure AWS Cloud Provider Settings lineinfile: dest: /etc/sysconfig/{{ openshift.common.service_type }}-node regexp: "{{ item.regex }}" line: "{{ item.line }}" create: true with_items: - - regex: '^OPTIONS=' - line: "OPTIONS=--loglevel={{ openshift.node.debug_level }}" - - regex: '^CONFIG_FILE=' - line: "CONFIG_FILE={{ openshift_node_config_file }}" - - regex: '^IMAGE_VERSION=' - line: "IMAGE_VERSION={{ openshift_version }}" + - regex: '^AWS_ACCESS_KEY_ID=' + line: "AWS_ACCESS_KEY_ID={{ openshift.cloudprovider.aws.access_key }}" + - regex: '^AWS_SECRET_ACCESS_KEY=' + line: "AWS_SECRET_ACCESS_KEY={{ openshift.cloudprovider.aws.secret_key }}" + when: "'cloudprovider' in openshift and 'aws' in openshift.cloudprovider and openshift.cloudprovider.kind == 'aws' and 'access_key' in openshift.cloudprovider.aws and 'secret_key' in openshift.cloudprovider.aws" notify: - restart node diff --git a/roles/openshift_node/tasks/systemd_units.yml b/roles/openshift_node/tasks/systemd_units.yml new file mode 100644 index 000000000..be4b4ed61 --- /dev/null +++ b/roles/openshift_node/tasks/systemd_units.yml @@ -0,0 +1,40 @@ +# This file is included both in the openshift_master role and in the upgrade +# playbooks. + +- name: Install Node docker service file + template: + dest: "/etc/systemd/system/{{ openshift.common.service_type }}-node.service" + src: openshift.docker.node.service + register: install_node_result + when: openshift.common.is_containerized | bool + +- name: Create the openvswitch service env file + template: + src: openvswitch.sysconfig.j2 + dest: /etc/sysconfig/openvswitch + when: openshift.common.is_containerized | bool + register: install_ovs_sysconfig + +- name: Install OpenvSwitch docker service file + template: + dest: "/etc/systemd/system/openvswitch.service" + src: openvswitch.docker.service + when: openshift.common.is_containerized | bool and openshift.common.use_openshift_sdn | bool + notify: + - restart openvswitch + +- name: Configure Node settings + lineinfile: + dest: /etc/sysconfig/{{ openshift.common.service_type }}-node + regexp: "{{ item.regex }}" + line: "{{ item.line }}" + create: true + with_items: + - regex: '^OPTIONS=' + line: "OPTIONS=--loglevel={{ openshift.node.debug_level }}" + - regex: '^CONFIG_FILE=' + line: "CONFIG_FILE={{ openshift_node_config_file }}" + - regex: '^IMAGE_VERSION=' + line: "IMAGE_VERSION={{ openshift_version }}" + notify: + - restart node diff --git a/roles/openshift_node/templates/openshift.docker.node.service b/roles/openshift_node/templates/openshift.docker.node.service index 65d2291bb..a8accca47 100644 --- a/roles/openshift_node/templates/openshift.docker.node.service +++ b/roles/openshift_node/templates/openshift.docker.node.service @@ -19,4 +19,4 @@ SyslogIdentifier={{ openshift.common.service_type }}-node Restart=always [Install] -WantedBy=multi-user.target +WantedBy=docker.service diff --git a/roles/openshift_node/templates/openvswitch.docker.service b/roles/openshift_node/templates/openvswitch.docker.service index 047f66fda..8052a3a39 100644 --- a/roles/openshift_node/templates/openvswitch.docker.service +++ b/roles/openshift_node/templates/openvswitch.docker.service @@ -13,4 +13,4 @@ SyslogIdentifier=openvswitch Restart=always [Install] -WantedBy=multi-user.target +WantedBy=docker.service diff --git a/roles/os_zabbix/vars/template_heartbeat.yml b/roles/os_zabbix/vars/template_heartbeat.yml index 8dbe0d0d6..ec953c79b 100644 --- a/roles/os_zabbix/vars/template_heartbeat.yml +++ b/roles/os_zabbix/vars/template_heartbeat.yml @@ -11,3 +11,8 @@ g_template_heartbeat: expression: '{Template Heartbeat:heartbeat.ping.nodata(20m)}=1' priority: avg url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_node_heartbeat.asciidoc' + + - name: 'Heartbeat.ping has failed (60 min) on {HOST.NAME}' + expression: '{Template Heartbeat:heartbeat.ping.nodata(60m)}=1' + priority: high + url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_node_heartbeat.asciidoc' diff --git a/roles/os_zabbix/vars/template_openshift_master.yml b/roles/os_zabbix/vars/template_openshift_master.yml index 705066b35..a38db9f65 100644 --- a/roles/os_zabbix/vars/template_openshift_master.yml +++ b/roles/os_zabbix/vars/template_openshift_master.yml @@ -93,6 +93,18 @@ g_template_openshift_master: applications: - Openshift Master + - key: openshift.master.pv.space.total + description: Shows the total space of pv + value_type: int + applications: + - Openshift Master + + - key: openshift.master.pv.space.available + description: Shows the available space of pv + value_type: int + applications: + - Openshift Master + - key: openshift.master.pv.total.count description: Total number of Persistent Volumes in the Openshift Cluster value_type: int @@ -279,6 +291,29 @@ g_template_openshift_master: applications: - Openshift Master Metrics + zdiscoveryrules: + - name: disc.pv + key: disc.pv + lifetime: 1 + description: "Dynamically register the Persistent Volumes" + + zitemprototypes: + - discoveryrule_key: disc.pv + name: "disc.pv.count.{#OSO_PV}" + key: "disc.pv.count[{#OSO_PV}]" + value_type: int + description: "Number of PV's of this size" + applications: + - Openshift Master + + - discoveryrule_key: disc.pv + name: "disc.pv.available.{#OSO_PV}" + key: "disc.pv.available[{#OSO_PV}]" + value_type: int + description: "Number of PV's of this size that are available" + applications: + - Openshift Master + ztriggers: - name: 'Openshift Master process not running on {HOST.NAME}' expression: '{Template Openshift Master:openshift.master.process.count.max(#3)}<1' |