refactoring for ansible_module.turbo integration (#313)

* refactoring for ansible_module.turbo integration

This refactoring prepares the integration of `ansible_module.turbo`

- Delay the loading of `common.py`, move the shared structure in
  `args_common`.
- Avoid the use of one single object per module, this to increase the
  amount of Python structure that we can cache.
- Cache the Kubernetes client.

See: https://github.com/ansible-collections/community.kubernetes/pull/270

Co-authored-by: Jill Rouleau <jill.rouleau@bespokess.com>
This commit is contained in:
Gonéri Le Bouder
2021-03-16 17:16:18 -04:00
committed by GitHub
parent 2f6fae322c
commit 39660dd40e
14 changed files with 798 additions and 892 deletions

View File

@@ -117,7 +117,7 @@ connections:
import json import json
from ansible.errors import AnsibleError 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 from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
try: try:
@@ -180,7 +180,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
for connection in connections: for connection in connections:
if not isinstance(connection, dict): if not isinstance(connection, dict):
raise K8sInventoryException("Expecting connection to be a dictionary.") 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)) name = connection.get('name', self.get_default_host_name(client.configuration.host))
if connection.get('namespaces'): if connection.get('namespaces'):
namespaces = connection['namespaces'] namespaces = connection['namespaces']
@@ -190,7 +190,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
self.get_pods_for_namespace(client, name, namespace) self.get_pods_for_namespace(client, name, namespace)
self.get_services_for_namespace(client, name, namespace) self.get_services_for_namespace(client, name, namespace)
else: else:
client = self.get_api_client() client = get_api_client()
name = self.get_default_host_name(client.configuration.host) name = self.get_default_host_name(client.configuration.host)
namespaces = self.get_available_namespaces(client) namespaces = self.get_available_namespaces(client)
for namespace in namespaces: for namespace in namespaces:

View File

@@ -198,7 +198,7 @@ from ansible.errors import AnsibleError
from ansible.module_utils.common._collections_compat import KeysView from ansible.module_utils.common._collections_compat import KeysView
from ansible.plugins.lookup import LookupBase 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: try:
@@ -235,7 +235,7 @@ class KubernetesLookup(K8sAnsibleMixin):
def run(self, terms, variables=None, **kwargs): def run(self, terms, variables=None, **kwargs):
self.params = kwargs self.params = kwargs
self.client = self.get_api_client() self.client = get_api_client()
cluster_info = kwargs.get('cluster_info') cluster_info = kwargs.get('cluster_info')
if cluster_info == 'version': if cluster_info == 'version':

View File

@@ -0,0 +1,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule # noqa: F401

View File

@@ -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',
}
}
}
}

View File

@@ -26,6 +26,7 @@ import sys
from datetime import datetime from datetime import datetime
from distutils.version import LooseVersion 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.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.six import iteritems, string_types from ansible.module_utils.six import iteritems, string_types
@@ -99,160 +100,43 @@ except ImportError as e:
K8S_IMP_ERR = traceback.format_exc() K8S_IMP_ERR = traceback.format_exc()
def list_dict_str(value): def configuration_digest(configuration):
if isinstance(value, (list, dict, string_types)): import hashlib
return value m = hashlib.sha256()
raise TypeError 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
ARG_ATTRIBUTES_BLACKLIST = ('property_path',) def get_api_client(module=None):
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 __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 = {} auth = {}
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 # If authorization variables aren't defined, look for them in environment variables
for true_name, arg_name in AUTH_ARG_MAP.items(): for true_name, arg_name in AUTH_ARG_MAP.items():
if auth_params.get(arg_name) is None: if module and module.params.get(arg_name):
auth[true_name] = module.params.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) 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 env_value is not None:
if AUTH_ARG_SPEC[arg_name].get('type') == 'bool': if AUTH_ARG_SPEC[arg_name].get('type') == 'bool':
env_value = env_value.lower() not in ['0', 'false', 'no'] env_value = env_value.lower() not in ['0', 'false', 'no']
auth[true_name] = env_value auth[true_name] = env_value
else:
auth[true_name] = auth_params[arg_name]
def auth_set(*names): def auth_set(*names):
return all([auth.get(name) for name in names]) return all([auth.get(name) for name in names])
@@ -264,7 +148,8 @@ class K8sAnsibleMixin(object):
try: try:
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config')) kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
except Exception as err: 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')
else: else:
# First try to do incluster config, then kubeconfig # First try to do incluster config, then kubeconfig
try: try:
@@ -273,7 +158,7 @@ class K8sAnsibleMixin(object):
try: try:
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config')) kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
except Exception as err: 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 # Override any values in the default configuration with Ansible parameters
# As of kubernetes-client v12.0.0, get_default_copy() is required here # As of kubernetes-client v12.0.0, get_default_copy() is required here
@@ -289,11 +174,33 @@ class K8sAnsibleMixin(object):
else: else:
setattr(configuration, key, value) setattr(configuration, key, value)
kubernetes.client.Configuration.set_default(configuration) digest = configuration_digest(configuration)
if digest in get_api_client._pool:
client = get_api_client._pool[digest]
return client
try: try:
return DynamicClient(kubernetes.client.ApiClient(configuration)) client = DynamicClient(kubernetes.client.ApiClient(configuration))
except Exception as err: except Exception as err:
self.fail(msg='Failed to get client due to %s' % to_native(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): def find_resource(self, kind, api_version, fail=False):
for attribute in ['kind', 'name', 'singular_name']: for attribute in ['kind', 'name', 'singular_name']:
@@ -513,8 +420,8 @@ class K8sAnsibleMixin(object):
predicate = _resource_absent predicate = _resource_absent
return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, sleep, timeout, state) return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, sleep, timeout, state)
def set_resource_definitions(self): def set_resource_definitions(self, module):
resource_definition = self.params.get('resource_definition') resource_definition = module.params.get('resource_definition')
self.resource_definitions = [] self.resource_definitions = []
@@ -529,7 +436,7 @@ class K8sAnsibleMixin(object):
else: else:
self.resource_definitions = [resource_definition] self.resource_definitions = [resource_definition]
src = self.params.get('src') src = module.params.get('src')
if src: if src:
self.resource_definitions = self.load_resource_definitions(src) self.resource_definitions = self.load_resource_definitions(src)
try: try:
@@ -539,12 +446,12 @@ class K8sAnsibleMixin(object):
if not resource_definition and not src: if not resource_definition and not src:
implicit_definition = dict( implicit_definition = dict(
kind=self.kind, kind=module.params['kind'],
apiVersion=self.api_version, apiVersion=module.params['api_version'],
metadata=dict(name=self.name) metadata=dict(name=module.params['name'])
) )
if self.namespace: if module.params.get('namespace'):
implicit_definition['metadata']['namespace'] = self.namespace implicit_definition['metadata']['namespace'] = module.params.get('namespace')
self.resource_definitions = [implicit_definition] self.resource_definitions = [implicit_definition]
def check_library_version(self): def check_library_version(self):
@@ -577,7 +484,7 @@ class K8sAnsibleMixin(object):
changed = False changed = False
results = [] results = []
try: try:
self.client = self.get_api_client() self.client = get_api_client()
# Hopefully the kubernetes client will provide its own exception class one day # Hopefully the kubernetes client will provide its own exception class one day
except (urllib3.exceptions.RequestError) as e: except (urllib3.exceptions.RequestError) as e:
self.fail_json(msg="Couldn't connect to Kubernetes: %s" % str(e)) self.fail_json(msg="Couldn't connect to Kubernetes: %s" % str(e))

View File

@@ -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 <http://www.gnu.org/licenses/>.
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

View File

@@ -263,74 +263,68 @@ result:
import copy import copy
from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.common import ( from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (
K8sAnsibleMixin, COMMON_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, AUTH_ARG_SPEC, AUTH_ARG_SPEC, WAIT_ARG_SPEC, NAME_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
WAIT_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
class KubernetesModule(K8sAnsibleMixin): def validate_spec():
@property
def validate_spec(self):
return dict( return dict(
fail_on_error=dict(type='bool'), fail_on_error=dict(type='bool'),
version=dict(), version=dict(),
strict=dict(type='bool', default=True) strict=dict(type='bool', default=True)
) )
@property
def argspec(self): def argspec():
argument_spec = copy.deepcopy(COMMON_ARG_SPEC) argument_spec = copy.deepcopy(COMMON_ARG_SPEC)
argument_spec.update(copy.deepcopy(NAME_ARG_SPEC)) argument_spec.update(copy.deepcopy(NAME_ARG_SPEC))
argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC)) argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC)) argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
argument_spec.update(copy.deepcopy(WAIT_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['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['validate'] = dict(type='dict', default=None, options=validate_spec())
argument_spec['append_hash'] = dict(type='bool', default=False) argument_spec['append_hash'] = dict(type='bool', default=False)
argument_spec['apply'] = dict(type='bool', default=False) argument_spec['apply'] = dict(type='bool', default=False)
argument_spec['template'] = dict(type='raw', default=None) argument_spec['template'] = dict(type='raw', default=None)
argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC)) argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC))
return argument_spec return argument_spec
def __init__(self, k8s_kind=None, *args, **kwargs):
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():
mutually_exclusive = [ mutually_exclusive = [
('resource_definition', 'src'), ('resource_definition', 'src'),
('merge_type', 'apply'), ('merge_type', 'apply'),
('template', 'resource_definition'), ('template', 'resource_definition'),
('template', 'src'), ('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)
module = AnsibleModule( k8s_ansible_mixin = K8sAnsibleMixin(module)
argument_spec=self.argspec, k8s_ansible_mixin.client = get_api_client(module=module)
mutually_exclusive=mutually_exclusive, execute_module(module, k8s_ansible_mixin)
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()
def main():
KubernetesModule().execute_module()
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -159,50 +159,19 @@ apis:
import copy 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.module_utils.parsing.convert_bool import boolean
from ansible_collections.community.kubernetes.plugins.module_utils.common import K8sAnsibleMixin, AUTH_ARG_SPEC from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_SPEC)
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 execute_module(module, client):
invalidate_cache = boolean(module.params.get('invalidate_cache', True), strict=False)
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: if invalidate_cache:
self.client.resources.invalidate_cache() client.resources.invalidate_cache()
results = {} results = {}
for resource in list(self.client.resources): from openshift.dynamic.resource import ResourceList
for resource in list(client.resources):
resource = resource[0] resource = resource[0]
if isinstance(resource, ResourceList): if isinstance(resource, ResourceList):
continue continue
@@ -216,7 +185,7 @@ class KubernetesInfoModule(K8sAnsibleMixin):
'short_names': resource.short_names if resource.short_names else [], 'short_names': resource.short_names if resource.short_names else [],
'singular_name': resource.singular_name, 'singular_name': resource.singular_name,
} }
configuration = self.client.configuration configuration = client.configuration
connection = { connection = {
'cert_file': configuration.cert_file, 'cert_file': configuration.cert_file,
'host': configuration.host, 'host': configuration.host,
@@ -226,21 +195,24 @@ class KubernetesInfoModule(K8sAnsibleMixin):
'username': configuration.username, 'username': configuration.username,
'verify_ssl': configuration.verify_ssl, 'verify_ssl': configuration.verify_ssl,
} }
from openshift import __version__ as version
version_info = { version_info = {
'client': version, 'client': version,
'server': self.client.version, 'server': client.version,
} }
self.module.exit_json(changed=False, apis=results, connection=connection, version=version_info) module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
@property
def argspec(self): def argspec():
spec = copy.deepcopy(AUTH_ARG_SPEC) spec = copy.deepcopy(AUTH_ARG_SPEC)
spec['invalidate_cache'] = dict(type='bool', default=True) spec['invalidate_cache'] = dict(type='bool', default=True)
return spec return spec
def main(): 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__': if __name__ == '__main__':

View File

@@ -118,10 +118,10 @@ except ImportError:
# ImportError are managed by the common module already. # ImportError are managed by the common module already.
pass 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.module_utils._text import to_native
from ansible_collections.community.kubernetes.plugins.module_utils.common import ( from ansible_collections.community.kubernetes.plugins.module_utils.common import (
K8sAnsibleMixin, AUTH_ARG_SPEC AUTH_ARG_SPEC
) )
try: try:
@@ -132,20 +132,7 @@ except ImportError:
pass pass
class KubernetesExecCommand(K8sAnsibleMixin): def argspec():
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 = copy.deepcopy(AUTH_ARG_SPEC)
spec['namespace'] = dict(type='str', required=True) spec['namespace'] = dict(type='str', required=True)
spec['pod'] = dict(type='str', required=True) spec['pod'] = dict(type='str', required=True)
@@ -153,29 +140,30 @@ class KubernetesExecCommand(K8sAnsibleMixin):
spec['command'] = dict(type='str', required=True) spec['command'] = dict(type='str', required=True)
return spec return spec
def execute_module(self):
def execute_module(module, k8s_ansible_mixin):
# Load kubernetes.client.Configuration # Load kubernetes.client.Configuration
self.get_api_client()
api = core_v1_api.CoreV1Api() api = core_v1_api.CoreV1Api()
# hack because passing the container as None breaks things # hack because passing the container as None breaks things
optional_kwargs = {} optional_kwargs = {}
if self.params.get('container'): if module.params.get('container'):
optional_kwargs['container'] = self.params['container'] optional_kwargs['container'] = module.params['container']
try: try:
resp = stream( resp = stream(
api.connect_get_namespaced_pod_exec, api.connect_get_namespaced_pod_exec,
self.params["pod"], module.params["pod"],
self.params["namespace"], module.params["namespace"],
command=shlex.split(self.params["command"]), command=shlex.split(module.params["command"]),
stdout=True, stdout=True,
stderr=True, stderr=True,
stdin=False, stdin=False,
tty=False, tty=False,
_preload_content=False, **optional_kwargs) _preload_content=False, **optional_kwargs)
except Exception as e: except Exception as e:
self.module.fail_json(msg="Failed to execute on pod %s" module.fail_json(msg="Failed to execute on pod %s"
" due to : %s" % (self.params.get('pod'), to_native(e))) " due to : %s" % (module.params.get('pod'), to_native(e)))
stdout, stderr, rc = [], [], 0 stdout, stderr, rc = [], [], 0
while resp.is_open(): while resp.is_open():
resp.update(timeout=1) resp.update(timeout=1)
@@ -190,7 +178,7 @@ class KubernetesExecCommand(K8sAnsibleMixin):
else: else:
rc = int(err['details']['causes'][0]['message']) rc = int(err['details']['causes'][0]['message'])
self.module.exit_json( module.exit_json(
# Some command might change environment, but ultimately failing at end # Some command might change environment, but ultimately failing at end
changed=True, changed=True,
stdout="".join(stdout), stdout="".join(stdout),
@@ -200,7 +188,16 @@ class KubernetesExecCommand(K8sAnsibleMixin):
def main(): 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__': if __name__ == '__main__':

View File

@@ -148,41 +148,27 @@ resources:
import copy import copy
from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.common import ( from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_SPEC, WAIT_ARG_SPEC)
K8sAnsibleMixin, AUTH_ARG_SPEC, WAIT_ARG_SPEC)
class KubernetesInfoModule(K8sAnsibleMixin): def execute_module(module, k8s_ansible_mixin):
facts = k8s_ansible_mixin.kubernetes_facts(
def __init__(self, *args, **kwargs): module.params["kind"],
module = AnsibleModule( module.params["api_version"],
argument_spec=self.argspec, name=module.params["name"],
supports_check_mode=True, 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"],
) )
self.module = module module.exit_json(changed=False, **facts)
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, def argspec():
**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 = copy.deepcopy(AUTH_ARG_SPEC)
args.update(WAIT_ARG_SPEC) args.update(WAIT_ARG_SPEC)
args.update( args.update(
@@ -199,7 +185,13 @@ class KubernetesInfoModule(K8sAnsibleMixin):
def main(): 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__': if __name__ == '__main__':

View File

@@ -111,29 +111,13 @@ log_lines:
import copy 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.module_utils.six import PY2
from ansible_collections.community.kubernetes.plugins.module_utils.common import ( from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_SPEC, NAME_ARG_SPEC)
K8sAnsibleMixin, AUTH_ARG_SPEC, NAME_ARG_SPEC)
class KubernetesLogModule(K8sAnsibleMixin): def argspec():
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 = copy.deepcopy(AUTH_ARG_SPEC)
args.update(NAME_ARG_SPEC) args.update(NAME_ARG_SPEC)
args.update( args.update(
@@ -145,35 +129,35 @@ class KubernetesLogModule(K8sAnsibleMixin):
) )
return args 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() def execute_module(module, k8s_ansible_mixin):
resource = self.find_resource(self.params['kind'], self.params['api_version'], fail=True) name = module.params.get('name')
v1_pods = self.find_resource('Pod', 'v1', fail=True) 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 'log' not in resource.subresources:
if not name: if not name:
self.fail(msg='name must be provided for resources that do not support the log subresource') module.fail(msg='name must be provided for resources that do not support the log subresource')
instance = resource.get(name=name, namespace=namespace) instance = resource.get(name=name, namespace=namespace)
label_selector = ','.join(self.extract_selectors(instance)) label_selector = ','.join(extract_selectors(module, instance))
resource = v1_pods resource = v1_pods
if label_selector: if label_selector:
instances = v1_pods.get(namespace=namespace, label_selector=label_selector) instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
if not instances.items: if not instances.items:
self.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector)) 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 # This matches the behavior of kubectl when logging pods via a selector
name = instances.items[0].metadata.name name = instances.items[0].metadata.name
resource = v1_pods resource = v1_pods
kwargs = {} kwargs = {}
if self.params.get('container'): if module.params.get('container'):
kwargs['query_params'] = dict(container=self.params['container']) kwargs['query_params'] = dict(container=module.params['container'])
log = serialize_log(resource.log.get( log = serialize_log(resource.log.get(
name=name, name=name,
@@ -182,14 +166,15 @@ class KubernetesLogModule(K8sAnsibleMixin):
**kwargs **kwargs
)) ))
self.exit_json(changed=False, log=log, log_lines=log.split('\n')) module.exit_json(changed=False, log=log, log_lines=log.split('\n'))
def extract_selectors(self, instance):
def extract_selectors(module, instance):
# Parses selectors on an object based on the specifications documented here: # Parses selectors on an object based on the specifications documented here:
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
selectors = [] selectors = []
if not instance.spec.selector: 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( 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)) '/'.join(instance.group, instance.apiVersion), instance.kind))
if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions): if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions):
@@ -217,7 +202,7 @@ class KubernetesLogModule(K8sAnsibleMixin):
values='({0})'.format(', '.join(expression.values)) values='({0})'.format(', '.join(expression.values))
)) ))
else: else:
self.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower())) module.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower()))
return selectors return selectors
@@ -229,7 +214,13 @@ def serialize_log(response):
def main(): 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__': if __name__ == '__main__':

View File

@@ -78,70 +78,59 @@ rollback_info:
import copy import copy
from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.common import ( from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (
K8sAnsibleMixin, AUTH_ARG_SPEC, NAME_ARG_SPEC) AUTH_ARG_SPEC, NAME_ARG_SPEC)
class KubernetesRollbackModule(K8sAnsibleMixin): def get_managed_resource(module):
managed_resource = {}
def __init__(self): kind = module.params['kind']
module = AnsibleModule( if kind == "DaemonSet":
argument_spec=self.argspec, managed_resource['kind'] = "ControllerRevision"
supports_check_mode=True, managed_resource['api_version'] = "apps/v1"
) elif kind == "Deployment":
self.module = module managed_resource['kind'] = "ReplicaSet"
self.params = self.module.params managed_resource['api_version'] = "apps/v1"
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: else:
self.fail(msg="Cannot perform rollback on resource of kind {0}".format(self.kind)) module.fail(msg="Cannot perform rollback on resource of kind {0}".format(kind))
return managed_resource
def execute_module(self):
def execute_module(module, k8s_ansible_mixin):
results = [] results = []
self.client = self.get_api_client()
resources = self.kubernetes_facts(self.kind, resources = k8s_ansible_mixin.kubernetes_facts(
self.api_version, module.params['kind'],
self.name, module.params['api_version'],
self.namespace, module.params['name'],
self.params['label_selectors'], module.params['namespace'],
self.params['field_selectors']) module.params['label_selectors'],
module.params['field_selectors'])
for resource in resources['resources']: for resource in resources['resources']:
result = self.perform_action(resource) result = perform_action(module, k8s_ansible_mixin, resource)
results.append(result) results.append(result)
self.exit_json(**{ module.exit_json(**{
'changed': True, 'changed': True,
'rollback_info': results 'rollback_info': results
}) })
def perform_action(self, resource):
if self.kind == "DaemonSet": def perform_action(module, k8s_ansible_mixin, resource):
if module.params['kind'] == "DaemonSet":
current_revision = resource['metadata']['generation'] current_revision = resource['metadata']['generation']
elif self.kind == "Deployment": elif module.params['kind'] == "Deployment":
current_revision = resource['metadata']['annotations']['deployment.kubernetes.io/revision'] current_revision = resource['metadata']['annotations']['deployment.kubernetes.io/revision']
managed_resources = self.kubernetes_facts(self.managed_resource['kind'], managed_resource = get_managed_resource(module)
self.managed_resource['api_version'], managed_resources = k8s_ansible_mixin.kubernetes_facts(
managed_resource['kind'],
managed_resource['api_version'],
'', '',
self.namespace, module.params['namespace'],
resource['spec'] resource['spec']
['selector'] ['selector']
['matchLabels'], ['matchLabels'],
@@ -150,7 +139,7 @@ class KubernetesRollbackModule(K8sAnsibleMixin):
prev_managed_resource = get_previous_revision(managed_resources['resources'], prev_managed_resource = get_previous_revision(managed_resources['resources'],
current_revision) current_revision)
if self.kind == "Deployment": if module.params['kind'] == "Deployment":
del prev_managed_resource['spec']['template']['metadata']['labels']['pod-template-hash'] del prev_managed_resource['spec']['template']['metadata']['labels']['pod-template-hash']
resource_patch = [{ resource_patch = [{
@@ -167,18 +156,19 @@ class KubernetesRollbackModule(K8sAnsibleMixin):
api_target = 'deployments' api_target = 'deployments'
content_type = 'application/json-patch+json' content_type = 'application/json-patch+json'
elif self.kind == "DaemonSet": elif module.params['kind'] == "DaemonSet":
resource_patch = prev_managed_resource["data"] resource_patch = prev_managed_resource["data"]
api_target = 'daemonsets' api_target = 'daemonsets'
content_type = 'application/strategic-merge-patch+json' content_type = 'application/strategic-merge-patch+json'
rollback = self.client.request("PATCH", rollback = k8s_ansible_mixin.client.request(
"PATCH",
"/apis/{0}/namespaces/{1}/{2}/{3}" "/apis/{0}/namespaces/{1}/{2}/{3}"
.format(self.api_version, .format(module.params['api_version'],
self.namespace, module.params['namespace'],
api_target, api_target,
self.name), module.params['name']),
body=resource_patch, body=resource_patch,
content_type=content_type) content_type=content_type)
@@ -188,8 +178,8 @@ class KubernetesRollbackModule(K8sAnsibleMixin):
result['resources'] = rollback.to_dict() result['resources'] = rollback.to_dict()
return result return result
@property
def argspec(self): def argspec():
args = copy.deepcopy(AUTH_ARG_SPEC) args = copy.deepcopy(AUTH_ARG_SPEC)
args.update(NAME_ARG_SPEC) args.update(NAME_ARG_SPEC)
args.update( args.update(
@@ -217,7 +207,12 @@ def get_previous_revision(all_resources, current_revision):
def main(): 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__': if __name__ == '__main__':

View File

@@ -118,11 +118,129 @@ result:
sample: 48 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(): 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__': if __name__ == '__main__':

View File

@@ -145,14 +145,12 @@ result:
''' '''
import copy import copy
import traceback
from collections import defaultdict from collections import defaultdict
from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.common import ( from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (
K8sAnsibleMixin, AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC) AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC)
SERVICE_ARG_SPEC = { SERVICE_ARG_SPEC = {
'apply': { 'apply': {
@@ -173,45 +171,11 @@ SERVICE_ARG_SPEC = {
} }
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): def merge_dicts(x, y):
for k in set(x.keys()).union(y.keys()): for k in set(x.keys()).union(y.keys()):
if k in x and k in y: if k in x and k in y:
if isinstance(x[k], dict) and isinstance(y[k], dict): if isinstance(x[k], dict) and isinstance(y[k], dict):
yield (k, dict(KubernetesService.merge_dicts(x[k], y[k]))) yield (k, dict(merge_dicts(x[k], y[k])))
else: else:
yield (k, y[k]) yield (k, y[k])
elif k in x: elif k in x:
@@ -219,8 +183,8 @@ class KubernetesService(K8sAnsibleMixin):
else: else:
yield (k, y[k]) yield (k, y[k])
@property
def argspec(self): def argspec():
""" argspec property builder """ """ argspec property builder """
argument_spec = copy.deepcopy(AUTH_ARG_SPEC) argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
argument_spec.update(COMMON_ARG_SPEC) argument_spec.update(COMMON_ARG_SPEC)
@@ -228,14 +192,15 @@ class KubernetesService(K8sAnsibleMixin):
argument_spec.update(SERVICE_ARG_SPEC) argument_spec.update(SERVICE_ARG_SPEC)
return argument_spec return argument_spec
def execute_module(self):
def execute_module(module, k8s_ansible_mixin):
""" Module execution """ """ Module execution """
self.client = self.get_api_client() k8s_ansible_mixin.set_resource_definitions(module)
api_version = 'v1' api_version = 'v1'
selector = self.params.get('selector') selector = module.params.get('selector')
service_type = self.params.get('type') service_type = module.params.get('type')
ports = self.params.get('ports') ports = module.params.get('ports')
definition = defaultdict(defaultdict) definition = defaultdict(defaultdict)
@@ -248,25 +213,27 @@ class KubernetesService(K8sAnsibleMixin):
def_spec['selector'] = selector def_spec['selector'] = selector
def_meta = definition['metadata'] def_meta = definition['metadata']
def_meta['name'] = self.params.get('name') def_meta['name'] = module.params.get('name')
def_meta['namespace'] = self.params.get('namespace') def_meta['namespace'] = module.params.get('namespace')
# 'resource_definition:' has lower priority than module parameters # 'resource_definition:' has lower priority than module parameters
definition = dict(self.merge_dicts(self.resource_definitions[0], definition)) definition = dict(merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition))
resource = self.find_resource('Service', api_version, fail=True) resource = k8s_ansible_mixin.find_resource('Service', api_version, fail=True)
definition = self.set_defaults(resource, definition) definition = k8s_ansible_mixin.set_defaults(resource, definition)
result = self.perform_action(resource, definition) result = k8s_ansible_mixin.perform_action(resource, definition)
self.exit_json(**result) module.exit_json(**result)
def main(): def main():
module = KubernetesService() module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
try: from ansible_collections.community.kubernetes.plugins.module_utils.common import (
module.execute_module() K8sAnsibleMixin, get_api_client)
except Exception as e:
module.fail_json(msg=str(e), exception=traceback.format_exc()) k8s_ansible_mixin = K8sAnsibleMixin(module)
k8s_ansible_mixin.client = get_api_client(module=module)
execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__': if __name__ == '__main__':