| #!/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() |