123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- #!/usr/bin/env python
- '''
- module for openshift cloud secrets
- '''
- # Examples:
- #
- # # to initiate and use /etc/origin/master/admin.kubeconfig file for auth
- # - name: list secrets
- # oc_secrets:
- # state: list
- # namespace: default
- #
- # # To get a specific secret named 'mysecret'
- # - name: list secrets
- # oc_secrets:
- # state: list
- # namespace: default
- # name: mysecret
- #
- # # To create a secret:
- # # This module expects the user to place the files on the remote server and pass them in.
- # - name: create a secret from file
- # oc_secrets:
- # state: present
- # namespace: default
- # name: mysecret
- # files:
- # - /tmp/config.yml
- # - /tmp/passwords.yml
- # delete_after: False
- # # To create a secret:
- # # This module expects the user to place the files on the remote server and pass them in.
- # - name: create a secret from content
- # oc_secrets:
- # state: present
- # namespace: default
- # name: mysecret
- # contents:
- # - path: /tmp/config.yml
- # content: "value=True\n"
- # - path: /tmp/passwords.yml
- # content: "test1\ntest2\ntest3\ntest4\n"
- #
- import os
- import shutil
- import json
- import atexit
- class OpenShiftOC(object):
- ''' 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 '''
- self.namespace = namespace
- self.name = secret_name
- self.verbose = verbose
- self.kubeconfig = kubeconfig
- def get_secrets(self):
- '''return a secret by name '''
- cmd = ['get', 'secrets', '-o', 'json', '-n', self.namespace]
- if self.name:
- cmd.append(self.name)
- rval = self.oc_cmd(cmd, output=True)
- # Ensure results are retuned in an array
- if rval.has_key('items'):
- rval['results'] = rval['items']
- elif not isinstance(rval['results'], list):
- rval['results'] = [rval['results']]
- return rval
- def delete_secret(self):
- '''return all pods '''
- return self.oc_cmd(['delete', 'secrets', self.name, '-n', self.namespace])
- def secret_new(self, files):
- '''Create a secret with all pods '''
- 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)
- @staticmethod
- def create_files_from_contents(data):
- '''Turn an array of dict: filename, content into a files array'''
- files = []
- for sfile in data:
- with open(sfile['path'], 'w') as fds:
- fds.write(sfile['content'])
- files.append(sfile['path'])
- # Register cleanup when module is done
- atexit.register(OpenShiftOC.cleanup, files)
- return files
- def update_secret(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' % secret['results']['metadata']['name']
- with open(sfile_path, 'w') as sfd:
- sfd.write(json.dumps(secret['results']))
- cmd = ['replace', '-f', sfile_path]
- if force:
- cmd = ['replace', '--force', '-f', sfile_path]
- atexit.register(OpenShiftOC.cleanup, [sfile_path])
- return self.oc_cmd(cmd)
- def prep_secret(self, files):
- ''' return what the secret would look like if created
- This is accomplished by passing -ojson. This will most likely change in the future
- '''
- 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)
- def oc_cmd(self, cmd, output=False):
- '''Base command for oc '''
- 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": {}
- }
- @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)
- def exists(results, _name):
- ''' Check to see if the results include the name '''
- if not results:
- return False
- if find_result(results, _name):
- return True
- return False
- def find_result(results, _name):
- ''' Find the specified result by name'''
- rval = None
- for result in results:
- #print "%s == %s" % (result['metadata']['name'], name)
- if result.has_key('metadata') and result['metadata']['name'] == _name:
- rval = result
- break
- return rval
- # Disabling too-many-branches. This is a yaml dictionary comparison function
- # pylint: disable=too-many-branches,too-many-return-statements
- 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 = check_def_equal(user_def[key], value)
- if not result:
- if debug:
- print "dict returned false"
- return False
- # Verify each key, value pair is the same
- else:
- if not user_def.has_key(key) or value != user_def[key]:
- if debug:
- print "value not equal; user_def does not have key"
- print value
- print user_def[key]
- return False
- return True
- 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 = OpenShiftOC(module.params['namespace'],
- module.params['name'],
- kubeconfig=module.params['kubeconfig'],
- verbose=module.params['debug'])
- state = module.params['state']
- api_rval = occmd.get_secrets()
- #####
- # 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 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_secret()
- 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 = OpenShiftOC.create_files_from_contents(module.params['contents'])
- else:
- module.fail_json(msg='Either specify files or contents.')
- ########
- # Create
- ########
- if not 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.secret_new(files)
- # Remove files
- if files and module.params['delete_after']:
- OpenShiftOC.cleanup(files)
- module.exit_json(changed=True, results=api_rval, state="present")
- ########
- # Update
- ########
- secret = occmd.prep_secret(files)
- if secret['returncode'] != 0:
- module.fail_json(msg=secret)
- if check_def_equal(secret['results'], api_rval['results'][0]):
- # Remove files
- if files and module.params['delete_after']:
- OpenShiftOC.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_secret(files, force=module.params['force'])
- # Remove files
- if files and module.params['delete_after']:
- OpenShiftOC.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()
|