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

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

View File

@@ -159,88 +159,60 @@ apis:
import copy
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible.module_utils.parsing.convert_bool import boolean
from ansible_collections.community.kubernetes.plugins.module_utils.common import K8sAnsibleMixin, AUTH_ARG_SPEC
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()
from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_SPEC)
class KubernetesInfoModule(K8sAnsibleMixin):
def __init__(self):
module = AnsibleModule(
argument_spec=self.argspec,
supports_check_mode=True,
)
self.module = module
self.params = self.module.params
if not HAS_K8S_INSTANCE_HELPER:
self.module.fail_json(msg=missing_required_lib("openshift >= 0.6.2", reason="for merge_type"),
exception=k8s_import_exception)
super(KubernetesInfoModule, self).__init__()
def execute_module(self):
self.client = self.get_api_client()
invalidate_cache = boolean(self.module.params.get('invalidate_cache', True), strict=False)
if invalidate_cache:
self.client.resources.invalidate_cache()
results = {}
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,
def execute_module(module, client):
invalidate_cache = boolean(module.params.get('invalidate_cache', True), strict=False)
if invalidate_cache:
client.resources.invalidate_cache()
results = {}
from openshift.dynamic.resource import ResourceList
for resource in list(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,
}
version_info = {
'client': version,
'server': self.client.version,
}
self.module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
configuration = client.configuration
connection = {
'cert_file': configuration.cert_file,
'host': configuration.host,
'password': configuration.password,
'proxy': configuration.proxy,
'ssl_ca_cert': configuration.ssl_ca_cert,
'username': configuration.username,
'verify_ssl': configuration.verify_ssl,
}
from openshift import __version__ as version
version_info = {
'client': version,
'server': client.version,
}
module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
@property
def argspec(self):
spec = copy.deepcopy(AUTH_ARG_SPEC)
spec['invalidate_cache'] = dict(type='bool', default=True)
return spec
def argspec():
spec = copy.deepcopy(AUTH_ARG_SPEC)
spec['invalidate_cache'] = dict(type='bool', default=True)
return spec
def main():
KubernetesInfoModule().execute_module()
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
from ansible_collections.community.kubernetes.plugins.module_utils.common import get_api_client
execute_module(module, client=get_api_client(module=module))
if __name__ == '__main__':

View File

@@ -118,10 +118,10 @@ except ImportError:
# ImportError are managed by the common module already.
pass
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
K8sAnsibleMixin, AUTH_ARG_SPEC
AUTH_ARG_SPEC
)
try:
@@ -132,75 +132,72 @@ except ImportError:
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 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(module, k8s_ansible_mixin):
def execute_module(self):
# Load kubernetes.client.Configuration
self.get_api_client()
api = core_v1_api.CoreV1Api()
# Load kubernetes.client.Configuration
api = core_v1_api.CoreV1Api()
# hack because passing the container as None breaks things
optional_kwargs = {}
if self.params.get('container'):
optional_kwargs['container'] = self.params['container']
try:
resp = stream(
api.connect_get_namespaced_pod_exec,
self.params["pod"],
self.params["namespace"],
command=shlex.split(self.params["command"]),
stdout=True,
stderr=True,
stdin=False,
tty=False,
_preload_content=False, **optional_kwargs)
except Exception as e:
self.module.fail_json(msg="Failed to execute on pod %s"
" due to : %s" % (self.params.get('pod'), to_native(e)))
stdout, stderr, rc = [], [], 0
while resp.is_open():
resp.update(timeout=1)
if resp.peek_stdout():
stdout.append(resp.read_stdout())
if resp.peek_stderr():
stderr.append(resp.read_stderr())
err = resp.read_channel(3)
err = yaml.safe_load(err)
if err['status'] == 'Success':
rc = 0
else:
rc = int(err['details']['causes'][0]['message'])
# hack because passing the container as None breaks things
optional_kwargs = {}
if module.params.get('container'):
optional_kwargs['container'] = module.params['container']
try:
resp = stream(
api.connect_get_namespaced_pod_exec,
module.params["pod"],
module.params["namespace"],
command=shlex.split(module.params["command"]),
stdout=True,
stderr=True,
stdin=False,
tty=False,
_preload_content=False, **optional_kwargs)
except Exception as e:
module.fail_json(msg="Failed to execute on pod %s"
" due to : %s" % (module.params.get('pod'), to_native(e)))
stdout, stderr, rc = [], [], 0
while resp.is_open():
resp.update(timeout=1)
if resp.peek_stdout():
stdout.append(resp.read_stdout())
if resp.peek_stderr():
stderr.append(resp.read_stderr())
err = resp.read_channel(3)
err = yaml.safe_load(err)
if err['status'] == 'Success':
rc = 0
else:
rc = int(err['details']['causes'][0]['message'])
self.module.exit_json(
# Some command might change environment, but ultimately failing at end
changed=True,
stdout="".join(stdout),
stderr="".join(stderr),
return_code=rc
)
module.exit_json(
# Some command might change environment, but ultimately failing at end
changed=True,
stdout="".join(stdout),
stderr="".join(stderr),
return_code=rc
)
def main():
KubernetesExecCommand().execute_module()
module = AnsibleModule(
argument_spec=argspec(),
supports_check_mode=True,
)
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
K8sAnsibleMixin, get_api_client)
k8s_ansible_mixin = K8sAnsibleMixin(module)
k8s_ansible_mixin.client = get_api_client(module=module)
execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':

View File

@@ -148,58 +148,50 @@ resources:
import copy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
K8sAnsibleMixin, AUTH_ARG_SPEC, WAIT_ARG_SPEC)
from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_SPEC, WAIT_ARG_SPEC)
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(
argument_spec=self.argspec,
supports_check_mode=True,
def argspec():
args = copy.deepcopy(AUTH_ARG_SPEC)
args.update(WAIT_ARG_SPEC)
args.update(
dict(
kind=dict(required=True),
api_version=dict(default='v1', aliases=['api', 'version']),
name=dict(),
namespace=dict(),
label_selectors=dict(type='list', elements='str', default=[]),
field_selectors=dict(type='list', elements='str', default=[]),
)
self.module = module
self.params = self.module.params
self.fail_json = self.module.fail_json
self.exit_json = self.module.exit_json
super(KubernetesInfoModule, self).__init__()
def execute_module(self):
self.client = self.get_api_client()
self.exit_json(changed=False,
**self.kubernetes_facts(self.params['kind'],
self.params['api_version'],
name=self.params['name'],
namespace=self.params['namespace'],
label_selectors=self.params['label_selectors'],
field_selectors=self.params['field_selectors'],
wait=self.params['wait'],
wait_sleep=self.params['wait_sleep'],
wait_timeout=self.params['wait_timeout'],
condition=self.params['wait_condition']))
@property
def argspec(self):
args = copy.deepcopy(AUTH_ARG_SPEC)
args.update(WAIT_ARG_SPEC)
args.update(
dict(
kind=dict(required=True),
api_version=dict(default='v1', aliases=['api', 'version']),
name=dict(),
namespace=dict(),
label_selectors=dict(type='list', elements='str', default=[]),
field_selectors=dict(type='list', elements='str', default=[]),
)
)
return args
)
return args
def main():
KubernetesInfoModule().execute_module()
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
K8sAnsibleMixin, get_api_client)
k8s_ansible_mixin = K8sAnsibleMixin(module)
k8s_ansible_mixin.client = get_api_client(module=module)
execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':

View File

@@ -111,116 +111,101 @@ log_lines:
import copy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible.module_utils.six import PY2
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
K8sAnsibleMixin, AUTH_ARG_SPEC, NAME_ARG_SPEC)
from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (AUTH_ARG_SPEC, NAME_ARG_SPEC)
class KubernetesLogModule(K8sAnsibleMixin):
def __init__(self):
module = AnsibleModule(
argument_spec=self.argspec,
supports_check_mode=True,
def argspec():
args = copy.deepcopy(AUTH_ARG_SPEC)
args.update(NAME_ARG_SPEC)
args.update(
dict(
kind=dict(type='str', default='Pod'),
container=dict(),
label_selectors=dict(type='list', elements='str', default=[]),
)
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__()
)
return args
@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):
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')
def execute_module(module, k8s_ansible_mixin):
name = module.params.get('name')
namespace = module.params.get('namespace')
label_selector = ','.join(module.params.get('label_selectors', {}))
if name and label_selector:
module.fail(msg='Only one of name or label_selectors can be provided')
self.client = self.get_api_client()
resource = self.find_resource(self.params['kind'], self.params['api_version'], fail=True)
v1_pods = self.find_resource('Pod', 'v1', fail=True)
resource = k8s_ansible_mixin.find_resource(module.params['kind'], module.params['api_version'], fail=True)
v1_pods = k8s_ansible_mixin.find_resource('Pod', 'v1', fail=True)
if 'log' not in resource.subresources:
if not name:
self.fail(msg='name must be provided for resources that do not support the log subresource')
instance = resource.get(name=name, namespace=namespace)
label_selector = ','.join(self.extract_selectors(instance))
resource = v1_pods
if 'log' not in resource.subresources:
if not name:
module.fail(msg='name must be provided for resources that do not support the log subresource')
instance = resource.get(name=name, namespace=namespace)
label_selector = ','.join(extract_selectors(module, instance))
resource = v1_pods
if label_selector:
instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
if not instances.items:
self.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector))
# This matches the behavior of kubectl when logging pods via a selector
name = instances.items[0].metadata.name
resource = v1_pods
if label_selector:
instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
if not instances.items:
module.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector))
# This matches the behavior of kubectl when logging pods via a selector
name = instances.items[0].metadata.name
resource = v1_pods
kwargs = {}
if self.params.get('container'):
kwargs['query_params'] = dict(container=self.params['container'])
kwargs = {}
if module.params.get('container'):
kwargs['query_params'] = dict(container=module.params['container'])
log = serialize_log(resource.log.get(
name=name,
namespace=namespace,
serialize=False,
**kwargs
))
log = serialize_log(resource.log.get(
name=name,
namespace=namespace,
serialize=False,
**kwargs
))
self.exit_json(changed=False, log=log, log_lines=log.split('\n'))
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):
# A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions
for k, v in dict(instance.spec.selector).items():
selectors.append('{0}={1}'.format(k, v))
return selectors
if instance.spec.selector.matchLabels:
for k, v in dict(instance.spec.selector.matchLabels).items():
selectors.append('{0}={1}'.format(k, v))
if instance.spec.selector.matchExpressions:
for expression in instance.spec.selector.matchExpressions:
operator = expression.operator
if operator == 'Exists':
selectors.append(expression.key)
elif operator == 'DoesNotExist':
selectors.append('!{0}'.format(expression.key))
elif operator in ['In', 'NotIn']:
selectors.append('{key} {operator} {values}'.format(
key=expression.key,
operator=operator.lower(),
values='({0})'.format(', '.join(expression.values))
))
else:
self.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower()))
def extract_selectors(module, instance):
# Parses selectors on an object based on the specifications documented here:
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
selectors = []
if not instance.spec.selector:
module.fail(msg='{0} {1} does not support the log subresource directly, and no Pod selector was found on the object'.format(
'/'.join(instance.group, instance.apiVersion), instance.kind))
if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions):
# A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions
for k, v in dict(instance.spec.selector).items():
selectors.append('{0}={1}'.format(k, v))
return selectors
if instance.spec.selector.matchLabels:
for k, v in dict(instance.spec.selector.matchLabels).items():
selectors.append('{0}={1}'.format(k, v))
if instance.spec.selector.matchExpressions:
for expression in instance.spec.selector.matchExpressions:
operator = expression.operator
if operator == 'Exists':
selectors.append(expression.key)
elif operator == 'DoesNotExist':
selectors.append('!{0}'.format(expression.key))
elif operator in ['In', 'NotIn']:
selectors.append('{key} {operator} {values}'.format(
key=expression.key,
operator=operator.lower(),
values='({0})'.format(', '.join(expression.values))
))
else:
module.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower()))
return selectors
def serialize_log(response):
if PY2:
@@ -229,7 +214,13 @@ def serialize_log(response):
def main():
KubernetesLogModule().execute_module()
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
K8sAnsibleMixin, get_api_client)
k8s_ansible_mixin = K8sAnsibleMixin(module)
k8s_ansible_mixin.client = get_api_client(module=module)
execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':

View File

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

View File

@@ -118,11 +118,129 @@ result:
sample: 48
'''
from ansible_collections.community.kubernetes.plugins.module_utils.scale import KubernetesAnsibleScaleModule
import copy
from ansible_collections.community.kubernetes.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible_collections.community.kubernetes.plugins.module_utils.args_common import (
AUTH_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_ARG_SPEC)
SCALE_ARG_SPEC = {
'replicas': {'type': 'int', 'required': True},
'current_replicas': {'type': 'int'},
'resource_version': {},
'wait': {'type': 'bool', 'default': True},
'wait_timeout': {'type': 'int', 'default': 20},
}
def execute_module(module, k8s_ansible_mixin,):
k8s_ansible_mixin.set_resource_definitions(module)
definition = k8s_ansible_mixin.resource_definitions[0]
name = definition['metadata']['name']
namespace = definition['metadata'].get('namespace')
api_version = definition['apiVersion']
kind = definition['kind']
current_replicas = module.params.get('current_replicas')
replicas = module.params.get('replicas')
resource_version = module.params.get('resource_version')
wait = module.params.get('wait')
wait_time = module.params.get('wait_timeout')
existing = None
existing_count = None
return_attributes = dict(changed=False, result=dict(), diff=dict())
if wait:
return_attributes['duration'] = 0
resource = k8s_ansible_mixin.find_resource(kind, api_version, fail=True)
from ansible_collections.community.kubernetes.plugins.module_utils.common import NotFoundError
try:
existing = resource.get(name=name, namespace=namespace)
return_attributes['result'] = existing.to_dict()
except NotFoundError as exc:
module.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc),
error=exc.value.get('status'))
if module.params['kind'] == 'job':
existing_count = existing.spec.parallelism
elif hasattr(existing.spec, 'replicas'):
existing_count = existing.spec.replicas
if existing_count is None:
module.fail_json(msg='Failed to retrieve the available count for the requested object.')
if resource_version and resource_version != existing.metadata.resourceVersion:
module.exit_json(**return_attributes)
if current_replicas is not None and existing_count != current_replicas:
module.exit_json(**return_attributes)
if existing_count != replicas:
return_attributes['changed'] = True
if not module.check_mode:
if module.params['kind'] == 'job':
existing.spec.parallelism = replicas
return_attributes['result'] = resource.patch(existing.to_dict()).to_dict()
else:
return_attributes = scale(module, k8s_ansible_mixin, resource, existing, replicas, wait, wait_time)
module.exit_json(**return_attributes)
def argspec():
args = copy.deepcopy(SCALE_ARG_SPEC)
args.update(RESOURCE_ARG_SPEC)
args.update(NAME_ARG_SPEC)
args.update(AUTH_ARG_SPEC)
return args
def scale(module, k8s_ansible_mixin, resource, existing_object, replicas, wait, wait_time):
name = existing_object.metadata.name
namespace = existing_object.metadata.namespace
kind = existing_object.kind
if not hasattr(resource, 'scale'):
module.fail_json(
msg="Cannot perform scale on resource of kind {0}".format(resource.kind)
)
scale_obj = {'kind': kind, 'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}}
existing = resource.get(name=name, namespace=namespace)
try:
resource.scale.patch(body=scale_obj)
except Exception as exc:
module.fail_json(msg="Scale request failed: {0}".format(exc))
k8s_obj = resource.get(name=name, namespace=namespace).to_dict()
match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
result = dict()
result['result'] = k8s_obj
result['changed'] = not match
result['diff'] = diffs
if wait:
success, result['result'], result['duration'] = k8s_ansible_mixin.wait(resource, scale_obj, 5, wait_time)
if not success:
module.fail_json(msg="Resource scaling timed out", **result)
return result
def main():
KubernetesAnsibleScaleModule().execute_module()
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
K8sAnsibleMixin, get_api_client)
k8s_ansible_mixin = K8sAnsibleMixin(module)
k8s_ansible_mixin.client = get_api_client(module=module)
execute_module(module, k8s_ansible_mixin)
if __name__ == '__main__':

View File

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