mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-07-29 02:44:41 +00:00
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:
@@ -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:
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|||||||
6
plugins/module_utils/ansiblemodule.py
Normal file
6
plugins/module_utils/ansiblemodule.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule # noqa: F401
|
||||||
133
plugins/module_utils/args_common.py
Normal file
133
plugins/module_utils/args_common.py
Normal 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',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,201 +100,107 @@ 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):
|
||||||
|
auth = {}
|
||||||
|
|
||||||
COMMON_ARG_SPEC = {
|
def _raise_or_fail(exc, msg):
|
||||||
'state': {
|
if module:
|
||||||
'default': 'present',
|
module.fail_json(msg % to_native(exc))
|
||||||
'choices': ['present', 'absent'],
|
else:
|
||||||
},
|
raise exc
|
||||||
'force': {
|
|
||||||
'type': 'bool',
|
|
||||||
'default': False,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
RESOURCE_ARG_SPEC = {
|
# If authorization variables aren't defined, look for them in environment variables
|
||||||
'resource_definition': {
|
for true_name, arg_name in AUTH_ARG_MAP.items():
|
||||||
'type': list_dict_str,
|
if module and module.params.get(arg_name):
|
||||||
'aliases': ['definition', 'inline']
|
auth[true_name] = module.params.get(arg_name)
|
||||||
},
|
else:
|
||||||
'src': {
|
env_value = os.getenv('K8S_AUTH_{0}'.format(arg_name.upper()), None) or os.getenv('K8S_AUTH_{0}'.format(true_name.upper()), None)
|
||||||
'type': 'path',
|
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
|
||||||
|
|
||||||
NAME_ARG_SPEC = {
|
def auth_set(*names):
|
||||||
'kind': {},
|
return all([auth.get(name) for name in names])
|
||||||
'name': {},
|
|
||||||
'namespace': {},
|
|
||||||
'api_version': {
|
|
||||||
'default': 'v1',
|
|
||||||
'aliases': ['api', 'version'],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
AUTH_ARG_SPEC = {
|
if auth_set('username', 'password', 'host') or auth_set('api_key', 'host'):
|
||||||
'kubeconfig': {
|
# We have enough in the parameters to authenticate, no need to load incluster or kubeconfig
|
||||||
'type': 'path',
|
pass
|
||||||
},
|
elif auth_set('kubeconfig') or auth_set('context'):
|
||||||
'context': {},
|
try:
|
||||||
'host': {},
|
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
|
||||||
'api_key': {
|
except Exception as err:
|
||||||
'no_log': True,
|
_raise_or_fail(err, 'Failed to load kubeconfig due to %s')
|
||||||
},
|
|
||||||
'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(
|
else:
|
||||||
wait=dict(type='bool', default=False),
|
# First try to do incluster config, then kubeconfig
|
||||||
wait_sleep=dict(type='int', default=5),
|
try:
|
||||||
wait_timeout=dict(type='int', default=120),
|
kubernetes.config.load_incluster_config()
|
||||||
wait_condition=dict(
|
except kubernetes.config.ConfigException:
|
||||||
type='dict',
|
try:
|
||||||
default=None,
|
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
|
||||||
options=dict(
|
except Exception as err:
|
||||||
type=dict(),
|
_raise_or_fail(err, 'Failed to load kubeconfig due to %s')
|
||||||
status=dict(type='str', default="True", choices=["True", "False", "Unknown"]),
|
|
||||||
reason=dict()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
DELETE_OPTS_ARG_SPEC = {
|
# Override any values in the default configuration with Ansible parameters
|
||||||
'propagationPolicy': {
|
# As of kubernetes-client v12.0.0, get_default_copy() is required here
|
||||||
'choices': ['Foreground', 'Background', 'Orphan'],
|
try:
|
||||||
},
|
configuration = kubernetes.client.Configuration().get_default_copy()
|
||||||
'gracePeriodSeconds': {
|
except AttributeError:
|
||||||
'type': 'int',
|
configuration = kubernetes.client.Configuration()
|
||||||
},
|
|
||||||
'preconditions': {
|
for key, value in iteritems(auth):
|
||||||
'type': 'dict',
|
if key in AUTH_ARG_MAP.keys() and value is not None:
|
||||||
'options': {
|
if key == 'api_key':
|
||||||
'resourceVersion': {
|
setattr(configuration, key, {'authorization': "Bearer {0}".format(value)})
|
||||||
'type': 'str',
|
else:
|
||||||
},
|
setattr(configuration, key, value)
|
||||||
'uid': {
|
|
||||||
'type': 'str',
|
digest = configuration_digest(configuration)
|
||||||
}
|
if digest in get_api_client._pool:
|
||||||
}
|
client = get_api_client._pool[digest]
|
||||||
}
|
return client
|
||||||
}
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
# Map kubernetes-client parameters to ansible parameters
|
get_api_client._pool = {}
|
||||||
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):
|
class K8sAnsibleMixin(object):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, module, *args, **kwargs):
|
||||||
if not HAS_K8S_MODULE_HELPER:
|
if not HAS_K8S_MODULE_HELPER:
|
||||||
self.fail_json(msg=missing_required_lib('openshift'), exception=K8S_IMP_ERR,
|
module.fail_json(msg=missing_required_lib('openshift'), exception=K8S_IMP_ERR,
|
||||||
error=to_native(k8s_import_exception))
|
error=to_native(k8s_import_exception))
|
||||||
self.openshift_version = openshift.__version__
|
self.openshift_version = openshift.__version__
|
||||||
|
|
||||||
if not HAS_YAML:
|
if not HAS_YAML:
|
||||||
self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
|
module.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 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:
|
|
||||||
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))
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
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))
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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():
|
||||||
|
return dict(
|
||||||
|
fail_on_error=dict(type='bool'),
|
||||||
|
version=dict(),
|
||||||
|
strict=dict(type='bool', default=True)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def validate_spec(self):
|
|
||||||
return dict(
|
|
||||||
fail_on_error=dict(type='bool'),
|
|
||||||
version=dict(),
|
|
||||||
strict=dict(type='bool', default=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
def argspec():
|
||||||
def argspec(self):
|
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=validate_spec())
|
||||||
argument_spec['validate'] = dict(type='dict', default=None, options=self.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):
|
|
||||||
mutually_exclusive = [
|
|
||||||
('resource_definition', 'src'),
|
|
||||||
('merge_type', 'apply'),
|
|
||||||
('template', 'resource_definition'),
|
|
||||||
('template', 'src'),
|
|
||||||
]
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
argument_spec=self.argspec,
|
k8s_ansible_mixin.module = module
|
||||||
mutually_exclusive=mutually_exclusive,
|
k8s_ansible_mixin.argspec = module.argument_spec
|
||||||
supports_check_mode=True,
|
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 = []
|
||||||
|
|
||||||
self.module = module
|
k8s_ansible_mixin.kind = k8s_ansible_mixin.params.get('kind')
|
||||||
self.check_mode = self.module.check_mode
|
k8s_ansible_mixin.api_version = k8s_ansible_mixin.params.get('api_version')
|
||||||
self.params = self.module.params
|
k8s_ansible_mixin.name = k8s_ansible_mixin.params.get('name')
|
||||||
self.fail_json = self.module.fail_json
|
k8s_ansible_mixin.namespace = k8s_ansible_mixin.params.get('namespace')
|
||||||
self.fail = self.module.fail_json
|
|
||||||
self.exit_json = self.module.exit_json
|
|
||||||
|
|
||||||
super(KubernetesModule, self).__init__(*args, **kwargs)
|
k8s_ansible_mixin.check_library_version()
|
||||||
|
k8s_ansible_mixin.set_resource_definitions(module)
|
||||||
self.client = None
|
k8s_ansible_mixin.execute_module()
|
||||||
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():
|
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__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -159,88 +159,60 @@ 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):
|
if invalidate_cache:
|
||||||
module = AnsibleModule(
|
client.resources.invalidate_cache()
|
||||||
argument_spec=self.argspec,
|
results = {}
|
||||||
supports_check_mode=True,
|
from openshift.dynamic.resource import ResourceList
|
||||||
)
|
for resource in list(client.resources):
|
||||||
self.module = module
|
resource = resource[0]
|
||||||
self.params = self.module.params
|
if isinstance(resource, ResourceList):
|
||||||
|
continue
|
||||||
if not HAS_K8S_INSTANCE_HELPER:
|
results[resource.group] = {
|
||||||
self.module.fail_json(msg=missing_required_lib("openshift >= 0.6.2", reason="for merge_type"),
|
'api_version': resource.group_version,
|
||||||
exception=k8s_import_exception)
|
'categories': resource.categories if resource.categories else [],
|
||||||
|
'kind': resource.kind,
|
||||||
super(KubernetesInfoModule, self).__init__()
|
'name': resource.name,
|
||||||
|
'namespaced': resource.namespaced,
|
||||||
def execute_module(self):
|
'preferred': resource.preferred,
|
||||||
self.client = self.get_api_client()
|
'short_names': resource.short_names if resource.short_names else [],
|
||||||
invalidate_cache = boolean(self.module.params.get('invalidate_cache', True), strict=False)
|
'singular_name': resource.singular_name,
|
||||||
if invalidate_cache:
|
|
||||||
self.client.resources.invalidate_cache()
|
|
||||||
results = {}
|
|
||||||
for resource in list(self.client.resources):
|
|
||||||
resource = resource[0]
|
|
||||||
if isinstance(resource, ResourceList):
|
|
||||||
continue
|
|
||||||
results[resource.group] = {
|
|
||||||
'api_version': resource.group_version,
|
|
||||||
'categories': resource.categories if resource.categories else [],
|
|
||||||
'kind': resource.kind,
|
|
||||||
'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 = {
|
configuration = client.configuration
|
||||||
'client': version,
|
connection = {
|
||||||
'server': self.client.version,
|
'cert_file': configuration.cert_file,
|
||||||
}
|
'host': configuration.host,
|
||||||
self.module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
|
'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)
|
||||||
|
|
||||||
@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__':
|
||||||
|
|||||||
@@ -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,75 +132,72 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class KubernetesExecCommand(K8sAnsibleMixin):
|
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 __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 execute_module(module, k8s_ansible_mixin):
|
||||||
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
|
||||||
# Load kubernetes.client.Configuration
|
api = core_v1_api.CoreV1Api()
|
||||||
self.get_api_client()
|
|
||||||
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)
|
||||||
if resp.peek_stdout():
|
if resp.peek_stdout():
|
||||||
stdout.append(resp.read_stdout())
|
stdout.append(resp.read_stdout())
|
||||||
if resp.peek_stderr():
|
if resp.peek_stderr():
|
||||||
stderr.append(resp.read_stderr())
|
stderr.append(resp.read_stderr())
|
||||||
err = resp.read_channel(3)
|
err = resp.read_channel(3)
|
||||||
err = yaml.safe_load(err)
|
err = yaml.safe_load(err)
|
||||||
if err['status'] == 'Success':
|
if err['status'] == 'Success':
|
||||||
rc = 0
|
rc = 0
|
||||||
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),
|
||||||
stderr="".join(stderr),
|
stderr="".join(stderr),
|
||||||
return_code=rc
|
return_code=rc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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__':
|
||||||
|
|||||||
@@ -148,58 +148,50 @@ 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(
|
||||||
|
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 __init__(self, *args, **kwargs):
|
|
||||||
module = AnsibleModule(
|
def argspec():
|
||||||
argument_spec=self.argspec,
|
args = copy.deepcopy(AUTH_ARG_SPEC)
|
||||||
supports_check_mode=True,
|
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
|
return args
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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__':
|
||||||
|
|||||||
@@ -111,116 +111,101 @@ 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():
|
||||||
|
args = copy.deepcopy(AUTH_ARG_SPEC)
|
||||||
def __init__(self):
|
args.update(NAME_ARG_SPEC)
|
||||||
module = AnsibleModule(
|
args.update(
|
||||||
argument_spec=self.argspec,
|
dict(
|
||||||
supports_check_mode=True,
|
kind=dict(type='str', default='Pod'),
|
||||||
|
container=dict(),
|
||||||
|
label_selectors=dict(type='list', elements='str', default=[]),
|
||||||
)
|
)
|
||||||
self.module = module
|
)
|
||||||
self.params = self.module.params
|
return args
|
||||||
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=[]),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return args
|
|
||||||
|
|
||||||
def execute_module(self):
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
name = self.params.get('name')
|
name = module.params.get('name')
|
||||||
namespace = self.params.get('namespace')
|
namespace = module.params.get('namespace')
|
||||||
label_selector = ','.join(self.params.get('label_selectors', {}))
|
label_selector = ','.join(module.params.get('label_selectors', {}))
|
||||||
if name and label_selector:
|
if name and label_selector:
|
||||||
self.fail(msg='Only one of name or label_selectors can be provided')
|
module.fail(msg='Only one of name or label_selectors can be provided')
|
||||||
|
|
||||||
self.client = self.get_api_client()
|
resource = k8s_ansible_mixin.find_resource(module.params['kind'], module.params['api_version'], fail=True)
|
||||||
resource = self.find_resource(self.params['kind'], self.params['api_version'], fail=True)
|
v1_pods = k8s_ansible_mixin.find_resource('Pod', 'v1', fail=True)
|
||||||
v1_pods = self.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,
|
||||||
namespace=namespace,
|
namespace=namespace,
|
||||||
serialize=False,
|
serialize=False,
|
||||||
**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):
|
|
||||||
# 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):
|
def extract_selectors(module, instance):
|
||||||
# A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions
|
# Parses selectors on an object based on the specifications documented here:
|
||||||
for k, v in dict(instance.spec.selector).items():
|
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
||||||
selectors.append('{0}={1}'.format(k, v))
|
selectors = []
|
||||||
return 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(
|
||||||
if instance.spec.selector.matchLabels:
|
'/'.join(instance.group, instance.apiVersion), instance.kind))
|
||||||
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()))
|
|
||||||
|
|
||||||
|
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
|
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):
|
def serialize_log(response):
|
||||||
if PY2:
|
if PY2:
|
||||||
@@ -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__':
|
||||||
|
|||||||
@@ -78,127 +78,117 @@ 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":
|
||||||
|
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
|
return args
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def get_previous_revision(all_resources, current_revision):
|
def get_previous_revision(all_resources, current_revision):
|
||||||
@@ -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__':
|
||||||
|
|||||||
@@ -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__':
|
||||||
|
|||||||
@@ -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,100 +171,69 @@ SERVICE_ARG_SPEC = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class KubernetesService(K8sAnsibleMixin):
|
def merge_dicts(x, y):
|
||||||
def __init__(self, *args, **kwargs):
|
for k in set(x.keys()).union(y.keys()):
|
||||||
mutually_exclusive = [
|
if k in x and k in y:
|
||||||
('resource_definition', 'src'),
|
if isinstance(x[k], dict) and isinstance(y[k], dict):
|
||||||
('merge_type', 'apply'),
|
yield (k, dict(merge_dicts(x[k], y[k])))
|
||||||
]
|
|
||||||
|
|
||||||
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])
|
|
||||||
else:
|
else:
|
||||||
yield (k, y[k])
|
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 execute_module(self):
|
def argspec():
|
||||||
""" Module execution """
|
""" argspec property builder """
|
||||||
self.client = self.get_api_client()
|
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
|
||||||
|
|
||||||
api_version = 'v1'
|
|
||||||
selector = self.params.get('selector')
|
|
||||||
service_type = self.params.get('type')
|
|
||||||
ports = self.params.get('ports')
|
|
||||||
|
|
||||||
definition = defaultdict(defaultdict)
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
|
""" Module execution """
|
||||||
|
k8s_ansible_mixin.set_resource_definitions(module)
|
||||||
|
|
||||||
definition['kind'] = 'Service'
|
api_version = 'v1'
|
||||||
definition['apiVersion'] = api_version
|
selector = module.params.get('selector')
|
||||||
|
service_type = module.params.get('type')
|
||||||
|
ports = module.params.get('ports')
|
||||||
|
|
||||||
def_spec = definition['spec']
|
definition = defaultdict(defaultdict)
|
||||||
def_spec['type'] = service_type
|
|
||||||
def_spec['ports'] = ports
|
|
||||||
def_spec['selector'] = selector
|
|
||||||
|
|
||||||
def_meta = definition['metadata']
|
definition['kind'] = 'Service'
|
||||||
def_meta['name'] = self.params.get('name')
|
definition['apiVersion'] = api_version
|
||||||
def_meta['namespace'] = self.params.get('namespace')
|
|
||||||
|
|
||||||
# 'resource_definition:' has lower priority than module parameters
|
def_spec = definition['spec']
|
||||||
definition = dict(self.merge_dicts(self.resource_definitions[0], definition))
|
def_spec['type'] = service_type
|
||||||
|
def_spec['ports'] = ports
|
||||||
|
def_spec['selector'] = selector
|
||||||
|
|
||||||
resource = self.find_resource('Service', api_version, fail=True)
|
def_meta = definition['metadata']
|
||||||
definition = self.set_defaults(resource, definition)
|
def_meta['name'] = module.params.get('name')
|
||||||
result = self.perform_action(resource, definition)
|
def_meta['namespace'] = module.params.get('namespace')
|
||||||
|
|
||||||
self.exit_json(**result)
|
# 'resource_definition:' has lower priority than module parameters
|
||||||
|
definition = dict(merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition))
|
||||||
|
|
||||||
|
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():
|
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__':
|
||||||
|
|||||||
Reference in New Issue
Block a user