diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4e490c60..2807a59c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -84,8 +84,10 @@ jobs:
with:
python-version: ${{ matrix.python_version }}
+ # The 3.3.0 release of molecule introduced a breaking change. See
+ # https://github.com/ansible-community/molecule/issues/3083
- name: Install molecule and openshift dependencies
- run: pip install ansible molecule yamllint openshift flake8
+ run: pip install ansible "molecule<3.3.0" yamllint openshift flake8
# The latest release doesn't work with Molecule currently.
# See: https://github.com/ansible-community/molecule/issues/2757
@@ -177,7 +179,7 @@ jobs:
python-version: ${{ matrix.python_version }}
- name: Install molecule and openshift dependencies
- run: pip install "ansible>=2.9.0,<2.10.0" molecule yamllint openshift flake8
+ run: pip install "ansible>=2.9.0,<2.10.0" "molecule<3.3.0" yamllint openshift flake8
- name: Create default collection path symlink
run: |
diff --git a/changelogs/fragments/379-remove-kubernetesrawmodule.yaml b/changelogs/fragments/379-remove-kubernetesrawmodule.yaml
new file mode 100644
index 00000000..680f544a
--- /dev/null
+++ b/changelogs/fragments/379-remove-kubernetesrawmodule.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - remove the deprecated ``KubernetesRawModule`` class (https://github.com/ansible-collections/community.kubernetes/issues/232).
diff --git a/changelogs/fragments/387-fix-helm-ignoring-context.yaml b/changelogs/fragments/387-fix-helm-ignoring-context.yaml
new file mode 100644
index 00000000..8e696b06
--- /dev/null
+++ b/changelogs/fragments/387-fix-helm-ignoring-context.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - helm - fix helm ignoring the kubeconfig context when passed through the ``context`` param or the ``K8S_AUTH_CONTEXT`` environment variable (https://github.com/ansible-collections/community.kubernetes/issues/385).
diff --git a/changelogs/fragments/k8s_inventory.yml b/changelogs/fragments/k8s_inventory.yml
new file mode 100644
index 00000000..7800c288
--- /dev/null
+++ b/changelogs/fragments/k8s_inventory.yml
@@ -0,0 +1,2 @@
+bugfixes:
+- k8s - fix get_api_client API in k8s inventory plugin (https://github.com/ansible-collections/community.kubernetes/pull/395).
diff --git a/meta/runtime.yml b/meta/runtime.yml
index a0329447..9f7fee5d 100644
--- a/meta/runtime.yml
+++ b/meta/runtime.yml
@@ -43,6 +43,9 @@ plugin_routing:
redirect: community.kubernetes.k8s_info
k8s_service:
redirect: community.kubernetes.k8s_info
+ inventory:
+ openshift:
+ redirect: community.okd.openshift
modules:
k8s_auth:
redirect: community.okd.k8s_auth
diff --git a/molecule/default/roles/helm/tasks/tests_chart.yml b/molecule/default/roles/helm/tasks/tests_chart.yml
index e0644048..2199991c 100644
--- a/molecule/default/roles/helm/tasks/tests_chart.yml
+++ b/molecule/default/roles/helm/tasks/tests_chart.yml
@@ -348,6 +348,24 @@
that:
result.stat.exists
+ - name: Release using non-existent context
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ create_namespace: true
+ context: does-not-exist
+ ignore_errors: yes
+ register: result
+
+ - name: Assert that release fails with non-existent context
+ assert:
+ that:
+ - result is failed
+ - "'context \"does-not-exist\" does not exist' in result.stderr"
+
always:
- name: Clean up temp dir
file:
diff --git a/plugins/inventory/k8s.py b/plugins/inventory/k8s.py
index ede54375..cc106fce 100644
--- a/plugins/inventory/k8s.py
+++ b/plugins/inventory/k8s.py
@@ -23,7 +23,7 @@
plugin:
description: token that ensures this is a source file for the 'k8s' plugin.
required: True
- choices: ['k8s']
+ choices: ['community.kubernetes.k8s', 'k8s']
connections:
description:
- Optional list of cluster connection settings. If no connections are provided, the default
@@ -117,7 +117,7 @@
import json
from ansible.errors import AnsibleError
-from ansible_collections.community.kubernetes.plugins.module_utils.common import K8sAnsibleMixin, HAS_K8S_MODULE_HELPER, k8s_import_exception
+from ansible_collections.community.kubernetes.plugins.module_utils.common import K8sAnsibleMixin, HAS_K8S_MODULE_HELPER, k8s_import_exception, get_api_client
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
try:
@@ -180,7 +180,7 @@ def fetch_objects(self, connections):
for connection in connections:
if not isinstance(connection, dict):
raise K8sInventoryException("Expecting connection to be a dictionary.")
- client = self.get_api_client(**connection)
+ client = get_api_client(**connection)
name = connection.get('name', self.get_default_host_name(client.configuration.host))
if connection.get('namespaces'):
namespaces = connection['namespaces']
@@ -190,7 +190,7 @@ def fetch_objects(self, connections):
self.get_pods_for_namespace(client, name, namespace)
self.get_services_for_namespace(client, name, namespace)
else:
- client = self.get_api_client()
+ client = get_api_client()
name = self.get_default_host_name(client.configuration.host)
namespaces = self.get_available_namespaces(client)
for namespace in namespaces:
diff --git a/plugins/inventory/openshift.py b/plugins/inventory/openshift.py
deleted file mode 100644
index f6c393bd..00000000
--- a/plugins/inventory/openshift.py
+++ /dev/null
@@ -1,202 +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.
- suboptions:
- 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: community.kubernetes.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: community.kubernetes.openshift
-connections:
- - namespaces:
- - testing
-
-# Use a custom config file, and a specific context.
-plugin: community.kubernetes.openshift
-connections:
- - kubeconfig: /path/to/config
- context: 'awx/192-168-64-4:8443/developer'
-'''
-
-from ansible_collections.community.kubernetes.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 = 'community.kubernetes.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/plugins/lookup/k8s.py b/plugins/lookup/k8s.py
index 68849053..fc4558cf 100644
--- a/plugins/lookup/k8s.py
+++ b/plugins/lookup/k8s.py
@@ -198,7 +198,7 @@
from ansible.module_utils.common._collections_compat import KeysView
from ansible.plugins.lookup import LookupBase
-from ansible_collections.community.kubernetes.plugins.module_utils.common import K8sAnsibleMixin
+from ansible_collections.community.kubernetes.plugins.module_utils.common import K8sAnsibleMixin, get_api_client
try:
@@ -235,7 +235,7 @@ def fail(self, msg=None):
def run(self, terms, variables=None, **kwargs):
self.params = kwargs
- self.client = self.get_api_client()
+ self.client = get_api_client()
cluster_info = kwargs.get('cluster_info')
if cluster_info == 'version':
diff --git a/plugins/module_utils/ansiblemodule.py b/plugins/module_utils/ansiblemodule.py
new file mode 100644
index 00000000..a647b163
--- /dev/null
+++ b/plugins/module_utils/ansiblemodule.py
@@ -0,0 +1,6 @@
+from __future__ import (absolute_import, division, print_function)
+
+__metaclass__ = type
+
+
+from ansible.module_utils.basic import AnsibleModule # noqa: F401
diff --git a/plugins/module_utils/args_common.py b/plugins/module_utils/args_common.py
new file mode 100644
index 00000000..fadaf44e
--- /dev/null
+++ b/plugins/module_utils/args_common.py
@@ -0,0 +1,133 @@
+from __future__ import (absolute_import, division, print_function)
+
+from ansible.module_utils.six import string_types
+
+__metaclass__ = type
+
+
+def list_dict_str(value):
+ if isinstance(value, (list, dict, string_types)):
+ return value
+ raise TypeError
+
+
+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': {
+ 'type': 'str',
+ },
+ 'persist_config': {
+ 'type': 'bool',
+ },
+}
+
+WAIT_ARG_SPEC = dict(
+ wait=dict(type='bool', default=False),
+ wait_sleep=dict(type='int', default=5),
+ wait_timeout=dict(type='int', default=120),
+ wait_condition=dict(
+ type='dict',
+ default=None,
+ options=dict(
+ type=dict(),
+ status=dict(default=True, choices=[True, False, "Unknown"]),
+ reason=dict()
+ )
+ )
+)
+
+# 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',
+}
+
+NAME_ARG_SPEC = {
+ 'kind': {},
+ 'name': {},
+ 'namespace': {},
+ 'api_version': {
+ 'default': 'v1',
+ 'aliases': ['api', 'version'],
+ },
+}
+
+COMMON_ARG_SPEC = {
+ 'state': {
+ 'default': 'present',
+ 'choices': ['present', 'absent'],
+ },
+ 'force': {
+ 'type': 'bool',
+ 'default': False,
+ },
+}
+
+RESOURCE_ARG_SPEC = {
+ 'resource_definition': {
+ 'type': list_dict_str,
+ 'aliases': ['definition', 'inline']
+ },
+ 'src': {
+ 'type': 'path',
+ },
+}
+
+ARG_ATTRIBUTES_BLACKLIST = ('property_path',)
+
+DELETE_OPTS_ARG_SPEC = {
+ 'propagationPolicy': {
+ 'choices': ['Foreground', 'Background', 'Orphan'],
+ },
+ 'gracePeriodSeconds': {
+ 'type': 'int',
+ },
+ 'preconditions': {
+ 'type': 'dict',
+ 'options': {
+ 'resourceVersion': {
+ 'type': 'str',
+ },
+ 'uid': {
+ 'type': 'str',
+ }
+ }
+ }
+}
diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py
index 3c44f5c9..40e9564e 100644
--- a/plugins/module_utils/common.py
+++ b/plugins/module_utils/common.py
@@ -26,6 +26,7 @@
from datetime import datetime
from distutils.version import LooseVersion
+from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_MAP, AUTH_ARG_SPEC)
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.six import iteritems, string_types
@@ -99,201 +100,109 @@
K8S_IMP_ERR = traceback.format_exc()
-def list_dict_str(value):
- if isinstance(value, (list, dict, 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_ARG_SPEC = {
- 'resource_definition': {
- 'type': list_dict_str,
- 'aliases': ['definition', 'inline']
- },
- 'src': {
- 'type': 'path',
- },
-}
-
-NAME_ARG_SPEC = {
- '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': {
- 'type': 'str',
- },
- 'persist_config': {
- 'type': 'bool',
- },
-}
-
-WAIT_ARG_SPEC = dict(
- wait=dict(type='bool', default=False),
- wait_sleep=dict(type='int', default=5),
- wait_timeout=dict(type='int', default=120),
- wait_condition=dict(
- type='dict',
- default=None,
- options=dict(
- type=dict(),
- status=dict(type='str', default="True", choices=["True", "False", "Unknown"]),
- reason=dict()
- )
- )
-)
-
-DELETE_OPTS_ARG_SPEC = {
- 'propagationPolicy': {
- 'choices': ['Foreground', 'Background', 'Orphan'],
- },
- 'gracePeriodSeconds': {
- 'type': 'int',
- },
- 'preconditions': {
- 'type': 'dict',
- 'options': {
- 'resourceVersion': {
- 'type': 'str',
- },
- 'uid': {
- 'type': 'str',
- }
- }
- }
-}
-
-
-# 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):
+def configuration_digest(configuration):
+ import hashlib
+ m = hashlib.sha256()
+ for k in AUTH_ARG_MAP:
+ if not hasattr(configuration, k):
+ v = None
+ else:
+ v = getattr(configuration, k)
+ if v and k in ["ssl_ca_cert", "cert_file", "key_file"]:
+ with open(str(v), "r") as fd:
+ content = fd.read()
+ m.update(content.encode())
+ else:
+ m.update(str(v).encode())
+ digest = m.hexdigest()
+ return digest
- def __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 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 get_api_client(module=None, **kwargs):
+ auth = {}
- def auth_set(*names):
- return all([auth.get(name) for name in names])
+ def _raise_or_fail(exc, msg):
+ if module:
+ module.fail_json(msg % to_native(exc))
+ else:
+ raise exc
+
+ # If authorization variables aren't defined, look for them in environment variables
+ for true_name, arg_name in AUTH_ARG_MAP.items():
+ if module and module.params.get(arg_name):
+ auth[true_name] = module.params.get(arg_name)
+ elif arg_name in kwargs and kwargs.get(arg_name) is not None:
+ auth[true_name] = kwargs.get(arg_name)
+ else:
+ 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
+
+ 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'):
+ try:
+ kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
+ except Exception as err:
+ _raise_or_fail(err, 'Failed to load kubeconfig due to %s')
- 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'):
+ else:
+ # First try to do incluster config, then kubeconfig
+ try:
+ kubernetes.config.load_incluster_config()
+ except kubernetes.config.ConfigException:
try:
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
except Exception as err:
- self.fail(msg='Failed to load kubeconfig due to %s' % to_native(err))
- else:
- # First try to do incluster config, then kubeconfig
- try:
- kubernetes.config.load_incluster_config()
- except kubernetes.config.ConfigException:
- try:
- kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
- except Exception as err:
- self.fail(msg='Failed to load kubeconfig due to %s' % to_native(err))
+ _raise_or_fail(err, 'Failed to load kubeconfig due to %s')
- # Override any values in the default configuration with Ansible parameters
- # As of kubernetes-client v12.0.0, get_default_copy() is required here
- try:
- configuration = kubernetes.client.Configuration().get_default_copy()
- except AttributeError:
- configuration = kubernetes.client.Configuration()
+ # Override any values in the default configuration with Ansible parameters
+ # As of kubernetes-client v12.0.0, get_default_copy() is required here
+ try:
+ configuration = kubernetes.client.Configuration().get_default_copy()
+ except AttributeError:
+ 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)
- 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)
+ digest = configuration_digest(configuration)
+ if digest in get_api_client._pool:
+ client = get_api_client._pool[digest]
+ return client
- kubernetes.client.Configuration.set_default(configuration)
- try:
- return DynamicClient(kubernetes.client.ApiClient(configuration))
- except Exception as err:
- self.fail(msg='Failed to get client due to %s' % to_native(err))
+ try:
+ client = DynamicClient(kubernetes.client.ApiClient(configuration))
+ except Exception as err:
+ _raise_or_fail(err, 'Failed to get client due to %s')
+
+ get_api_client._pool[digest] = client
+ return client
+
+
+get_api_client._pool = {}
+
+
+class K8sAnsibleMixin(object):
+
+ def __init__(self, module, *args, **kwargs):
+ if not HAS_K8S_MODULE_HELPER:
+ module.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:
+ module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
def find_resource(self, kind, api_version, fail=False):
for attribute in ['kind', 'name', 'singular_name']:
@@ -513,8 +422,8 @@ def _resource_absent(resource):
predicate = _resource_absent
return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, sleep, timeout, state)
- def set_resource_definitions(self):
- resource_definition = self.params.get('resource_definition')
+ def set_resource_definitions(self, module):
+ resource_definition = module.params.get('resource_definition')
self.resource_definitions = []
@@ -529,7 +438,7 @@ def set_resource_definitions(self):
else:
self.resource_definitions = [resource_definition]
- src = self.params.get('src')
+ src = module.params.get('src')
if src:
self.resource_definitions = self.load_resource_definitions(src)
try:
@@ -539,12 +448,12 @@ def set_resource_definitions(self):
if not resource_definition and not src:
implicit_definition = dict(
- kind=self.kind,
- apiVersion=self.api_version,
- metadata=dict(name=self.name)
+ kind=module.params['kind'],
+ apiVersion=module.params['api_version'],
+ metadata=dict(name=module.params['name'])
)
- if self.namespace:
- implicit_definition['metadata']['namespace'] = self.namespace
+ if module.params.get('namespace'):
+ implicit_definition['metadata']['namespace'] = module.params.get('namespace')
self.resource_definitions = [implicit_definition]
def check_library_version(self):
@@ -577,7 +486,7 @@ def execute_module(self):
changed = False
results = []
try:
- self.client = self.get_api_client()
+ self.client = 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))
diff --git a/plugins/module_utils/helm.py b/plugins/module_utils/helm.py
index e5a7e901..021a74da 100644
--- a/plugins/module_utils/helm.py
+++ b/plugins/module_utils/helm.py
@@ -28,8 +28,8 @@ def prepare_helm_environ_update(module):
environ_update = {}
file_to_cleam_up = None
kubeconfig_path = module.params.get('kubeconfig')
- if module.params.get('kube_context') is not None:
- environ_update["HELM_KUBECONTEXT"] = module.params.get('kube_context')
+ if module.params.get('context') is not None:
+ environ_update["HELM_KUBECONTEXT"] = module.params.get('context')
if module.params.get('release_namespace'):
environ_update["HELM_NAMESPACE"] = module.params.get('release_namespace')
if module.params.get("api_key"):
diff --git a/plugins/module_utils/raw.py b/plugins/module_utils/raw.py
deleted file mode 100644
index a353f1cb..00000000
--- a/plugins/module_utils/raw.py
+++ /dev/null
@@ -1,97 +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
-
-import copy
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.community.kubernetes.plugins.module_utils.common import (
- K8sAnsibleMixin, AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_ARG_SPEC)
-
-
-class KubernetesRawModule(K8sAnsibleMixin):
- # NOTE: This class KubernetesRawModule is deprecated in favor of
- # class K8sAnsibleMixin and will be removed 2.0.0 release.
- # Please use K8sAnsibleMixin instead.
- @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(NAME_ARG_SPEC))
- argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
- argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
- argument_spec['merge_type'] = dict(type='list', elements='str', 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):
- mutually_exclusive = [
- ('resource_definition', 'src'),
- ('merge_type', 'apply'),
- ]
-
- module = AnsibleModule(
- argument_spec=self.argspec,
- mutually_exclusive=mutually_exclusive,
- supports_check_mode=True,
- )
-
- self.module = module
- self.check_mode = self.module.check_mode
- self.params = self.module.params
- self.fail_json = self.module.fail_json
- self.fail = self.module.fail_json
- self.exit_json = self.module.exit_json
-
- self.module.warn("class KubernetesRawModule is deprecated"
- " and will be removed in 2.0.0. Please use K8sAnsibleMixin instead.")
- super(KubernetesRawModule, self).__init__(*args, **kwargs)
-
- self.client = None
- self.warnings = []
-
- 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')
-
- self.check_library_version()
- self.set_resource_definitions()
diff --git a/plugins/module_utils/scale.py b/plugins/module_utils/scale.py
deleted file mode 100644
index 55bab010..00000000
--- a/plugins/module_utils/scale.py
+++ /dev/null
@@ -1,166 +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
-
-import copy
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.community.kubernetes.plugins.module_utils.common import (
- AUTH_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_ARG_SPEC, K8sAnsibleMixin)
-
-try:
- from openshift.dynamic.exceptions import NotFoundError
-except ImportError:
- 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(K8sAnsibleMixin):
-
- def __init__(self, k8s_kind=None, *args, **kwargs):
- self.client = None
- self.warnings = []
-
- mutually_exclusive = [
- ('resource_definition', 'src'),
- ]
-
- module = AnsibleModule(
- argument_spec=self.argspec,
- mutually_exclusive=mutually_exclusive,
- supports_check_mode=True,
- )
-
- self.module = module
- self.params = self.module.params
- self.check_mode = self.module.check_mode
- self.fail_json = self.module.fail_json
- self.fail = self.module.fail_json
- self.exit_json = self.module.exit_json
- super(KubernetesAnsibleScaleModule, self).__init__()
-
- 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')
- self.set_resource_definitions()
-
- 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(), diff=dict())
- if wait:
- return_attributes['duration'] = 0
-
- resource = self.find_resource(kind, api_version, fail=True)
-
- try:
- existing = resource.get(name=name, namespace=namespace)
- return_attributes['result'] = existing.to_dict()
- except NotFoundError 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
- return_attributes['result'] = resource.patch(existing.to_dict()).to_dict()
- else:
- return_attributes = self.scale(resource, existing, replicas, wait, wait_time)
-
- self.exit_json(**return_attributes)
-
- @property
- def argspec(self):
- args = copy.deepcopy(SCALE_ARG_SPEC)
- args.update(RESOURCE_ARG_SPEC)
- args.update(NAME_ARG_SPEC)
- args.update(AUTH_ARG_SPEC)
- return args
-
- def scale(self, resource, existing_object, replicas, wait, wait_time):
- name = existing_object.metadata.name
- namespace = existing_object.metadata.namespace
- kind = existing_object.kind
-
- if not hasattr(resource, 'scale'):
- self.fail_json(
- msg="Cannot perform scale on resource of kind {0}".format(resource.kind)
- )
-
- scale_obj = {'kind': kind, 'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}}
-
- existing = resource.get(name=name, namespace=namespace)
-
- try:
- resource.scale.patch(body=scale_obj)
- except Exception as exc:
- self.fail_json(msg="Scale request failed: {0}".format(exc))
-
- k8s_obj = resource.get(name=name, namespace=namespace).to_dict()
- match, diffs = self.diff_objects(existing.to_dict(), k8s_obj)
- result = dict()
- result['result'] = k8s_obj
- result['changed'] = not match
- result['diff'] = diffs
-
- if wait:
- success, result['result'], result['duration'] = self.wait(resource, scale_obj, 5, wait_time)
- if not success:
- self.fail_json(msg="Resource scaling timed out", **result)
- return result
diff --git a/plugins/modules/k8s.py b/plugins/modules/k8s.py
index 6d0afdc4..4baefa14 100644
--- a/plugins/modules/k8s.py
+++ b/plugins/modules/k8s.py
@@ -221,6 +221,23 @@
community.kubernetes.k8s:
state: present
src: ~/metrics-server.yaml
+
+# Wait for a Deployment to pause before continuing
+- name: Pause a Deployment.
+ community.kubernetes.k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: example
+ namespace: testing
+ spec:
+ paused: True
+ wait: yes
+ wait_condition:
+ type: Progressing
+ status: Unknown
+ reason: DeploymentPaused
'''
RETURN = r'''
@@ -263,74 +280,68 @@
import copy
-from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.community.kubernetes.plugins.module_utils.common import (
- K8sAnsibleMixin, COMMON_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, AUTH_ARG_SPEC,
- WAIT_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
-
-
-class KubernetesModule(K8sAnsibleMixin):
-
- @property
- def validate_spec(self):
- return dict(
- fail_on_error=dict(type='bool'),
- version=dict(),
- strict=dict(type='bool', default=True)
- )
-
- @property
- def argspec(self):
- argument_spec = copy.deepcopy(COMMON_ARG_SPEC)
- argument_spec.update(copy.deepcopy(NAME_ARG_SPEC))
- argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
- argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
- argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC))
- argument_spec['merge_type'] = dict(type='list', elements='str', choices=['json', 'merge', 'strategic-merge'])
- 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)
- argument_spec['template'] = dict(type='raw', default=None)
- argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC))
- return argument_spec
-
- def __init__(self, k8s_kind=None, *args, **kwargs):
- mutually_exclusive = [
- ('resource_definition', 'src'),
- ('merge_type', 'apply'),
- ('template', 'resource_definition'),
- ('template', 'src'),
- ]
-
- module = AnsibleModule(
- argument_spec=self.argspec,
- mutually_exclusive=mutually_exclusive,
- supports_check_mode=True,
- )
-
- self.module = module
- self.check_mode = self.module.check_mode
- self.params = self.module.params
- self.fail_json = self.module.fail_json
- self.fail = self.module.fail_json
- self.exit_json = self.module.exit_json
-
- super(KubernetesModule, self).__init__(*args, **kwargs)
-
- self.client = None
- self.warnings = []
-
- 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')
-
- self.check_library_version()
- self.set_resource_definitions()
+from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
+from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC, WAIT_ARG_SPEC, NAME_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
+
+
+def validate_spec():
+ return dict(
+ fail_on_error=dict(type='bool'),
+ version=dict(),
+ strict=dict(type='bool', default=True)
+ )
+
+
+def argspec():
+ argument_spec = copy.deepcopy(COMMON_ARG_SPEC)
+ argument_spec.update(copy.deepcopy(NAME_ARG_SPEC))
+ argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
+ argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
+ argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC))
+ argument_spec['merge_type'] = dict(type='list', elements='str', choices=['json', 'merge', 'strategic-merge'])
+ argument_spec['validate'] = dict(type='dict', default=None, options=validate_spec())
+ argument_spec['append_hash'] = dict(type='bool', default=False)
+ argument_spec['apply'] = dict(type='bool', default=False)
+ argument_spec['template'] = dict(type='raw', default=None)
+ argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC))
+ return argument_spec
+
+
+def execute_module(module, k8s_ansible_mixin):
+ k8s_ansible_mixin.module = module
+ k8s_ansible_mixin.argspec = module.argument_spec
+ k8s_ansible_mixin.check_mode = k8s_ansible_mixin.module.check_mode
+ k8s_ansible_mixin.params = k8s_ansible_mixin.module.params
+ k8s_ansible_mixin.fail_json = k8s_ansible_mixin.module.fail_json
+ k8s_ansible_mixin.fail = k8s_ansible_mixin.module.fail_json
+ k8s_ansible_mixin.exit_json = k8s_ansible_mixin.module.exit_json
+ k8s_ansible_mixin.warnings = []
+
+ k8s_ansible_mixin.kind = k8s_ansible_mixin.params.get('kind')
+ k8s_ansible_mixin.api_version = k8s_ansible_mixin.params.get('api_version')
+ k8s_ansible_mixin.name = k8s_ansible_mixin.params.get('name')
+ k8s_ansible_mixin.namespace = k8s_ansible_mixin.params.get('namespace')
+
+ k8s_ansible_mixin.check_library_version()
+ k8s_ansible_mixin.set_resource_definitions(module)
+ k8s_ansible_mixin.execute_module()
def main():
- KubernetesModule().execute_module()
+ mutually_exclusive = [
+ ('resource_definition', 'src'),
+ ('merge_type', 'apply'),
+ ('template', 'resource_definition'),
+ ('template', 'src'),
+ ]
+ module = AnsibleModule(argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True)
+ from ansible_collections.community.kubernetes.plugins.module_utils.common import (
+ K8sAnsibleMixin, get_api_client)
+
+ k8s_ansible_mixin = K8sAnsibleMixin(module)
+ k8s_ansible_mixin.client = get_api_client(module=module)
+ execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':
diff --git a/plugins/modules/k8s_cluster_info.py b/plugins/modules/k8s_cluster_info.py
index a80a6d62..f15fce40 100644
--- a/plugins/modules/k8s_cluster_info.py
+++ b/plugins/modules/k8s_cluster_info.py
@@ -144,89 +144,60 @@
import copy
-import traceback
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible.module_utils.parsing.convert_bool import boolean
-from ansible_collections.community.kubernetes.plugins.module_utils.common import K8sAnsibleMixin, AUTH_ARG_SPEC
from collections import defaultdict
-
-try:
- try:
- from openshift import __version__ as version
- # >=0.10
- from openshift.dynamic.resource import ResourceList
- except ImportError:
- # <0.10
- from openshift.dynamic.client import ResourceList
- HAS_K8S_INSTANCE_HELPER = True
- k8s_import_exception = None
-except ImportError:
- HAS_K8S_INSTANCE_HELPER = False
- k8s_import_exception = traceback.format_exc()
-
-
-class KubernetesInfoModule(K8sAnsibleMixin):
-
- def __init__(self):
- module = AnsibleModule(
- argument_spec=self.argspec,
- supports_check_mode=True,
- )
- self.module = module
- self.params = self.module.params
-
- if not HAS_K8S_INSTANCE_HELPER:
- self.module.fail_json(msg=missing_required_lib("openshift >= 0.6.2", reason="for merge_type"),
- exception=k8s_import_exception)
-
- super(KubernetesInfoModule, self).__init__()
-
- def execute_module(self):
- self.client = self.get_api_client()
- invalidate_cache = boolean(self.module.params.get('invalidate_cache', True), strict=False)
- if invalidate_cache:
- self.client.resources.invalidate_cache()
- results = defaultdict(dict)
- for resource in list(self.client.resources):
- resource = resource[0]
- if isinstance(resource, ResourceList):
- continue
- key=resource.group_version if resource.group == '' else '/'.join([ resource.group,resource.group_version.split('/')[-1] ])
- results[key][resource.kind] = {
- 'categories': resource.categories if resource.categories else [],
- 'name': resource.name,
- 'namespaced': resource.namespaced,
- 'preferred': resource.preferred,
- 'short_names': resource.short_names if resource.short_names else [],
- 'singular_name': resource.singular_name,
- }
-
- configuration = self.client.configuration
- connection = {
- 'cert_file': configuration.cert_file,
- 'host': configuration.host,
- 'password': configuration.password,
- 'proxy': configuration.proxy,
- 'ssl_ca_cert': configuration.ssl_ca_cert,
- 'username': configuration.username,
- 'verify_ssl': configuration.verify_ssl,
- }
- version_info = {
- 'client': version,
- 'server': self.client.version,
+from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_SPEC)
+
+
+def execute_module(module, client):
+ invalidate_cache = boolean(module.params.get('invalidate_cache', True), strict=False)
+ if invalidate_cache:
+ client.resources.invalidate_cache()
+ results = defaultdict(dict)
+ from openshift.dynamic.resource import ResourceList
+ for resource in list(client.resources):
+ resource = resource[0]
+ if isinstance(resource, ResourceList):
+ continue
+ key=resource.group_version if resource.group == '' else '/'.join([ resource.group,resource.group_version.split('/')[-1] ])
+ results[key][resource.kind] = {
+ 'categories': resource.categories if resource.categories else [],
+ 'name': resource.name,
+ 'namespaced': resource.namespaced,
+ 'preferred': resource.preferred,
+ 'short_names': resource.short_names if resource.short_names else [],
+ 'singular_name': resource.singular_name,
}
- self.module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
-
- @property
- def argspec(self):
- spec = copy.deepcopy(AUTH_ARG_SPEC)
- spec['invalidate_cache'] = dict(type='bool', default=True)
- return spec
+ configuration = client.configuration
+ connection = {
+ 'cert_file': configuration.cert_file,
+ 'host': configuration.host,
+ 'password': configuration.password,
+ 'proxy': configuration.proxy,
+ 'ssl_ca_cert': configuration.ssl_ca_cert,
+ 'username': configuration.username,
+ 'verify_ssl': configuration.verify_ssl,
+ }
+ from openshift import __version__ as version
+ version_info = {
+ 'client': version,
+ 'server': client.version,
+ }
+ module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
+
+
+def argspec():
+ spec = copy.deepcopy(AUTH_ARG_SPEC)
+ spec['invalidate_cache'] = dict(type='bool', default=True)
+ return spec
def main():
- KubernetesInfoModule().execute_module()
+ module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
+ from ansible_collections.community.kubernetes.plugins.module_utils.common import get_api_client
+ execute_module(module, client=get_api_client(module=module))
if __name__ == '__main__':
diff --git a/plugins/modules/k8s_exec.py b/plugins/modules/k8s_exec.py
index a2542b25..55e60849 100644
--- a/plugins/modules/k8s_exec.py
+++ b/plugins/modules/k8s_exec.py
@@ -118,10 +118,10 @@
# ImportError are managed by the common module already.
pass
-from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
- K8sAnsibleMixin, AUTH_ARG_SPEC
+ AUTH_ARG_SPEC
)
try:
@@ -132,75 +132,72 @@
pass
-class KubernetesExecCommand(K8sAnsibleMixin):
-
- def __init__(self):
- module = AnsibleModule(
- argument_spec=self.argspec,
- supports_check_mode=True,
- )
- self.module = module
- self.params = self.module.params
- self.fail_json = self.module.fail_json
- super(KubernetesExecCommand, self).__init__()
-
- @property
- def argspec(self):
- spec = copy.deepcopy(AUTH_ARG_SPEC)
- spec['namespace'] = dict(type='str', required=True)
- spec['pod'] = dict(type='str', required=True)
- spec['container'] = dict(type='str')
- spec['command'] = dict(type='str', required=True)
- return spec
-
- def execute_module(self):
- # Load kubernetes.client.Configuration
- self.get_api_client()
- api = core_v1_api.CoreV1Api()
-
- # hack because passing the container as None breaks things
- optional_kwargs = {}
- if self.params.get('container'):
- optional_kwargs['container'] = self.params['container']
- try:
- resp = stream(
- api.connect_get_namespaced_pod_exec,
- self.params["pod"],
- self.params["namespace"],
- command=shlex.split(self.params["command"]),
- stdout=True,
- stderr=True,
- stdin=False,
- tty=False,
- _preload_content=False, **optional_kwargs)
- except Exception as e:
- self.module.fail_json(msg="Failed to execute on pod %s"
- " due to : %s" % (self.params.get('pod'), to_native(e)))
- stdout, stderr, rc = [], [], 0
- while resp.is_open():
- resp.update(timeout=1)
- if resp.peek_stdout():
- stdout.append(resp.read_stdout())
- if resp.peek_stderr():
- stderr.append(resp.read_stderr())
- err = resp.read_channel(3)
- err = yaml.safe_load(err)
- if err['status'] == 'Success':
- rc = 0
- else:
- rc = int(err['details']['causes'][0]['message'])
-
- self.module.exit_json(
- # Some command might change environment, but ultimately failing at end
- changed=True,
- stdout="".join(stdout),
- stderr="".join(stderr),
- return_code=rc
- )
+def argspec():
+ spec = copy.deepcopy(AUTH_ARG_SPEC)
+ spec['namespace'] = dict(type='str', required=True)
+ spec['pod'] = dict(type='str', required=True)
+ spec['container'] = dict(type='str')
+ spec['command'] = dict(type='str', required=True)
+ return spec
+
+
+def execute_module(module, k8s_ansible_mixin):
+
+ # Load kubernetes.client.Configuration
+ api = core_v1_api.CoreV1Api()
+
+ # hack because passing the container as None breaks things
+ optional_kwargs = {}
+ if module.params.get('container'):
+ optional_kwargs['container'] = module.params['container']
+ try:
+ resp = stream(
+ api.connect_get_namespaced_pod_exec,
+ module.params["pod"],
+ module.params["namespace"],
+ command=shlex.split(module.params["command"]),
+ stdout=True,
+ stderr=True,
+ stdin=False,
+ tty=False,
+ _preload_content=False, **optional_kwargs)
+ except Exception as e:
+ module.fail_json(msg="Failed to execute on pod %s"
+ " due to : %s" % (module.params.get('pod'), to_native(e)))
+ stdout, stderr, rc = [], [], 0
+ while resp.is_open():
+ resp.update(timeout=1)
+ if resp.peek_stdout():
+ stdout.append(resp.read_stdout())
+ if resp.peek_stderr():
+ stderr.append(resp.read_stderr())
+ err = resp.read_channel(3)
+ err = yaml.safe_load(err)
+ if err['status'] == 'Success':
+ rc = 0
+ else:
+ rc = int(err['details']['causes'][0]['message'])
+
+ module.exit_json(
+ # Some command might change environment, but ultimately failing at end
+ changed=True,
+ stdout="".join(stdout),
+ stderr="".join(stderr),
+ return_code=rc
+ )
def main():
- KubernetesExecCommand().execute_module()
+ module = AnsibleModule(
+ argument_spec=argspec(),
+ supports_check_mode=True,
+ )
+ from ansible_collections.community.kubernetes.plugins.module_utils.common import (
+ K8sAnsibleMixin, get_api_client)
+
+ k8s_ansible_mixin = K8sAnsibleMixin(module)
+ k8s_ansible_mixin.client = get_api_client(module=module)
+ execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':
diff --git a/plugins/modules/k8s_info.py b/plugins/modules/k8s_info.py
index 07536e2e..0119d61e 100644
--- a/plugins/modules/k8s_info.py
+++ b/plugins/modules/k8s_info.py
@@ -148,58 +148,50 @@
import copy
-from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.community.kubernetes.plugins.module_utils.common import (
- K8sAnsibleMixin, AUTH_ARG_SPEC, WAIT_ARG_SPEC)
-
-
-class KubernetesInfoModule(K8sAnsibleMixin):
-
- def __init__(self, *args, **kwargs):
- module = AnsibleModule(
- argument_spec=self.argspec,
- supports_check_mode=True,
+from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
+from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_SPEC, WAIT_ARG_SPEC)
+
+
+def execute_module(module, k8s_ansible_mixin):
+ facts = k8s_ansible_mixin.kubernetes_facts(
+ module.params["kind"],
+ module.params["api_version"],
+ name=module.params["name"],
+ namespace=module.params["namespace"],
+ label_selectors=module.params["label_selectors"],
+ field_selectors=module.params["field_selectors"],
+ wait=module.params["wait"],
+ wait_sleep=module.params["wait_sleep"],
+ wait_timeout=module.params["wait_timeout"],
+ condition=module.params["wait_condition"],
+ )
+ module.exit_json(changed=False, **facts)
+
+
+def argspec():
+ args = copy.deepcopy(AUTH_ARG_SPEC)
+ args.update(WAIT_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', elements='str', default=[]),
+ field_selectors=dict(type='list', elements='str', default=[]),
)
- self.module = module
- self.params = self.module.params
- self.fail_json = self.module.fail_json
- self.exit_json = self.module.exit_json
- super(KubernetesInfoModule, self).__init__()
-
- 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'],
- name=self.params['name'],
- namespace=self.params['namespace'],
- label_selectors=self.params['label_selectors'],
- field_selectors=self.params['field_selectors'],
- wait=self.params['wait'],
- wait_sleep=self.params['wait_sleep'],
- wait_timeout=self.params['wait_timeout'],
- condition=self.params['wait_condition']))
-
- @property
- def argspec(self):
- args = copy.deepcopy(AUTH_ARG_SPEC)
- args.update(WAIT_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', elements='str', default=[]),
- field_selectors=dict(type='list', elements='str', default=[]),
- )
- )
- return args
+ )
+ return args
def main():
- KubernetesInfoModule().execute_module()
+ module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
+ from ansible_collections.community.kubernetes.plugins.module_utils.common import (
+ K8sAnsibleMixin, get_api_client)
+
+ k8s_ansible_mixin = K8sAnsibleMixin(module)
+ k8s_ansible_mixin.client = get_api_client(module=module)
+ execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':
diff --git a/plugins/modules/k8s_log.py b/plugins/modules/k8s_log.py
index e7b75711..50b568b4 100644
--- a/plugins/modules/k8s_log.py
+++ b/plugins/modules/k8s_log.py
@@ -111,116 +111,101 @@
import copy
-from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible.module_utils.six import PY2
-from ansible_collections.community.kubernetes.plugins.module_utils.common import (
- K8sAnsibleMixin, AUTH_ARG_SPEC, NAME_ARG_SPEC)
+from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_SPEC, NAME_ARG_SPEC)
-class KubernetesLogModule(K8sAnsibleMixin):
-
- def __init__(self):
- module = AnsibleModule(
- argument_spec=self.argspec,
- supports_check_mode=True,
- )
- self.module = module
- self.params = self.module.params
- self.fail_json = self.module.fail_json
- self.fail = self.module.fail_json
- self.exit_json = self.module.exit_json
- super(KubernetesLogModule, self).__init__()
-
- @property
- def argspec(self):
- args = copy.deepcopy(AUTH_ARG_SPEC)
- args.update(NAME_ARG_SPEC)
- args.update(
- dict(
- kind=dict(type='str', default='Pod'),
- container=dict(),
- label_selectors=dict(type='list', elements='str', default=[]),
- )
+def argspec():
+ args = copy.deepcopy(AUTH_ARG_SPEC)
+ args.update(NAME_ARG_SPEC)
+ args.update(
+ dict(
+ kind=dict(type='str', default='Pod'),
+ container=dict(),
+ label_selectors=dict(type='list', elements='str', default=[]),
)
- return args
-
- def execute_module(self):
- name = self.params.get('name')
- namespace = self.params.get('namespace')
- label_selector = ','.join(self.params.get('label_selectors', {}))
- if name and label_selector:
- self.fail(msg='Only one of name or label_selectors can be provided')
-
- self.client = self.get_api_client()
- resource = self.find_resource(self.params['kind'], self.params['api_version'], fail=True)
- v1_pods = self.find_resource('Pod', 'v1', fail=True)
-
- if 'log' not in resource.subresources:
- if not name:
- self.fail(msg='name must be provided for resources that do not support the log subresource')
- instance = resource.get(name=name, namespace=namespace)
- label_selector = ','.join(self.extract_selectors(instance))
- resource = v1_pods
-
- if label_selector:
- instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
- if not instances.items:
- self.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector))
- # This matches the behavior of kubectl when logging pods via a selector
- name = instances.items[0].metadata.name
- resource = v1_pods
-
- kwargs = {}
- if self.params.get('container'):
- kwargs['query_params'] = dict(container=self.params['container'])
-
- log = serialize_log(resource.log.get(
- name=name,
- namespace=namespace,
- serialize=False,
- **kwargs
- ))
-
- self.exit_json(changed=False, log=log, log_lines=log.split('\n'))
-
- def extract_selectors(self, instance):
- # Parses selectors on an object based on the specifications documented here:
- # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
- selectors = []
- if not instance.spec.selector:
- self.fail(msg='{0} {1} does not support the log subresource directly, and no Pod selector was found on the object'.format(
- '/'.join(instance.group, instance.apiVersion), instance.kind))
-
- if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions):
- # A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions
- for k, v in dict(instance.spec.selector).items():
- selectors.append('{0}={1}'.format(k, v))
- return selectors
-
- if instance.spec.selector.matchLabels:
- for k, v in dict(instance.spec.selector.matchLabels).items():
- selectors.append('{0}={1}'.format(k, v))
-
- if instance.spec.selector.matchExpressions:
- for expression in instance.spec.selector.matchExpressions:
- operator = expression.operator
-
- if operator == 'Exists':
- selectors.append(expression.key)
- elif operator == 'DoesNotExist':
- selectors.append('!{0}'.format(expression.key))
- elif operator in ['In', 'NotIn']:
- selectors.append('{key} {operator} {values}'.format(
- key=expression.key,
- operator=operator.lower(),
- values='({0})'.format(', '.join(expression.values))
- ))
- else:
- self.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower()))
-
+ )
+ return args
+
+
+def execute_module(module, k8s_ansible_mixin):
+ name = module.params.get('name')
+ namespace = module.params.get('namespace')
+ label_selector = ','.join(module.params.get('label_selectors', {}))
+ if name and label_selector:
+ module.fail(msg='Only one of name or label_selectors can be provided')
+
+ resource = k8s_ansible_mixin.find_resource(module.params['kind'], module.params['api_version'], fail=True)
+ v1_pods = k8s_ansible_mixin.find_resource('Pod', 'v1', fail=True)
+
+ if 'log' not in resource.subresources:
+ if not name:
+ module.fail(msg='name must be provided for resources that do not support the log subresource')
+ instance = resource.get(name=name, namespace=namespace)
+ label_selector = ','.join(extract_selectors(module, instance))
+ resource = v1_pods
+
+ if label_selector:
+ instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
+ if not instances.items:
+ module.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector))
+ # This matches the behavior of kubectl when logging pods via a selector
+ name = instances.items[0].metadata.name
+ resource = v1_pods
+
+ kwargs = {}
+ if module.params.get('container'):
+ kwargs['query_params'] = dict(container=module.params['container'])
+
+ log = serialize_log(resource.log.get(
+ name=name,
+ namespace=namespace,
+ serialize=False,
+ **kwargs
+ ))
+
+ module.exit_json(changed=False, log=log, log_lines=log.split('\n'))
+
+
+def extract_selectors(module, instance):
+ # Parses selectors on an object based on the specifications documented here:
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
+ selectors = []
+ if not instance.spec.selector:
+ module.fail(msg='{0} {1} does not support the log subresource directly, and no Pod selector was found on the object'.format(
+ '/'.join(instance.group, instance.apiVersion), instance.kind))
+
+ if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions):
+ # A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions
+ for k, v in dict(instance.spec.selector).items():
+ selectors.append('{0}={1}'.format(k, v))
return selectors
+ if instance.spec.selector.matchLabels:
+ for k, v in dict(instance.spec.selector.matchLabels).items():
+ selectors.append('{0}={1}'.format(k, v))
+
+ if instance.spec.selector.matchExpressions:
+ for expression in instance.spec.selector.matchExpressions:
+ operator = expression.operator
+
+ if operator == 'Exists':
+ selectors.append(expression.key)
+ elif operator == 'DoesNotExist':
+ selectors.append('!{0}'.format(expression.key))
+ elif operator in ['In', 'NotIn']:
+ selectors.append('{key} {operator} {values}'.format(
+ key=expression.key,
+ operator=operator.lower(),
+ values='({0})'.format(', '.join(expression.values))
+ ))
+ else:
+ module.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower()))
+
+ return selectors
+
def serialize_log(response):
if PY2:
@@ -229,7 +214,13 @@ def serialize_log(response):
def main():
- KubernetesLogModule().execute_module()
+ module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
+ from ansible_collections.community.kubernetes.plugins.module_utils.common import (
+ K8sAnsibleMixin, get_api_client)
+
+ k8s_ansible_mixin = K8sAnsibleMixin(module)
+ k8s_ansible_mixin.client = get_api_client(module=module)
+ execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':
diff --git a/plugins/modules/k8s_rollback.py b/plugins/modules/k8s_rollback.py
index 7ccd4153..20465b44 100644
--- a/plugins/modules/k8s_rollback.py
+++ b/plugins/modules/k8s_rollback.py
@@ -78,127 +78,117 @@
import copy
-from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.community.kubernetes.plugins.module_utils.common import (
- K8sAnsibleMixin, AUTH_ARG_SPEC, NAME_ARG_SPEC)
-
-
-class KubernetesRollbackModule(K8sAnsibleMixin):
-
- def __init__(self):
- module = AnsibleModule(
- argument_spec=self.argspec,
- supports_check_mode=True,
+from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
+from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC, NAME_ARG_SPEC)
+
+
+def get_managed_resource(module):
+ managed_resource = {}
+
+ kind = module.params['kind']
+ if kind == "DaemonSet":
+ managed_resource['kind'] = "ControllerRevision"
+ managed_resource['api_version'] = "apps/v1"
+ elif kind == "Deployment":
+ managed_resource['kind'] = "ReplicaSet"
+ managed_resource['api_version'] = "apps/v1"
+ else:
+ module.fail(msg="Cannot perform rollback on resource of kind {0}".format(kind))
+ return managed_resource
+
+
+def execute_module(module, k8s_ansible_mixin):
+ results = []
+
+ resources = k8s_ansible_mixin.kubernetes_facts(
+ module.params['kind'],
+ module.params['api_version'],
+ module.params['name'],
+ module.params['namespace'],
+ module.params['label_selectors'],
+ module.params['field_selectors'])
+
+ for resource in resources['resources']:
+ result = perform_action(module, k8s_ansible_mixin, resource)
+ results.append(result)
+
+ module.exit_json(**{
+ 'changed': True,
+ 'rollback_info': results
+ })
+
+
+def perform_action(module, k8s_ansible_mixin, resource):
+ if module.params['kind'] == "DaemonSet":
+ current_revision = resource['metadata']['generation']
+ elif module.params['kind'] == "Deployment":
+ current_revision = resource['metadata']['annotations']['deployment.kubernetes.io/revision']
+
+ managed_resource = get_managed_resource(module)
+ managed_resources = k8s_ansible_mixin.kubernetes_facts(
+ managed_resource['kind'],
+ managed_resource['api_version'],
+ '',
+ module.params['namespace'],
+ resource['spec']
+ ['selector']
+ ['matchLabels'],
+ '')
+
+ prev_managed_resource = get_previous_revision(managed_resources['resources'],
+ current_revision)
+
+ if module.params['kind'] == "Deployment":
+ del prev_managed_resource['spec']['template']['metadata']['labels']['pod-template-hash']
+
+ resource_patch = [{
+ "op": "replace",
+ "path": "/spec/template",
+ "value": prev_managed_resource['spec']['template']
+ }, {
+ "op": "replace",
+ "path": "/metadata/annotations",
+ "value": {
+ "deployment.kubernetes.io/revision": prev_managed_resource['metadata']['annotations']['deployment.kubernetes.io/revision']
+ }
+ }]
+
+ api_target = 'deployments'
+ content_type = 'application/json-patch+json'
+ elif module.params['kind'] == "DaemonSet":
+ resource_patch = prev_managed_resource["data"]
+
+ api_target = 'daemonsets'
+ content_type = 'application/strategic-merge-patch+json'
+
+ rollback = k8s_ansible_mixin.client.request(
+ "PATCH",
+ "/apis/{0}/namespaces/{1}/{2}/{3}"
+ .format(module.params['api_version'],
+ module.params['namespace'],
+ api_target,
+ module.params['name']),
+ body=resource_patch,
+ content_type=content_type)
+
+ result = {'changed': True}
+ result['method'] = 'patch'
+ result['body'] = resource_patch
+ result['resources'] = rollback.to_dict()
+ return result
+
+
+def argspec():
+ args = copy.deepcopy(AUTH_ARG_SPEC)
+ args.update(NAME_ARG_SPEC)
+ args.update(
+ dict(
+ label_selectors=dict(type='list', elements='str', default=[]),
+ field_selectors=dict(type='list', elements='str', default=[]),
)
- self.module = module
- self.params = self.module.params
- self.fail_json = self.module.fail_json
- self.fail = self.module.fail_json
- self.exit_json = self.module.exit_json
- super(KubernetesRollbackModule, self).__init__()
-
- self.kind = self.params['kind']
- self.api_version = self.params['api_version']
- self.name = self.params['name']
- self.namespace = self.params['namespace']
- self.managed_resource = {}
-
- if self.kind == "DaemonSet":
- self.managed_resource['kind'] = "ControllerRevision"
- self.managed_resource['api_version'] = "apps/v1"
- elif self.kind == "Deployment":
- self.managed_resource['kind'] = "ReplicaSet"
- self.managed_resource['api_version'] = "apps/v1"
- else:
- self.fail(msg="Cannot perform rollback on resource of kind {0}".format(self.kind))
-
- def execute_module(self):
- results = []
- self.client = self.get_api_client()
-
- resources = self.kubernetes_facts(self.kind,
- self.api_version,
- self.name,
- self.namespace,
- self.params['label_selectors'],
- self.params['field_selectors'])
-
- for resource in resources['resources']:
- result = self.perform_action(resource)
- results.append(result)
-
- self.exit_json(**{
- 'changed': True,
- 'rollback_info': results
- })
-
- def perform_action(self, resource):
- if self.kind == "DaemonSet":
- current_revision = resource['metadata']['generation']
- elif self.kind == "Deployment":
- current_revision = resource['metadata']['annotations']['deployment.kubernetes.io/revision']
-
- managed_resources = self.kubernetes_facts(self.managed_resource['kind'],
- self.managed_resource['api_version'],
- '',
- self.namespace,
- resource['spec']
- ['selector']
- ['matchLabels'],
- '')
-
- prev_managed_resource = get_previous_revision(managed_resources['resources'],
- current_revision)
-
- if self.kind == "Deployment":
- del prev_managed_resource['spec']['template']['metadata']['labels']['pod-template-hash']
-
- resource_patch = [{
- "op": "replace",
- "path": "/spec/template",
- "value": prev_managed_resource['spec']['template']
- }, {
- "op": "replace",
- "path": "/metadata/annotations",
- "value": {
- "deployment.kubernetes.io/revision": prev_managed_resource['metadata']['annotations']['deployment.kubernetes.io/revision']
- }
- }]
-
- api_target = 'deployments'
- content_type = 'application/json-patch+json'
- elif self.kind == "DaemonSet":
- resource_patch = prev_managed_resource["data"]
-
- api_target = 'daemonsets'
- content_type = 'application/strategic-merge-patch+json'
-
- rollback = self.client.request("PATCH",
- "/apis/{0}/namespaces/{1}/{2}/{3}"
- .format(self.api_version,
- self.namespace,
- api_target,
- self.name),
- body=resource_patch,
- content_type=content_type)
-
- result = {'changed': True}
- result['method'] = 'patch'
- result['body'] = resource_patch
- result['resources'] = rollback.to_dict()
- return result
-
- @property
- def argspec(self):
- args = copy.deepcopy(AUTH_ARG_SPEC)
- args.update(NAME_ARG_SPEC)
- args.update(
- dict(
- label_selectors=dict(type='list', elements='str', default=[]),
- field_selectors=dict(type='list', elements='str', default=[]),
- )
- )
- return args
+ )
+ return args
def get_previous_revision(all_resources, current_revision):
@@ -217,7 +207,12 @@ def get_previous_revision(all_resources, current_revision):
def main():
- KubernetesRollbackModule().execute_module()
+ module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
+ from ansible_collections.community.kubernetes.plugins.module_utils.common import (K8sAnsibleMixin, get_api_client)
+
+ k8s_ansible_mixin = K8sAnsibleMixin(module)
+ k8s_ansible_mixin.client = get_api_client(module=module)
+ execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':
diff --git a/plugins/modules/k8s_scale.py b/plugins/modules/k8s_scale.py
index 9e63366a..c15bd5e0 100644
--- a/plugins/modules/k8s_scale.py
+++ b/plugins/modules/k8s_scale.py
@@ -118,11 +118,129 @@
sample: 48
'''
-from ansible_collections.community.kubernetes.plugins.module_utils.scale import KubernetesAnsibleScaleModule
+import copy
+
+from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
+from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_ARG_SPEC)
+
+
+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},
+}
+
+
+def execute_module(module, k8s_ansible_mixin,):
+ k8s_ansible_mixin.set_resource_definitions(module)
+
+ definition = k8s_ansible_mixin.resource_definitions[0]
+
+ name = definition['metadata']['name']
+ namespace = definition['metadata'].get('namespace')
+ api_version = definition['apiVersion']
+ kind = definition['kind']
+ current_replicas = module.params.get('current_replicas')
+ replicas = module.params.get('replicas')
+ resource_version = module.params.get('resource_version')
+
+ wait = module.params.get('wait')
+ wait_time = module.params.get('wait_timeout')
+ existing = None
+ existing_count = None
+ return_attributes = dict(changed=False, result=dict(), diff=dict())
+ if wait:
+ return_attributes['duration'] = 0
+
+ resource = k8s_ansible_mixin.find_resource(kind, api_version, fail=True)
+
+ from ansible_collections.community.kubernetes.plugins.module_utils.common import NotFoundError
+
+ try:
+ existing = resource.get(name=name, namespace=namespace)
+ return_attributes['result'] = existing.to_dict()
+ except NotFoundError as exc:
+ module.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc),
+ error=exc.value.get('status'))
+
+ if module.params['kind'] == 'job':
+ existing_count = existing.spec.parallelism
+ elif hasattr(existing.spec, 'replicas'):
+ existing_count = existing.spec.replicas
+
+ if existing_count is None:
+ module.fail_json(msg='Failed to retrieve the available count for the requested object.')
+
+ if resource_version and resource_version != existing.metadata.resourceVersion:
+ module.exit_json(**return_attributes)
+
+ if current_replicas is not None and existing_count != current_replicas:
+ module.exit_json(**return_attributes)
+
+ if existing_count != replicas:
+ return_attributes['changed'] = True
+ if not module.check_mode:
+ if module.params['kind'] == 'job':
+ existing.spec.parallelism = replicas
+ return_attributes['result'] = resource.patch(existing.to_dict()).to_dict()
+ else:
+ return_attributes = scale(module, k8s_ansible_mixin, resource, existing, replicas, wait, wait_time)
+
+ module.exit_json(**return_attributes)
+
+
+def argspec():
+ args = copy.deepcopy(SCALE_ARG_SPEC)
+ args.update(RESOURCE_ARG_SPEC)
+ args.update(NAME_ARG_SPEC)
+ args.update(AUTH_ARG_SPEC)
+ return args
+
+
+def scale(module, k8s_ansible_mixin, resource, existing_object, replicas, wait, wait_time):
+ name = existing_object.metadata.name
+ namespace = existing_object.metadata.namespace
+ kind = existing_object.kind
+
+ if not hasattr(resource, 'scale'):
+ module.fail_json(
+ msg="Cannot perform scale on resource of kind {0}".format(resource.kind)
+ )
+
+ scale_obj = {'kind': kind, 'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}}
+
+ existing = resource.get(name=name, namespace=namespace)
+
+ try:
+ resource.scale.patch(body=scale_obj)
+ except Exception as exc:
+ module.fail_json(msg="Scale request failed: {0}".format(exc))
+
+ k8s_obj = resource.get(name=name, namespace=namespace).to_dict()
+ match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
+ result = dict()
+ result['result'] = k8s_obj
+ result['changed'] = not match
+ result['diff'] = diffs
+
+ if wait:
+ success, result['result'], result['duration'] = k8s_ansible_mixin.wait(resource, scale_obj, 5, wait_time)
+ if not success:
+ module.fail_json(msg="Resource scaling timed out", **result)
+ return result
def main():
- KubernetesAnsibleScaleModule().execute_module()
+ module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
+ from ansible_collections.community.kubernetes.plugins.module_utils.common import (
+ K8sAnsibleMixin, get_api_client)
+
+ k8s_ansible_mixin = K8sAnsibleMixin(module)
+ k8s_ansible_mixin.client = get_api_client(module=module)
+ execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':
diff --git a/plugins/modules/k8s_service.py b/plugins/modules/k8s_service.py
index ad2ed240..945df2d4 100644
--- a/plugins/modules/k8s_service.py
+++ b/plugins/modules/k8s_service.py
@@ -145,14 +145,12 @@
'''
import copy
-import traceback
from collections import defaultdict
-from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.community.kubernetes.plugins.module_utils.common import (
- K8sAnsibleMixin, AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC)
-
+from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
+from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC)
SERVICE_ARG_SPEC = {
'apply': {
@@ -173,100 +171,69 @@
}
-class KubernetesService(K8sAnsibleMixin):
- def __init__(self, *args, **kwargs):
- mutually_exclusive = [
- ('resource_definition', 'src'),
- ('merge_type', 'apply'),
- ]
-
- module = AnsibleModule(
- argument_spec=self.argspec,
- mutually_exclusive=mutually_exclusive,
- supports_check_mode=True,
- )
-
- self.module = module
- self.check_mode = self.module.check_mode
- self.params = self.module.params
- self.fail_json = self.module.fail_json
- self.fail = self.module.fail_json
- self.exit_json = self.module.exit_json
-
- super(KubernetesService, self).__init__(*args, **kwargs)
-
- self.client = None
- self.warnings = []
-
- self.kind = self.params.get('kind')
- self.api_version = self.params.get('api_version')
- self.name = self.params.get('name')
- self.namespace = self.params.get('namespace')
-
- self.check_library_version()
- self.set_resource_definitions()
-
- @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])
+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(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(COMMON_ARG_SPEC)
- argument_spec.update(RESOURCE_ARG_SPEC)
- argument_spec.update(SERVICE_ARG_SPEC)
- return argument_spec
+def argspec():
+ """ argspec property builder """
+ argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
+ argument_spec.update(COMMON_ARG_SPEC)
+ argument_spec.update(RESOURCE_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')
+def execute_module(module, k8s_ansible_mixin):
+ """ Module execution """
+ k8s_ansible_mixin.set_resource_definitions(module)
- definition = defaultdict(defaultdict)
+ api_version = 'v1'
+ selector = module.params.get('selector')
+ service_type = module.params.get('type')
+ ports = module.params.get('ports')
- definition['kind'] = 'Service'
- definition['apiVersion'] = api_version
+ definition = defaultdict(defaultdict)
- def_spec = definition['spec']
- def_spec['type'] = service_type
- def_spec['ports'] = ports
- def_spec['selector'] = selector
+ definition['kind'] = 'Service'
+ definition['apiVersion'] = api_version
- def_meta = definition['metadata']
- def_meta['name'] = self.params.get('name')
- def_meta['namespace'] = self.params.get('namespace')
+ def_spec = definition['spec']
+ def_spec['type'] = service_type
+ def_spec['ports'] = ports
+ def_spec['selector'] = selector
- # 'resource_definition:' has lower priority than module parameters
- definition = dict(self.merge_dicts(self.resource_definitions[0], definition))
+ def_meta = definition['metadata']
+ def_meta['name'] = module.params.get('name')
+ def_meta['namespace'] = module.params.get('namespace')
- resource = self.find_resource('Service', api_version, fail=True)
- definition = self.set_defaults(resource, definition)
- result = self.perform_action(resource, definition)
+ # 'resource_definition:' has lower priority than module parameters
+ definition = dict(merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition))
- self.exit_json(**result)
+ resource = k8s_ansible_mixin.find_resource('Service', api_version, fail=True)
+ definition = k8s_ansible_mixin.set_defaults(resource, definition)
+ result = k8s_ansible_mixin.perform_action(resource, definition)
+
+ module.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())
+ module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
+ from ansible_collections.community.kubernetes.plugins.module_utils.common import (
+ K8sAnsibleMixin, get_api_client)
+
+ k8s_ansible_mixin = K8sAnsibleMixin(module)
+ k8s_ansible_mixin.client = get_api_client(module=module)
+ execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':
diff --git a/tests/integration/targets/kubernetes/defaults/main.yml b/tests/integration/targets/kubernetes/defaults/main.yml
deleted file mode 100644
index e46ca26f..00000000
--- a/tests/integration/targets/kubernetes/defaults/main.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-k8s_openshift: true
diff --git a/tests/integration/targets/kubernetes/tasks/main.yml b/tests/integration/targets/kubernetes/tasks/main.yml
index de85850d..fb8be1a1 100644
--- a/tests/integration/targets/kubernetes/tasks/main.yml
+++ b/tests/integration/targets/kubernetes/tasks/main.yml
@@ -77,11 +77,3 @@
path: "{{ virtualenv }}"
state: absent
no_log: yes
-
-# Test openshift
-
-- debug:
- var: k8s_openshift
-
-- include: openshift.yml
- when: k8s_openshift | bool
diff --git a/tests/integration/targets/kubernetes/tasks/openshift.yml b/tests/integration/targets/kubernetes/tasks/openshift.yml
deleted file mode 100644
index af0f51a7..00000000
--- a/tests/integration/targets/kubernetes/tasks/openshift.yml
+++ /dev/null
@@ -1,62 +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