#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2019, Fabian von Feilitzsch <@fabianvf> # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = r''' module: k8s_log short_description: Fetch logs from Kubernetes resources version_added: "0.10.0" author: - "Fabian von Feilitzsch (@fabianvf)" description: - Use the Kubernetes Python client to perform read operations on K8s log endpoints. - Authenticate using either a config file, certificates, password or token. - Supports check mode. - Analogous to `kubectl logs` or `oc logs` extends_documentation_fragment: - kubernetes.core.k8s_auth_options - kubernetes.core.k8s_name_options options: kind: description: - Use to specify an object model. - Use in conjunction with I(api_version), I(name), and I(namespace) to identify a specific object. - If using I(label_selectors), cannot be overridden. type: str default: Pod name: description: - Use to specify an object name. - Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a specific object. - Only one of I(name) or I(label_selectors) may be provided. type: str label_selectors: description: - List of label selectors to use to filter results - Only one of I(name) or I(label_selectors) may be provided. type: list elements: str container: description: - Use to specify the container within a pod to grab the log from. - If there is only one container, this will default to that container. - If there is more than one container, this option is required. required: no type: str since_seconds: description: - A relative time in seconds before the current time from which to show logs. required: no type: str version_added: '2.2.0' requirements: - "python >= 3.6" - "kubernetes >= 12.0.0" - "PyYAML >= 3.11" ''' EXAMPLES = r''' - name: Get a log from a Pod kubernetes.core.k8s_log: name: example-1 namespace: testing register: log # This will get the log from the first Pod found matching the selector - name: Log a Pod matching a label selector kubernetes.core.k8s_log: namespace: testing label_selectors: - app=example register: log # This will get the log from a single Pod managed by this Deployment - name: Get a log from a Deployment kubernetes.core.k8s_log: api_version: apps/v1 kind: Deployment namespace: testing name: example since_seconds: "4000" register: log # This will get the log from a single Pod managed by this DeploymentConfig - name: Get a log from a DeploymentConfig kubernetes.core.k8s_log: api_version: apps.openshift.io/v1 kind: DeploymentConfig namespace: testing name: example register: log ''' RETURN = r''' log: type: str description: - The text log of the object returned: success log_lines: type: list description: - The log of the object, split on newlines returned: success ''' import copy from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule from ansible.module_utils.six import PY2 from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC, NAME_ARG_SPEC) def argspec(): args = copy.deepcopy(AUTH_ARG_SPEC) args.update(NAME_ARG_SPEC) args.update( dict( kind=dict(type='str', default='Pod'), container=dict(), since_seconds=dict(), label_selectors=dict(type='list', elements='str', default=[]), ) ) return args def execute_module(module, k8s_ansible_mixin): name = module.params.get('name') namespace = module.params.get('namespace') label_selector = ','.join(module.params.get('label_selectors', {})) if name and label_selector: module.fail(msg='Only one of name or label_selectors can be provided') resource = k8s_ansible_mixin.find_resource(module.params['kind'], module.params['api_version'], fail=True) v1_pods = k8s_ansible_mixin.find_resource('Pod', 'v1', fail=True) if 'log' not in resource.subresources: if not name: module.fail(msg='name must be provided for resources that do not support the log subresource') instance = resource.get(name=name, namespace=namespace) label_selector = ','.join(extract_selectors(module, instance)) resource = v1_pods if label_selector: instances = v1_pods.get(namespace=namespace, label_selector=label_selector) if not instances.items: module.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector)) # This matches the behavior of kubectl when logging pods via a selector name = instances.items[0].metadata.name resource = v1_pods kwargs = {} if module.params.get('container'): kwargs['query_params'] = dict(container=module.params['container']) if module.params.get('since_seconds'): kwargs.setdefault('query_params', {}).update({'sinceSeconds': module.params['since_seconds']}) log = serialize_log(resource.log.get( name=name, namespace=namespace, serialize=False, **kwargs )) module.exit_json(changed=False, log=log, log_lines=log.split('\n')) def extract_selectors(module, instance): # Parses selectors on an object based on the specifications documented here: # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors selectors = [] if not instance.spec.selector: module.fail(msg='{0} {1} does not support the log subresource directly, and no Pod selector was found on the object'.format( '/'.join(instance.group, instance.apiVersion), instance.kind)) if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions): # A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions for k, v in dict(instance.spec.selector).items(): selectors.append('{0}={1}'.format(k, v)) return selectors if instance.spec.selector.matchLabels: for k, v in dict(instance.spec.selector.matchLabels).items(): selectors.append('{0}={1}'.format(k, v)) if instance.spec.selector.matchExpressions: for expression in instance.spec.selector.matchExpressions: operator = expression.operator if operator == 'Exists': selectors.append(expression.key) elif operator == 'DoesNotExist': selectors.append('!{0}'.format(expression.key)) elif operator in ['In', 'NotIn']: selectors.append('{key} {operator} {values}'.format( key=expression.key, operator=operator.lower(), values='({0})'.format(', '.join(expression.values)) )) else: module.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower())) return selectors def serialize_log(response): if PY2: return response.data return response.data.decode('utf8') def main(): module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True) from ansible_collections.kubernetes.core.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__': main()