#!/usr/bin/python # -*- coding: utf-8 -*- DOCUMENTATION = """ --- module: kube short_description: Manage Kubernetes Cluster description: - Create, replace, remove, and stop resources within a Kubernetes Cluster version_added: "2.0" options: name: required: false default: null description: - The name associated with resource filename: required: false default: null description: - The path and filename of the resource(s) definition file(s). - To operate on several files this can accept a comma separated list of files or a list of files. aliases: [ 'files', 'file', 'filenames' ] kubectl: required: false default: null description: - The path to the kubectl bin namespace: required: false default: null description: - The namespace associated with the resource(s) resource: required: false default: null description: - The resource to perform an action on. pods (po), replicationControllers (rc), services (svc) label: required: false default: null description: - The labels used to filter specific resources. server: required: false default: null description: - The url for the API server that commands are executed against. force: required: false default: false description: - A flag to indicate to force delete, replace, or stop. wait: required: false default: false description: - A flag to indicate to wait for resources to be created before continuing to the next step all: required: false default: false description: - A flag to indicate delete all, stop all, or all namespaces when checking exists. log_level: required: false default: 0 description: - Indicates the level of verbosity of logging by kubectl. state: required: false choices: ['present', 'absent', 'latest', 'reloaded', 'stopped'] default: present description: - present handles checking existence or creating if definition file provided, absent handles deleting resource(s) based on other options, latest handles creating or updating based on existence, reloaded handles updating resource(s) definition using definition file, stopped handles stopping resource(s) based on other options. recursive: required: false default: false description: - Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory. requirements: - kubectl author: "Kenny Jones (@kenjones-cisco)" """ EXAMPLES = """ - name: test nginx is present kube: name=nginx resource=rc state=present - name: test nginx is stopped kube: name=nginx resource=rc state=stopped - name: test nginx is absent kube: name=nginx resource=rc state=absent - name: test nginx is present kube: filename=/tmp/nginx.yml - name: test nginx and postgresql are present kube: files=/tmp/nginx.yml,/tmp/postgresql.yml - name: test nginx and postgresql are present kube: files: - /tmp/nginx.yml - /tmp/postgresql.yml """ class KubeManager(object): def __init__(self, module): self.module = module self.kubectl = module.params.get('kubectl') if self.kubectl is None: self.kubectl = module.get_bin_path('kubectl', True) self.base_cmd = [self.kubectl] if module.params.get('server'): self.base_cmd.append('--server=' + module.params.get('server')) if module.params.get('log_level'): self.base_cmd.append('--v=' + str(module.params.get('log_level'))) if module.params.get('namespace'): self.base_cmd.append('--namespace=' + module.params.get('namespace')) self.all = module.params.get('all') self.force = module.params.get('force') self.wait = module.params.get('wait') self.name = module.params.get('name') self.filename = [f.strip() for f in module.params.get('filename') or []] self.resource = module.params.get('resource') self.label = module.params.get('label') self.recursive = module.params.get('recursive') def _execute(self, cmd): args = self.base_cmd + cmd try: rc, out, err = self.module.run_command(args) if rc != 0: self.module.fail_json( msg='error running kubectl (%s) command (rc=%d), out=\'%s\', err=\'%s\'' % (' '.join(args), rc, out, err)) except Exception as exc: self.module.fail_json( msg='error running kubectl (%s) command: %s' % (' '.join(args), str(exc))) return out.splitlines() def _execute_nofail(self, cmd): args = self.base_cmd + cmd rc, out, err = self.module.run_command(args) if rc != 0: return None return out.splitlines() def create(self, check=True, force=True): if check and self.exists(): return [] cmd = ['apply'] if force: cmd.append('--force') if self.wait: cmd.append('--wait') if self.recursive: cmd.append('--recursive={}'.format(self.recursive)) if not self.filename: self.module.fail_json(msg='filename required to create') cmd.append('--filename=' + ','.join(self.filename)) return self._execute(cmd) def replace(self, force=True): cmd = ['apply'] if force: cmd.append('--force') if self.wait: cmd.append('--wait') if self.recursive: cmd.append('--recursive={}'.format(self.recursive)) if not self.filename: self.module.fail_json(msg='filename required to reload') cmd.append('--filename=' + ','.join(self.filename)) return self._execute(cmd) def delete(self): if not self.force and not self.exists(): return [] cmd = ['delete'] if self.filename: cmd.append('--filename=' + ','.join(self.filename)) if self.recursive: cmd.append('--recursive={}'.format(self.recursive)) else: if not self.resource: self.module.fail_json(msg='resource required to delete without filename') cmd.append(self.resource) if self.name: cmd.append(self.name) if self.label: cmd.append('--selector=' + self.label) if self.all: cmd.append('--all') if self.force: cmd.append('--ignore-not-found') if self.recursive: cmd.append('--recursive={}'.format(self.recursive)) return self._execute(cmd) def exists(self): cmd = ['get'] if self.filename: cmd.append('--filename=' + ','.join(self.filename)) if self.recursive: cmd.append('--recursive={}'.format(self.recursive)) else: if not self.resource: self.module.fail_json(msg='resource required without filename') cmd.append(self.resource) if self.name: cmd.append(self.name) if self.label: cmd.append('--selector=' + self.label) if self.all: cmd.append('--all-namespaces') cmd.append('--no-headers') result = self._execute_nofail(cmd) if not result: return False return True # TODO: This is currently unused, perhaps convert to 'scale' with a replicas param? def stop(self): if not self.force and not self.exists(): return [] cmd = ['stop'] if self.filename: cmd.append('--filename=' + ','.join(self.filename)) if self.recursive: cmd.append('--recursive={}'.format(self.recursive)) else: if not self.resource: self.module.fail_json(msg='resource required to stop without filename') cmd.append(self.resource) if self.name: cmd.append(self.name) if self.label: cmd.append('--selector=' + self.label) if self.all: cmd.append('--all') if self.force: cmd.append('--ignore-not-found') return self._execute(cmd) def main(): module = AnsibleModule( argument_spec=dict( name=dict(), filename=dict(type='list', aliases=['files', 'file', 'filenames']), namespace=dict(), resource=dict(), label=dict(), server=dict(), kubectl=dict(), force=dict(default=False, type='bool'), wait=dict(default=False, type='bool'), all=dict(default=False, type='bool'), log_level=dict(default=0, type='int'), state=dict(default='present', choices=['present', 'absent', 'latest', 'reloaded', 'stopped', 'exists']), recursive=dict(default=False, type='bool'), ), mutually_exclusive=[['filename', 'list']] ) changed = False manager = KubeManager(module) state = module.params.get('state') if state == 'present': result = manager.create(check=False) elif state == 'absent': result = manager.delete() elif state == 'reloaded': result = manager.replace() elif state == 'stopped': result = manager.stop() elif state == 'latest': result = manager.replace() elif state == 'exists': result = manager.exists() module.exit_json(changed=changed, msg='%s' % result) else: module.fail_json(msg='Unrecognized state %s.' % state) module.exit_json(changed=changed, msg='success: %s' % (' '.join(result)) ) from ansible.module_utils.basic import * # noqa if __name__ == '__main__': main()