diff --git a/ansible_mitogen/connection.py b/ansible_mitogen/connection.py index ae6268de..708b6c13 100644 --- a/ansible_mitogen/connection.py +++ b/ansible_mitogen/connection.py @@ -133,11 +133,10 @@ def _connect_kubectl(spec): return { 'method': 'kubectl', 'kwargs': { - 'username': spec['remote_user'], 'pod': spec['remote_addr'], - #'container': spec['container'], 'python_path': spec['python_path'], 'connect_timeout': spec['ansible_ssh_timeout'] or spec['timeout'], + 'kubectl_args': spec['extra_args'], } } @@ -393,6 +392,8 @@ def config_from_play_context(transport, inventory_name, connection): connection.get_task_var('mitogen_machinectl_path'), 'mitogen_ssh_debug_level': connection.get_task_var('mitogen_ssh_debug_level'), + 'extra_args': + connection.get_extra_args(), } @@ -790,6 +791,14 @@ class Connection(ansible.plugins.connection.ConnectionBase): ansible_mitogen.target.create_fork_child ) + def get_extra_args(self): + """ + Overridden by connections/mitogen_kubectl.py to a list of additional + arguments for the command. + """ + # TODO: maybe use this for SSH too. + return [] + def get_default_cwd(self): """ Overridden by connections/mitogen_local.py to emulate behaviour of CWD diff --git a/ansible_mitogen/plugins/connection/mitogen_kubectl.py b/ansible_mitogen/plugins/connection/mitogen_kubectl.py index 43d162f8..5ffe3f7b 100644 --- a/ansible_mitogen/plugins/connection/mitogen_kubectl.py +++ b/ansible_mitogen/plugins/connection/mitogen_kubectl.py @@ -31,6 +31,9 @@ from __future__ import absolute_import import os.path import sys +import ansible.plugins.connection.kubectl +from ansible.module_utils.six import iteritems + try: import ansible_mitogen except ImportError: @@ -43,3 +46,11 @@ import ansible_mitogen.connection class Connection(ansible_mitogen.connection.Connection): transport = 'kubectl' + + def get_extra_args(self): + parameters = [] + for key, option in iteritems(ansible.plugins.connection.kubectl.CONNECTION_OPTIONS): + if self.get_task_var('ansible_' + key) is not None: + parameters += [ option, self.get_task_var('ansible_' + key) ] + + return parameters diff --git a/docs/api.rst b/docs/api.rst index f365372d..6a9da1d8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -589,7 +589,7 @@ Router Class Filename or complete path to the ``jexec`` binary. ``PATH`` will be searched if given as a filename. Defaults to ``/usr/sbin/jexec``. - .. method:: kubectl (pid=None, container=None, kubectl_path=None, username=None, \**kwargs) + .. method:: kubectl (pod, kubectl_path=None, kubectl_args=None, \**kwargs) Construct a context in a container via the Kubernetes ``kubectl`` program. @@ -598,16 +598,11 @@ Router Class :param str pod: Kubernetes pod to connect to. - :param str container: - Optional container within pod to connect to. If the pod has only - one container, this parameter is not required. Defaults to - :data:`None`. :param str kubectl_path: Filename or complete path to the ``kubectl`` binary. ``PATH`` will be searched if given as a filename. Defaults to ``kubectl``. - :param str username: - Optional username to authenticate to the Kubernetes API server - with. within the container to :func:`setuid` to. + :param list kubectl_args: + Additional arguments to pass to the ``kubectl`` command. .. method:: lxc (container, lxc_attach_path=None, \**kwargs) @@ -729,7 +724,7 @@ Router Class :class:`mitogen.core.StreamError` to be raised, and that attributes of the stream match the actual behaviour of ``sudo``. - .. method:: ssh (hostname, username=None, ssh_path=None, port=None, check_host_keys='enforce', password=None, identity_file=None, identities_only=True, compression=True, \**kwargs) + .. method:: ssh (hostname, username=None, ssh_path=None, ssh_args=None, port=None, check_host_keys='enforce', password=None, identity_file=None, identities_only=True, compression=True, \**kwargs) Construct a remote context over an OpenSSH ``ssh`` invocation. @@ -747,6 +742,8 @@ Router Class the username to use. :param str ssh_path: Absolute or relative path to ``ssh``. Defaults to ``ssh``. + :param list ssh_args: + Additional arguments to pass to the SSH command. :param int port: Port number to connect to; default is unspecified, which causes SSH to pick the port number. diff --git a/docs/changelog.rst b/docs/changelog.rst index c64e999b..6f5ac94e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -41,7 +41,8 @@ Enhancements `uri `_). See :ref:`ansible_tempfiles` for a complete description. -* `#376 `_: the ``kubectl`` connection +* `#376 `_, + `#377 `_: the ``kubectl`` connection type is now supported. Contributed by Yannig Perré. * `084c0ac0 `_: avoid a diff --git a/mitogen/kubectl.py b/mitogen/kubectl.py index 26480f7d..c2be24c1 100644 --- a/mitogen/kubectl.py +++ b/mitogen/kubectl.py @@ -35,41 +35,31 @@ import mitogen.parent LOG = logging.getLogger(__name__) + class Stream(mitogen.parent.Stream): child_is_immediate_subprocess = True pod = None - container = None - username = None kubectl_path = 'kubectl' + kubectl_args = None # TODO: better way of capturing errors such as "No such container." create_child_args = { 'merge_stdio': True } - def construct(self, pod, container=None, kubectl_path=None, username=None, - **kwargs): + def construct(self, pod, kubectl_path=None, kubectl_args=None, **kwargs): super(Stream, self).construct(**kwargs) + assert pod self.pod = pod - if container: - self.container = container if kubectl_path: self.kubectl_path = kubectl_path - if username: - self.username = username + self.kubectl_args = kubectl_args or [] def connect(self): super(Stream, self).connect() - self.name = u'kubectl.' + (self.pod) + str(self.container) + self.name = u'kubectl.%s%s' % (self.pod, self.kubectl_args) def get_boot_command(self): - args = ['exec', '-it', self.pod] - if self.username: - args += ['--username=' + self.username] - - if self.container: - args += ['--container=' + self.container] - bits = [self.kubectl_path] - - return bits + args + [ "--" ] + super(Stream, self).get_boot_command() + bits = [self.kubectl_path] + self.kubectl_args + ['exec', '-it', self.pod] + return bits + ["--"] + super(Stream, self).get_boot_command() diff --git a/tests/ansible/test-kubectl.yml b/tests/ansible/test-kubectl.yml index 05fc6517..d2be9ba5 100644 --- a/tests/ansible/test-kubectl.yml +++ b/tests/ansible/test-kubectl.yml @@ -1,8 +1,11 @@ --- - name: "Create pod" - tags: always + tags: create hosts: localhost + vars: + pod_count: 10 + loop_count: 5 gather_facts: no tasks: - name: Create a test pod @@ -19,7 +22,10 @@ - name: python2 image: python:2 args: [ "sleep", "100000" ] - loop: "{{ range(10)|list }}" + - name: python3 + image: python:3 + args: [ "sleep", "100000" ] + loop: "{{ range(pod_count|int)|list }}" - name: "Wait pod to be running" debug: { msg: "pod is running" } @@ -30,7 +36,7 @@ delay: 2 vars: pod_def: "{{lookup('k8s', kind='Pod', namespace='default', resource_name='test-pod-' ~ item)}}" - loop: "{{ range(10)|list }}" + loop: "{{ range(pod_count|int)|list }}" - name: "Add pod to pods group" add_host: @@ -39,45 +45,95 @@ ansible_connection: "kubectl" changed_when: no tags: "always" - loop: "{{ range(10)|list }}" + loop: "{{ range(pod_count|int)|list }}" - name: "Test kubectl connection (default strategy)" tags: default hosts: pods strategy: "linear" + vars: + pod_count: 10 + loop_count: 5 gather_facts: no tasks: - name: "Simple shell with linear" shell: ls /tmp - loop: [ 1, 2, 3, 4, 5 ] + loop: "{{ range(loop_count|int)|list }}" - name: "Simple file with linear" file: path: "/etc" state: directory - loop: [ 1, 2, 3, 4, 5 ] + loop: "{{ range(loop_count|int)|list }}" + + - block: + - name: "Check python version on python3 container" + command: python --version + vars: + ansible_kubectl_container: python3 + register: _ + + - assert: { that: "'Python 3' in _.stdout" } + + - debug: var=_.stdout,_.stderr + run_once: yes + + - name: "Check python version on default container" + command: python --version + register: _ + + - assert: { that: "'Python 2' in _.stderr" } + + - debug: var=_.stdout,_.stderr + run_once: yes - name: "Test kubectl connection (mitogen strategy)" tags: mitogen hosts: pods strategy: "mitogen_linear" + vars: + pod_count: 10 + loop_count: 5 gather_facts: no tasks: - name: "Simple shell with mitogen" shell: ls /tmp - loop: [ 1, 2, 3, 4, 5 ] + loop: "{{ range(loop_count|int)|list }}" - name: "Simple file with mitogen" file: path: "/etc" state: directory - loop: [ 1, 2, 3, 4, 5 ] - register: _ + loop: "{{ range(loop_count|int)|list }}" + + - block: + - name: "Check python version on python3 container" + command: python --version + vars: + ansible_kubectl_container: python3 + register: _ + + - assert: { that: "'Python 3' in _.stdout" } + + - debug: var=_.stdout,_.stderr + run_once: yes + + - name: "Check python version on default container" + command: python --version + register: _ + + - assert: { that: "'Python 2' in _.stderr" } + + - debug: var=_.stdout,_.stderr + run_once: yes + tags: check - name: "Destroy pod" tags: cleanup - hosts: localhost + hosts: pods gather_facts: no + vars: + ansible_connection: "local" tasks: - name: Destroy pod k8s: @@ -86,6 +142,5 @@ apiVersion: v1 kind: Pod metadata: - name: test-pod-{{item}} + name: "{{inventory_hostname}}" namespace: default - loop: "{{ range(10)|list }}"