diff options
| author | Kenny Woodson <kwoodson@redhat.com> | 2016-03-28 17:44:48 -0400 | 
|---|---|---|
| committer | Kenny Woodson <kwoodson@redhat.com> | 2016-03-30 12:19:31 -0400 | 
| commit | 15d730f3aec1f579dbd3cc5310264c68eb78e242 (patch) | |
| tree | b32f0efa16101b1f47d70514d7a318c46fe3d76b | |
| parent | e1f6415cbcc4145402011fb4183a5dead2f22124 (diff) | |
| download | openshift-15d730f3aec1f579dbd3cc5310264c68eb78e242.tar.gz openshift-15d730f3aec1f579dbd3cc5310264c68eb78e242.tar.bz2 openshift-15d730f3aec1f579dbd3cc5310264c68eb78e242.tar.xz openshift-15d730f3aec1f579dbd3cc5310264c68eb78e242.zip | |
Moving generation of ansible module side by side with module.
28 files changed, 1969 insertions, 410 deletions
| 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..0796d807e --- /dev/null +++ b/roles/lib_openshift_api/build/ansible/obj.py @@ -0,0 +1,132 @@ +# 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', +                               '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..ab70dd7f3 --- /dev/null +++ b/roles/lib_openshift_api/build/generate.py @@ -0,0 +1,53 @@ +#!/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" + + + +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', +                         ], +        } + + +def main(): +    ''' combine the necessary files to create the ansible module ''' +    openshift_ansible = ('../library/') +    for fname, parts in FILES.items(): +        with open(os.path.join(openshift_ansible, fname), 'w') as afd: +            afd.seek(0) +            afd.write(GEN_STR) +            for fpart in parts: +                with open(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/library/oc_service.py b/roles/lib_openshift_api/build/src/base.py index e7bd2514e..31c102e5d 100644 --- a/roles/lib_openshift_api/library/oc_service.py +++ b/roles/lib_openshift_api/build/src/base.py @@ -1,7 +1,8 @@ -#!/usr/bin/env python +# pylint: skip-file  ''' -  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 @@ -9,6 +10,7 @@ import shutil  import subprocess  import yaml +# pylint: disable=too-few-public-methods  class OpenShiftCLI(object):      ''' Class to wrap the oc command line tools '''      def __init__(self, @@ -20,22 +22,39 @@ class OpenShiftCLI(object):          self.verbose = verbose          self.kubeconfig = kubeconfig -    def replace(self, fname, force=False): +    # 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]) +        for key, value in content.items(): +            yed.put(key, value) + +        atexit.register(Utils.cleanup, [fname]) + +        return self._replace(fname, force) + +    def _replace(self, fname, force=False):          '''return all pods ''' -        cmd = ['replace', '-f', fname] +        cmd = ['-n', self.namespace, 'replace', '-f', fname]          if force: -            cmd = ['replace', '--force', '-f', fname] +            cmd.append('--force')          return self.oc_cmd(cmd) -    def create(self, fname): +    def _create(self, fname):          '''return all pods '''          return self.oc_cmd(['create', '-f', fname, '-n', self.namespace]) -    def delete(self, resource, rname): +    def _delete(self, resource, rname):          '''return all pods '''          return self.oc_cmd(['delete', resource, rname, '-n', self.namespace]) -    def get(self, resource, rname=None): +    def _get(self, resource, rname=None):          '''return a secret by name '''          cmd = ['get', resource, '-o', 'json', '-n', self.namespace]          if rname: @@ -96,7 +115,7 @@ class Utils(object):          path = os.path.join('/tmp', rname)          with open(path, 'w') as fds:              if ftype == 'yaml': -                fds.write(yaml.dump(data, default_flow_style=False)) +                fds.write(yaml.safe_dump(data, default_flow_style=False))              elif ftype == 'json':                  fds.write(json.dumps(data)) @@ -173,7 +192,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: @@ -221,158 +240,3 @@ class Utils(object):                      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, self).__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.create(sfile) - -    def get_services(self): -        '''return a secret by name ''' -        return self.get('services', self.name) - -    def delete_service(self): -        '''return all pods ''' -        return self.delete('service', self.name) - -    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. -        ''' -        return self.replace(sfile, force=force) - - -# 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'), -            input_type=dict(default='yaml', -                            choices=['json', 'yaml'], -                            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['input_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_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/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..7992c90dd --- /dev/null +++ b/roles/lib_openshift_api/build/test/files/dc.yml @@ -0,0 +1,121 @@ +apiVersion: v1 +kind: DeploymentConfig +metadata: +  creationTimestamp: 2016-03-18T19:47:45Z +  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: 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: 1 + 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_deploymentconfig.py b/roles/lib_openshift_api/library/oc_obj.py index fbdaa8e9c..2e07e5bb3 100644 --- a/roles/lib_openshift_api/library/oc_deploymentconfig.py +++ b/roles/lib_openshift_api/library/oc_obj.py @@ -1,7 +1,15 @@ -#!/usr/bin/env python +#!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 @@ -9,6 +17,7 @@ import shutil  import subprocess  import yaml +# pylint: disable=too-few-public-methods  class OpenShiftCLI(object):      ''' Class to wrap the oc command line tools '''      def __init__(self, @@ -20,22 +29,39 @@ class OpenShiftCLI(object):          self.verbose = verbose          self.kubeconfig = kubeconfig -    def replace(self, fname, force=False): +    # 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]) +        for key, value in content.items(): +            yed.put(key, value) + +        atexit.register(Utils.cleanup, [fname]) + +        return self._replace(fname, force) + +    def _replace(self, fname, force=False):          '''return all pods ''' -        cmd = ['replace', '-f', fname] +        cmd = ['-n', self.namespace, 'replace', '-f', fname]          if force: -            cmd = ['replace', '--force', '-f', fname] +            cmd.append('--force')          return self.oc_cmd(cmd) -    def create(self, fname): +    def _create(self, fname):          '''return all pods '''          return self.oc_cmd(['create', '-f', fname, '-n', self.namespace]) -    def delete(self, resource, rname): +    def _delete(self, resource, rname):          '''return all pods '''          return self.oc_cmd(['delete', resource, rname, '-n', self.namespace]) -    def get(self, resource, rname=None): +    def _get(self, resource, rname=None):          '''return a secret by name '''          cmd = ['get', resource, '-o', 'json', '-n', self.namespace]          if rname: @@ -96,7 +122,7 @@ class Utils(object):          path = os.path.join('/tmp', rname)          with open(path, 'w') as fds:              if ftype == 'yaml': -                fds.write(yaml.dump(data, default_flow_style=False)) +                fds.write(yaml.safe_dump(data, default_flow_style=False))              elif ftype == 'json':                  fds.write(json.dumps(data)) @@ -173,7 +199,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: @@ -222,45 +248,246 @@ class Utils(object):          return True -class DeploymentConfig(OpenShiftCLI): -    ''' Class to wrap the oc command line tools -    ''' +class YeditException(Exception): +    ''' Exception class for Yedit ''' +    pass + +class Yedit(object): +    ''' Class to modify yaml files ''' + +    def __init__(self, filename=None, content=None): +        self.content = content +        self.filename = filename +        self.__yaml_dict = content +        if self.filename and not self.content: +            self.get() +        elif self.filename and self.content: +            self.write() + +    @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, 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] + +    @staticmethod +    def add_entry(data, keys, item): +        ''' Add an item to 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 not in data: +                data[key] = {} + +            if not isinstance(data, dict): +                raise YeditException('Invalid add_entry called on a [%s] of type [%s].' % (data, type(data))) +            else: +                Yedit.add_entry(data[key], rest, item) + +        else: +            data[keys] = item + + +    @staticmethod +    def get_entry(data, keys): +        ''' Get an item from a dictionary with key notation a.b.c +            d = {'a': {'b': 'c'}}} +            keys = 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))) + +            else: +                return Yedit.get_entry(data[key], rest) + +        else: +            return data.get(keys, None) + + +    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 get(self): +        ''' return yaml file ''' +        contents = self.read() + +        if not contents: +            return None + +        # check if it is yaml +        try: +            self.yaml_dict = yaml.load(contents) +        except yaml.YAMLError as _: +            # Error loading yaml +            return None + +        return self.yaml_dict + +    def delete(self, key): +        ''' put key, value into a yaml file ''' +        try: +            entry = Yedit.get_entry(self.yaml_dict, key) +        except KeyError as _: +            entry = None +        if not entry: +            return  (False, self.yaml_dict) + +        Yedit.remove_entry(self.yaml_dict, key) +        self.write() +        return (True, self.get()) + +    def put(self, key, value): +        ''' put key, value into a yaml file ''' +        try: +            entry = Yedit.get_entry(self.yaml_dict, key) +        except KeyError as _: +            entry = None + +        if entry == value: +            return (False, self.yaml_dict) + +        Yedit.add_entry(self.yaml_dict, key, value) +        self.write() +        return (True, self.get()) + +    def create(self, key, value): +        ''' create the file ''' +        if not self.exists(): +            self.yaml_dict = {key: value} +            self.write() +            return (True, self.get()) + +        return (False, self.get()) + +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, -                 dname=None, +                 rname=None,                   kubeconfig='/etc/origin/master/admin.kubeconfig',                   verbose=False):          ''' Constructor for OpenshiftOC ''' -        super(DeploymentConfig, self).__init__(namespace, kubeconfig) +        super(OCObject, self).__init__(namespace, kubeconfig) +        self.kind = kind          self.namespace = namespace -        self.name = dname +        self.name = rname          self.kubeconfig = kubeconfig          self.verbose = verbose -    def get_dc(self): +    def get(self):          '''return a deploymentconfig by name ''' -        return self.get('dc', self.name) +        return self._get(self.kind, rname=self.name) -    def delete_dc(self): +    def delete(self):          '''return all pods ''' -        return self.delete('dc', self.name) +        return self._delete(self.kind, self.name) -    def new_dc(self, dfile): +    def create(self, files=None, content=None):          '''Create a deploymentconfig ''' -        return self.create(dfile) +        if files: +            return self._create(files[0]) + +        return self._create(Utils.create_files_from_contents(content)) -    def update_dc(self, dfile, force=False): + +    # 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.          ''' -        return self.replace(dfile, force) +        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 deploymentconfig +    ansible oc module for services      '''      module = AnsibleModule( @@ -271,24 +498,30 @@ def main():              debug=dict(default=False, type='bool'),              namespace=dict(default='default', type='str'),              name=dict(default=None, type='str'), -            deploymentconfig_file=dict(default=None, type='str'), -            input_type=dict(default='yaml', choices=['yaml', 'json'], type='str'), +            files=dict(default=None, type='list'), +            kind=dict(required=True, +                      type='str', +                      choices=['dc', 'deploymentconfig', +                               'svc', 'service', +                               'secret', +                              ]),              delete_after=dict(default=False, type='bool'),              content=dict(default=None, type='dict'),              force=dict(default=False, type='bool'),          ), -        mutually_exclusive=[["contents", "deploymentconfig_file"]], +        mutually_exclusive=[["content", "files"]],          supports_check_mode=True,      ) -    occmd = DeploymentConfig(module.params['namespace'], -                             dname=module.params['name'], -                             kubeconfig=module.params['kubeconfig'], -                             verbose=module.params['debug']) +    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 = occmd.get_dc() +    api_rval = ocobj.get()      #####      # Get @@ -308,18 +541,10 @@ def main():          if module.check_mode:              module.exit_json(change=False, msg='Would have performed a delete.') -        api_rval = occmd.delete_dc() +        api_rval = ocobj.delete()          module.exit_json(changed=True, results=api_rval, state="absent") -      if state == 'present': -        if module.params['deploymentconfig_file']: -            dfile = module.params['deploymentconfig_file'] -        elif module.params['content']: -            dfile = Utils.create_file('dc', module.params['content']) -        else: -            module.fail_json(msg="Please specify content or deploymentconfig file.") -          ########          # Create          ######## @@ -328,40 +553,54 @@ def main():              if module.check_mode:                  module.exit_json(change=False, msg='Would have performed a create.') -            api_rval = occmd.new_dc(dfile) +            # Create it here +            api_rval = ocobj.create(module.params['files'], module.params['content']) +            if api_rval['returncode'] != 0: +                module.fail_json(msg=api_rval) -            # Remove files -            if module.params['deploymentconfig_file'] and module.params['delete_after']: -                Utils.cleanup([dfile]) +            # 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 Utils.check_def_equal(Utils.get_resource_file(dfile), api_rval['results'][0]): +        # 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) -            # Remove files -            if module.params['deploymentconfig_file'] and module.params['delete_after']: -                Utils.cleanup([dfile]) +        # 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'], state="present") +            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_dc(dfile, force=module.params['force']) +        api_rval = ocobj.update(module.params['files'], +                                module.params['content'], +                                module.params['force']) -        # Remove files -        if module.params['deploymentconfig_file'] and module.params['delete_after']: -            Utils.cleanup([dfile])          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") diff --git a/roles/lib_openshift_api/library/oc_secret.py b/roles/lib_openshift_api/library/oc_secret.py index 96a0f1db1..985ff8bfa 100644 --- a/roles/lib_openshift_api/library/oc_secret.py +++ b/roles/lib_openshift_api/library/oc_secret.py @@ -1,7 +1,15 @@ -#!/usr/bin/env python +#!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 @@ -9,6 +17,7 @@ import shutil  import subprocess  import yaml +# pylint: disable=too-few-public-methods  class OpenShiftCLI(object):      ''' Class to wrap the oc command line tools '''      def __init__(self, @@ -20,22 +29,39 @@ class OpenShiftCLI(object):          self.verbose = verbose          self.kubeconfig = kubeconfig -    def replace(self, fname, force=False): +    # 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]) +        for key, value in content.items(): +            yed.put(key, value) + +        atexit.register(Utils.cleanup, [fname]) + +        return self._replace(fname, force) + +    def _replace(self, fname, force=False):          '''return all pods ''' -        cmd = ['replace', '-f', fname] +        cmd = ['-n', self.namespace, 'replace', '-f', fname]          if force: -            cmd = ['replace', '--force', '-f', fname] +            cmd.append('--force')          return self.oc_cmd(cmd) -    def create(self, fname): +    def _create(self, fname):          '''return all pods '''          return self.oc_cmd(['create', '-f', fname, '-n', self.namespace]) -    def delete(self, resource, rname): +    def _delete(self, resource, rname):          '''return all pods '''          return self.oc_cmd(['delete', resource, rname, '-n', self.namespace]) -    def get(self, resource, rname=None): +    def _get(self, resource, rname=None):          '''return a secret by name '''          cmd = ['get', resource, '-o', 'json', '-n', self.namespace]          if rname: @@ -96,7 +122,7 @@ class Utils(object):          path = os.path.join('/tmp', rname)          with open(path, 'w') as fds:              if ftype == 'yaml': -                fds.write(yaml.dump(data, default_flow_style=False)) +                fds.write(yaml.safe_dump(data, default_flow_style=False))              elif ftype == 'json':                  fds.write(json.dumps(data)) @@ -173,7 +199,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: @@ -222,6 +248,165 @@ class Utils(object):          return True +class YeditException(Exception): +    ''' Exception class for Yedit ''' +    pass + +class Yedit(object): +    ''' Class to modify yaml files ''' + +    def __init__(self, filename=None, content=None): +        self.content = content +        self.filename = filename +        self.__yaml_dict = content +        if self.filename and not self.content: +            self.get() +        elif self.filename and self.content: +            self.write() + +    @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, 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] + +    @staticmethod +    def add_entry(data, keys, item): +        ''' Add an item to 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 not in data: +                data[key] = {} + +            if not isinstance(data, dict): +                raise YeditException('Invalid add_entry called on a [%s] of type [%s].' % (data, type(data))) +            else: +                Yedit.add_entry(data[key], rest, item) + +        else: +            data[keys] = item + + +    @staticmethod +    def get_entry(data, keys): +        ''' Get an item from a dictionary with key notation a.b.c +            d = {'a': {'b': 'c'}}} +            keys = 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))) + +            else: +                return Yedit.get_entry(data[key], rest) + +        else: +            return data.get(keys, None) + + +    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 get(self): +        ''' return yaml file ''' +        contents = self.read() + +        if not contents: +            return None + +        # check if it is yaml +        try: +            self.yaml_dict = yaml.load(contents) +        except yaml.YAMLError as _: +            # Error loading yaml +            return None + +        return self.yaml_dict + +    def delete(self, key): +        ''' put key, value into a yaml file ''' +        try: +            entry = Yedit.get_entry(self.yaml_dict, key) +        except KeyError as _: +            entry = None +        if not entry: +            return  (False, self.yaml_dict) + +        Yedit.remove_entry(self.yaml_dict, key) +        self.write() +        return (True, self.get()) + +    def put(self, key, value): +        ''' put key, value into a yaml file ''' +        try: +            entry = Yedit.get_entry(self.yaml_dict, key) +        except KeyError as _: +            entry = None + +        if entry == value: +            return (False, self.yaml_dict) + +        Yedit.add_entry(self.yaml_dict, key, value) +        self.write() +        return (True, self.get()) + +    def create(self, key, value): +        ''' create the file ''' +        if not self.exists(): +            self.yaml_dict = {key: value} +            self.write() +            return (True, self.get()) + +        return (False, self.get()) +  class Secret(OpenShiftCLI):      ''' Class to wrap the oc command line tools      ''' @@ -237,16 +422,16 @@ class Secret(OpenShiftCLI):          self.kubeconfig = kubeconfig          self.verbose = verbose -    def get_secrets(self): +    def get(self):          '''return a secret by name ''' -        return self.get('secrets', self.name) +        return self._get('secrets', self.name) -    def delete_secret(self): -        '''return all pods ''' -        return self.delete('secrets', self.name) +    def delete(self): +        '''delete a secret by name''' +        return self._delete('secrets', self.name) -    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) @@ -256,7 +441,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. @@ -272,7 +457,7 @@ class Secret(OpenShiftCLI):          atexit.register(Utils.cleanup, [sfile_path]) -        return self.replace(sfile_path, force=force) +        return self._replace(sfile_path, force=force)      def prep_secret(self, files=None, contents=None):          ''' return what the secret would look like if created @@ -319,7 +504,7 @@ def main():      state = module.params['state'] -    api_rval = occmd.get_secrets() +    api_rval = occmd.get()      #####      # Get @@ -339,7 +524,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") @@ -359,7 +544,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']: @@ -386,7 +571,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_yaml_editor/build/ansible/yedit.py b/roles/lib_yaml_editor/build/ansible/yedit.py new file mode 100644 index 000000000..bf868fb71 --- /dev/null +++ b/roles/lib_yaml_editor/build/ansible/yedit.py @@ -0,0 +1,66 @@ +#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.get() +    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) +            module.exit_json(changed=rval[0], results=rval[1], state="present") + +        if not module.params['content']: +            rval = yamlfile.create(module.params['key'], value) +        else: +            yamlfile.write() +            rval = yamlfile.get() +        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..6521ff2c1 --- /dev/null +++ b/roles/lib_yaml_editor/build/generate.py @@ -0,0 +1,42 @@ +#!/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" + +FILES = {'yedit.py':  ['src/base.py', 'src/yedit.py', 'ansible/yedit.py'], +        } + + +def main(): +    ''' combine the necessary files to create the ansible module ''' +    openshift_ansible = ('../library/') +    for fname, parts in FILES.items(): +        with open(os.path.join(openshift_ansible, fname), 'w') as afd: +            afd.seek(0) +            afd.write(GEN_STR) +            for fpart in parts: +                with open(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..ad8b041cf --- /dev/null +++ b/roles/lib_yaml_editor/build/src/base.py @@ -0,0 +1,9 @@ +# pylint: skip-file + +''' +module for managing yaml files +''' + +import os +import yaml + 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..4f6a91d8b --- /dev/null +++ b/roles/lib_yaml_editor/build/src/yedit.py @@ -0,0 +1,160 @@ +# pylint: skip-file + +class YeditException(Exception): +    ''' Exception class for Yedit ''' +    pass + +class Yedit(object): +    ''' Class to modify yaml files ''' + +    def __init__(self, filename=None, content=None): +        self.content = content +        self.filename = filename +        self.__yaml_dict = content +        if self.filename and not self.content: +            self.get() +        elif self.filename and self.content: +            self.write() + +    @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, 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] + +    @staticmethod +    def add_entry(data, keys, item): +        ''' Add an item to 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 not in data: +                data[key] = {} + +            if not isinstance(data, dict): +                raise YeditException('Invalid add_entry called on a [%s] of type [%s].' % (data, type(data))) +            else: +                Yedit.add_entry(data[key], rest, item) + +        else: +            data[keys] = item + + +    @staticmethod +    def get_entry(data, keys): +        ''' Get an item from a dictionary with key notation a.b.c +            d = {'a': {'b': 'c'}}} +            keys = 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))) + +            else: +                return Yedit.get_entry(data[key], rest) + +        else: +            return data.get(keys, None) + + +    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 get(self): +        ''' return yaml file ''' +        contents = self.read() + +        if not contents: +            return None + +        # check if it is yaml +        try: +            self.yaml_dict = yaml.load(contents) +        except yaml.YAMLError as _: +            # Error loading yaml +            return None + +        return self.yaml_dict + +    def delete(self, key): +        ''' put key, value into a yaml file ''' +        try: +            entry = Yedit.get_entry(self.yaml_dict, key) +        except KeyError as _: +            entry = None +        if not entry: +            return  (False, self.yaml_dict) + +        Yedit.remove_entry(self.yaml_dict, key) +        self.write() +        return (True, self.get()) + +    def put(self, key, value): +        ''' put key, value into a yaml file ''' +        try: +            entry = Yedit.get_entry(self.yaml_dict, key) +        except KeyError as _: +            entry = None + +        if entry == value: +            return (False, self.yaml_dict) + +        Yedit.add_entry(self.yaml_dict, key, value) +        self.write() +        return (True, self.get()) + +    def create(self, key, value): +        ''' create the file ''' +        if not self.exists(): +            self.yaml_dict = {key: value} +            self.write() +            return (True, self.get()) + +        return (False, self.get()) 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..2a7a89ce2 --- /dev/null +++ b/roles/lib_yaml_editor/build/test/foo.yml @@ -0,0 +1 @@ +foo: barplus 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 9b565d0c7..356cf07a5 100644 --- a/roles/lib_yaml_editor/library/yedit.py +++ b/roles/lib_yaml_editor/library/yedit.py @@ -1,11 +1,20 @@ -#!/usr/bin/env python +#!usr/bin/env python +#     ___ ___ _  _ ___ ___    _ _____ ___ ___ +#    / __| __| \| | __| _ \  /_\_   _| __|   \ +#   | (_ | _|| .` | _||   / / _ \| | | _|| |) | +#    \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____ +#   |   \ / _ \  | \| |/ _ \_   _| | __|   \_ _|_   _| +#   | |) | (_) | | .` | (_) || |   | _|| |) | |  | | +#   |___/ \___/  |_|\_|\___/ |_|   |___|___/___| |_| +  ''' -module for openshift cloud secrets +module for managing yaml files  '''  import os  import yaml +  class YeditException(Exception):      ''' Exception class for Yedit '''      pass @@ -13,10 +22,14 @@ class YeditException(Exception):  class Yedit(object):      ''' Class to modify yaml files ''' -    def __init__(self, filename): +    def __init__(self, filename=None, content=None): +        self.content = content          self.filename = filename -        self.__yaml_dict = None -        self.get() +        self.__yaml_dict = content +        if self.filename and not self.content: +            self.get() +        elif self.filename and self.content: +            self.write()      @property      def yaml_dict(self): @@ -84,8 +97,11 @@ class Yedit(object):      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,6 +121,7 @@ class Yedit(object):              return True          return False +      def get(self):          ''' return yaml file '''          contents = self.read() @@ -157,7 +174,6 @@ class Yedit(object):          return (False, self.get()) -  def main():      '''      ansible oc module for secrets @@ -168,19 +184,19 @@ 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()      if not rval and state != 'present': @@ -205,7 +221,11 @@ def main():              rval = yamlfile.put(module.params['key'], value)              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: +            yamlfile.write() +            rval = yamlfile.get()          module.exit_json(changed=rval[0], results=rval[1], state="present")      module.exit_json(failed=True, diff --git a/test/env-setup b/test/env-setup index 156593571..b05df0f9e 100644 --- a/test/env-setup +++ b/test/env-setup @@ -2,7 +2,7 @@  CUR_PATH=$(pwd) -PREFIX_PYTHONPATH=$CUR_PATH/inventory/ +PREFIX_PYTHONPATH=$CUR_PATH/inventory/:$CUR_PATH/roles/lib_yaml_editor/build/src  export PYTHONPATH=$PREFIX_PYTHONPATH:$PYTHONPATH diff --git a/test/units/yedit_test.py b/test/units/yedit_test.py index cdd2d2b59..e701cfa7c 100755 --- a/test/units/yedit_test.py +++ b/test/units/yedit_test.py @@ -5,164 +5,12 @@  import unittest  import os -import yaml - -class YeditException(Exception): -    ''' Exception class for Yedit ''' -    pass - -class Yedit(object): -    ''' Class to modify yaml files ''' - -    def __init__(self, filename): -        self.filename = filename -        self.__yaml_dict = None -        self.get() - -    @property -    def yaml_dict(self): -        ''' get property 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, 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] - -    @staticmethod -    def add_entry(data, keys, item): -        ''' Add an item to 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 not in data: -                data[key] = {} - -            if not isinstance(data, dict): -                raise YeditException('Invalid add_entry called on data [%s].' % data) -            else: -                Yedit.add_entry(data[key], rest, item) - -        else: -            data[keys] = item - - -    @staticmethod -    def get_entry(data, keys): -        ''' Get an item from a dictionary with key notation a.b.c -            d = {'a': {'b': 'c'}}} -            keys = 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))) - -            else: -                return Yedit.get_entry(data[key], rest) - -        else: -            return data.get(keys, None) - - -    def write(self): -        ''' write to file ''' -        with open(self.filename, 'w') as yfd: -            yfd.write(yaml.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 get(self): -        ''' return yaml file ''' -        contents = self.read() - -        if not contents: -            return None - -        # check if it is yaml -        try: -            self.yaml_dict = yaml.load(contents) -        except yaml.YAMLError as _: -            # Error loading yaml -            return None - -        return self.yaml_dict - -    def delete(self, key): -        ''' put key, value into a yaml file ''' -        try: -            entry = Yedit.get_entry(self.yaml_dict, key) -        except KeyError as _: -            entry = None -        if not entry: -            return  (False, self.yaml_dict) - -        Yedit.remove_entry(self.yaml_dict, key) -        self.write() -        return (True, self.get()) - -    def put(self, key, value): -        ''' put key, value into a yaml file ''' -        try: -            entry = Yedit.get_entry(self.yaml_dict, key) -        except KeyError as _: -            entry = None - -        if entry == value: -            return (False, self.yaml_dict) - -        Yedit.add_entry(self.yaml_dict, key, value) -        self.write() -        return (True, self.get()) - -    def create(self, key, value): -        ''' create the file ''' -        if not self.exists(): -            self.yaml_dict = {key: value} -            self.write() -            return (True, self.get()) - -        return (False, self.get()) - -  # Removing invalid variable names for tests so that I can  # keep them brief -# pylint: disable=invalid-name +# pylint: disable=invalid-name,no-name-in-module +from yedit import Yedit +  class YeditTest(unittest.TestCase):      '''       Test class for yedit @@ -226,6 +74,15 @@ class YeditTest(unittest.TestCase):          yed.write()          yed.get()          self.assertTrue(yed.yaml_dict.has_key('foo')) +        self.assertTrue(yed.yaml_dict['foo'] == 'bar') + +    def test_create_content(self): +        '''Testing a create with content ''' +        content = {"foo": "bar"} +        yed = Yedit("yedit_test.yml", content) +        yed.write() +        yed.get() +        self.assertTrue(yed.yaml_dict.has_key('foo'))          self.assertTrue(yed.yaml_dict['foo'], 'bar')      def tearDown(self): | 
