From fb6bc5058a9ed5b7bee6a9ca3e533949e35f92a7 Mon Sep 17 00:00:00 2001 From: Ansible Core Team Date: Mon, 9 Mar 2020 09:40:30 +0000 Subject: [PATCH] Migrated to community.kubernetes --- lib/ansible/module_utils/k8s/__init__.py | 0 lib/ansible/module_utils/k8s/common.py | 290 ---------- lib/ansible/module_utils/k8s/raw.py | 519 ------------------ lib/ansible/module_utils/k8s/scale.py | 243 -------- .../modules/clustering/k8s/_k8s_facts.py | 1 - .../modules/clustering/k8s/_k8s_raw.py | 1 - lib/ansible/modules/clustering/k8s/k8s.py | 274 --------- .../modules/clustering/k8s/k8s_auth.py | 336 ------------ .../modules/clustering/k8s/k8s_info.py | 179 ------ .../modules/clustering/k8s/k8s_scale.py | 129 ----- .../modules/clustering/k8s/k8s_service.py | 267 --------- .../clustering/openshift/_openshift_raw.py | 1 - .../clustering/openshift/_openshift_scale.py | 1 - .../plugins/doc_fragments/k8s_auth_options.py | 95 ---- .../plugins/doc_fragments/k8s_name_options.py | 45 -- .../doc_fragments/k8s_resource_options.py | 29 - .../doc_fragments/k8s_scale_options.py | 39 -- .../doc_fragments/k8s_state_options.py | 27 - lib/ansible/plugins/filter/k8s.py | 40 -- lib/ansible/plugins/inventory/k8s.py | 360 ------------ lib/ansible/plugins/inventory/openshift.py | 201 ------- lib/ansible/plugins/lookup/_openshift.py | 1 - lib/ansible/plugins/lookup/k8s.py | 299 ---------- test/integration/targets/k8s/README.md | 23 - test/integration/targets/k8s/aliases | 2 - .../integration/targets/k8s/defaults/main.yml | 32 -- .../targets/k8s/files/crd-resource.yml | 20 - .../k8s/files/kuard-extra-property.yml | 21 - .../targets/k8s/files/kuard-invalid-type.yml | 20 - .../targets/k8s/files/setup-crd.yml | 14 - test/integration/targets/k8s/meta/main.yml | 2 - .../targets/k8s/tasks/append_hash.yml | 68 --- test/integration/targets/k8s/tasks/apply.yml | 277 ---------- test/integration/targets/k8s/tasks/crd.yml | 71 --- test/integration/targets/k8s/tasks/delete.yml | 101 ---- .../targets/k8s/tasks/full_test.yml | 375 ------------- test/integration/targets/k8s/tasks/lists.yml | 140 ----- test/integration/targets/k8s/tasks/main.yml | 92 ---- .../k8s/tasks/older_openshift_fail.yml | 69 --- .../targets/k8s/tasks/openshift.yml | 61 -- .../targets/k8s/tasks/validate_installed.yml | 125 ----- .../k8s/tasks/validate_not_installed.yml | 23 - test/integration/targets/k8s/tasks/waiter.yml | 355 ------------ test/sanity/ignore.txt | 33 -- 44 files changed, 5301 deletions(-) delete mode 100644 lib/ansible/module_utils/k8s/__init__.py delete mode 100644 lib/ansible/module_utils/k8s/common.py delete mode 100644 lib/ansible/module_utils/k8s/raw.py delete mode 100644 lib/ansible/module_utils/k8s/scale.py delete mode 120000 lib/ansible/modules/clustering/k8s/_k8s_facts.py delete mode 120000 lib/ansible/modules/clustering/k8s/_k8s_raw.py delete mode 100644 lib/ansible/modules/clustering/k8s/k8s.py delete mode 100644 lib/ansible/modules/clustering/k8s/k8s_auth.py delete mode 100644 lib/ansible/modules/clustering/k8s/k8s_info.py delete mode 100644 lib/ansible/modules/clustering/k8s/k8s_scale.py delete mode 100644 lib/ansible/modules/clustering/k8s/k8s_service.py delete mode 120000 lib/ansible/modules/clustering/openshift/_openshift_raw.py delete mode 120000 lib/ansible/modules/clustering/openshift/_openshift_scale.py delete mode 100644 lib/ansible/plugins/doc_fragments/k8s_auth_options.py delete mode 100644 lib/ansible/plugins/doc_fragments/k8s_name_options.py delete mode 100644 lib/ansible/plugins/doc_fragments/k8s_resource_options.py delete mode 100644 lib/ansible/plugins/doc_fragments/k8s_scale_options.py delete mode 100644 lib/ansible/plugins/doc_fragments/k8s_state_options.py delete mode 100644 lib/ansible/plugins/filter/k8s.py delete mode 100644 lib/ansible/plugins/inventory/k8s.py delete mode 100644 lib/ansible/plugins/inventory/openshift.py delete mode 120000 lib/ansible/plugins/lookup/_openshift.py delete mode 100644 lib/ansible/plugins/lookup/k8s.py delete mode 100644 test/integration/targets/k8s/README.md delete mode 100644 test/integration/targets/k8s/aliases delete mode 100644 test/integration/targets/k8s/defaults/main.yml delete mode 100644 test/integration/targets/k8s/files/crd-resource.yml delete mode 100644 test/integration/targets/k8s/files/kuard-extra-property.yml delete mode 100644 test/integration/targets/k8s/files/kuard-invalid-type.yml delete mode 100644 test/integration/targets/k8s/files/setup-crd.yml delete mode 100644 test/integration/targets/k8s/meta/main.yml delete mode 100644 test/integration/targets/k8s/tasks/append_hash.yml delete mode 100644 test/integration/targets/k8s/tasks/apply.yml delete mode 100644 test/integration/targets/k8s/tasks/crd.yml delete mode 100644 test/integration/targets/k8s/tasks/delete.yml delete mode 100644 test/integration/targets/k8s/tasks/full_test.yml delete mode 100644 test/integration/targets/k8s/tasks/lists.yml delete mode 100644 test/integration/targets/k8s/tasks/main.yml delete mode 100644 test/integration/targets/k8s/tasks/older_openshift_fail.yml delete mode 100644 test/integration/targets/k8s/tasks/openshift.yml delete mode 100644 test/integration/targets/k8s/tasks/validate_installed.yml delete mode 100644 test/integration/targets/k8s/tasks/validate_not_installed.yml delete mode 100644 test/integration/targets/k8s/tasks/waiter.yml diff --git a/lib/ansible/module_utils/k8s/__init__.py b/lib/ansible/module_utils/k8s/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/lib/ansible/module_utils/k8s/common.py b/lib/ansible/module_utils/k8s/common.py deleted file mode 100644 index d86659f0098..00000000000 --- a/lib/ansible/module_utils/k8s/common.py +++ /dev/null @@ -1,290 +0,0 @@ -# Copyright 2018 Red Hat | Ansible -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -from __future__ import absolute_import, division, print_function - -import copy -import json -import os -import traceback - - -from ansible.module_utils.basic import AnsibleModule, missing_required_lib -from ansible.module_utils.common.dict_transformations import recursive_diff -from ansible.module_utils.six import iteritems, string_types -from ansible.module_utils._text import to_native - -K8S_IMP_ERR = None -try: - import kubernetes - import openshift - from openshift.dynamic import DynamicClient - from openshift.dynamic.exceptions import ResourceNotFoundError, ResourceNotUniqueError - HAS_K8S_MODULE_HELPER = True - k8s_import_exception = None -except ImportError as e: - HAS_K8S_MODULE_HELPER = False - k8s_import_exception = e - K8S_IMP_ERR = traceback.format_exc() - -YAML_IMP_ERR = None -try: - import yaml - HAS_YAML = True -except ImportError: - YAML_IMP_ERR = traceback.format_exc() - HAS_YAML = False - -try: - import urllib3 - urllib3.disable_warnings() -except ImportError: - pass - - -def list_dict_str(value): - if isinstance(value, list): - return value - elif isinstance(value, dict): - return value - elif isinstance(value, string_types): - return value - raise TypeError - - -ARG_ATTRIBUTES_BLACKLIST = ('property_path',) - -COMMON_ARG_SPEC = { - 'state': { - 'default': 'present', - 'choices': ['present', 'absent'], - }, - 'force': { - 'type': 'bool', - 'default': False, - }, - 'resource_definition': { - 'type': list_dict_str, - 'aliases': ['definition', 'inline'] - }, - 'src': { - 'type': 'path', - }, - 'kind': {}, - 'name': {}, - 'namespace': {}, - 'api_version': { - 'default': 'v1', - 'aliases': ['api', 'version'], - }, -} - -AUTH_ARG_SPEC = { - 'kubeconfig': { - 'type': 'path', - }, - 'context': {}, - 'host': {}, - 'api_key': { - 'no_log': True, - }, - 'username': {}, - 'password': { - 'no_log': True, - }, - 'validate_certs': { - 'type': 'bool', - 'aliases': ['verify_ssl'], - }, - 'ca_cert': { - 'type': 'path', - 'aliases': ['ssl_ca_cert'], - }, - 'client_cert': { - 'type': 'path', - 'aliases': ['cert_file'], - }, - 'client_key': { - 'type': 'path', - 'aliases': ['key_file'], - }, - 'proxy': {}, - 'persist_config': { - 'type': 'bool', - }, -} - -# Map kubernetes-client parameters to ansible parameters -AUTH_ARG_MAP = { - 'kubeconfig': 'kubeconfig', - 'context': 'context', - 'host': 'host', - 'api_key': 'api_key', - 'username': 'username', - 'password': 'password', - 'verify_ssl': 'validate_certs', - 'ssl_ca_cert': 'ca_cert', - 'cert_file': 'client_cert', - 'key_file': 'client_key', - 'proxy': 'proxy', - 'persist_config': 'persist_config', -} - - -class K8sAnsibleMixin(object): - _argspec_cache = None - - @property - def argspec(self): - """ - Introspect the model properties, and return an Ansible module arg_spec dict. - :return: dict - """ - if self._argspec_cache: - return self._argspec_cache - argument_spec = copy.deepcopy(COMMON_ARG_SPEC) - argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC)) - self._argspec_cache = argument_spec - return self._argspec_cache - - def get_api_client(self, **auth_params): - auth_params = auth_params or getattr(self, 'params', {}) - auth = {} - - # If authorization variables aren't defined, look for them in environment variables - for true_name, arg_name in AUTH_ARG_MAP.items(): - if auth_params.get(arg_name) is None: - env_value = os.getenv('K8S_AUTH_{0}'.format(arg_name.upper()), None) or os.getenv('K8S_AUTH_{0}'.format(true_name.upper()), None) - if env_value is not None: - if AUTH_ARG_SPEC[arg_name].get('type') == 'bool': - env_value = env_value.lower() not in ['0', 'false', 'no'] - auth[true_name] = env_value - else: - auth[true_name] = auth_params[arg_name] - - def auth_set(*names): - return all([auth.get(name) for name in names]) - - if auth_set('username', 'password', 'host') or auth_set('api_key', 'host'): - # We have enough in the parameters to authenticate, no need to load incluster or kubeconfig - pass - elif auth_set('kubeconfig') or auth_set('context'): - kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config')) - else: - # First try to do incluster config, then kubeconfig - try: - kubernetes.config.load_incluster_config() - except kubernetes.config.ConfigException: - kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config')) - - # Override any values in the default configuration with Ansible parameters - configuration = kubernetes.client.Configuration() - for key, value in iteritems(auth): - if key in AUTH_ARG_MAP.keys() and value is not None: - if key == 'api_key': - setattr(configuration, key, {'authorization': "Bearer {0}".format(value)}) - else: - setattr(configuration, key, value) - - kubernetes.client.Configuration.set_default(configuration) - return DynamicClient(kubernetes.client.ApiClient(configuration)) - - def find_resource(self, kind, api_version, fail=False): - for attribute in ['kind', 'name', 'singular_name']: - try: - return self.client.resources.get(**{'api_version': api_version, attribute: kind}) - except (ResourceNotFoundError, ResourceNotUniqueError): - pass - try: - return self.client.resources.get(api_version=api_version, short_names=[kind]) - except (ResourceNotFoundError, ResourceNotUniqueError): - if fail: - self.fail(msg='Failed to find exact match for {0}.{1} by [kind, name, singularName, shortNames]'.format(api_version, kind)) - - def kubernetes_facts(self, kind, api_version, name=None, namespace=None, label_selectors=None, field_selectors=None): - resource = self.find_resource(kind, api_version) - if not resource: - return dict(resources=[]) - try: - result = resource.get(name=name, - namespace=namespace, - label_selector=','.join(label_selectors), - field_selector=','.join(field_selectors)).to_dict() - except openshift.dynamic.exceptions.NotFoundError: - return dict(resources=[]) - - if 'items' in result: - return dict(resources=result['items']) - else: - return dict(resources=[result]) - - def remove_aliases(self): - """ - The helper doesn't know what to do with aliased keys - """ - for k, v in iteritems(self.argspec): - if 'aliases' in v: - for alias in v['aliases']: - if alias in self.params: - self.params.pop(alias) - - def load_resource_definitions(self, src): - """ Load the requested src path """ - result = None - path = os.path.normpath(src) - if not os.path.exists(path): - self.fail(msg="Error accessing {0}. Does the file exist?".format(path)) - try: - with open(path, 'r') as f: - result = list(yaml.safe_load_all(f)) - except (IOError, yaml.YAMLError) as exc: - self.fail(msg="Error loading resource_definition: {0}".format(exc)) - return result - - @staticmethod - def diff_objects(existing, new): - result = dict() - diff = recursive_diff(existing, new) - if diff: - result['before'] = diff[0] - result['after'] = diff[1] - return not diff, result - - -class KubernetesAnsibleModule(AnsibleModule, K8sAnsibleMixin): - resource_definition = None - api_version = None - kind = None - - def __init__(self, *args, **kwargs): - - kwargs['argument_spec'] = self.argspec - AnsibleModule.__init__(self, *args, **kwargs) - - if not HAS_K8S_MODULE_HELPER: - self.fail_json(msg=missing_required_lib('openshift'), exception=K8S_IMP_ERR, - error=to_native(k8s_import_exception)) - self.openshift_version = openshift.__version__ - - if not HAS_YAML: - self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR) - - def execute_module(self): - raise NotImplementedError() - - def fail(self, msg=None): - self.fail_json(msg=msg) diff --git a/lib/ansible/module_utils/k8s/raw.py b/lib/ansible/module_utils/k8s/raw.py deleted file mode 100644 index 06272b8158b..00000000000 --- a/lib/ansible/module_utils/k8s/raw.py +++ /dev/null @@ -1,519 +0,0 @@ -# -# Copyright 2018 Red Hat | Ansible -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -from __future__ import absolute_import, division, print_function - -import copy -from datetime import datetime -from distutils.version import LooseVersion -import time -import sys -import traceback - -from ansible.module_utils.basic import missing_required_lib -from ansible.module_utils.k8s.common import AUTH_ARG_SPEC, COMMON_ARG_SPEC -from ansible.module_utils.six import string_types -from ansible.module_utils.k8s.common import KubernetesAnsibleModule -from ansible.module_utils.common.dict_transformations import dict_merge - - -try: - import yaml - from openshift.dynamic.exceptions import DynamicApiError, NotFoundError, ConflictError, ForbiddenError, KubernetesValidateMissing - import urllib3 -except ImportError: - # Exceptions handled in common - pass - -try: - import kubernetes_validate - HAS_KUBERNETES_VALIDATE = True -except ImportError: - HAS_KUBERNETES_VALIDATE = False - -K8S_CONFIG_HASH_IMP_ERR = None -try: - from openshift.helper.hashes import generate_hash - HAS_K8S_CONFIG_HASH = True -except ImportError: - K8S_CONFIG_HASH_IMP_ERR = traceback.format_exc() - HAS_K8S_CONFIG_HASH = False - -HAS_K8S_APPLY = None -try: - from openshift.dynamic.apply import apply_object - HAS_K8S_APPLY = True -except ImportError: - HAS_K8S_APPLY = False - - -class KubernetesRawModule(KubernetesAnsibleModule): - - @property - def validate_spec(self): - return dict( - fail_on_error=dict(type='bool'), - version=dict(), - strict=dict(type='bool', default=True) - ) - - @property - def condition_spec(self): - return dict( - type=dict(), - status=dict(default=True, choices=[True, False, "Unknown"]), - reason=dict() - ) - - @property - def argspec(self): - argument_spec = copy.deepcopy(COMMON_ARG_SPEC) - argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC)) - argument_spec['merge_type'] = dict(type='list', choices=['json', 'merge', 'strategic-merge']) - argument_spec['wait'] = dict(type='bool', default=False) - argument_spec['wait_sleep'] = dict(type='int', default=5) - argument_spec['wait_timeout'] = dict(type='int', default=120) - argument_spec['wait_condition'] = dict(type='dict', default=None, options=self.condition_spec) - argument_spec['validate'] = dict(type='dict', default=None, options=self.validate_spec) - argument_spec['append_hash'] = dict(type='bool', default=False) - argument_spec['apply'] = dict(type='bool', default=False) - return argument_spec - - def __init__(self, k8s_kind=None, *args, **kwargs): - self.client = None - self.warnings = [] - - mutually_exclusive = [ - ('resource_definition', 'src'), - ('merge_type', 'apply'), - ] - - KubernetesAnsibleModule.__init__(self, *args, - mutually_exclusive=mutually_exclusive, - supports_check_mode=True, - **kwargs) - self.kind = k8s_kind or self.params.get('kind') - self.api_version = self.params.get('api_version') - self.name = self.params.get('name') - self.namespace = self.params.get('namespace') - resource_definition = self.params.get('resource_definition') - validate = self.params.get('validate') - if validate: - if LooseVersion(self.openshift_version) < LooseVersion("0.8.0"): - self.fail_json(msg="openshift >= 0.8.0 is required for validate") - self.append_hash = self.params.get('append_hash') - if self.append_hash: - if not HAS_K8S_CONFIG_HASH: - self.fail_json(msg=missing_required_lib("openshift >= 0.7.2", reason="for append_hash"), - exception=K8S_CONFIG_HASH_IMP_ERR) - if self.params['merge_type']: - if LooseVersion(self.openshift_version) < LooseVersion("0.6.2"): - self.fail_json(msg=missing_required_lib("openshift >= 0.6.2", reason="for merge_type")) - self.apply = self.params.get('apply', False) - if self.apply: - if not HAS_K8S_APPLY: - self.fail_json(msg=missing_required_lib("openshift >= 0.9.2", reason="for apply")) - - if resource_definition: - if isinstance(resource_definition, string_types): - try: - self.resource_definitions = yaml.safe_load_all(resource_definition) - except (IOError, yaml.YAMLError) as exc: - self.fail(msg="Error loading resource_definition: {0}".format(exc)) - elif isinstance(resource_definition, list): - self.resource_definitions = resource_definition - else: - self.resource_definitions = [resource_definition] - src = self.params.get('src') - if src: - self.resource_definitions = self.load_resource_definitions(src) - try: - self.resource_definitions = [item for item in self.resource_definitions if item] - except AttributeError: - pass - - if not resource_definition and not src: - implicit_definition = dict( - kind=self.kind, - apiVersion=self.api_version, - metadata=dict(name=self.name) - ) - if self.namespace: - implicit_definition['metadata']['namespace'] = self.namespace - self.resource_definitions = [implicit_definition] - - def flatten_list_kind(self, list_resource, definitions): - flattened = [] - parent_api_version = list_resource.group_version if list_resource else None - parent_kind = list_resource.kind[:-4] if list_resource else None - for definition in definitions.get('items', []): - resource = self.find_resource(definition.get('kind', parent_kind), definition.get('apiVersion', parent_api_version), fail=True) - flattened.append((resource, self.set_defaults(resource, definition))) - return flattened - - def execute_module(self): - changed = False - results = [] - try: - self.client = self.get_api_client() - # Hopefully the kubernetes client will provide its own exception class one day - except (urllib3.exceptions.RequestError) as e: - self.fail_json(msg="Couldn't connect to Kubernetes: %s" % str(e)) - - flattened_definitions = [] - for definition in self.resource_definitions: - kind = definition.get('kind', self.kind) - api_version = definition.get('apiVersion', self.api_version) - if kind.endswith('List'): - resource = self.find_resource(kind, api_version, fail=False) - flattened_definitions.extend(self.flatten_list_kind(resource, definition)) - else: - resource = self.find_resource(kind, api_version, fail=True) - flattened_definitions.append((resource, definition)) - - for (resource, definition) in flattened_definitions: - kind = definition.get('kind', self.kind) - api_version = definition.get('apiVersion', self.api_version) - definition = self.set_defaults(resource, definition) - self.warnings = [] - if self.params['validate'] is not None: - self.warnings = self.validate(definition) - result = self.perform_action(resource, definition) - result['warnings'] = self.warnings - changed = changed or result['changed'] - results.append(result) - - if len(results) == 1: - self.exit_json(**results[0]) - - self.exit_json(**{ - 'changed': changed, - 'result': { - 'results': results - } - }) - - def validate(self, resource): - def _prepend_resource_info(resource, msg): - return "%s %s: %s" % (resource['kind'], resource['metadata']['name'], msg) - - try: - warnings, errors = self.client.validate(resource, self.params['validate'].get('version'), self.params['validate'].get('strict')) - except KubernetesValidateMissing: - self.fail_json(msg="kubernetes-validate python library is required to validate resources") - - if errors and self.params['validate']['fail_on_error']: - self.fail_json(msg="\n".join([_prepend_resource_info(resource, error) for error in errors])) - else: - return [_prepend_resource_info(resource, msg) for msg in warnings + errors] - - def set_defaults(self, resource, definition): - definition['kind'] = resource.kind - definition['apiVersion'] = resource.group_version - metadata = definition.get('metadata', {}) - if self.name and not metadata.get('name'): - metadata['name'] = self.name - if resource.namespaced and self.namespace and not metadata.get('namespace'): - metadata['namespace'] = self.namespace - definition['metadata'] = metadata - return definition - - def perform_action(self, resource, definition): - result = {'changed': False, 'result': {}} - state = self.params.get('state', None) - force = self.params.get('force', False) - name = definition['metadata'].get('name') - namespace = definition['metadata'].get('namespace') - existing = None - wait = self.params.get('wait') - wait_sleep = self.params.get('wait_sleep') - wait_timeout = self.params.get('wait_timeout') - wait_condition = None - if self.params.get('wait_condition') and self.params['wait_condition'].get('type'): - wait_condition = self.params['wait_condition'] - - self.remove_aliases() - - try: - # ignore append_hash for resources other than ConfigMap and Secret - if self.append_hash and definition['kind'] in ['ConfigMap', 'Secret']: - name = '%s-%s' % (name, generate_hash(definition)) - definition['metadata']['name'] = name - params = dict(name=name) - if namespace: - params['namespace'] = namespace - existing = resource.get(**params) - except NotFoundError: - # Remove traceback so that it doesn't show up in later failures - try: - sys.exc_clear() - except AttributeError: - # no sys.exc_clear on python3 - pass - except ForbiddenError as exc: - if definition['kind'] in ['Project', 'ProjectRequest'] and state != 'absent': - return self.create_project_request(definition) - self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc.body), - error=exc.status, status=exc.status, reason=exc.reason) - except DynamicApiError as exc: - self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc.body), - error=exc.status, status=exc.status, reason=exc.reason) - - if state == 'absent': - result['method'] = "delete" - if not existing: - # The object already does not exist - return result - else: - # Delete the object - result['changed'] = True - if not self.check_mode: - try: - k8s_obj = resource.delete(**params) - result['result'] = k8s_obj.to_dict() - except DynamicApiError as exc: - self.fail_json(msg="Failed to delete object: {0}".format(exc.body), - error=exc.status, status=exc.status, reason=exc.reason) - if wait: - success, resource, duration = self.wait(resource, definition, wait_sleep, wait_timeout, 'absent') - result['duration'] = duration - if not success: - self.fail_json(msg="Resource deletion timed out", **result) - return result - else: - if self.apply: - if self.check_mode: - ignored, k8s_obj = apply_object(resource, definition) - else: - try: - k8s_obj = resource.apply(definition, namespace=namespace).to_dict() - except DynamicApiError as exc: - msg = "Failed to apply object: {0}".format(exc.body) - if self.warnings: - msg += "\n" + "\n ".join(self.warnings) - self.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason) - success = True - result['result'] = k8s_obj - if wait: - success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition) - if existing: - existing = existing.to_dict() - else: - existing = {} - match, diffs = self.diff_objects(existing, result['result']) - result['changed'] = not match - result['diff'] = diffs - result['method'] = 'apply' - if not success: - self.fail_json(msg="Resource apply timed out", **result) - return result - - if not existing: - if self.check_mode: - k8s_obj = definition - else: - try: - k8s_obj = resource.create(definition, namespace=namespace).to_dict() - except ConflictError: - # Some resources, like ProjectRequests, can't be created multiple times, - # because the resources that they create don't match their kind - # In this case we'll mark it as unchanged and warn the user - self.warn("{0} was not found, but creating it returned a 409 Conflict error. This can happen \ - if the resource you are creating does not directly create a resource of the same kind.".format(name)) - return result - except DynamicApiError as exc: - msg = "Failed to create object: {0}".format(exc.body) - if self.warnings: - msg += "\n" + "\n ".join(self.warnings) - self.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason) - success = True - result['result'] = k8s_obj - if wait and not self.check_mode: - success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition) - result['changed'] = True - result['method'] = 'create' - if not success: - self.fail_json(msg="Resource creation timed out", **result) - return result - - match = False - diffs = [] - - if existing and force: - if self.check_mode: - k8s_obj = definition - else: - try: - k8s_obj = resource.replace(definition, name=name, namespace=namespace, append_hash=self.append_hash).to_dict() - except DynamicApiError as exc: - msg = "Failed to replace object: {0}".format(exc.body) - if self.warnings: - msg += "\n" + "\n ".join(self.warnings) - self.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason) - match, diffs = self.diff_objects(existing.to_dict(), k8s_obj) - success = True - result['result'] = k8s_obj - if wait: - success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition) - match, diffs = self.diff_objects(existing.to_dict(), result['result']) - result['changed'] = not match - result['method'] = 'replace' - result['diff'] = diffs - if not success: - self.fail_json(msg="Resource replacement timed out", **result) - return result - - # Differences exist between the existing obj and requested params - if self.check_mode: - k8s_obj = dict_merge(existing.to_dict(), definition) - else: - if LooseVersion(self.openshift_version) < LooseVersion("0.6.2"): - k8s_obj, error = self.patch_resource(resource, definition, existing, name, - namespace) - else: - for merge_type in self.params['merge_type'] or ['strategic-merge', 'merge']: - k8s_obj, error = self.patch_resource(resource, definition, existing, name, - namespace, merge_type=merge_type) - if not error: - break - if error: - self.fail_json(**error) - - success = True - result['result'] = k8s_obj - if wait: - success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition) - match, diffs = self.diff_objects(existing.to_dict(), result['result']) - result['changed'] = not match - result['method'] = 'patch' - result['diff'] = diffs - - if not success: - self.fail_json(msg="Resource update timed out", **result) - return result - - def patch_resource(self, resource, definition, existing, name, namespace, merge_type=None): - try: - params = dict(name=name, namespace=namespace) - if merge_type: - params['content_type'] = 'application/{0}-patch+json'.format(merge_type) - k8s_obj = resource.patch(definition, **params).to_dict() - match, diffs = self.diff_objects(existing.to_dict(), k8s_obj) - error = {} - return k8s_obj, {} - except DynamicApiError as exc: - msg = "Failed to patch object: {0}".format(exc.body) - if self.warnings: - msg += "\n" + "\n ".join(self.warnings) - error = dict(msg=msg, error=exc.status, status=exc.status, reason=exc.reason, warnings=self.warnings) - return None, error - - def create_project_request(self, definition): - definition['kind'] = 'ProjectRequest' - result = {'changed': False, 'result': {}} - resource = self.find_resource('ProjectRequest', definition['apiVersion'], fail=True) - if not self.check_mode: - try: - k8s_obj = resource.create(definition) - result['result'] = k8s_obj.to_dict() - except DynamicApiError as exc: - self.fail_json(msg="Failed to create object: {0}".format(exc.body), - error=exc.status, status=exc.status, reason=exc.reason) - result['changed'] = True - result['method'] = 'create' - return result - - def _wait_for(self, resource, name, namespace, predicate, sleep, timeout, state): - start = datetime.now() - - def _wait_for_elapsed(): - return (datetime.now() - start).seconds - - response = None - while _wait_for_elapsed() < timeout: - try: - response = resource.get(name=name, namespace=namespace) - if predicate(response): - if response: - return True, response.to_dict(), _wait_for_elapsed() - else: - return True, {}, _wait_for_elapsed() - time.sleep(sleep) - except NotFoundError: - if state == 'absent': - return True, {}, _wait_for_elapsed() - if response: - response = response.to_dict() - return False, response, _wait_for_elapsed() - - def wait(self, resource, definition, sleep, timeout, state='present', condition=None): - - def _deployment_ready(deployment): - # FIXME: frustratingly bool(deployment.status) is True even if status is empty - # Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty - return (deployment.status and deployment.status.replicas is not None and - deployment.status.availableReplicas == deployment.status.replicas and - deployment.status.observedGeneration == deployment.metadata.generation) - - def _pod_ready(pod): - return (pod.status and pod.status.containerStatuses is not None and - all([container.ready for container in pod.status.containerStatuses])) - - def _daemonset_ready(daemonset): - return (daemonset.status and daemonset.status.desiredNumberScheduled is not None and - daemonset.status.numberReady == daemonset.status.desiredNumberScheduled and - daemonset.status.observedGeneration == daemonset.metadata.generation) - - def _custom_condition(resource): - if not resource.status or not resource.status.conditions: - return False - match = [x for x in resource.status.conditions if x.type == condition['type']] - if not match: - return False - # There should never be more than one condition of a specific type - match = match[0] - if match.status == 'Unknown': - if match.status == condition['status']: - if 'reason' not in condition: - return True - if condition['reason']: - return match.reason == condition['reason'] - return False - status = True if match.status == 'True' else False - if status == condition['status']: - if condition.get('reason'): - return match.reason == condition['reason'] - return True - return False - - def _resource_absent(resource): - return not resource - - waiter = dict( - Deployment=_deployment_ready, - DaemonSet=_daemonset_ready, - Pod=_pod_ready - ) - kind = definition['kind'] - if state == 'present' and not condition: - predicate = waiter.get(kind, lambda x: x) - elif state == 'present' and condition: - predicate = _custom_condition - else: - predicate = _resource_absent - return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, sleep, timeout, state) diff --git a/lib/ansible/module_utils/k8s/scale.py b/lib/ansible/module_utils/k8s/scale.py deleted file mode 100644 index 8a2a27fbd02..00000000000 --- a/lib/ansible/module_utils/k8s/scale.py +++ /dev/null @@ -1,243 +0,0 @@ -# -# Copyright 2018 Red Hat | Ansible -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -from __future__ import absolute_import, division, print_function - -import copy -import math -import time - -from ansible.module_utils.k8s.common import AUTH_ARG_SPEC, COMMON_ARG_SPEC -from ansible.module_utils.k8s.common import KubernetesAnsibleModule -from ansible.module_utils.six import string_types - -try: - import yaml - from openshift import watch - from openshift.dynamic.client import ResourceInstance - from openshift.helper.exceptions import KubernetesException -except ImportError as exc: - class KubernetesException(Exception): - pass - - -SCALE_ARG_SPEC = { - 'replicas': {'type': 'int', 'required': True}, - 'current_replicas': {'type': 'int'}, - 'resource_version': {}, - 'wait': {'type': 'bool', 'default': True}, - 'wait_timeout': {'type': 'int', 'default': 20}, -} - - -class KubernetesAnsibleScaleModule(KubernetesAnsibleModule): - - def __init__(self, k8s_kind=None, *args, **kwargs): - self.client = None - self.warnings = [] - - mutually_exclusive = [ - ('resource_definition', 'src'), - ] - - KubernetesAnsibleModule.__init__(self, *args, - mutually_exclusive=mutually_exclusive, - supports_check_mode=True, - **kwargs) - self.kind = k8s_kind or self.params.get('kind') - self.api_version = self.params.get('api_version') - self.name = self.params.get('name') - self.namespace = self.params.get('namespace') - resource_definition = self.params.get('resource_definition') - - if resource_definition: - if isinstance(resource_definition, string_types): - try: - self.resource_definitions = yaml.safe_load_all(resource_definition) - except (IOError, yaml.YAMLError) as exc: - self.fail(msg="Error loading resource_definition: {0}".format(exc)) - elif isinstance(resource_definition, list): - self.resource_definitions = resource_definition - else: - self.resource_definitions = [resource_definition] - src = self.params.get('src') - if src: - self.resource_definitions = self.load_resource_definitions(src) - - if not resource_definition and not src: - implicit_definition = dict( - kind=self.kind, - apiVersion=self.api_version, - metadata=dict(name=self.name) - ) - if self.namespace: - implicit_definition['metadata']['namespace'] = self.namespace - self.resource_definitions = [implicit_definition] - - def execute_module(self): - definition = self.resource_definitions[0] - - self.client = self.get_api_client() - - name = definition['metadata']['name'] - namespace = definition['metadata'].get('namespace') - api_version = definition['apiVersion'] - kind = definition['kind'] - current_replicas = self.params.get('current_replicas') - replicas = self.params.get('replicas') - resource_version = self.params.get('resource_version') - - wait = self.params.get('wait') - wait_time = self.params.get('wait_timeout') - existing = None - existing_count = None - return_attributes = dict(changed=False, result=dict()) - - resource = self.find_resource(kind, api_version, fail=True) - - try: - existing = resource.get(name=name, namespace=namespace) - return_attributes['result'] = existing.to_dict() - except KubernetesException as exc: - self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc), - error=exc.value.get('status')) - - if self.kind == 'job': - existing_count = existing.spec.parallelism - elif hasattr(existing.spec, 'replicas'): - existing_count = existing.spec.replicas - - if existing_count is None: - self.fail_json(msg='Failed to retrieve the available count for the requested object.') - - if resource_version and resource_version != existing.metadata.resourceVersion: - self.exit_json(**return_attributes) - - if current_replicas is not None and existing_count != current_replicas: - self.exit_json(**return_attributes) - - if existing_count != replicas: - return_attributes['changed'] = True - if not self.check_mode: - if self.kind == 'job': - existing.spec.parallelism = replicas - k8s_obj = resource.patch(existing.to_dict()) - else: - k8s_obj = self.scale(resource, existing, replicas, wait, wait_time) - return_attributes['result'] = k8s_obj.to_dict() - - self.exit_json(**return_attributes) - - @property - def argspec(self): - args = copy.deepcopy(COMMON_ARG_SPEC) - args.pop('state') - args.pop('force') - args.update(AUTH_ARG_SPEC) - args.update(SCALE_ARG_SPEC) - return args - - def scale(self, resource, existing_object, replicas, wait, wait_time): - name = existing_object.metadata.name - namespace = existing_object.metadata.namespace - - if not hasattr(resource, 'scale'): - self.fail_json( - msg="Cannot perform scale on resource of kind {0}".format(resource.kind) - ) - - scale_obj = {'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}} - - return_obj = None - stream = None - - if wait: - w, stream = self._create_stream(resource, namespace, wait_time) - - try: - resource.scale.patch(body=scale_obj) - except Exception as exc: - self.fail_json( - msg="Scale request failed: {0}".format(exc) - ) - - if wait and stream is not None: - return_obj = self._read_stream(resource, w, stream, name, replicas) - - if not return_obj: - return_obj = self._wait_for_response(resource, name, namespace) - - return return_obj - - def _create_stream(self, resource, namespace, wait_time): - """ Create a stream of events for the object """ - w = None - stream = None - try: - w = watch.Watch() - w._api_client = self.client.client - if namespace: - stream = w.stream(resource.get, serialize=False, namespace=namespace, timeout_seconds=wait_time) - else: - stream = w.stream(resource.get, serialize=False, namespace=namespace, timeout_seconds=wait_time) - except KubernetesException: - pass - return w, stream - - def _read_stream(self, resource, watcher, stream, name, replicas): - """ Wait for ready_replicas to equal the requested number of replicas. """ - return_obj = None - try: - for event in stream: - if event.get('object'): - obj = ResourceInstance(resource, event['object']) - if obj.metadata.name == name and hasattr(obj, 'status'): - if replicas == 0: - if not hasattr(obj.status, 'readyReplicas') or not obj.status.readyReplicas: - return_obj = obj - watcher.stop() - break - if hasattr(obj.status, 'readyReplicas') and obj.status.readyReplicas == replicas: - return_obj = obj - watcher.stop() - break - except Exception as exc: - self.fail_json(msg="Exception reading event stream: {0}".format(exc)) - - if not return_obj: - self.fail_json(msg="Error fetching the patched object. Try a higher wait_timeout value.") - if replicas and return_obj.status.readyReplicas is None: - self.fail_json(msg="Failed to fetch the number of ready replicas. Try a higher wait_timeout value.") - if replicas and return_obj.status.readyReplicas != replicas: - self.fail_json(msg="Number of ready replicas is {0}. Failed to reach {1} ready replicas within " - "the wait_timeout period.".format(return_obj.status.ready_replicas, replicas)) - return return_obj - - def _wait_for_response(self, resource, name, namespace): - """ Wait for an API response """ - tries = 0 - half = math.ceil(20 / 2) - obj = None - - while tries <= half: - obj = resource.get(name=name, namespace=namespace) - if obj: - break - tries += 2 - time.sleep(2) - return obj diff --git a/lib/ansible/modules/clustering/k8s/_k8s_facts.py b/lib/ansible/modules/clustering/k8s/_k8s_facts.py deleted file mode 120000 index 667980ed38a..00000000000 --- a/lib/ansible/modules/clustering/k8s/_k8s_facts.py +++ /dev/null @@ -1 +0,0 @@ -k8s_info.py \ No newline at end of file diff --git a/lib/ansible/modules/clustering/k8s/_k8s_raw.py b/lib/ansible/modules/clustering/k8s/_k8s_raw.py deleted file mode 120000 index b0ee072f962..00000000000 --- a/lib/ansible/modules/clustering/k8s/_k8s_raw.py +++ /dev/null @@ -1 +0,0 @@ -k8s.py \ No newline at end of file diff --git a/lib/ansible/modules/clustering/k8s/k8s.py b/lib/ansible/modules/clustering/k8s/k8s.py deleted file mode 100644 index f3938bf39cd..00000000000 --- a/lib/ansible/modules/clustering/k8s/k8s.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2018, Chris Houseknecht <@chouseknecht> -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' - -module: k8s - -short_description: Manage Kubernetes (K8s) objects - -version_added: "2.6" - -author: - - "Chris Houseknecht (@chouseknecht)" - - "Fabian von Feilitzsch (@fabianvf)" - -description: - - Use the OpenShift Python client to perform CRUD operations on K8s objects. - - Pass the object definition from a source file or inline. See examples for reading - files and using Jinja templates or vault-encrypted files. - - Access to the full range of K8s APIs. - - Use the M(k8s_info) module to obtain a list of items about an object of type C(kind) - - Authenticate using either a config file, certificates, password or token. - - Supports check mode. - -extends_documentation_fragment: - - k8s_state_options - - k8s_name_options - - k8s_resource_options - - k8s_auth_options - -notes: - - If your OpenShift Python library is not 0.9.0 or newer and you are trying to - remove an item from an associative array/dictionary, for example a label or - an annotation, you will need to explicitly set the value of the item to be - removed to `null`. Simply deleting the entry in the dictionary will not - remove it from openshift or kubernetes. - -options: - merge_type: - description: - - Whether to override the default patch merge approach with a specific type. By default, the strategic - merge will typically be used. - - For example, Custom Resource Definitions typically aren't updatable by the usual strategic merge. You may - want to use C(merge) if you see "strategic merge patch format is not supported" - - See U(https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment) - - Requires openshift >= 0.6.2 - - If more than one merge_type is given, the merge_types will be tried in order - - If openshift >= 0.6.2, this defaults to C(['strategic-merge', 'merge']), which is ideal for using the same parameters - on resource kinds that combine Custom Resources and built-in resources. For openshift < 0.6.2, the default - is simply C(strategic-merge). - - mutually exclusive with C(apply) - choices: - - json - - merge - - strategic-merge - type: list - version_added: "2.7" - wait: - description: - - Whether to wait for certain resource kinds to end up in the desired state. By default the module exits once Kubernetes has - received the request - - Implemented for C(state=present) for C(Deployment), C(DaemonSet) and C(Pod), and for C(state=absent) for all resource kinds. - - For resource kinds without an implementation, C(wait) returns immediately unless C(wait_condition) is set. - default: no - type: bool - version_added: "2.8" - wait_sleep: - description: - - Number of seconds to sleep between checks. - default: 5 - version_added: "2.9" - wait_timeout: - description: - - How long in seconds to wait for the resource to end up in the desired state. Ignored if C(wait) is not set. - default: 120 - version_added: "2.8" - wait_condition: - description: - - Specifies a custom condition on the status to wait for. Ignored if C(wait) is not set or is set to False. - suboptions: - type: - description: - - The type of condition to wait for. For example, the C(Pod) resource will set the C(Ready) condition (among others) - - Required if you are specifying a C(wait_condition). If left empty, the C(wait_condition) field will be ignored. - - The possible types for a condition are specific to each resource type in Kubernetes. See the API documentation of the status field - for a given resource to see possible choices. - status: - description: - - The value of the status field in your desired condition. - - For example, if a C(Deployment) is paused, the C(Progressing) C(type) will have the C(Unknown) status. - choices: - - True - - False - - Unknown - reason: - description: - - The value of the reason field in your desired condition - - For example, if a C(Deployment) is paused, The C(Progressing) C(type) will have the C(DeploymentPaused) reason. - - The possible reasons in a condition are specific to each resource type in Kubernetes. See the API documentation of the status field - for a given resource to see possible choices. - version_added: "2.8" - validate: - description: - - how (if at all) to validate the resource definition against the kubernetes schema. - Requires the kubernetes-validate python module - suboptions: - fail_on_error: - description: whether to fail on validation errors. - required: yes - type: bool - version: - description: version of Kubernetes to validate against. defaults to Kubernetes server version - strict: - description: whether to fail when passing unexpected properties - default: no - type: bool - version_added: "2.8" - append_hash: - description: - - Whether to append a hash to a resource name for immutability purposes - - Applies only to ConfigMap and Secret resources - - The parameter will be silently ignored for other resource kinds - - The full definition of an object is needed to generate the hash - this means that deleting an object created with append_hash - will only work if the same object is passed with state=absent (alternatively, just use state=absent with the name including - the generated hash and append_hash=no) - type: bool - version_added: "2.8" - apply: - description: - - C(apply) compares the desired resource definition with the previously supplied resource definition, - ignoring properties that are automatically generated - - C(apply) works better with Services than 'force=yes' - - mutually exclusive with C(merge_type) - type: bool - version_added: "2.9" - -requirements: - - "python >= 2.7" - - "openshift >= 0.6" - - "PyYAML >= 3.11" -''' - -EXAMPLES = ''' -- name: Create a k8s namespace - k8s: - name: testing - api_version: v1 - kind: Namespace - state: present - -- name: Create a Service object from an inline definition - k8s: - state: present - definition: - apiVersion: v1 - kind: Service - metadata: - name: web - namespace: testing - labels: - app: galaxy - service: web - spec: - selector: - app: galaxy - service: web - ports: - - protocol: TCP - targetPort: 8000 - name: port-8000-tcp - port: 8000 - -- name: Remove an existing Service object - k8s: - state: absent - api_version: v1 - kind: Service - namespace: testing - name: web - -# Passing the object definition from a file - -- name: Create a Deployment by reading the definition from a local file - k8s: - state: present - src: /testing/deployment.yml - -- name: >- - Read definition file from the Ansible controller file system. - If the definition file has been encrypted with Ansible Vault it will automatically be decrypted. - k8s: - state: present - definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}" - -- name: Read definition file from the Ansible controller file system after Jinja templating - k8s: - state: present - definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}" - -- name: fail on validation errors - k8s: - state: present - definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}" - validate: - fail_on_error: yes - -- name: warn on validation errors, check for unexpected properties - k8s: - state: present - definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}" - validate: - fail_on_error: no - strict: yes -''' - -RETURN = ''' -result: - description: - - The created, patched, or otherwise present object. Will be empty in the case of a deletion. - returned: success - type: complex - contains: - api_version: - description: The versioned schema of this representation of an object. - returned: success - type: str - kind: - description: Represents the REST resource this object represents. - returned: success - type: str - metadata: - description: Standard object metadata. Includes name, namespace, annotations, labels, etc. - returned: success - type: complex - spec: - description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). - returned: success - type: complex - status: - description: Current status details for the object. - returned: success - type: complex - items: - description: Returned only when multiple yaml documents are passed to src or resource_definition - returned: when resource_definition or src contains list of objects - type: list - duration: - description: elapsed time of task in seconds - returned: when C(wait) is true - type: int - sample: 48 -''' - -from ansible.module_utils.k8s.raw import KubernetesRawModule - - -def main(): - KubernetesRawModule().execute_module() - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/clustering/k8s/k8s_auth.py b/lib/ansible/modules/clustering/k8s/k8s_auth.py deleted file mode 100644 index 8dfd2efcb67..00000000000 --- a/lib/ansible/modules/clustering/k8s/k8s_auth.py +++ /dev/null @@ -1,336 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 2018, KubeVirt Team <@kubevirt> -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' - -module: k8s_auth - -short_description: Authenticate to Kubernetes clusters which require an explicit login step - -version_added: "2.8" - -author: KubeVirt Team (@kubevirt) - -description: - - "This module handles authenticating to Kubernetes clusters requiring I(explicit) authentication procedures, - meaning ones where a client logs in (obtains an authentication token), performs API operations using said - token and then logs out (revokes the token). An example of a Kubernetes distribution requiring this module - is OpenShift." - - "On the other hand a popular configuration for username+password authentication is one utilizing HTTP Basic - Auth, which does not involve any additional login/logout steps (instead login credentials can be attached - to each and every API call performed) and as such is handled directly by the C(k8s) module (and other - resource–specific modules) by utilizing the C(host), C(username) and C(password) parameters. Please - consult your preferred module's documentation for more details." - -options: - state: - description: - - If set to I(present) connect to the API server using the URL specified in C(host) and attempt to log in. - - If set to I(absent) attempt to log out by revoking the authentication token specified in C(api_key). - default: present - choices: - - present - - absent - host: - description: - - Provide a URL for accessing the API server. - required: true - username: - description: - - Provide a username for authenticating with the API server. - password: - description: - - Provide a password for authenticating with the API server. - ca_cert: - description: - - "Path to a CA certificate file used to verify connection to the API server. The full certificate chain - must be provided to avoid certificate validation errors." - aliases: [ ssl_ca_cert ] - validate_certs: - description: - - "Whether or not to verify the API server's SSL certificates." - type: bool - default: true - aliases: [ verify_ssl ] - api_key: - description: - - When C(state) is set to I(absent), this specifies the token to revoke. - -requirements: - - python >= 2.7 - - urllib3 - - requests - - requests-oauthlib -''' - -EXAMPLES = ''' -- hosts: localhost - module_defaults: - group/k8s: - host: https://k8s.example.com/ - ca_cert: ca.pem - tasks: - - block: - # It's good practice to store login credentials in a secure vault and not - # directly in playbooks. - - include_vars: k8s_passwords.yml - - - name: Log in (obtain access token) - k8s_auth: - username: admin - password: "{{ k8s_admin_password }}" - register: k8s_auth_results - - # Previous task provides the token/api_key, while all other parameters - # are taken from module_defaults - - name: Get a list of all pods from any namespace - k8s_info: - api_key: "{{ k8s_auth_results.k8s_auth.api_key }}" - kind: Pod - register: pod_list - - always: - - name: If login succeeded, try to log out (revoke access token) - when: k8s_auth_results.k8s_auth.api_key is defined - k8s_auth: - state: absent - api_key: "{{ k8s_auth_results.k8s_auth.api_key }}" -''' - -# Returned value names need to match k8s modules parameter names, to make it -# easy to pass returned values of k8s_auth to other k8s modules. -# Discussion: https://github.com/ansible/ansible/pull/50807#discussion_r248827899 -RETURN = ''' -k8s_auth: - description: Kubernetes authentication facts. - returned: success - type: complex - contains: - api_key: - description: Authentication token. - returned: success - type: str - host: - description: URL for accessing the API server. - returned: success - type: str - ca_cert: - description: Path to a CA certificate file used to verify connection to the API server. - returned: success - type: str - validate_certs: - description: "Whether or not to verify the API server's SSL certificates." - returned: success - type: bool - username: - description: Username for authenticating with the API server. - returned: success - type: str -''' - - -import traceback - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six.moves.urllib_parse import urlparse, parse_qs, urlencode - -# 3rd party imports -try: - import requests - HAS_REQUESTS = True -except ImportError: - HAS_REQUESTS = False - -try: - from requests_oauthlib import OAuth2Session - HAS_REQUESTS_OAUTH = True -except ImportError: - HAS_REQUESTS_OAUTH = False - -try: - from urllib3.util import make_headers - HAS_URLLIB3 = True -except ImportError: - HAS_URLLIB3 = False - - -K8S_AUTH_ARG_SPEC = { - 'state': { - 'default': 'present', - 'choices': ['present', 'absent'], - }, - 'host': {'required': True}, - 'username': {}, - 'password': {'no_log': True}, - 'ca_cert': {'type': 'path', 'aliases': ['ssl_ca_cert']}, - 'validate_certs': { - 'type': 'bool', - 'default': True, - 'aliases': ['verify_ssl'] - }, - 'api_key': {'no_log': True}, -} - - -class KubernetesAuthModule(AnsibleModule): - def __init__(self): - AnsibleModule.__init__( - self, - argument_spec=K8S_AUTH_ARG_SPEC, - required_if=[ - ('state', 'present', ['username', 'password']), - ('state', 'absent', ['api_key']), - ] - ) - - if not HAS_REQUESTS: - self.fail("This module requires the python 'requests' package. Try `pip install requests`.") - - if not HAS_REQUESTS_OAUTH: - self.fail("This module requires the python 'requests-oauthlib' package. Try `pip install requests-oauthlib`.") - - if not HAS_URLLIB3: - self.fail("This module requires the python 'urllib3' package. Try `pip install urllib3`.") - - def execute_module(self): - state = self.params.get('state') - verify_ssl = self.params.get('validate_certs') - ssl_ca_cert = self.params.get('ca_cert') - - self.auth_username = self.params.get('username') - self.auth_password = self.params.get('password') - self.auth_api_key = self.params.get('api_key') - self.con_host = self.params.get('host') - - # python-requests takes either a bool or a path to a ca file as the 'verify' param - if verify_ssl and ssl_ca_cert: - self.con_verify_ca = ssl_ca_cert # path - else: - self.con_verify_ca = verify_ssl # bool - - # Get needed info to access authorization APIs - self.openshift_discover() - - if state == 'present': - new_api_key = self.openshift_login() - result = dict( - host=self.con_host, - validate_certs=verify_ssl, - ca_cert=ssl_ca_cert, - api_key=new_api_key, - username=self.auth_username, - ) - else: - self.openshift_logout() - result = dict() - - self.exit_json(changed=False, k8s_auth=result) - - def openshift_discover(self): - url = '{0}/.well-known/oauth-authorization-server'.format(self.con_host) - ret = requests.get(url, verify=self.con_verify_ca) - - if ret.status_code != 200: - self.fail_request("Couldn't find OpenShift's OAuth API", method='GET', url=url, - reason=ret.reason, status_code=ret.status_code) - - try: - oauth_info = ret.json() - - self.openshift_auth_endpoint = oauth_info['authorization_endpoint'] - self.openshift_token_endpoint = oauth_info['token_endpoint'] - except Exception as e: - self.fail_json(msg="Something went wrong discovering OpenShift OAuth details.", - exception=traceback.format_exc()) - - def openshift_login(self): - os_oauth = OAuth2Session(client_id='openshift-challenging-client') - authorization_url, state = os_oauth.authorization_url(self.openshift_auth_endpoint, - state="1", code_challenge_method='S256') - auth_headers = make_headers(basic_auth='{0}:{1}'.format(self.auth_username, self.auth_password)) - - # Request authorization code using basic auth credentials - ret = os_oauth.get( - authorization_url, - headers={'X-Csrf-Token': state, 'authorization': auth_headers.get('authorization')}, - verify=self.con_verify_ca, - allow_redirects=False - ) - - if ret.status_code != 302: - self.fail_request("Authorization failed.", method='GET', url=authorization_url, - reason=ret.reason, status_code=ret.status_code) - - # In here we have `code` and `state`, I think `code` is the important one - qwargs = {} - for k, v in parse_qs(urlparse(ret.headers['Location']).query).items(): - qwargs[k] = v[0] - qwargs['grant_type'] = 'authorization_code' - - # Using authorization code given to us in the Location header of the previous request, request a token - ret = os_oauth.post( - self.openshift_token_endpoint, - headers={ - 'Accept': 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded', - # This is just base64 encoded 'openshift-challenging-client:' - 'Authorization': 'Basic b3BlbnNoaWZ0LWNoYWxsZW5naW5nLWNsaWVudDo=' - }, - data=urlencode(qwargs), - verify=self.con_verify_ca - ) - - if ret.status_code != 200: - self.fail_request("Failed to obtain an authorization token.", method='POST', - url=self.openshift_token_endpoint, - reason=ret.reason, status_code=ret.status_code) - - return ret.json()['access_token'] - - def openshift_logout(self): - url = '{0}/apis/oauth.openshift.io/v1/oauthaccesstokens/{1}'.format(self.con_host, self.auth_api_key) - headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': 'Bearer {0}'.format(self.auth_api_key) - } - json = { - "apiVersion": "oauth.openshift.io/v1", - "kind": "DeleteOptions" - } - - ret = requests.delete(url, headers=headers, json=json, verify=self.con_verify_ca) - # Ignore errors, the token will time out eventually anyway - - def fail(self, msg=None): - self.fail_json(msg=msg) - - def fail_request(self, msg, **kwargs): - req_info = {} - for k, v in kwargs.items(): - req_info['req_' + k] = v - self.fail_json(msg=msg, **req_info) - - -def main(): - module = KubernetesAuthModule() - try: - module.execute_module() - except Exception as e: - module.fail_json(msg=str(e), exception=traceback.format_exc()) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/clustering/k8s/k8s_info.py b/lib/ansible/modules/clustering/k8s/k8s_info.py deleted file mode 100644 index 99a8fd8cecb..00000000000 --- a/lib/ansible/modules/clustering/k8s/k8s_info.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2018, Will Thames <@willthames> -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' -module: k8s_info - -short_description: Describe Kubernetes (K8s) objects - -version_added: "2.7" - -author: - - "Will Thames (@willthames)" - -description: - - Use the OpenShift Python client to perform read operations on K8s objects. - - Access to the full range of K8s APIs. - - Authenticate using either a config file, certificates, password or token. - - Supports check mode. - - This module was called C(k8s_facts) before Ansible 2.9. The usage did not change. - -options: - api_version: - description: - - Use to specify the API version. in conjunction with I(kind), I(name), and I(namespace) to identify a - specific object. - default: v1 - aliases: - - api - - version - kind: - description: - - Use to specify an object model. Use in conjunction with I(api_version), I(name), and I(namespace) to identify a - specific object. - required: yes - name: - description: - - Use to specify an object name. Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a - specific object. - namespace: - description: - - Use to specify an object namespace. Use in conjunction with I(api_version), I(kind), and I(name) - to identify a specific object. - label_selectors: - description: List of label selectors to use to filter results - field_selectors: - description: List of field selectors to use to filter results - -extends_documentation_fragment: - - k8s_auth_options - -requirements: - - "python >= 2.7" - - "openshift >= 0.6" - - "PyYAML >= 3.11" -''' - -EXAMPLES = ''' -- name: Get an existing Service object - k8s_info: - api_version: v1 - kind: Service - name: web - namespace: testing - register: web_service - -- name: Get a list of all service objects - k8s_info: - api_version: v1 - kind: Service - namespace: testing - register: service_list - -- name: Get a list of all pods from any namespace - k8s_info: - kind: Pod - register: pod_list - -- name: Search for all Pods labelled app=web - k8s_info: - kind: Pod - label_selectors: - - app = web - - tier in (dev, test) - -- name: Search for all running pods - k8s_info: - kind: Pod - field_selectors: - - status.phase=Running -''' - -RETURN = ''' -resources: - description: - - The object(s) that exists - returned: success - type: complex - contains: - api_version: - description: The versioned schema of this representation of an object. - returned: success - type: str - kind: - description: Represents the REST resource this object represents. - returned: success - type: str - metadata: - description: Standard object metadata. Includes name, namespace, annotations, labels, etc. - returned: success - type: dict - spec: - description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). - returned: success - type: dict - status: - description: Current status details for the object. - returned: success - type: dict -''' - - -from ansible.module_utils.k8s.common import KubernetesAnsibleModule, AUTH_ARG_SPEC -import copy - - -class KubernetesInfoModule(KubernetesAnsibleModule): - - def __init__(self, *args, **kwargs): - KubernetesAnsibleModule.__init__(self, *args, - supports_check_mode=True, - **kwargs) - if self._name == 'k8s_facts': - self.deprecate("The 'k8s_facts' module has been renamed to 'k8s_info'", version='2.13') - - def execute_module(self): - self.client = self.get_api_client() - - self.exit_json(changed=False, - **self.kubernetes_facts(self.params['kind'], - self.params['api_version'], - self.params['name'], - self.params['namespace'], - self.params['label_selectors'], - self.params['field_selectors'])) - - @property - def argspec(self): - args = copy.deepcopy(AUTH_ARG_SPEC) - args.update( - dict( - kind=dict(required=True), - api_version=dict(default='v1', aliases=['api', 'version']), - name=dict(), - namespace=dict(), - label_selectors=dict(type='list', default=[]), - field_selectors=dict(type='list', default=[]), - ) - ) - return args - - -def main(): - KubernetesInfoModule().execute_module() - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/clustering/k8s/k8s_scale.py b/lib/ansible/modules/clustering/k8s/k8s_scale.py deleted file mode 100644 index 2afb0f959fa..00000000000 --- a/lib/ansible/modules/clustering/k8s/k8s_scale.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2018, Chris Houseknecht <@chouseknecht> -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - - -__metaclass__ = type - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' - -module: k8s_scale - -short_description: Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job. - -version_added: "2.5" - -author: - - "Chris Houseknecht (@chouseknecht)" - - "Fabian von Feilitzsch (@fabianvf)" - -description: - - Similar to the kubectl scale command. Use to set the number of replicas for a Deployment, ReplicaSet, - or Replication Controller, or the parallelism attribute of a Job. Supports check mode. - -extends_documentation_fragment: - - k8s_name_options - - k8s_auth_options - - k8s_resource_options - - k8s_scale_options - -requirements: - - "python >= 2.7" - - "openshift >= 0.6" - - "PyYAML >= 3.11" -''' - -EXAMPLES = ''' -- name: Scale deployment up, and extend timeout - k8s_scale: - api_version: v1 - kind: Deployment - name: elastic - namespace: myproject - replicas: 3 - wait_timeout: 60 - -- name: Scale deployment down when current replicas match - k8s_scale: - api_version: v1 - kind: Deployment - name: elastic - namespace: myproject - current_replicas: 3 - replicas: 2 - -- name: Increase job parallelism - k8s_scale: - api_version: batch/v1 - kind: job - name: pi-with-timeout - namespace: testing - replicas: 2 - -# Match object using local file or inline definition - -- name: Scale deployment based on a file from the local filesystem - k8s_scale: - src: /myproject/elastic_deployment.yml - replicas: 3 - wait: no - -- name: Scale deployment based on a template output - k8s_scale: - resource_definition: "{{ lookup('template', '/myproject/elastic_deployment.yml') | from_yaml }}" - replicas: 3 - wait: no - -- name: Scale deployment based on a file from the Ansible controller filesystem - k8s_scale: - resource_definition: "{{ lookup('file', '/myproject/elastic_deployment.yml') | from_yaml }}" - replicas: 3 - wait: no -''' - -RETURN = ''' -result: - description: - - If a change was made, will return the patched object, otherwise returns the existing object. - returned: success - type: complex - contains: - api_version: - description: The versioned schema of this representation of an object. - returned: success - type: str - kind: - description: Represents the REST resource this object represents. - returned: success - type: str - metadata: - description: Standard object metadata. Includes name, namespace, annotations, labels, etc. - returned: success - type: complex - spec: - description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). - returned: success - type: complex - status: - description: Current status details for the object. - returned: success - type: complex -''' - -from ansible.module_utils.k8s.scale import KubernetesAnsibleScaleModule - - -def main(): - KubernetesAnsibleScaleModule().execute_module() - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/clustering/k8s/k8s_service.py b/lib/ansible/modules/clustering/k8s/k8s_service.py deleted file mode 100644 index 82a898283c1..00000000000 --- a/lib/ansible/modules/clustering/k8s/k8s_service.py +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 2018, KubeVirt Team <@kubevirt> -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' - -module: k8s_service - -short_description: Manage Services on Kubernetes - -version_added: "2.8" - -author: KubeVirt Team (@kubevirt) - -description: - - Use Openshift Python SDK to manage Services on Kubernetes - -extends_documentation_fragment: - - k8s_auth_options - -options: - resource_definition: - description: - - A partial YAML definition of the Service object being created/updated. Here you can define Kubernetes - Service Resource parameters not covered by this module's parameters. - - "NOTE: I(resource_definition) has lower priority than module parameters. If you try to define e.g. - I(metadata.namespace) here, that value will be ignored and I(metadata) used instead." - aliases: - - definition - - inline - type: dict - state: - description: - - Determines if an object should be created, patched, or deleted. When set to C(present), an object will be - created, if it does not already exist. If set to C(absent), an existing object will be deleted. If set to - C(present), an existing object will be patched, if its attributes differ from those specified using - module options and I(resource_definition). - default: present - choices: - - present - - absent - force: - description: - - If set to C(True), and I(state) is C(present), an existing object will be replaced. - default: false - type: bool - merge_type: - description: - - Whether to override the default patch merge approach with a specific type. By default, the strategic - merge will typically be used. - - For example, Custom Resource Definitions typically aren't updatable by the usual strategic merge. You may - want to use C(merge) if you see "strategic merge patch format is not supported" - - See U(https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment) - - Requires openshift >= 0.6.2 - - If more than one merge_type is given, the merge_types will be tried in order - - If openshift >= 0.6.2, this defaults to C(['strategic-merge', 'merge']), which is ideal for using the same parameters - on resource kinds that combine Custom Resources and built-in resources. For openshift < 0.6.2, the default - is simply C(strategic-merge). - choices: - - json - - merge - - strategic-merge - type: list - name: - description: - - Use to specify a Service object name. - required: true - type: str - namespace: - description: - - Use to specify a Service object namespace. - required: true - type: str - type: - description: - - Specifies the type of Service to create. - - See U(https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) - choices: - - NodePort - - ClusterIP - - LoadBalancer - - ExternalName - ports: - description: - - A list of ports to expose. - - U(https://kubernetes.io/docs/concepts/services-networking/service/#multi-port-services) - type: list - selector: - description: - - Label selectors identify objects this Service should apply to. - - U(https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) - type: dict - -requirements: - - python >= 2.7 - - openshift >= 0.6.2 -''' - -EXAMPLES = ''' -- name: Expose https port with ClusterIP - k8s_service: - state: present - name: test-https - namespace: default - ports: - - port: 443 - protocol: TCP - selector: - key: special - -- name: Expose https port with ClusterIP using spec - k8s_service: - state: present - name: test-https - namespace: default - inline: - spec: - ports: - - port: 443 - protocol: TCP - selector: - key: special -''' - -RETURN = ''' -result: - description: - - The created, patched, or otherwise present Service object. Will be empty in the case of a deletion. - returned: success - type: complex - contains: - api_version: - description: The versioned schema of this representation of an object. - returned: success - type: str - kind: - description: Always 'Service'. - returned: success - type: str - metadata: - description: Standard object metadata. Includes name, namespace, annotations, labels, etc. - returned: success - type: complex - spec: - description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). - returned: success - type: complex - status: - description: Current status details for the object. - returned: success - type: complex -''' - -import copy -import traceback - -from collections import defaultdict - -from ansible.module_utils.k8s.common import AUTH_ARG_SPEC, COMMON_ARG_SPEC -from ansible.module_utils.k8s.raw import KubernetesRawModule - - -SERVICE_ARG_SPEC = { - 'state': { - 'default': 'present', - 'choices': ['present', 'absent'], - }, - 'force': { - 'type': 'bool', - 'default': False, - }, - 'resource_definition': { - 'type': 'dict', - 'aliases': ['definition', 'inline'] - }, - 'name': {'required': True}, - 'namespace': {'required': True}, - 'merge_type': {'type': 'list', 'choices': ['json', 'merge', 'strategic-merge']}, - 'selector': {'type': 'dict'}, - 'type': { - 'type': 'str', - 'choices': [ - 'NodePort', 'ClusterIP', 'LoadBalancer', 'ExternalName' - ], - }, - 'ports': {'type': 'list'}, -} - - -class KubernetesService(KubernetesRawModule): - def __init__(self, *args, **kwargs): - super(KubernetesService, self).__init__(*args, k8s_kind='Service', **kwargs) - - @staticmethod - def merge_dicts(x, y): - for k in set(x.keys()).union(y.keys()): - if k in x and k in y: - if isinstance(x[k], dict) and isinstance(y[k], dict): - yield (k, dict(KubernetesService.merge_dicts(x[k], y[k]))) - else: - yield (k, y[k]) - elif k in x: - yield (k, x[k]) - else: - yield (k, y[k]) - - @property - def argspec(self): - """ argspec property builder """ - argument_spec = copy.deepcopy(AUTH_ARG_SPEC) - argument_spec.update(SERVICE_ARG_SPEC) - return argument_spec - - def execute_module(self): - """ Module execution """ - self.client = self.get_api_client() - - api_version = 'v1' - selector = self.params.get('selector') - service_type = self.params.get('type') - ports = self.params.get('ports') - - definition = defaultdict(defaultdict) - - definition['kind'] = 'Service' - definition['apiVersion'] = api_version - - def_spec = definition['spec'] - def_spec['type'] = service_type - def_spec['ports'] = ports - def_spec['selector'] = selector - - def_meta = definition['metadata'] - def_meta['name'] = self.params.get('name') - def_meta['namespace'] = self.params.get('namespace') - - # 'resource_definition:' has lower priority than module parameters - definition = dict(self.merge_dicts(self.resource_definitions[0], definition)) - - resource = self.find_resource('Service', api_version, fail=True) - definition = self.set_defaults(resource, definition) - result = self.perform_action(resource, definition) - - self.exit_json(**result) - - -def main(): - module = KubernetesService() - try: - module.execute_module() - except Exception as e: - module.fail_json(msg=str(e), exception=traceback.format_exc()) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/clustering/openshift/_openshift_raw.py b/lib/ansible/modules/clustering/openshift/_openshift_raw.py deleted file mode 120000 index 53f9af49a89..00000000000 --- a/lib/ansible/modules/clustering/openshift/_openshift_raw.py +++ /dev/null @@ -1 +0,0 @@ -../k8s/k8s.py \ No newline at end of file diff --git a/lib/ansible/modules/clustering/openshift/_openshift_scale.py b/lib/ansible/modules/clustering/openshift/_openshift_scale.py deleted file mode 120000 index bfa5417ea0e..00000000000 --- a/lib/ansible/modules/clustering/openshift/_openshift_scale.py +++ /dev/null @@ -1 +0,0 @@ -../k8s/k8s_scale.py \ No newline at end of file diff --git a/lib/ansible/plugins/doc_fragments/k8s_auth_options.py b/lib/ansible/plugins/doc_fragments/k8s_auth_options.py deleted file mode 100644 index 186202e9f5b..00000000000 --- a/lib/ansible/plugins/doc_fragments/k8s_auth_options.py +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: (c) 2018, Red Hat | Ansible -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# Options for authenticating with the API. - - -class ModuleDocFragment(object): - - DOCUMENTATION = r''' -options: - host: - description: - - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable. - type: str - api_key: - description: - - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable. - type: str - kubeconfig: - description: - - Path to an existing Kubernetes config file. If not provided, and no other connection - options are provided, the openshift client will attempt to load the default - configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG environment - variable. - type: path - context: - description: - - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable. - type: str - username: - description: - - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment - variable. - - Please note that this only works with clusters configured to use HTTP Basic Auth. If your cluster has a - different form of authentication (e.g. OAuth2 in OpenShift), this option will not work as expected and you - should look into the C(k8s_auth) module, as that might do what you need. - type: str - password: - description: - - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment - variable. - - Please read the description of the C(username) option for a discussion of when this option is applicable. - type: str - client_cert: - description: - - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment - variable. - type: path - aliases: [ cert_file ] - client_key: - description: - - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment - variable. - type: path - aliases: [ key_file ] - ca_cert: - description: - - Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to - avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable. - type: path - aliases: [ ssl_ca_cert ] - validate_certs: - description: - - Whether or not to verify the API server's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL - environment variable. - type: bool - aliases: [ verify_ssl ] - proxy: - description: - - The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable. - - Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY). - version_added: "2.9" - persist_config: - description: - - Whether or not to save the kube config refresh tokens. - Can also be specified via K8S_AUTH_PERSIST_CONFIG environment variable. - - When the k8s context is using a user credentials with refresh tokens (like oidc or gke/gcloud auth), - the token is refreshed by the k8s python client library but not saved by default. So the old refresh token can - expire and the next auth might fail. Setting this flag to true will tell the k8s python client to save the - new refresh token to the kube config file. - - Default to false. - - Please note that the current version of the k8s python client library does not support setting this flag to True yet. - - "The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169" - type: bool - version_added: "2.10" -notes: - - "The OpenShift Python client wraps the K8s Python client, providing full access to - all of the APIS and models available on both platforms. For API version details and - additional information visit https://github.com/openshift/openshift-restclient-python" - - "To avoid SSL certificate validation errors when C(validate_certs) is I(True), the full - certificate chain for the API server must be provided via C(ca_cert) or in the - kubeconfig file." -''' diff --git a/lib/ansible/plugins/doc_fragments/k8s_name_options.py b/lib/ansible/plugins/doc_fragments/k8s_name_options.py deleted file mode 100644 index 74375cc17ac..00000000000 --- a/lib/ansible/plugins/doc_fragments/k8s_name_options.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: (c) 2018, Red Hat | Ansible -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# Options for selecting or identifying a specific K8s object - - -class ModuleDocFragment(object): - - DOCUMENTATION = r''' -options: - api_version: - description: - - Use to specify the API version. Use to create, delete, or discover an object without providing a full - resource definition. Use in conjunction with I(kind), I(name), and I(namespace) to identify a - specific object. If I(resource definition) is provided, the I(apiVersion) from the I(resource_definition) - will override this option. - type: str - default: v1 - aliases: - - api - - version - kind: - description: - - Use to specify an object model. Use to create, delete, or discover an object without providing a full - resource definition. Use in conjunction with I(api_version), I(name), and I(namespace) to identify a - specific object. If I(resource definition) is provided, the I(kind) from the I(resource_definition) - will override this option. - type: str - name: - description: - - Use to specify an object name. Use to create, delete, or discover an object without providing a full - resource definition. Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a - specific object. If I(resource definition) is provided, the I(metadata.name) value from the - I(resource_definition) will override this option. - type: str - namespace: - description: - - Use to specify an object namespace. Useful when creating, deleting, or discovering an object without - providing a full resource definition. Use in conjunction with I(api_version), I(kind), and I(name) - to identify a specfic object. If I(resource definition) is provided, the I(metadata.namespace) value - from the I(resource_definition) will override this option. - type: str -''' diff --git a/lib/ansible/plugins/doc_fragments/k8s_resource_options.py b/lib/ansible/plugins/doc_fragments/k8s_resource_options.py deleted file mode 100644 index 6e35a8ea3f7..00000000000 --- a/lib/ansible/plugins/doc_fragments/k8s_resource_options.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: (c) 2018, Red Hat | Ansible -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# Options for providing an object configuration - - -class ModuleDocFragment(object): - - DOCUMENTATION = r''' -options: - resource_definition: - description: - - "Provide a valid YAML definition (either as a string, list, or dict) for an object when creating or updating. NOTE: I(kind), I(api_version), I(name), - and I(namespace) will be overwritten by corresponding values found in the provided I(resource_definition)." - aliases: - - definition - - inline - src: - description: - - "Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually - exclusive with I(resource_definition). NOTE: I(kind), I(api_version), I(name), and I(namespace) will be - overwritten by corresponding values found in the configuration read in from the I(src) file." - - Reads from the local file system. To read from the Ansible controller's file system, including vaulted files, use the file lookup - plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to - I(resource_definition). See Examples below. - type: path -''' diff --git a/lib/ansible/plugins/doc_fragments/k8s_scale_options.py b/lib/ansible/plugins/doc_fragments/k8s_scale_options.py deleted file mode 100644 index 11b156c1fc0..00000000000 --- a/lib/ansible/plugins/doc_fragments/k8s_scale_options.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: (c) 2018, Red Hat | Ansible -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# Options used by scale modules. - - -class ModuleDocFragment(object): - - DOCUMENTATION = r''' -options: - replicas: - description: - - The desired number of replicas. - type: int - current_replicas: - description: - - For Deployment, ReplicaSet, Replication Controller, only scale, if the number of existing replicas - matches. In the case of a Job, update parallelism only if the current parallelism value matches. - type: int - resource_version: - description: - - Only attempt to scale, if the current object version matches. - type: str - wait: - description: - - For Deployment, ReplicaSet, Replication Controller, wait for the status value of I(ready_replicas) to change - to the number of I(replicas). In the case of a Job, this option is ignored. - type: bool - default: yes - wait_timeout: - description: - - When C(wait) is I(True), the number of seconds to wait for the I(ready_replicas) status to equal I(replicas). - If the status is not reached within the allotted time, an error will result. In the case of a Job, this option - is ignored. - type: int - default: 20 -''' diff --git a/lib/ansible/plugins/doc_fragments/k8s_state_options.py b/lib/ansible/plugins/doc_fragments/k8s_state_options.py deleted file mode 100644 index 724962068d0..00000000000 --- a/lib/ansible/plugins/doc_fragments/k8s_state_options.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: (c) 2018, Red Hat | Ansible -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# Options for specifying object state - - -class ModuleDocFragment(object): - - DOCUMENTATION = r''' -options: - state: - description: - - Determines if an object should be created, patched, or deleted. When set to C(present), an object will be - created, if it does not already exist. If set to C(absent), an existing object will be deleted. If set to - C(present), an existing object will be patched, if its attributes differ from those specified using - I(resource_definition) or I(src). - type: str - default: present - choices: [ absent, present ] - force: - description: - - If set to C(yes), and I(state) is C(present), an existing object will be replaced. - type: bool - default: no -''' diff --git a/lib/ansible/plugins/filter/k8s.py b/lib/ansible/plugins/filter/k8s.py deleted file mode 100644 index f6cb0579859..00000000000 --- a/lib/ansible/plugins/filter/k8s.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) 2017 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - - -ANSIBLE_METADATA = { - 'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community' -} - - -try: - from openshift.helper.hashes import generate_hash - HAS_GENERATE_HASH = True -except ImportError: - HAS_GENERATE_HASH = False - -from ansible.errors import AnsibleFilterError - - -def k8s_config_resource_name(resource): - if not HAS_GENERATE_HASH: - raise AnsibleFilterError("k8s_config_resource_name requires openshift>=0.7.2") - try: - return resource['metadata']['name'] + '-' + generate_hash(resource) - except KeyError: - raise AnsibleFilterError("resource must have a metadata.name key to generate a resource name") - - -# ---- Ansible filters ---- -class FilterModule(object): - - def filters(self): - return { - 'k8s_config_resource_name': k8s_config_resource_name - } diff --git a/lib/ansible/plugins/inventory/k8s.py b/lib/ansible/plugins/inventory/k8s.py deleted file mode 100644 index 9ea955410d0..00000000000 --- a/lib/ansible/plugins/inventory/k8s.py +++ /dev/null @@ -1,360 +0,0 @@ -# Copyright (c) 2018 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = ''' - name: k8s - plugin_type: inventory - author: - - Chris Houseknecht <@chouseknecht> - - Fabian von Feilitzsch <@fabianvf> - - short_description: Kubernetes (K8s) inventory source - - description: - - Fetch containers and services for one or more clusters - - Groups by cluster name, namespace, namespace_services, namespace_pods, and labels - - Uses k8s.(yml|yaml) YAML configuration file to set parameter values. - - options: - plugin: - description: token that ensures this is a source file for the 'k8s' plugin. - required: True - choices: ['k8s'] - connections: - description: - - Optional list of cluster connection settings. If no connections are provided, the default - I(~/.kube/config) and active context will be used, and objects will be returned for all namespaces - the active user is authorized to access. - name: - description: - - Optional name to assign to the cluster. If not provided, a name is constructed from the server - and port. - kubeconfig: - description: - - Path to an existing Kubernetes config file. If not provided, and no other connection - options are provided, the OpenShift client will attempt to load the default - configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG - environment variable. - context: - description: - - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment - variable. - host: - description: - - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable. - api_key: - description: - - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment - variable. - username: - description: - - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME - environment variable. - password: - description: - - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD - environment variable. - client_cert: - description: - - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE - environment variable. - aliases: [ cert_file ] - client_key: - description: - - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE - environment variable. - aliases: [ key_file ] - ca_cert: - description: - - Path to a CA certificate used to authenticate with the API. Can also be specified via - K8S_AUTH_SSL_CA_CERT environment variable. - aliases: [ ssl_ca_cert ] - validate_certs: - description: - - "Whether or not to verify the API server's SSL certificates. Can also be specified via - K8S_AUTH_VERIFY_SSL environment variable." - type: bool - aliases: [ verify_ssl ] - namespaces: - description: - - List of namespaces. If not specified, will fetch all containers for all namespaces user is authorized - to access. - - requirements: - - "python >= 2.7" - - "openshift >= 0.6" - - "PyYAML >= 3.11" -''' - -EXAMPLES = ''' -# File must be named k8s.yaml or k8s.yml - -# Authenticate with token, and return all pods and services for all namespaces -plugin: k8s -connections: - - host: https://192.168.64.4:8443 - token: xxxxxxxxxxxxxxxx - validate_certs: false - -# Use default config (~/.kube/config) file and active context, and return objects for a specific namespace -plugin: k8s -connections: - - namespaces: - - testing - -# Use a custom config file, and a specific context. -plugin: k8s -connections: - - kubeconfig: /path/to/config - context: 'awx/192-168-64-4:8443/developer' -''' - -import json - -from ansible.errors import AnsibleError -from ansible.module_utils.k8s.common import K8sAnsibleMixin, HAS_K8S_MODULE_HELPER, k8s_import_exception -from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable - -try: - from openshift.dynamic.exceptions import DynamicApiError -except ImportError: - pass - - -def format_dynamic_api_exc(exc): - if exc.body: - if exc.headers and exc.headers.get('Content-Type') == 'application/json': - message = json.loads(exc.body).get('message') - if message: - return message - return exc.body - else: - return '%s Reason: %s' % (exc.status, exc.reason) - - -class K8sInventoryException(Exception): - pass - - -class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleMixin): - NAME = 'k8s' - - transport = 'kubectl' - - def parse(self, inventory, loader, path, cache=True): - super(InventoryModule, self).parse(inventory, loader, path) - cache_key = self._get_cache_prefix(path) - config_data = self._read_config_data(path) - self.setup(config_data, cache, cache_key) - - def setup(self, config_data, cache, cache_key): - connections = config_data.get('connections') - - if not HAS_K8S_MODULE_HELPER: - raise K8sInventoryException( - "This module requires the OpenShift Python client. Try `pip install openshift`. Detail: {0}".format(k8s_import_exception) - ) - - source_data = None - if cache and cache_key in self._cache: - try: - source_data = self._cache[cache_key] - except KeyError: - pass - - if not source_data: - self.fetch_objects(connections) - - def fetch_objects(self, connections): - - if connections: - if not isinstance(connections, list): - raise K8sInventoryException("Expecting connections to be a list.") - - for connection in connections: - if not isinstance(connection, dict): - raise K8sInventoryException("Expecting connection to be a dictionary.") - client = self.get_api_client(**connection) - name = connection.get('name', self.get_default_host_name(client.configuration.host)) - if connection.get('namespaces'): - namespaces = connection['namespaces'] - else: - namespaces = self.get_available_namespaces(client) - for namespace in namespaces: - self.get_pods_for_namespace(client, name, namespace) - self.get_services_for_namespace(client, name, namespace) - else: - client = self.get_api_client() - name = self.get_default_host_name(client.configuration.host) - namespaces = self.get_available_namespaces(client) - for namespace in namespaces: - self.get_pods_for_namespace(client, name, namespace) - self.get_services_for_namespace(client, name, namespace) - - @staticmethod - def get_default_host_name(host): - return host.replace('https://', '').replace('http://', '').replace('.', '-').replace(':', '_') - - def get_available_namespaces(self, client): - v1_namespace = client.resources.get(api_version='v1', kind='Namespace') - try: - obj = v1_namespace.get() - except DynamicApiError as exc: - self.display.debug(exc) - raise K8sInventoryException('Error fetching Namespace list: %s' % format_dynamic_api_exc(exc)) - return [namespace.metadata.name for namespace in obj.items] - - def get_pods_for_namespace(self, client, name, namespace): - v1_pod = client.resources.get(api_version='v1', kind='Pod') - try: - obj = v1_pod.get(namespace=namespace) - except DynamicApiError as exc: - self.display.debug(exc) - raise K8sInventoryException('Error fetching Pod list: %s' % format_dynamic_api_exc(exc)) - - namespace_group = 'namespace_{0}'.format(namespace) - namespace_pods_group = '{0}_pods'.format(namespace_group) - - self.inventory.add_group(name) - self.inventory.add_group(namespace_group) - self.inventory.add_child(name, namespace_group) - self.inventory.add_group(namespace_pods_group) - self.inventory.add_child(namespace_group, namespace_pods_group) - - for pod in obj.items: - pod_name = pod.metadata.name - pod_groups = [] - pod_annotations = {} if not pod.metadata.annotations else dict(pod.metadata.annotations) - - if pod.metadata.labels: - # create a group for each label_value - for key, value in pod.metadata.labels: - group_name = 'label_{0}_{1}'.format(key, value) - if group_name not in pod_groups: - pod_groups.append(group_name) - self.inventory.add_group(group_name) - pod_labels = dict(pod.metadata.labels) - else: - pod_labels = {} - - if not pod.status.containerStatuses: - continue - - for container in pod.status.containerStatuses: - # add each pod_container to the namespace group, and to each label_value group - container_name = '{0}_{1}'.format(pod.metadata.name, container.name) - self.inventory.add_host(container_name) - self.inventory.add_child(namespace_pods_group, container_name) - if pod_groups: - for group in pod_groups: - self.inventory.add_child(group, container_name) - - # Add hostvars - self.inventory.set_variable(container_name, 'object_type', 'pod') - self.inventory.set_variable(container_name, 'labels', pod_labels) - self.inventory.set_variable(container_name, 'annotations', pod_annotations) - self.inventory.set_variable(container_name, 'cluster_name', pod.metadata.clusterName) - self.inventory.set_variable(container_name, 'pod_node_name', pod.spec.nodeName) - self.inventory.set_variable(container_name, 'pod_name', pod.spec.name) - self.inventory.set_variable(container_name, 'pod_host_ip', pod.status.hostIP) - self.inventory.set_variable(container_name, 'pod_phase', pod.status.phase) - self.inventory.set_variable(container_name, 'pod_ip', pod.status.podIP) - self.inventory.set_variable(container_name, 'pod_self_link', pod.metadata.selfLink) - self.inventory.set_variable(container_name, 'pod_resource_version', pod.metadata.resourceVersion) - self.inventory.set_variable(container_name, 'pod_uid', pod.metadata.uid) - self.inventory.set_variable(container_name, 'container_name', container.image) - self.inventory.set_variable(container_name, 'container_image', container.image) - if container.state.running: - self.inventory.set_variable(container_name, 'container_state', 'Running') - if container.state.terminated: - self.inventory.set_variable(container_name, 'container_state', 'Terminated') - if container.state.waiting: - self.inventory.set_variable(container_name, 'container_state', 'Waiting') - self.inventory.set_variable(container_name, 'container_ready', container.ready) - self.inventory.set_variable(container_name, 'ansible_remote_tmp', '/tmp/') - self.inventory.set_variable(container_name, 'ansible_connection', self.transport) - self.inventory.set_variable(container_name, 'ansible_{0}_pod'.format(self.transport), - pod_name) - self.inventory.set_variable(container_name, 'ansible_{0}_container'.format(self.transport), - container.name) - self.inventory.set_variable(container_name, 'ansible_{0}_namespace'.format(self.transport), - namespace) - - def get_services_for_namespace(self, client, name, namespace): - v1_service = client.resources.get(api_version='v1', kind='Service') - try: - obj = v1_service.get(namespace=namespace) - except DynamicApiError as exc: - self.display.debug(exc) - raise K8sInventoryException('Error fetching Service list: %s' % format_dynamic_api_exc(exc)) - - namespace_group = 'namespace_{0}'.format(namespace) - namespace_services_group = '{0}_services'.format(namespace_group) - - self.inventory.add_group(name) - self.inventory.add_group(namespace_group) - self.inventory.add_child(name, namespace_group) - self.inventory.add_group(namespace_services_group) - self.inventory.add_child(namespace_group, namespace_services_group) - - for service in obj.items: - service_name = service.metadata.name - service_labels = {} if not service.metadata.labels else dict(service.metadata.labels) - service_annotations = {} if not service.metadata.annotations else dict(service.metadata.annotations) - - self.inventory.add_host(service_name) - - if service.metadata.labels: - # create a group for each label_value - for key, value in service.metadata.labels: - group_name = 'label_{0}_{1}'.format(key, value) - self.inventory.add_group(group_name) - self.inventory.add_child(group_name, service_name) - - try: - self.inventory.add_child(namespace_services_group, service_name) - except AnsibleError as e: - raise - - ports = [{'name': port.name, - 'port': port.port, - 'protocol': port.protocol, - 'targetPort': port.targetPort, - 'nodePort': port.nodePort} for port in service.spec.ports or []] - - # add hostvars - self.inventory.set_variable(service_name, 'object_type', 'service') - self.inventory.set_variable(service_name, 'labels', service_labels) - self.inventory.set_variable(service_name, 'annotations', service_annotations) - self.inventory.set_variable(service_name, 'cluster_name', service.metadata.clusterName) - self.inventory.set_variable(service_name, 'ports', ports) - self.inventory.set_variable(service_name, 'type', service.spec.type) - self.inventory.set_variable(service_name, 'self_link', service.metadata.selfLink) - self.inventory.set_variable(service_name, 'resource_version', service.metadata.resourceVersion) - self.inventory.set_variable(service_name, 'uid', service.metadata.uid) - - if service.spec.externalTrafficPolicy: - self.inventory.set_variable(service_name, 'external_traffic_policy', - service.spec.externalTrafficPolicy) - if service.spec.externalIPs: - self.inventory.set_variable(service_name, 'external_ips', service.spec.externalIPs) - - if service.spec.externalName: - self.inventory.set_variable(service_name, 'external_name', service.spec.externalName) - - if service.spec.healthCheckNodePort: - self.inventory.set_variable(service_name, 'health_check_node_port', - service.spec.healthCheckNodePort) - if service.spec.loadBalancerIP: - self.inventory.set_variable(service_name, 'load_balancer_ip', - service.spec.loadBalancerIP) - if service.spec.selector: - self.inventory.set_variable(service_name, 'selector', dict(service.spec.selector)) - - if hasattr(service.status.loadBalancer, 'ingress') and service.status.loadBalancer.ingress: - load_balancer = [{'hostname': ingress.hostname, - 'ip': ingress.ip} for ingress in service.status.loadBalancer.ingress] - self.inventory.set_variable(service_name, 'load_balancer', load_balancer) diff --git a/lib/ansible/plugins/inventory/openshift.py b/lib/ansible/plugins/inventory/openshift.py deleted file mode 100644 index db3392cde2b..00000000000 --- a/lib/ansible/plugins/inventory/openshift.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright (c) 2018 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) - -__metaclass__ = type - -DOCUMENTATION = ''' - name: openshift - plugin_type: inventory - author: - - Chris Houseknecht <@chouseknecht> - - short_description: OpenShift inventory source - - description: - - Fetch containers, services and routes for one or more clusters - - Groups by cluster name, namespace, namespace_services, namespace_pods, namespace_routes, and labels - - Uses openshift.(yml|yaml) YAML configuration file to set parameter values. - - options: - plugin: - description: token that ensures this is a source file for the 'openshift' plugin. - required: True - choices: ['openshift'] - connections: - description: - - Optional list of cluster connection settings. If no connections are provided, the default - I(~/.kube/config) and active context will be used, and objects will be returned for all namespaces - the active user is authorized to access. - name: - description: - - Optional name to assign to the cluster. If not provided, a name is constructed from the server - and port. - kubeconfig: - description: - - Path to an existing Kubernetes config file. If not provided, and no other connection - options are provided, the OpenShift client will attempt to load the default - configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG - environment variable. - context: - description: - - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment - variable. - host: - description: - - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable. - api_key: - description: - - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment - variable. - username: - description: - - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME - environment variable. - password: - description: - - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD - environment variable. - client_cert: - description: - - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE - environment variable. - aliases: [ cert_file ] - client_key: - description: - - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE - environment variable. - aliases: [ key_file ] - ca_cert: - description: - - Path to a CA certificate used to authenticate with the API. Can also be specified via - K8S_AUTH_SSL_CA_CERT environment variable. - aliases: [ ssl_ca_cert ] - validate_certs: - description: - - "Whether or not to verify the API server's SSL certificates. Can also be specified via - K8S_AUTH_VERIFY_SSL environment variable." - type: bool - aliases: [ verify_ssl ] - namespaces: - description: - - List of namespaces. If not specified, will fetch all containers for all namespaces user is authorized - to access. - - requirements: - - "python >= 2.7" - - "openshift >= 0.6" - - "PyYAML >= 3.11" -''' - -EXAMPLES = ''' -# File must be named openshift.yaml or openshift.yml - -# Authenticate with token, and return all pods and services for all namespaces -plugin: openshift -connections: - - host: https://192.168.64.4:8443 - api_key: xxxxxxxxxxxxxxxx - verify_ssl: false - -# Use default config (~/.kube/config) file and active context, and return objects for a specific namespace -plugin: openshift -connections: - - namespaces: - - testing - -# Use a custom config file, and a specific context. -plugin: openshift -connections: - - kubeconfig: /path/to/config - context: 'awx/192-168-64-4:8443/developer' -''' - -from ansible.plugins.inventory.k8s import K8sInventoryException, InventoryModule as K8sInventoryModule, format_dynamic_api_exc - -try: - from openshift.dynamic.exceptions import DynamicApiError -except ImportError: - pass - - -class InventoryModule(K8sInventoryModule): - NAME = 'openshift' - - transport = 'oc' - - def fetch_objects(self, connections): - super(InventoryModule, self).fetch_objects(connections) - - if connections: - if not isinstance(connections, list): - raise K8sInventoryException("Expecting connections to be a list.") - - for connection in connections: - client = self.get_api_client(**connection) - name = connection.get('name', self.get_default_host_name(client.configuration.host)) - if connection.get('namespaces'): - namespaces = connection['namespaces'] - else: - namespaces = self.get_available_namespaces(client) - for namespace in namespaces: - self.get_routes_for_namespace(client, name, namespace) - else: - client = self.get_api_client() - name = self.get_default_host_name(client.configuration.host) - namespaces = self.get_available_namespaces(client) - for namespace in namespaces: - self.get_routes_for_namespace(client, name, namespace) - - def get_routes_for_namespace(self, client, name, namespace): - v1_route = client.resources.get(api_version='v1', kind='Route') - try: - obj = v1_route.get(namespace=namespace) - except DynamicApiError as exc: - self.display.debug(exc) - raise K8sInventoryException('Error fetching Routes list: %s' % format_dynamic_api_exc(exc)) - - namespace_group = 'namespace_{0}'.format(namespace) - namespace_routes_group = '{0}_routes'.format(namespace_group) - - self.inventory.add_group(name) - self.inventory.add_group(namespace_group) - self.inventory.add_child(name, namespace_group) - self.inventory.add_group(namespace_routes_group) - self.inventory.add_child(namespace_group, namespace_routes_group) - for route in obj.items: - route_name = route.metadata.name - route_annotations = {} if not route.metadata.annotations else dict(route.metadata.annotations) - - self.inventory.add_host(route_name) - - if route.metadata.labels: - # create a group for each label_value - for key, value in route.metadata.labels: - group_name = 'label_{0}_{1}'.format(key, value) - self.inventory.add_group(group_name) - self.inventory.add_child(group_name, route_name) - route_labels = dict(route.metadata.labels) - else: - route_labels = {} - - self.inventory.add_child(namespace_routes_group, route_name) - - # add hostvars - self.inventory.set_variable(route_name, 'labels', route_labels) - self.inventory.set_variable(route_name, 'annotations', route_annotations) - self.inventory.set_variable(route_name, 'cluster_name', route.metadata.clusterName) - self.inventory.set_variable(route_name, 'object_type', 'route') - self.inventory.set_variable(route_name, 'self_link', route.metadata.selfLink) - self.inventory.set_variable(route_name, 'resource_version', route.metadata.resourceVersion) - self.inventory.set_variable(route_name, 'uid', route.metadata.uid) - - if route.spec.host: - self.inventory.set_variable(route_name, 'host', route.spec.host) - - if route.spec.path: - self.inventory.set_variable(route_name, 'path', route.spec.path) - - if hasattr(route.spec.port, 'targetPort') and route.spec.port.targetPort: - self.inventory.set_variable(route_name, 'port', dict(route.spec.port)) diff --git a/lib/ansible/plugins/lookup/_openshift.py b/lib/ansible/plugins/lookup/_openshift.py deleted file mode 120000 index b0ee072f962..00000000000 --- a/lib/ansible/plugins/lookup/_openshift.py +++ /dev/null @@ -1 +0,0 @@ -k8s.py \ No newline at end of file diff --git a/lib/ansible/plugins/lookup/k8s.py b/lib/ansible/plugins/lookup/k8s.py deleted file mode 100644 index aa2affcfb7a..00000000000 --- a/lib/ansible/plugins/lookup/k8s.py +++ /dev/null @@ -1,299 +0,0 @@ -# -# Copyright 2018 Red Hat | Ansible -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -from __future__ import (absolute_import, division, print_function) - -__metaclass__ = type - -DOCUMENTATION = """ - lookup: k8s - - version_added: "2.5" - - short_description: Query the K8s API - - description: - - Uses the OpenShift Python client to fetch a specific object by name, all matching objects within a - namespace, or all matching objects for all namespaces, as well as information about the cluster. - - Provides access the full range of K8s APIs. - - Enables authentication via config file, certificates, password or token. - - options: - cluster_info: - description: - - Use to specify the type of cluster information you are attempting to retrieve. Will take priority - over all the other options. - api_version: - description: - - Use to specify the API version. If I(resource definition) is provided, the I(apiVersion) from the - I(resource_definition) will override this option. - default: v1 - kind: - description: - - Use to specify an object model. If I(resource definition) is provided, the I(kind) from a - I(resource_definition) will override this option. - required: true - resource_name: - description: - - Fetch a specific object by name. If I(resource definition) is provided, the I(metadata.name) value - from the I(resource_definition) will override this option. - namespace: - description: - - Limit the objects returned to a specific namespace. If I(resource definition) is provided, the - I(metadata.namespace) value from the I(resource_definition) will override this option. - label_selector: - description: - - Additional labels to include in the query. Ignored when I(resource_name) is provided. - field_selector: - description: - - Specific fields on which to query. Ignored when I(resource_name) is provided. - resource_definition: - description: - - "Provide a YAML configuration for an object. NOTE: I(kind), I(api_version), I(resource_name), - and I(namespace) will be overwritten by corresponding values found in the provided I(resource_definition)." - src: - description: - - "Provide a path to a file containing a valid YAML definition of an object dated. Mutually - exclusive with I(resource_definition). NOTE: I(kind), I(api_version), I(resource_name), and I(namespace) - will be overwritten by corresponding values found in the configuration read in from the I(src) file." - - Reads from the local file system. To read from the Ansible controller's file system, use the file lookup - plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to - I(resource_definition). See Examples below. - host: - description: - - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable. - api_key: - description: - - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable. - kubeconfig: - description: - - Path to an existing Kubernetes config file. If not provided, and no other connection - options are provided, the openshift client will attempt to load the default - configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG environment - variable. - context: - description: - - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment - variable. - username: - description: - - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment - variable. - password: - description: - - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment - variable. - client_cert: - description: - - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE - environment - variable. - aliases: [ cert_file ] - client_key: - description: - - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment - variable. - aliases: [ key_file ] - ca_cert: - description: - - Path to a CA certificate used to authenticate with the API. Can also be specified via K8S_AUTH_SSL_CA_CERT - environment variable. - aliases: [ ssl_ca_cert ] - validate_certs: - description: - - Whether or not to verify the API server's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL - environment variable. - type: bool - aliases: [ verify_ssl ] - - requirements: - - "python >= 2.7" - - "openshift >= 0.6" - - "PyYAML >= 3.11" - - notes: - - "The OpenShift Python client wraps the K8s Python client, providing full access to - all of the APIS and models available on both platforms. For API version details and - additional information visit https://github.com/openshift/openshift-restclient-python" -""" - -EXAMPLES = """ -- name: Fetch a list of namespaces - set_fact: - projects: "{{ lookup('k8s', api_version='v1', kind='Namespace') }}" - -- name: Fetch all deployments - set_fact: - deployments: "{{ lookup('k8s', kind='Deployment') }}" - -- name: Fetch all deployments in a namespace - set_fact: - deployments: "{{ lookup('k8s', kind='Deployment', namespace='testing') }}" - -- name: Fetch a specific deployment by name - set_fact: - deployments: "{{ lookup('k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}" - -- name: Fetch with label selector - set_fact: - service: "{{ lookup('k8s', kind='Service', label_selector='app=galaxy') }}" - -# Use parameters from a YAML config - -- name: Load config from the Ansible controller filesystem - set_fact: - config: "{{ lookup('file', 'service.yml') | from_yaml }}" - -- name: Using the config (loaded from a file in prior task), fetch the latest version of the object - set_fact: - service: "{{ lookup('k8s', resource_definition=config) }}" - -- name: Use a config from the local filesystem - set_fact: - service: "{{ lookup('k8s', src='service.yml') }}" -""" - -RETURN = """ - _list: - description: - - One ore more object definitions returned from the API. - type: complex - contains: - api_version: - description: The versioned schema of this representation of an object. - returned: success - type: str - kind: - description: Represents the REST resource this object represents. - returned: success - type: str - metadata: - description: Standard object metadata. Includes name, namespace, annotations, labels, etc. - returned: success - type: complex - spec: - description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). - returned: success - type: complex - status: - description: Current status details for the object. - returned: success - type: complex -""" - -from ansible.plugins.lookup import LookupBase - -from ansible.module_utils.k8s.common import K8sAnsibleMixin - -from ansible.errors import AnsibleError - - -try: - from openshift.dynamic import DynamicClient - from openshift.dynamic.exceptions import NotFoundError - HAS_K8S_MODULE_HELPER = True - k8s_import_exception = None -except ImportError as e: - HAS_K8S_MODULE_HELPER = False - k8s_import_exception = e - -try: - import yaml - HAS_YAML = True -except ImportError: - HAS_YAML = False - - -class KubernetesLookup(K8sAnsibleMixin): - - def __init__(self): - - if not HAS_K8S_MODULE_HELPER: - raise Exception( - "Requires the OpenShift Python client. Try `pip install openshift`. Detail: {0}".format(k8s_import_exception) - ) - - if not HAS_YAML: - raise Exception( - "Requires PyYAML. Try `pip install PyYAML`" - ) - - self.kind = None - self.name = None - self.namespace = None - self.api_version = None - self.label_selector = None - self.field_selector = None - self.include_uninitialized = None - self.resource_definition = None - self.helper = None - self.connection = {} - - def fail(self, msg=None): - raise AnsibleError(msg) - - def run(self, terms, variables=None, **kwargs): - self.params = kwargs - self.client = self.get_api_client() - - cluster_info = kwargs.get('cluster_info') - if cluster_info == 'version': - return [self.client.version] - if cluster_info == 'api_groups': - return [self.client.resources.api_groups] - - self.kind = kwargs.get('kind') - self.name = kwargs.get('resource_name') - self.namespace = kwargs.get('namespace') - self.api_version = kwargs.get('api_version', 'v1') - self.label_selector = kwargs.get('label_selector') - self.field_selector = kwargs.get('field_selector') - self.include_uninitialized = kwargs.get('include_uninitialized', False) - - resource_definition = kwargs.get('resource_definition') - src = kwargs.get('src') - if src: - resource_definition = self.load_resource_definitions(src)[0] - if resource_definition: - self.kind = resource_definition.get('kind', self.kind) - self.api_version = resource_definition.get('apiVersion', self.api_version) - self.name = resource_definition.get('metadata', {}).get('name', self.name) - self.namespace = resource_definition.get('metadata', {}).get('namespace', self.namespace) - - if not self.kind: - raise AnsibleError( - "Error: no Kind specified. Use the 'kind' parameter, or provide an object YAML configuration " - "using the 'resource_definition' parameter." - ) - - resource = self.find_resource(self.kind, self.api_version, fail=True) - try: - k8s_obj = resource.get(name=self.name, namespace=self.namespace, label_selector=self.label_selector, field_selector=self.field_selector) - except NotFoundError: - return [] - - if self.name: - return [k8s_obj.to_dict()] - - return k8s_obj.to_dict().get('items') - - -class LookupModule(LookupBase): - - def run(self, terms, variables=None, **kwargs): - return KubernetesLookup().run(terms, variables=variables, **kwargs) diff --git a/test/integration/targets/k8s/README.md b/test/integration/targets/k8s/README.md deleted file mode 100644 index 7cb72b16e26..00000000000 --- a/test/integration/targets/k8s/README.md +++ /dev/null @@ -1,23 +0,0 @@ -Wait tests ----------- - -wait tests require at least one node, and don't work on the normal k8s -openshift-origin container as provided by ansible-test --docker -v k8s - -minikube, Kubernetes from Docker or any other Kubernetes service will -suffice. - -If kubectl is already using the right config file and context, you can -just do - -``` -cd test/integration/targets/k8s -./runme.sh -vv -``` - -otherwise set one or both of `K8S_AUTH_KUBECONFIG` and `K8S_AUTH_CONTEXT` -and use the same command - - - - diff --git a/test/integration/targets/k8s/aliases b/test/integration/targets/k8s/aliases deleted file mode 100644 index 326e691050b..00000000000 --- a/test/integration/targets/k8s/aliases +++ /dev/null @@ -1,2 +0,0 @@ -cloud/openshift -shippable/cloud/group1 diff --git a/test/integration/targets/k8s/defaults/main.yml b/test/integration/targets/k8s/defaults/main.yml deleted file mode 100644 index 68fde7c4122..00000000000 --- a/test/integration/targets/k8s/defaults/main.yml +++ /dev/null @@ -1,32 +0,0 @@ -recreate_crd_default_merge_expectation: recreate_crd is not failed - -k8s_pod_metadata: - labels: - app: "{{ k8s_pod_name }}" - -k8s_pod_spec: - containers: - - image: "{{ k8s_pod_image }}" - imagePullPolicy: Always - name: "{{ k8s_pod_name }}" - command: "{{ k8s_pod_command }}" - readinessProbe: - initialDelaySeconds: 15 - exec: - command: - - /bin/true - resources: - limits: - cpu: "100m" - memory: "100Mi" - ports: "{{ k8s_pod_ports }}" - -k8s_pod_command: [] - -k8s_pod_ports: [] - -k8s_pod_template: - metadata: "{{ k8s_pod_metadata }}" - spec: "{{ k8s_pod_spec }}" - -k8s_openshift: yes diff --git a/test/integration/targets/k8s/files/crd-resource.yml b/test/integration/targets/k8s/files/crd-resource.yml deleted file mode 100644 index 9804d4d14e1..00000000000 --- a/test/integration/targets/k8s/files/crd-resource.yml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: certmanager.k8s.io/v1alpha1 -kind: Certificate -metadata: - name: acme-crt -spec: - secretName: acme-crt-secret - dnsNames: - - foo.example.com - - bar.example.com - acme: - config: - - ingressClass: nginx - domains: - - foo.example.com - - bar.example.com - issuerRef: - name: letsencrypt-prod - # We can reference ClusterIssuers by changing the kind here. - # The default value is Issuer (i.e. a locally namespaced Issuer) - kind: Issuer diff --git a/test/integration/targets/k8s/files/kuard-extra-property.yml b/test/integration/targets/k8s/files/kuard-extra-property.yml deleted file mode 100644 index 2d5b799434a..00000000000 --- a/test/integration/targets/k8s/files/kuard-extra-property.yml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - labels: - app: kuard - name: kuard - namespace: default -spec: - replicas: 3 - selector: - matchLabels: - app: kuard - unwanted: value - template: - metadata: - labels: - app: kuard - spec: - containers: - - image: gcr.io/kuar-demo/kuard-amd64:1 - name: kuard diff --git a/test/integration/targets/k8s/files/kuard-invalid-type.yml b/test/integration/targets/k8s/files/kuard-invalid-type.yml deleted file mode 100644 index 7a5f73d9108..00000000000 --- a/test/integration/targets/k8s/files/kuard-invalid-type.yml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - labels: - app: kuard - name: kuard - namespace: default -spec: - replicas: hello - selector: - matchLabels: - app: kuard - template: - metadata: - labels: - app: kuard - spec: - containers: - - image: gcr.io/kuar-demo/kuard-amd64:1 - name: kuard diff --git a/test/integration/targets/k8s/files/setup-crd.yml b/test/integration/targets/k8s/files/setup-crd.yml deleted file mode 100644 index a8e2d51e806..00000000000 --- a/test/integration/targets/k8s/files/setup-crd.yml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: certificates.certmanager.k8s.io -spec: - group: certmanager.k8s.io - version: v1alpha1 - scope: Namespaced - names: - kind: Certificate - plural: certificates - shortNames: - - cert - - certs diff --git a/test/integration/targets/k8s/meta/main.yml b/test/integration/targets/k8s/meta/main.yml deleted file mode 100644 index 1810d4bec98..00000000000 --- a/test/integration/targets/k8s/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ -dependencies: - - setup_remote_tmp_dir diff --git a/test/integration/targets/k8s/tasks/append_hash.yml b/test/integration/targets/k8s/tasks/append_hash.yml deleted file mode 100644 index 876e876a29b..00000000000 --- a/test/integration/targets/k8s/tasks/append_hash.yml +++ /dev/null @@ -1,68 +0,0 @@ -- block: - - name: Ensure that append_hash namespace exists - k8s: - kind: Namespace - name: append-hash - - - name: create k8s_resource variable - set_fact: - k8s_resource: - metadata: - name: config-map-test - namespace: append-hash - apiVersion: v1 - kind: ConfigMap - data: - hello: world - - - name: Create config map - k8s: - definition: "{{ k8s_resource }}" - append_hash: yes - register: k8s_configmap1 - - - name: check configmap is created with a hash - assert: - that: - - k8s_configmap1 is changed - - k8s_configmap1.result.metadata.name != 'config-map-test' - - k8s_configmap1.result.metadata.name[:-10] == 'config-map-test-' - - - name: recreate same config map - k8s: - definition: "{{ k8s_resource }}" - append_hash: yes - register: k8s_configmap2 - - - name: check configmaps are different - assert: - that: - - k8s_configmap2 is not changed - - k8s_configmap1.result.metadata.name == k8s_configmap2.result.metadata.name - - - name: add key to config map - k8s: - definition: - metadata: - name: config-map-test - namespace: append-hash - apiVersion: v1 - kind: ConfigMap - data: - hello: world - another: value - append_hash: yes - register: k8s_configmap3 - - - name: check configmaps are different - assert: - that: - - k8s_configmap3 is changed - - k8s_configmap1.result.metadata.name != k8s_configmap3.result.metadata.name - - always: - - name: ensure that namespace is removed - k8s: - kind: Namespace - name: append-hash - state: absent diff --git a/test/integration/targets/k8s/tasks/apply.yml b/test/integration/targets/k8s/tasks/apply.yml deleted file mode 100644 index cf51123124d..00000000000 --- a/test/integration/targets/k8s/tasks/apply.yml +++ /dev/null @@ -1,277 +0,0 @@ -- block: - - python_requirements_info: - dependencies: - - openshift - - kubernetes - - - set_fact: - apply_namespace: apply - - - name: ensure namespace exists - k8s: - definition: - apiVersion: v1 - kind: Namespace - metadata: - name: "{{ apply_namespace }}" - - - name: add a configmap - k8s: - name: "apply-configmap" - namespace: "{{ apply_namespace }}" - definition: - kind: ConfigMap - apiVersion: v1 - data: - one: "1" - two: "2" - three: "3" - apply: yes - register: k8s_configmap - - - name: check configmap was created - assert: - that: - - k8s_configmap is changed - - k8s_configmap.result.metadata.annotations|default(False) - - - name: add same configmap again - k8s: - definition: - kind: ConfigMap - apiVersion: v1 - metadata: - name: "apply-configmap" - namespace: "{{ apply_namespace }}" - data: - one: "1" - two: "2" - three: "3" - apply: yes - register: k8s_configmap_2 - - - name: check nothing changed - assert: - that: - - k8s_configmap_2 is not changed - - - name: add same configmap again with check mode on - k8s: - definition: - kind: ConfigMap - apiVersion: v1 - metadata: - name: "apply-configmap" - namespace: "{{ apply_namespace }}" - data: - one: "1" - two: "2" - three: "3" - apply: yes - check_mode: yes - register: k8s_configmap_check - - - name: check nothing changed - assert: - that: - - k8s_configmap_check is not changed - - - name: add same configmap again but using name and namespace args - k8s: - name: "apply-configmap" - namespace: "{{ apply_namespace }}" - definition: - kind: ConfigMap - apiVersion: v1 - data: - one: "1" - two: "2" - three: "3" - apply: yes - register: k8s_configmap_2a - - - name: check nothing changed - assert: - that: - - k8s_configmap_2a is not changed - - - name: update configmap - k8s: - definition: - kind: ConfigMap - apiVersion: v1 - metadata: - name: "apply-configmap" - namespace: "{{ apply_namespace }}" - data: - one: "1" - three: "3" - four: "4" - apply: yes - register: k8s_configmap_3 - - - name: ensure that configmap has been correctly updated - assert: - that: - - k8s_configmap_3 is changed - - "'four' in k8s_configmap_3.result.data" - - "'two' not in k8s_configmap_3.result.data" - - - name: add a service - k8s: - definition: - apiVersion: v1 - kind: Service - metadata: - name: apply-svc - namespace: "{{ apply_namespace }}" - spec: - selector: - app: whatever - ports: - - name: http - port: 8080 - targetPort: 8080 - type: NodePort - apply: yes - register: k8s_service - - - name: add exactly same service - k8s: - definition: - apiVersion: v1 - kind: Service - metadata: - name: apply-svc - namespace: "{{ apply_namespace }}" - spec: - selector: - app: whatever - ports: - - name: http - port: 8080 - targetPort: 8080 - type: NodePort - apply: yes - register: k8s_service_2 - - - name: check nothing changed - assert: - that: - - k8s_service_2 is not changed - - - name: change service ports - k8s: - definition: - apiVersion: v1 - kind: Service - metadata: - name: apply-svc - namespace: "{{ apply_namespace }}" - spec: - selector: - app: whatever - ports: - - name: http - port: 8081 - targetPort: 8081 - type: NodePort - apply: yes - register: k8s_service_3 - - - name: check ports are correct - assert: - that: - - k8s_service_3 is changed - - k8s_service_3.result.spec.ports | length == 1 - - k8s_service_3.result.spec.ports[0].port == 8081 - - - name: insert new service port - k8s: - definition: - apiVersion: v1 - kind: Service - metadata: - name: apply-svc - namespace: "{{ apply_namespace }}" - spec: - selector: - app: whatever - ports: - - name: mesh - port: 8080 - targetPort: 8080 - - name: http - port: 8081 - targetPort: 8081 - type: NodePort - apply: yes - register: k8s_service_4 - - - name: check ports are correct - assert: - that: - - k8s_service_4 is changed - - k8s_service_4.result.spec.ports | length == 2 - - k8s_service_4.result.spec.ports[0].port == 8080 - - k8s_service_4.result.spec.ports[1].port == 8081 - - - name: remove new service port (check mode) - k8s: - definition: - apiVersion: v1 - kind: Service - metadata: - name: apply-svc - namespace: "{{ apply_namespace }}" - spec: - selector: - app: whatever - ports: - - name: http - port: 8081 - targetPort: 8081 - type: NodePort - apply: yes - check_mode: yes - register: k8s_service_check - - - name: check ports are correct - assert: - that: - - k8s_service_check is changed - - k8s_service_check.result.spec.ports | length == 1 - - k8s_service_check.result.spec.ports[0].port == 8081 - - - name: remove new service port - k8s: - definition: - apiVersion: v1 - kind: Service - metadata: - name: apply-svc - namespace: "{{ apply_namespace }}" - spec: - selector: - app: whatever - ports: - - name: http - port: 8081 - targetPort: 8081 - type: NodePort - apply: yes - register: k8s_service_5 - - - name: check ports are correct - assert: - that: - - k8s_service_5 is changed - - k8s_service_5.result.spec.ports | length == 1 - - k8s_service_5.result.spec.ports[0].port == 8081 - - always: - - name: remove namespace - k8s: - kind: Namespace - name: "{{ apply_namespace }}" - state: absent diff --git a/test/integration/targets/k8s/tasks/crd.yml b/test/integration/targets/k8s/tasks/crd.yml deleted file mode 100644 index c9e47632f95..00000000000 --- a/test/integration/targets/k8s/tasks/crd.yml +++ /dev/null @@ -1,71 +0,0 @@ -# TODO: This is the only way I could get the kubeconfig, I don't know why. Running the lookup outside of debug seems to return an empty string -#- debug: msg={{ lookup('env', 'K8S_AUTH_KUBECONFIG') }} -# register: kubeconfig - -# Kubernetes resources - -- block: - - name: Create a namespace - k8s: - name: crd - kind: Namespace - - - name: install custom resource definitions - k8s: - definition: "{{ lookup('file', role_path + '/files/setup-crd.yml') }}" - - - name: pause 5 seconds to avoid race condition - pause: - seconds: 5 - - - name: create custom resource definition - k8s: - definition: "{{ lookup('file', role_path + '/files/crd-resource.yml') }}" - namespace: crd - apply: "{{ create_crd_with_apply | default(omit) }}" - register: create_crd - - - name: patch custom resource definition - k8s: - definition: "{{ lookup('file', role_path + '/files/crd-resource.yml') }}" - namespace: crd - register: recreate_crd - ignore_errors: yes - - - name: assert that recreating crd is as expected - assert: - that: - - recreate_crd_default_merge_expectation - - - block: - - name: recreate custom resource definition with merge_type - k8s: - definition: "{{ lookup('file', role_path + '/files/crd-resource.yml') }}" - merge_type: merge - namespace: crd - register: recreate_crd_with_merge - - - name: recreate custom resource definition with merge_type list - k8s: - definition: "{{ lookup('file', role_path + '/files/crd-resource.yml') }}" - merge_type: - - strategic-merge - - merge - namespace: crd - register: recreate_crd_with_merge_list - when: recreate_crd is successful - - - - name: remove crd - k8s: - definition: "{{ lookup('file', role_path + '/files/crd-resource.yml') }}" - namespace: crd - state: absent - - always: - - name: remove crd namespace - k8s: - kind: Namespace - name: crd - state: absent - ignore_errors: yes diff --git a/test/integration/targets/k8s/tasks/delete.yml b/test/integration/targets/k8s/tasks/delete.yml deleted file mode 100644 index fef6e5e9f60..00000000000 --- a/test/integration/targets/k8s/tasks/delete.yml +++ /dev/null @@ -1,101 +0,0 @@ -- name: ensure that there are actually some nodes - k8s_info: - kind: Node - register: nodes - -- block: - - set_fact: - delete_namespace: delete - - - name: ensure namespace exists - k8s: - definition: - apiVersion: v1 - kind: Namespace - metadata: - name: "{{ delete_namespace }}" - - - name: add a daemonset - k8s: - definition: - apiVersion: extensions/v1beta1 - kind: DaemonSet - metadata: - name: delete-daemonset - namespace: "{{ delete_namespace }}" - spec: - selector: - matchLabels: - app: "{{ k8s_pod_name }}" - template: "{{ k8s_pod_template }}" - wait: yes - wait_timeout: 180 - vars: - k8s_pod_name: delete-ds - k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:1 - register: ds - - - name: check that daemonset wait worked - assert: - that: - - ds.result.status.currentNumberScheduled == ds.result.status.desiredNumberScheduled - - - name: check if pods exist - k8s_info: - namespace: "{{ delete_namespace }}" - kind: Pod - label_selectors: - - "app={{ k8s_pod_name }}" - vars: - k8s_pod_name: delete-ds - register: pods_create - - - name: assert that there are pods - assert: - that: - - pods_create.resources - - - name: remove the daemonset - k8s: - kind: DaemonSet - name: delete-daemonset - namespace: "{{ delete_namespace }}" - state: absent - wait: yes - - - name: show status of pods - k8s_info: - namespace: "{{ delete_namespace }}" - kind: Pod - label_selectors: - - "app={{ k8s_pod_name }}" - vars: - k8s_pod_name: delete-ds - - - name: wait for background deletion - pause: - seconds: 30 - - - name: check if pods still exist - k8s_info: - namespace: "{{ delete_namespace }}" - kind: Pod - label_selectors: - - "app={{ k8s_pod_name }}" - vars: - k8s_pod_name: delete-ds - register: pods_delete - - - name: assert that deleting the daemonset deleted the pods - assert: - that: - - not pods_delete.resources - - always: - - name: remove namespace - k8s: - kind: Namespace - name: "{{ delete_namespace }}" - state: absent - - when: (nodes.resources | length) > 0 diff --git a/test/integration/targets/k8s/tasks/full_test.yml b/test/integration/targets/k8s/tasks/full_test.yml deleted file mode 100644 index fdf3d700dcc..00000000000 --- a/test/integration/targets/k8s/tasks/full_test.yml +++ /dev/null @@ -1,375 +0,0 @@ -# TODO: This is the only way I could get the kubeconfig, I don't know why. Running the lookup outside of debug seems to return an empty string -#- debug: msg={{ lookup('env', 'K8S_AUTH_KUBECONFIG') }} -# register: kubeconfig - -# Kubernetes resources - -- include_tasks: delete.yml -- include_tasks: apply.yml -- include_tasks: waiter.yml - -- block: - - name: Create a namespace - k8s: - name: testing - kind: Namespace - register: output - - - name: show output - debug: - var: output - - - name: Setting validate_certs to true causes a failure - k8s: - name: testing - kind: Namespace - validate_certs: yes - ignore_errors: yes - register: output - - - name: assert that validate_certs caused a failure (and therefore was correctly translated to verify_ssl) - assert: - that: - - output is failed - - - name: k8s_info works with empty resources - k8s_info: - kind: Deployment - namespace: testing - api_version: extensions/v1beta1 - register: k8s_info - - - name: assert that k8s_info is in correct format - assert: - that: - - "'resources' in k8s_info" - - not k8s_info.resources - - - name: Create a service - k8s: - state: present - resource_definition: &svc - apiVersion: v1 - kind: Service - metadata: - name: web - namespace: testing - labels: - app: galaxy - service: web - spec: - selector: - app: galaxy - service: web - ports: - - protocol: TCP - targetPort: 8000 - name: port-8000-tcp - port: 8000 - register: output - - - name: show output - debug: - var: output - - - name: Create the service again - k8s: - state: present - resource_definition: *svc - register: output - - - name: Service creation should be idempotent - assert: - that: not output.changed - - - name: Create a ConfigMap - k8s: - kind: ConfigMap - name: test-force-update - namespace: testing - definition: - data: - key: value - - - name: Force update ConfigMap - k8s: - kind: ConfigMap - name: test-force-update - namespace: testing - definition: - data: - key: newvalue - force: yes - - - name: Create PVC - k8s: - state: present - inline: &pvc - apiVersion: v1 - kind: PersistentVolumeClaim - metadata: - name: elastic-volume - namespace: testing - spec: - resources: - requests: - storage: 5Gi - accessModes: - - ReadWriteOnce - - - name: Show output - debug: - var: output - - - name: Create the PVC again - k8s: - state: present - inline: *pvc - - - name: PVC creation should be idempotent - assert: - that: not output.changed - - - name: Create deployment - k8s: - state: present - inline: &deployment - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: elastic - labels: - app: galaxy - service: elastic - namespace: testing - spec: - template: - metadata: - labels: - app: galaxy - service: elastic - spec: - containers: - - name: elastic - volumeMounts: - - mountPath: /usr/share/elasticsearch/data - name: elastic-volume - command: ['elasticsearch'] - image: 'ansible/galaxy-elasticsearch:2.4.6' - volumes: - - name: elastic-volume - persistentVolumeClaim: - claimName: elastic-volume - replicas: 1 - strategy: - type: RollingUpdate - register: output - - - name: Show output - debug: - var: output - - - name: Create deployment again - k8s: - state: present - inline: *deployment - register: output - - - name: Deployment creation should be idempotent - assert: - that: not output.changed - - - debug: - var: k8s_openshift - - - include: openshift.yml - when: k8s_openshift | bool - - ### Type tests - - name: Create a namespace from a string - k8s: - definition: |+ - --- - kind: Namespace - apiVersion: v1 - metadata: - name: testing1 - - - name: Namespace should exist - k8s_info: - kind: Namespace - api_version: v1 - name: testing1 - register: k8s_info_testing1 - failed_when: not k8s_info_testing1.resources or k8s_info_testing1.resources[0].status.phase != "Active" - - - name: Create resources from a multidocument yaml string - k8s: - definition: |+ - --- - kind: Namespace - apiVersion: v1 - metadata: - name: testing2 - --- - kind: Namespace - apiVersion: v1 - metadata: - name: testing3 - - - name: Lookup namespaces - k8s_info: - api_version: v1 - kind: Namespace - name: "{{ item }}" - loop: - - testing2 - - testing3 - register: k8s_namespaces - - - name: Resources should exist - assert: - that: item.resources[0].status.phase == 'Active' - loop: "{{ k8s_namespaces.results }}" - - - name: Delete resources from a multidocument yaml string - k8s: - state: absent - definition: |+ - --- - kind: Namespace - apiVersion: v1 - metadata: - name: testing2 - --- - kind: Namespace - apiVersion: v1 - metadata: - name: testing3 - - - name: Lookup namespaces - k8s_info: - api_version: v1 - kind: Namespace - name: "{{ item }}" - loop: - - testing2 - - testing3 - register: k8s_namespaces - - - name: Resources should not exist - assert: - that: - - not item.resources or item.resources[0].status.phase == "Terminating" - loop: "{{ k8s_namespaces.results }}" - - - name: Create resources from a list - k8s: - definition: - - kind: Namespace - apiVersion: v1 - metadata: - name: testing4 - - kind: Namespace - apiVersion: v1 - metadata: - name: testing5 - - - name: Lookup namespaces - k8s_info: - api_version: v1 - kind: Namespace - name: "{{ item }}" - loop: - - testing4 - - testing5 - register: k8s_namespaces - - - name: Resources should exist - assert: - that: item.resources[0].status.phase == 'Active' - loop: "{{ k8s_namespaces.results }}" - - - name: Delete resources from a list - k8s: - state: absent - definition: - - kind: Namespace - apiVersion: v1 - metadata: - name: testing4 - - kind: Namespace - apiVersion: v1 - metadata: - name: testing5 - - - k8s_info: - api_version: v1 - kind: Namespace - name: "{{ item }}" - loop: - - testing4 - - testing5 - register: k8s_info - - - name: Resources are terminating if still in results - assert: - that: not item.resources or item.resources[0].status.phase == "Terminating" - loop: "{{ k8s_info.results }}" - - - name: Create resources from a yaml string ending with --- - k8s: - definition: |+ - --- - kind: Namespace - apiVersion: v1 - metadata: - name: testing6 - --- - - - name: Namespace should exist - k8s_info: - kind: Namespace - api_version: v1 - name: testing6 - register: k8s_info_testing6 - failed_when: not k8s_info_testing6.resources or k8s_info_testing6.resources[0].status.phase != "Active" - - - include_tasks: crd.yml - - include_tasks: lists.yml - - include_tasks: append_hash.yml - - always: - - name: Delete all namespaces - k8s: - state: absent - definition: - - kind: Namespace - apiVersion: v1 - metadata: - name: testing - - kind: Namespace - apiVersion: v1 - metadata: - name: testing1 - - kind: Namespace - apiVersion: v1 - metadata: - name: testing2 - - kind: Namespace - apiVersion: v1 - metadata: - name: testing3 - - kind: Namespace - apiVersion: v1 - metadata: - name: testing4 - - kind: Namespace - apiVersion: v1 - metadata: - name: testing5 - - kind: Namespace - apiVersion: v1 - metadata: - name: testing6 - ignore_errors: yes diff --git a/test/integration/targets/k8s/tasks/lists.yml b/test/integration/targets/k8s/tasks/lists.yml deleted file mode 100644 index 4d464df734f..00000000000 --- a/test/integration/targets/k8s/tasks/lists.yml +++ /dev/null @@ -1,140 +0,0 @@ ---- - -- name: Ensure testing1 namespace exists - k8s: - api_version: v1 - kind: Namespace - name: testing1 - -- block: - - name: Create configmaps - k8s: - namespace: testing1 - definition: - apiVersion: v1 - kind: ConfigMapList - items: '{{ configmaps }}' - - - name: Get ConfigMaps - k8s_info: - api_version: v1 - kind: ConfigMap - namespace: testing1 - label_selectors: - - app=test - register: cms - - - name: All three configmaps should exist - assert: - that: item.data.a is defined - with_items: '{{ cms.resources }}' - - - name: Delete configmaps - k8s: - state: absent - namespace: testing1 - definition: - apiVersion: v1 - kind: ConfigMapList - items: '{{ configmaps }}' - - - name: Get ConfigMaps - k8s_info: - api_version: v1 - kind: ConfigMap - namespace: testing1 - label_selectors: - - app=test - register: cms - - - name: All three configmaps should not exist - assert: - that: not cms.resources - vars: - configmaps: - - metadata: - name: list-example-1 - labels: - app: test - data: - a: first - - metadata: - name: list-example-2 - labels: - app: test - data: - a: second - - metadata: - name: list-example-3 - labels: - app: test - data: - a: third - -- block: - - name: Create list of arbitrary resources - k8s: - namespace: testing1 - definition: - apiVersion: v1 - kind: List - namespace: testing1 - items: '{{ resources }}' - - - name: Get the created resources - k8s_info: - api_version: '{{ item.apiVersion }}' - kind: '{{ item.kind }}' - namespace: testing1 - name: '{{ item.metadata.name }}' - register: list_resources - with_items: '{{ resources }}' - - - name: All resources should exist - assert: - that: ((list_resources.results | sum(attribute="resources", start=[])) | length) == (resources | length) - - - name: Delete list of arbitrary resources - k8s: - state: absent - namespace: testing1 - definition: - apiVersion: v1 - kind: List - namespace: testing1 - items: '{{ resources }}' - - - name: Get the resources - k8s_info: - api_version: '{{ item.apiVersion }}' - kind: '{{ item.kind }}' - namespace: testing1 - name: '{{ item.metadata.name }}' - register: list_resources - with_items: '{{ resources }}' - - - name: The resources should not exist - assert: - that: not ((list_resources.results | sum(attribute="resources", start=[])) | length) - vars: - resources: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: list-example-4 - data: - key: value - - apiVersion: v1 - kind: Service - metadata: - name: list-example-svc - labels: - app: test - spec: - selector: - app: test - ports: - - protocol: TCP - targetPort: 8000 - name: port-8000-tcp - port: 8000 diff --git a/test/integration/targets/k8s/tasks/main.yml b/test/integration/targets/k8s/tasks/main.yml deleted file mode 100644 index bb11bbc6862..00000000000 --- a/test/integration/targets/k8s/tasks/main.yml +++ /dev/null @@ -1,92 +0,0 @@ -- set_fact: - virtualenv: "{{ remote_tmp_dir }}/virtualenv" - virtualenv_command: "{{ ansible_python_interpreter }} -m virtualenv" - -- set_fact: - virtualenv_interpreter: "{{ virtualenv }}/bin/python" - -- pip: - name: virtualenv - -# Test graceful failure for missing kubernetes-validate - -- pip: - name: - - openshift>=0.9.2 - - coverage - virtualenv: "{{ virtualenv }}" - virtualenv_command: "{{ virtualenv_command }}" - virtualenv_site_packages: no - -- include_tasks: validate_not_installed.yml - vars: - ansible_python_interpreter: "{{ virtualenv_interpreter }}" - -- file: - path: "{{ virtualenv }}" - state: absent - no_log: yes - -# Test validate with kubernetes-validate - -- pip: - name: - - kubernetes-validate==1.12.0 - - openshift>=0.9.2 - - coverage - virtualenv: "{{ virtualenv }}" - virtualenv_command: "{{ virtualenv_command }}" - virtualenv_site_packages: no - -- include_tasks: validate_installed.yml - vars: - ansible_python_interpreter: "{{ virtualenv_interpreter }}" - playbook_namespace: ansible-test-k8s-validate - -- file: - path: "{{ virtualenv }}" - state: absent - no_log: yes - -# Test graceful failure for older versions of openshift - -- pip: - name: - - openshift==0.6.0 - - kubernetes==6.0.0 - - coverage - virtualenv: "{{ virtualenv }}" - virtualenv_command: "{{ virtualenv_command }}" - virtualenv_site_packages: no - -- include_tasks: older_openshift_fail.yml - vars: - ansible_python_interpreter: "{{ virtualenv_interpreter }}" - recreate_crd_default_merge_expectation: recreate_crd is failed - playbook_namespace: ansible-test-k8s-older-openshift - -- file: - path: "{{ virtualenv }}" - state: absent - no_log: yes - -# Run full test suite - -- pip: - name: - - openshift>=0.9.2 - - coverage - virtualenv: "{{ virtualenv }}" - virtualenv_command: "{{ virtualenv_command }}" - virtualenv_site_packages: no - -- include_tasks: full_test.yml - vars: - ansible_python_interpreter: "{{ virtualenv_interpreter }}" - create_crd_with_apply: no - playbook_namespace: ansible-test-k8s-full - -- file: - path: "{{ virtualenv }}" - state: absent - no_log: yes diff --git a/test/integration/targets/k8s/tasks/older_openshift_fail.yml b/test/integration/targets/k8s/tasks/older_openshift_fail.yml deleted file mode 100644 index 2acf3d2175c..00000000000 --- a/test/integration/targets/k8s/tasks/older_openshift_fail.yml +++ /dev/null @@ -1,69 +0,0 @@ - - python_requirements_info: - dependencies: - - openshift==0.6.0 - - kubernetes==6.0.0 - - # append_hash - - name: use append_hash with ConfigMap - k8s: - definition: - metadata: - name: config-map-test - namespace: "{{ playbook_namespace }}" - apiVersion: v1 - kind: ConfigMap - data: - hello: world - append_hash: yes - ignore_errors: yes - register: k8s_append_hash - - - name: assert that append_hash fails gracefully - assert: - that: - - k8s_append_hash is failed - - "'Failed to import the required Python library (openshift >= 0.7.2)' in k8s_append_hash.msg" - - "'. This is required for append_hash.' in k8s_append_hash.msg" - - # validate - - name: attempt to use validate with older openshift - k8s: - definition: - metadata: - name: config-map-test - namespace: "{{ playbook_namespace }}" - apiVersion: v1 - kind: ConfigMap - data: - hello: world - validate: - fail_on_error: yes - ignore_errors: yes - register: k8s_validate - - - name: assert that validate fails gracefully - assert: - that: - - k8s_validate is failed - - "k8s_validate.msg == 'openshift >= 0.8.0 is required for validate'" - - # apply - - name: attempt to use apply with older openshift - k8s: - definition: - metadata: - name: config-map-test - namespace: "{{ playbook_namespace }}" - apiVersion: v1 - kind: ConfigMap - data: - hello: world - apply: yes - ignore_errors: yes - register: k8s_apply - - - name: assert that apply fails gracefully - assert: - that: - - k8s_apply is failed - - "k8s_apply.msg.startswith('Failed to import the required Python library (openshift >= 0.9.2)')" diff --git a/test/integration/targets/k8s/tasks/openshift.yml b/test/integration/targets/k8s/tasks/openshift.yml deleted file mode 100644 index f4a9006119f..00000000000 --- a/test/integration/targets/k8s/tasks/openshift.yml +++ /dev/null @@ -1,61 +0,0 @@ -# OpenShift Resources -- name: Create a project - k8s: - name: testing - kind: Project - api_version: v1 - apply: no - register: output - -- name: show output - debug: - var: output - -- name: Create deployment config - k8s: - state: present - inline: &dc - apiVersion: v1 - kind: DeploymentConfig - metadata: - name: elastic - labels: - app: galaxy - service: elastic - namespace: testing - spec: - template: - metadata: - labels: - app: galaxy - service: elastic - spec: - containers: - - name: elastic - volumeMounts: - - mountPath: /usr/share/elasticsearch/data - name: elastic-volume - command: ['elasticsearch'] - image: 'ansible/galaxy-elasticsearch:2.4.6' - volumes: - - name: elastic-volume - persistentVolumeClaim: - claimName: elastic-volume - replicas: 1 - strategy: - type: Rolling - register: output - -- name: Show output - debug: - var: output - -- name: Create deployment config again - k8s: - state: present - inline: *dc - register: output - -- name: DC creation should be idempotent - assert: - that: not output.changed diff --git a/test/integration/targets/k8s/tasks/validate_installed.yml b/test/integration/targets/k8s/tasks/validate_installed.yml deleted file mode 100644 index 224bd2eb68c..00000000000 --- a/test/integration/targets/k8s/tasks/validate_installed.yml +++ /dev/null @@ -1,125 +0,0 @@ -- block: - - name: Create a namespace - k8s: - name: "{{ playbook_namespace }}" - kind: Namespace - - - copy: - src: files - dest: "{{ remote_tmp_dir }}" - - - name: incredibly simple ConfigMap - k8s: - definition: - apiVersion: v1 - kind: ConfigMap - metadata: - name: hello - namespace: "{{ playbook_namespace }}" - validate: - fail_on_error: yes - register: k8s_with_validate - - - name: assert that k8s_with_validate succeeds - assert: - that: - - k8s_with_validate is successful - - - name: extra property does not fail without strict - k8s: - src: "{{ remote_tmp_dir }}/files/kuard-extra-property.yml" - namespace: "{{ playbook_namespace }}" - validate: - fail_on_error: yes - strict: no - - - name: extra property fails with strict - k8s: - src: "{{ remote_tmp_dir }}/files/kuard-extra-property.yml" - namespace: "{{ playbook_namespace }}" - validate: - fail_on_error: yes - strict: yes - ignore_errors: yes - register: extra_property - - - name: check that extra property fails with strict - assert: - that: - - extra_property is failed - - - name: invalid type fails at validation stage - k8s: - src: "{{ remote_tmp_dir }}/files/kuard-invalid-type.yml" - namespace: "{{ playbook_namespace }}" - validate: - fail_on_error: yes - strict: no - ignore_errors: yes - register: invalid_type - - - name: check that invalid type fails - assert: - that: - - invalid_type is failed - - - name: invalid type fails with warnings when fail_on_error is False - k8s: - src: "{{ remote_tmp_dir }}/files/kuard-invalid-type.yml" - namespace: "{{ playbook_namespace }}" - validate: - fail_on_error: no - strict: no - ignore_errors: yes - register: invalid_type_no_fail - - - name: check that invalid type fails - assert: - that: - - invalid_type_no_fail is failed - - - name: setup custom resource definition - k8s: - src: "{{ remote_tmp_dir }}/files/setup-crd.yml" - - - name: wait a few seconds - pause: - seconds: 5 - - - name: add custom resource definition - k8s: - src: "{{ remote_tmp_dir }}/files/crd-resource.yml" - namespace: "{{ playbook_namespace }}" - validate: - fail_on_error: yes - strict: yes - register: unknown_kind - - - name: check that unknown kind warns - assert: - that: - - unknown_kind is successful - - "'warnings' in unknown_kind" - - always: - - name: remove custom resource - k8s: - definition: "{{ lookup('file', role_path + '/files/crd-resource.yml') }}" - namespace: "{{ playbook_namespace }}" - state: absent - ignore_errors: yes - - - name: remove custom resource definitions - k8s: - definition: "{{ lookup('file', role_path + '/files/setup-crd.yml') }}" - state: absent - - - name: Delete namespace - k8s: - state: absent - definition: - - kind: Namespace - apiVersion: v1 - metadata: - name: "{{ playbook_namespace }}" - ignore_errors: yes diff --git a/test/integration/targets/k8s/tasks/validate_not_installed.yml b/test/integration/targets/k8s/tasks/validate_not_installed.yml deleted file mode 100644 index ecd17f7ea98..00000000000 --- a/test/integration/targets/k8s/tasks/validate_not_installed.yml +++ /dev/null @@ -1,23 +0,0 @@ - - python_requirements_info: - dependencies: - - openshift - - kubernetes - - kubernetes-validate - - - k8s: - definition: - apiVersion: v1 - kind: ConfigMap - metadata: - name: hello - namespace: default - validate: - fail_on_error: yes - ignore_errors: yes - register: k8s_no_validate - - - name: assert that k8s_no_validate fails gracefully - assert: - that: - - k8s_no_validate is failed - - "k8s_no_validate.msg == 'kubernetes-validate python library is required to validate resources'" diff --git a/test/integration/targets/k8s/tasks/waiter.yml b/test/integration/targets/k8s/tasks/waiter.yml deleted file mode 100644 index 757d7899aca..00000000000 --- a/test/integration/targets/k8s/tasks/waiter.yml +++ /dev/null @@ -1,355 +0,0 @@ -- name: ensure that there are actually some nodes - k8s_info: - kind: Node - register: nodes - -- block: - - set_fact: - wait_namespace: wait - - - name: ensure namespace exists - k8s: - definition: - apiVersion: v1 - kind: Namespace - metadata: - name: "{{ wait_namespace }}" - - - name: add a simple pod - k8s: - definition: - apiVersion: v1 - kind: Pod - metadata: - name: "{{ k8s_pod_name }}" - namespace: "{{ wait_namespace }}" - spec: "{{ k8s_pod_spec }}" - wait: yes - vars: - k8s_pod_name: wait-pod - k8s_pod_image: alpine:3.8 - k8s_pod_command: - - sleep - - "10000" - register: wait_pod - ignore_errors: yes - - - name: assert that pod creation succeeded - assert: - that: - - wait_pod is successful - - - name: add a daemonset - k8s: - definition: - apiVersion: extensions/v1beta1 - kind: DaemonSet - metadata: - name: wait-daemonset - namespace: "{{ wait_namespace }}" - spec: - selector: - matchLabels: - app: "{{ k8s_pod_name }}" - template: "{{ k8s_pod_template }}" - wait: yes - wait_sleep: 3 - wait_timeout: 180 - vars: - k8s_pod_name: wait-ds - k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:1 - register: ds - - - name: check that daemonset wait worked - assert: - that: - - ds.result.status.currentNumberScheduled == ds.result.status.desiredNumberScheduled - - - name: update a daemonset in check_mode - k8s: - definition: - apiVersion: extensions/v1beta1 - kind: DaemonSet - metadata: - name: wait-daemonset - namespace: "{{ wait_namespace }}" - spec: - selector: - matchLabels: - app: "{{ k8s_pod_name }}" - updateStrategy: - type: RollingUpdate - template: "{{ k8s_pod_template }}" - wait: yes - wait_sleep: 3 - wait_timeout: 180 - vars: - k8s_pod_name: wait-ds - k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:2 - register: update_ds_check_mode - - - name: check that check_mode returned changed - assert: - that: - - update_ds_check_mode is changed - - - name: update a daemonset - k8s: - definition: - apiVersion: extensions/v1beta1 - kind: DaemonSet - metadata: - name: wait-daemonset - namespace: "{{ wait_namespace }}" - spec: - selector: - matchLabels: - app: "{{ k8s_pod_name }}" - updateStrategy: - type: RollingUpdate - template: "{{ k8s_pod_template }}" - wait: yes - wait_sleep: 3 - wait_timeout: 180 - vars: - k8s_pod_name: wait-ds - k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:3 - register: ds - - - name: get updated pods - k8s_info: - api_version: v1 - kind: Pod - namespace: "{{ wait_namespace }}" - label_selectors: - - app=wait-ds - register: updated_ds_pods - - - name: check that daemonset wait worked - assert: - that: - - ds.result.status.currentNumberScheduled == ds.result.status.desiredNumberScheduled - - updated_ds_pods.resources[0].spec.containers[0].image.endswith(":3") - - - name: add a crashing pod - k8s: - definition: - apiVersion: v1 - kind: Pod - metadata: - name: "{{ k8s_pod_name }}" - namespace: "{{ wait_namespace }}" - spec: "{{ k8s_pod_spec }}" - wait: yes - wait_sleep: 1 - wait_timeout: 30 - vars: - k8s_pod_name: wait-crash-pod - k8s_pod_image: alpine:3.8 - k8s_pod_command: - - /bin/false - register: crash_pod - ignore_errors: yes - - - name: check that task failed - assert: - that: - - crash_pod is failed - - - name: use a non-existent image - k8s: - definition: - apiVersion: v1 - kind: Pod - metadata: - name: "{{ k8s_pod_name }}" - namespace: "{{ wait_namespace }}" - spec: "{{ k8s_pod_spec }}" - wait: yes - wait_sleep: 1 - wait_timeout: 30 - vars: - k8s_pod_name: wait-no-image-pod - k8s_pod_image: i_made_this_up:and_this_too - register: no_image_pod - ignore_errors: yes - - - name: check that task failed - assert: - that: - - no_image_pod is failed - - - name: add a deployment - k8s: - definition: - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: wait-deploy - namespace: "{{ wait_namespace }}" - spec: - replicas: 3 - selector: - matchLabels: - app: "{{ k8s_pod_name }}" - template: "{{ k8s_pod_template }}" - wait: yes - vars: - k8s_pod_name: wait-deploy - k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:1 - k8s_pod_ports: - - containerPort: 8080 - name: http - protocol: TCP - - register: deploy - - - name: check that deployment wait worked - assert: - that: - - deploy.result.status.availableReplicas == deploy.result.status.replicas - - - name: update a deployment - k8s: - definition: - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: wait-deploy - namespace: "{{ wait_namespace }}" - spec: - replicas: 3 - selector: - matchLabels: - app: "{{ k8s_pod_name }}" - template: "{{ k8s_pod_template }}" - wait: yes - vars: - k8s_pod_name: wait-deploy - k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:2 - k8s_pod_ports: - - containerPort: 8080 - name: http - protocol: TCP - register: update_deploy - - - name: get updated pods - k8s_info: - api_version: v1 - kind: Pod - namespace: "{{ wait_namespace }}" - label_selectors: - - app=wait-deploy - register: updated_deploy_pods - - - name: check that deployment wait worked - assert: - that: - - deploy.result.status.availableReplicas == deploy.result.status.replicas - - updated_deploy_pods.resources[0].spec.containers[0].image.endswith(":2") - - - name: pause a deployment - k8s: - definition: - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: wait-deploy - namespace: "{{ wait_namespace }}" - spec: - paused: True - apply: no - wait: yes - wait_condition: - type: Progressing - status: Unknown - reason: DeploymentPaused - register: pause_deploy - - - name: check that paused deployment wait worked - assert: - that: - - condition.reason == "DeploymentPaused" - - condition.status == "Unknown" - vars: - condition: '{{ pause_deploy.result.status.conditions | json_query("[?type==`Progressing`]") | first }}' - - - name: add a service based on the deployment - k8s: - definition: - apiVersion: v1 - kind: Service - metadata: - name: wait-svc - namespace: "{{ wait_namespace }}" - spec: - selector: - app: "{{ k8s_pod_name }}" - ports: - - port: 8080 - targetPort: 8080 - protocol: TCP - wait: yes - vars: - k8s_pod_name: wait-deploy - register: service - - - name: assert that waiting for service works - assert: - that: - - service is successful - - - name: add a crashing deployment - k8s: - definition: - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: wait-crash-deploy - namespace: "{{ wait_namespace }}" - spec: - replicas: 3 - selector: - matchLabels: - app: "{{ k8s_pod_name }}" - template: "{{ k8s_pod_template }}" - wait: yes - vars: - k8s_pod_name: wait-crash-deploy - k8s_pod_image: alpine:3.8 - k8s_pod_command: - - /bin/false - register: wait_crash_deploy - ignore_errors: yes - - - name: check that task failed - assert: - that: - - wait_crash_deploy is failed - - - name: remove Pod with very short timeout - k8s: - api_version: v1 - kind: Pod - name: wait-pod - namespace: "{{ wait_namespace }}" - state: absent - wait: yes - wait_sleep: 2 - wait_timeout: 5 - ignore_errors: yes - register: short_wait_remove_pod - - - name: check that task failed - assert: - that: - - short_wait_remove_pod is failed - - always: - - name: remove namespace - k8s: - kind: Namespace - name: "{{ wait_namespace }}" - state: absent - - when: (nodes.resources | length) > 0 diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index c403c134689..e545d49f7b2 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -96,9 +96,6 @@ lib/ansible/module_utils/ismount.py future-import-boilerplate lib/ansible/module_utils/ismount.py metaclass-boilerplate lib/ansible/module_utils/json_utils.py future-import-boilerplate lib/ansible/module_utils/json_utils.py metaclass-boilerplate -lib/ansible/module_utils/k8s/common.py metaclass-boilerplate -lib/ansible/module_utils/k8s/raw.py metaclass-boilerplate -lib/ansible/module_utils/k8s/scale.py metaclass-boilerplate lib/ansible/module_utils/net_tools/netbox/netbox_utils.py future-import-boilerplate lib/ansible/module_utils/netapp.py future-import-boilerplate lib/ansible/module_utils/netapp.py metaclass-boilerplate @@ -1677,26 +1674,6 @@ lib/ansible/modules/cloud/vmware_httpapi/vmware_cis_category_info.py validate-mo lib/ansible/modules/cloud/vmware_httpapi/vmware_cis_category_info.py validate-modules:parameter-list-no-elements lib/ansible/modules/cloud/vmware_httpapi/vmware_core_info.py validate-modules:doc-required-mismatch lib/ansible/modules/cloud/vmware_httpapi/vmware_core_info.py validate-modules:parameter-list-no-elements -lib/ansible/modules/clustering/k8s/k8s.py validate-modules:doc-default-does-not-match-spec -lib/ansible/modules/clustering/k8s/k8s.py validate-modules:doc-missing-type -lib/ansible/modules/clustering/k8s/k8s.py validate-modules:doc-required-mismatch -lib/ansible/modules/clustering/k8s/k8s.py validate-modules:parameter-list-no-elements -lib/ansible/modules/clustering/k8s/k8s.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/clustering/k8s/k8s.py validate-modules:return-syntax-error -lib/ansible/modules/clustering/k8s/k8s_auth.py validate-modules:doc-missing-type -lib/ansible/modules/clustering/k8s/k8s_auth.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/clustering/k8s/k8s_info.py validate-modules:doc-missing-type -lib/ansible/modules/clustering/k8s/k8s_info.py validate-modules:parameter-list-no-elements -lib/ansible/modules/clustering/k8s/k8s_info.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/clustering/k8s/k8s_scale.py validate-modules:doc-missing-type -lib/ansible/modules/clustering/k8s/k8s_scale.py validate-modules:doc-required-mismatch -lib/ansible/modules/clustering/k8s/k8s_scale.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/clustering/k8s/k8s_scale.py validate-modules:return-syntax-error -lib/ansible/modules/clustering/k8s/k8s_service.py validate-modules:doc-missing-type -lib/ansible/modules/clustering/k8s/k8s_service.py validate-modules:mutually_exclusive-unknown -lib/ansible/modules/clustering/k8s/k8s_service.py validate-modules:parameter-list-no-elements -lib/ansible/modules/clustering/k8s/k8s_service.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/clustering/k8s/k8s_service.py validate-modules:return-syntax-error lib/ansible/modules/commands/command.py validate-modules:doc-missing-type lib/ansible/modules/commands/command.py validate-modules:nonexistent-parameter-documented lib/ansible/modules/commands/command.py validate-modules:parameter-list-no-elements @@ -4287,16 +4264,6 @@ lib/ansible/plugins/doc_fragments/iosxr.py future-import-boilerplate lib/ansible/plugins/doc_fragments/iosxr.py metaclass-boilerplate lib/ansible/plugins/doc_fragments/junos.py future-import-boilerplate lib/ansible/plugins/doc_fragments/junos.py metaclass-boilerplate -lib/ansible/plugins/doc_fragments/k8s_auth_options.py future-import-boilerplate -lib/ansible/plugins/doc_fragments/k8s_auth_options.py metaclass-boilerplate -lib/ansible/plugins/doc_fragments/k8s_name_options.py future-import-boilerplate -lib/ansible/plugins/doc_fragments/k8s_name_options.py metaclass-boilerplate -lib/ansible/plugins/doc_fragments/k8s_resource_options.py future-import-boilerplate -lib/ansible/plugins/doc_fragments/k8s_resource_options.py metaclass-boilerplate -lib/ansible/plugins/doc_fragments/k8s_scale_options.py future-import-boilerplate -lib/ansible/plugins/doc_fragments/k8s_scale_options.py metaclass-boilerplate -lib/ansible/plugins/doc_fragments/k8s_state_options.py future-import-boilerplate -lib/ansible/plugins/doc_fragments/k8s_state_options.py metaclass-boilerplate lib/ansible/plugins/doc_fragments/meraki.py future-import-boilerplate lib/ansible/plugins/doc_fragments/meraki.py metaclass-boilerplate lib/ansible/plugins/doc_fragments/netapp.py future-import-boilerplate