From 97fc3805a4dfac68577bb94c950db11da088d2f1 Mon Sep 17 00:00:00 2001 From: Yuriy Novostavskiy Date: Tue, 7 May 2024 18:27:09 +0300 Subject: [PATCH] add support of kubectl_local_env_vars (#698) (#702) add support of kubectl_local_env_vars (#698) SUMMARY Support of local environmental variable that may be required to be set on Ansible Controller before the connection is set and may be used for kubectl command. This PR addressed for #698 The main idea is to have the support of additional/extra local environmental variable that may be required for kubectl itself, i.e. for authorization in case of public clouds ISSUE TYPE Feature Pull Request COMPONENT NAME kubernetes.core.kubectl connection plugin ADDITIONAL INFORMATION This PR attempts to implement local env support for the kubectl connection plugin that may be useful in case of using kubectl against public cloud kubernetes environment that uses some authorization (i.e. aws cli) additionally to kubeconfig file. More detail in #698 The output that shows that the connection plugin can use local environment variable for kubectl command (with some debug that used during development but removed then): root@ubuntu-shell:/# cat test.yaml - hosts: localhost gather_facts: no any_errors_fatal: yes vars: ansible_connection: "kubectl" ansible_kubectl_namespace: "test" ansible_kubectl_config: "/.kube/config" ansible_kubectl_pod: "ubuntu" ansible_kubectl_container: "ubuntu" ansible_kubectl_local_env_vars: TESTVAR1: "test" TESTVAR2: "test" TESTVAR3: "test" environment: TEST_ENV1: value1 TEST_ENV2: value2 tasks: - name: test ansible.builtin.shell: env register: result - debug: var: result.stdout_lines root@ubuntu-shell:/# ansible-playbook test.yaml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [localhost] ************************************************************************************************************************************** TASK [test] ******************************************************************************************************************************************* changed: [localhost] TASK [debug] ****************************************************************************************************************************************** ok: [localhost] => { "result.stdout_lines": [ "KUBERNETES_PORT=tcp://10.96.0.1:443", "KUBERNETES_SERVICE_PORT=443", "HOSTNAME=ubuntu", "HOME=/root", "LC_CTYPE=C.UTF-8", "TEST_ENV1=value1", "TEST_ENV2=value2", "TERM=xterm", "KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "KUBERNETES_PORT_443_TCP_PORT=443", "KUBERNETES_PORT_443_TCP_PROTO=tcp", "KUBERNETES_SERVICE_PORT_HTTPS=443", "KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443", "KUBERNETES_SERVICE_HOST=10.96.0.1", "PWD=/" ] } PLAY RECAP ******************************************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 root@ubuntu-shell:/# ansible-playbook test.yaml -vvv ansible-playbook [core 2.14.5] config file = None configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/local/lib/python3.10/dist-packages/ansible ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections executable location = /usr/local/bin/ansible-playbook python version = 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] (/usr/bin/python3) jinja version = 3.1.3 libyaml = True No config file found; using defaults host_list declined parsing /etc/ansible/hosts as it did not pass its verify_file() method Skipping due to inventory source not existing or not being readable by the current user script declined parsing /etc/ansible/hosts as it did not pass its verify_file() method auto declined parsing /etc/ansible/hosts as it did not pass its verify_file() method Skipping due to inventory source not existing or not being readable by the current user yaml declined parsing /etc/ansible/hosts as it did not pass its verify_file() method Skipping due to inventory source not existing or not being readable by the current user ini declined parsing /etc/ansible/hosts as it did not pass its verify_file() method Skipping due to inventory source not existing or not being readable by the current user toml declined parsing /etc/ansible/hosts as it did not pass its verify_file() method [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' Skipping callback 'default', as we already have a stdout callback. Skipping callback 'minimal', as we already have a stdout callback. Skipping callback 'oneline', as we already have a stdout callback. PLAYBOOK: test.yaml *********************************************************************************************************************************** 1 plays in test.yaml PLAY [localhost] ************************************************************************************************************************************** TASK [test] ******************************************************************************************************************************************* task path: /test.yaml:19 redirecting (type: connection) ansible.builtin.kubectl to kubernetes.core.kubectl <127.0.0.1> ESTABLISH kubectl CONNECTION <127.0.0.1> ENV: KUBERNETES_SERVICE_PORT_HTTPS=443 <127.0.0.1> ENV: KUBERNETES_SERVICE_PORT=443 <127.0.0.1> ENV: HOSTNAME=ubuntu-shell <127.0.0.1> ENV: PWD=/ <127.0.0.1> ENV: HOME=/root <127.0.0.1> ENV: KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443 <127.0.0.1> ENV: LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36: <127.0.0.1> ENV: TERM=xterm <127.0.0.1> ENV: SHLVL=1 <127.0.0.1> ENV: KUBERNETES_PORT_443_TCP_PROTO=tcp <127.0.0.1> ENV: KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1 <127.0.0.1> ENV: KUBERNETES_SERVICE_HOST=10.96.0.1 <127.0.0.1> ENV: KUBERNETES_PORT=tcp://10.96.0.1:443 <127.0.0.1> ENV: KUBERNETES_PORT_443_TCP_PORT=443 <127.0.0.1> ENV: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin <127.0.0.1> ENV: _=/usr/local/bin/ansible-playbook <127.0.0.1> ENV: LC_CTYPE=C.UTF-8 <127.0.0.1> ENV: TESTVAR1=test <127.0.0.1> ENV: TESTVAR2=test <127.0.0.1> ENV: TESTVAR3=test <127.0.0.1> EXEC ['/usr/local/bin/kubectl', '-n', 'test', '--kubeconfig', '/.kube/config', 'exec', '-i', 'ubuntu', '-c', 'ubuntu', '--', '/bin/sh', '-c', "/bin/sh -c 'echo ~ && sleep 0'"] <127.0.0.1> EXEC ['/usr/local/bin/kubectl', '-n', 'test', '--kubeconfig', '/.kube/config', 'exec', '-i', 'ubuntu', '-c', 'ubuntu', '--', '/bin/sh', '-c', '/bin/sh -c \'( umask 77 && mkdir -p "` echo /root/.ansible/tmp `"&& mkdir "` echo /root/.ansible/tmp/ansible-tmp-1713785852.548581-6866-69007595335133 `" && echo ansible-tmp-1713785852.548581-6866-69007595335133="` echo /root/.ansible/tmp/ansible-tmp-1713785852.548581-6866-69007595335133 `" ) && sleep 0\''] Using module file /usr/local/lib/python3.10/dist-packages/ansible/modules/command.py <127.0.0.1> PUT /root/.ansible/tmp/ansible-local-6862s5_lr_wb/tmpxwmx0qeh TO /root/.ansible/tmp/ansible-tmp-1713785852.548581-6866-69007595335133/AnsiballZ_command.py <127.0.0.1> EXEC ['/usr/local/bin/kubectl', '-n', 'test', '--kubeconfig', '/.kube/config', 'exec', '-i', 'ubuntu', '-c', 'ubuntu', '--', '/bin/sh', '-c', "/bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1713785852.548581-6866-69007595335133/ /root/.ansible/tmp/ansible-tmp-1713785852.548581-6866-69007595335133/AnsiballZ_command.py && sleep 0'"] <127.0.0.1> EXEC ['/usr/local/bin/kubectl', '-n', 'test', '--kubeconfig', '/.kube/config', 'exec', '-i', 'ubuntu', '-c', 'ubuntu', '--', '/bin/sh', '-c', "/bin/sh -c 'TEST_ENV1=value1 TEST_ENV2=value2 /usr/bin/python3 /root/.ansible/tmp/ansible-tmp-1713785852.548581-6866-69007595335133/AnsiballZ_command.py && sleep 0'"] <127.0.0.1> EXEC ['/usr/local/bin/kubectl', '-n', 'test', '--kubeconfig', '/.kube/config', 'exec', '-i', 'ubuntu', '-c', 'ubuntu', '--', '/bin/sh', '-c', "/bin/sh -c 'rm -f -r /root/.ansible/tmp/ansible-tmp-1713785852.548581-6866-69007595335133/ > /dev/null 2>&1 && sleep 0'"] changed: [localhost] => { "changed": true, "cmd": "env", "delta": "0:00:00.005088", "end": "2024-04-22 11:37:33.655340", "invocation": { "module_args": { "_raw_params": "env", "_uses_shell": true, "argv": null, "chdir": null, "creates": null, "executable": null, "removes": null, "stdin": null, "stdin_add_newline": true, "strip_empty_ends": true } }, "msg": "", "rc": 0, "start": "2024-04-22 11:37:33.650252", "stderr": "", "stderr_lines": [], "stdout": "KUBERNETES_PORT=tcp://10.96.0.1:443\nKUBERNETES_SERVICE_PORT=443\nHOSTNAME=ubuntu\nHOME=/root\nLC_CTYPE=C.UTF-8\nTEST_ENV1=value1\nTEST_ENV2=value2\nTERM=xterm\nKUBERNETES_PORT_443_TCP_ADDR=10.96.0.1\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nKUBERNETES_PORT_443_TCP_PORT=443\nKUBERNETES_PORT_443_TCP_PROTO=tcp\nKUBERNETES_SERVICE_PORT_HTTPS=443\nKUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443\nKUBERNETES_SERVICE_HOST=10.96.0.1\nPWD=/", "stdout_lines": [ "KUBERNETES_PORT=tcp://10.96.0.1:443", "KUBERNETES_SERVICE_PORT=443", "HOSTNAME=ubuntu", "HOME=/root", "LC_CTYPE=C.UTF-8", "TEST_ENV1=value1", "TEST_ENV2=value2", "TERM=xterm", "KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "KUBERNETES_PORT_443_TCP_PORT=443", "KUBERNETES_PORT_443_TCP_PROTO=tcp", "KUBERNETES_SERVICE_PORT_HTTPS=443", "KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443", "KUBERNETES_SERVICE_HOST=10.96.0.1", "PWD=/" ] } TASK [debug] ****************************************************************************************************************************************** task path: /test.yaml:22 redirecting (type: connection) ansible.builtin.kubectl to kubernetes.core.kubectl ok: [localhost] => { "result.stdout_lines": [ "KUBERNETES_PORT=tcp://10.96.0.1:443", "KUBERNETES_SERVICE_PORT=443", "HOSTNAME=ubuntu", "HOME=/root", "LC_CTYPE=C.UTF-8", "TEST_ENV1=value1", "TEST_ENV2=value2", "TERM=xterm", "KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "KUBERNETES_PORT_443_TCP_PORT=443", "KUBERNETES_PORT_443_TCP_PROTO=tcp", "KUBERNETES_SERVICE_PORT_HTTPS=443", "KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443", "KUBERNETES_SERVICE_HOST=10.96.0.1", "PWD=/" ] } PLAY RECAP ******************************************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 root@ubuntu-shell:/# Reviewed-by: Bikouo Aubin Reviewed-by: Yuriy Novostavskiy Reviewed-by: Mike Graves (cherry picked from commit fb25ff44f1190aab9356fcf83a82ee2184fc1d31) --- ...l-local-env-vars-for-connection-plugin.yml | 3 ++ docs/kubernetes.core.kubectl_connection.rst | 21 +++++++++++ plugins/connection/kubectl.py | 35 +++++++++++++++++-- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/20240426-add-support-of-kubectl-local-env-vars-for-connection-plugin.yml diff --git a/changelogs/fragments/20240426-add-support-of-kubectl-local-env-vars-for-connection-plugin.yml b/changelogs/fragments/20240426-add-support-of-kubectl-local-env-vars-for-connection-plugin.yml new file mode 100644 index 0000000000..226ad58271 --- /dev/null +++ b/changelogs/fragments/20240426-add-support-of-kubectl-local-env-vars-for-connection-plugin.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - kubectl - added support of local enviroment variable that will be used for kubectl and may be requried for establishing connections ifself (https://github.com/ansible-collections/kubernetes.core/pull/702) diff --git a/docs/kubernetes.core.kubectl_connection.rst b/docs/kubernetes.core.kubectl_connection.rst index 0bb06eb1c1..48db359929 100644 --- a/docs/kubernetes.core.kubectl_connection.rst +++ b/docs/kubernetes.core.kubectl_connection.rst @@ -211,6 +211,27 @@ Parameters
The configuration can be provided as dictionary. Added in version 2.4.0.
+ + +
+ kubectl_local_env_vars + +
+ dictionary +
+
added in 3.1.0
+ + + Default:
{}
+ + +
var: ansible_kubectl_local_env_vars
+ + +
Local enviromantal variable to be passed locally to the kubectl command line.
+
Please be aware that this passes information directly on the command line and it could expose sensitive data.
+ +
diff --git a/plugins/connection/kubectl.py b/plugins/connection/kubectl.py index 8aff4efed7..2a5e1b988f 100644 --- a/plugins/connection/kubectl.py +++ b/plugins/connection/kubectl.py @@ -72,6 +72,15 @@ - name: ansible_kubectl_extra_args env: - name: K8S_AUTH_EXTRA_ARGS + kubectl_local_env_vars: + description: + - Local enviromantal variable to be passed locally to the kubectl command line. + - Please be aware that this passes information directly on the command line and it could expose sensitive data. + default: {} + type: dict + version_added: 3.1.0 + vars: + - name: ansible_kubectl_local_env_vars kubectl_kubeconfig: description: - Path to a kubectl config file. Defaults to I(~/.kube/config) @@ -301,6 +310,19 @@ def _build_exec_cmd(self, cmd): return local_cmd, censored_local_cmd + def _local_env(self): + """Return a dict of local environment variables to pass to the kubectl command""" + local_env = {} + local_local_env_vars_name = "{0}_local_env_vars".format(self.transport) + local_env_vars = self.get_option(local_local_env_vars_name) + if local_env_vars: + if isinstance(local_env_vars, dict): + local_env_vars = json.dumps(local_env_vars) + local_env = os.environ.copy() + local_env.update(json.loads(local_env_vars)) + return local_env + return None + def _connect(self, port=None): """Connect to the container. Nothing to do""" super(Connection, self)._connect() @@ -329,6 +351,7 @@ def exec_command(self, cmd, in_data=None, sudoable=False): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=self._local_env(), ) stdout, stderr = p.communicate(in_data) @@ -378,7 +401,11 @@ def put_file(self, in_path, out_path): args = [to_bytes(i, errors="surrogate_or_strict") for i in args] try: p = subprocess.Popen( - args, stdin=in_file, stdout=subprocess.PIPE, stderr=subprocess.PIPE + args, + stdin=in_file, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=self._local_env(), ) except OSError: raise AnsibleError( @@ -415,7 +442,11 @@ def fetch_file(self, in_path, out_path): ) as out_file: try: p = subprocess.Popen( - args, stdin=subprocess.PIPE, stdout=out_file, stderr=subprocess.PIPE + args, + stdin=subprocess.PIPE, + stdout=out_file, + stderr=subprocess.PIPE, + env=self._local_env(), ) except OSError: raise AnsibleError(