From 15d730f3aec1f579dbd3cc5310264c68eb78e242 Mon Sep 17 00:00:00 2001
From: Kenny Woodson <kwoodson@redhat.com>
Date: Mon, 28 Mar 2016 17:44:48 -0400
Subject: Moving generation of ansible module side by side with module.

---
 roles/lib_openshift_api/build/ansible/obj.py       | 132 +++++
 roles/lib_openshift_api/build/ansible/secret.py    | 121 ++++
 roles/lib_openshift_api/build/generate.py          |  53 ++
 roles/lib_openshift_api/build/src/base.py          | 242 ++++++++
 roles/lib_openshift_api/build/src/obj.py           |  78 +++
 roles/lib_openshift_api/build/src/secret.py        |  68 +++
 roles/lib_openshift_api/build/test/README          |   5 +
 .../build/test/deploymentconfig.yml                | 120 ++++
 .../lib_openshift_api/build/test/files/config.yml  |   1 +
 .../lib_openshift_api/build/test/files/dc-mod.yml  | 124 +++++
 roles/lib_openshift_api/build/test/files/dc.yml    | 121 ++++
 .../build/test/files/passwords.yml                 |   4 +
 .../build/test/files/router-mod.json               |  30 +
 .../lib_openshift_api/build/test/files/router.json |  29 +
 roles/lib_openshift_api/build/test/roles           |   1 +
 roles/lib_openshift_api/build/test/secrets.yml     |  81 +++
 roles/lib_openshift_api/build/test/services.yml    | 133 +++++
 .../library/oc_deploymentconfig.py                 | 377 -------------
 roles/lib_openshift_api/library/oc_obj.py          | 616 +++++++++++++++++++++
 roles/lib_openshift_api/library/oc_secret.py       | 231 +++++++-
 roles/lib_openshift_api/library/oc_service.py      | 378 -------------
 21 files changed, 2167 insertions(+), 778 deletions(-)
 create mode 100644 roles/lib_openshift_api/build/ansible/obj.py
 create mode 100644 roles/lib_openshift_api/build/ansible/secret.py
 create mode 100755 roles/lib_openshift_api/build/generate.py
 create mode 100644 roles/lib_openshift_api/build/src/base.py
 create mode 100644 roles/lib_openshift_api/build/src/obj.py
 create mode 100644 roles/lib_openshift_api/build/src/secret.py
 create mode 100644 roles/lib_openshift_api/build/test/README
 create mode 100755 roles/lib_openshift_api/build/test/deploymentconfig.yml
 create mode 100644 roles/lib_openshift_api/build/test/files/config.yml
 create mode 100644 roles/lib_openshift_api/build/test/files/dc-mod.yml
 create mode 100644 roles/lib_openshift_api/build/test/files/dc.yml
 create mode 100644 roles/lib_openshift_api/build/test/files/passwords.yml
 create mode 100644 roles/lib_openshift_api/build/test/files/router-mod.json
 create mode 100644 roles/lib_openshift_api/build/test/files/router.json
 create mode 120000 roles/lib_openshift_api/build/test/roles
 create mode 100755 roles/lib_openshift_api/build/test/secrets.yml
 create mode 100755 roles/lib_openshift_api/build/test/services.yml
 delete mode 100644 roles/lib_openshift_api/library/oc_deploymentconfig.py
 create mode 100644 roles/lib_openshift_api/library/oc_obj.py
 delete mode 100644 roles/lib_openshift_api/library/oc_service.py

(limited to 'roles/lib_openshift_api')

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/build/src/base.py b/roles/lib_openshift_api/build/src/base.py
new file mode 100644
index 000000000..31c102e5d
--- /dev/null
+++ b/roles/lib_openshift_api/build/src/base.py
@@ -0,0 +1,242 @@
+# pylint: skip-file
+'''
+   OpenShiftCLI class that wraps the oc commands in a subprocess
+'''
+
+import atexit
+import json
+import os
+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,
+                 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])
+        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 = ['-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)
+
+        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_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.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/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_deploymentconfig.py
deleted file mode 100644
index fbdaa8e9c..000000000
--- a/roles/lib_openshift_api/library/oc_deploymentconfig.py
+++ /dev/null
@@ -1,377 +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
-
-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 replace(self, fname, force=False):
-        '''return all pods '''
-        cmd = ['replace', '-f', fname]
-        if force:
-            cmd = ['replace', '--force', '-f', fname]
-        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)
-
-        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_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.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.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, 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 DeploymentConfig(OpenShiftCLI):
-    ''' Class to wrap the oc command line tools
-    '''
-    def __init__(self,
-                 namespace,
-                 dname=None,
-                 kubeconfig='/etc/origin/master/admin.kubeconfig',
-                 verbose=False):
-        ''' Constructor for OpenshiftOC '''
-        super(DeploymentConfig, self).__init__(namespace, kubeconfig)
-        self.namespace = namespace
-        self.name = dname
-        self.kubeconfig = kubeconfig
-        self.verbose = verbose
-
-    def get_dc(self):
-        '''return a deploymentconfig by name '''
-        return self.get('dc', self.name)
-
-    def delete_dc(self):
-        '''return all pods '''
-        return self.delete('dc', self.name)
-
-    def new_dc(self, dfile):
-        '''Create a deploymentconfig '''
-        return self.create(dfile)
-
-    def update_dc(self, dfile, 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)
-
-
-# pylint: disable=too-many-branches
-def main():
-    '''
-    ansible oc module for deploymentconfig
-    '''
-
-    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'),
-            deploymentconfig_file=dict(default=None, type='str'),
-            input_type=dict(default='yaml', choices=['yaml', 'json'], type='str'),
-            delete_after=dict(default=False, type='bool'),
-            content=dict(default=None, type='dict'),
-            force=dict(default=False, type='bool'),
-        ),
-        mutually_exclusive=[["contents", "deploymentconfig_file"]],
-
-        supports_check_mode=True,
-    )
-    occmd = DeploymentConfig(module.params['namespace'],
-                             dname=module.params['name'],
-                             kubeconfig=module.params['kubeconfig'],
-                             verbose=module.params['debug'])
-
-    state = module.params['state']
-
-    api_rval = occmd.get_dc()
-
-    #####
-    # 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_dc()
-        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
-        ########
-        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.new_dc(dfile)
-
-            # 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)
-
-            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]):
-
-            # Remove files
-            if module.params['deploymentconfig_file'] and module.params['delete_after']:
-                Utils.cleanup([dfile])
-
-            module.exit_json(changed=False, results=api_rval['results'], 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'])
-
-        # 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)
-
-
-        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..2e07e5bb3
--- /dev/null
+++ b/roles/lib_openshift_api/library/oc_obj.py
@@ -0,0 +1,616 @@
+#!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
+
+# 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])
+        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 = ['-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)
+
+        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_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.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 '''
+
+    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,
+                 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',
+                               '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 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_openshift_api/library/oc_service.py b/roles/lib_openshift_api/library/oc_service.py
deleted file mode 100644
index e7bd2514e..000000000
--- a/roles/lib_openshift_api/library/oc_service.py
+++ /dev/null
@@ -1,378 +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
-
-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 replace(self, fname, force=False):
-        '''return all pods '''
-        cmd = ['replace', '-f', fname]
-        if force:
-            cmd = ['replace', '--force', '-f', fname]
-        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)
-
-        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_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.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.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, 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 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()
-- 
cgit v1.2.3