From 221631c06a89dc1ff53140c57adbd8522259258c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A9ri=20Le=20Bouder?= Date: Fri, 11 Dec 2020 11:41:08 -0500 Subject: [PATCH] helm: add support for the K8S_ envvars (#319) Add support for: - K8S_AUTH_HOST - K8S_AUTH_API_KEY - K8S_AUTH_VERIFY_SSL - K8S_AUTH_SSL_CA_CERT This commit also refactor the way we pass K8S related configuration to `helm`: All the calls are now done in a new module_utils module (`helm.py`). The handling of the `kube_*` variables has also been moved in this new module. We need https://github.com/helm/helm/pull/8622 to be able to ignore the certificate validation. As a workaround, the generate a temporary kubeconfig configuration file. Closes: #279 --- ...lm-honors-HELM_-environment-variables.yaml | 4 + .../default/roles/helm/tasks/run_test.yml | 3 + .../roles/helm/tasks/test_read_envvars.yml | 10 ++ plugins/doc_fragments/helm_common_options.py | 25 ++++ plugins/module_utils/helm.py | 119 ++++++++++++++++++ plugins/modules/helm.py | 76 ++++------- plugins/modules/helm_info.py | 55 +++----- plugins/modules/helm_plugin.py | 32 +++-- plugins/modules/helm_plugin_info.py | 28 ++--- plugins/modules/helm_repository.py | 11 +- test-requirements.txt | 1 + tests/unit/module_utils/test_helm.py | 100 +++++++++++++++ tests/unit/requirements.txt | 2 + 13 files changed, 340 insertions(+), 126 deletions(-) create mode 100644 changelogs/fragments/319-helm-honors-HELM_-environment-variables.yaml create mode 100644 molecule/default/roles/helm/tasks/test_read_envvars.yml create mode 100644 plugins/module_utils/helm.py create mode 100644 test-requirements.txt create mode 100644 tests/unit/module_utils/test_helm.py create mode 100644 tests/unit/requirements.txt diff --git a/changelogs/fragments/319-helm-honors-HELM_-environment-variables.yaml b/changelogs/fragments/319-helm-honors-HELM_-environment-variables.yaml new file mode 100644 index 00000000..114c2683 --- /dev/null +++ b/changelogs/fragments/319-helm-honors-HELM_-environment-variables.yaml @@ -0,0 +1,4 @@ +--- +minor_changes: +- The Helm modules (``helm``, ``helm_info``, ``helm_plugin``, ``helm_plugin_info``, ``helm_plugin_repository``) accept + the K8S environment variables like the other modules of the collections. diff --git a/molecule/default/roles/helm/tasks/run_test.yml b/molecule/default/roles/helm/tasks/run_test.yml index 0384a2e4..06bda06d 100644 --- a/molecule/default/roles/helm/tasks/run_test.yml +++ b/molecule/default/roles/helm/tasks/run_test.yml @@ -12,6 +12,9 @@ - name: "Install {{ helm_version }}" include_tasks: install.yml +- name: "Ensure we honor the environment variables" + include_tasks: test_read_envvars.yml + - name: tests_repository include_tasks: tests_repository.yml diff --git a/molecule/default/roles/helm/tasks/test_read_envvars.yml b/molecule/default/roles/helm/tasks/test_read_envvars.yml new file mode 100644 index 00000000..b167d59e --- /dev/null +++ b/molecule/default/roles/helm/tasks/test_read_envvars.yml @@ -0,0 +1,10 @@ +- name: Pass a bogus server through the K8S_AUTH_HOST environment variable and ensure helm fails as expected + helm: + binary_path: "{{ helm_binary }}" + state: absent + name: does-not-exist + namespace: "{{ helm_namespace }}" + environment: + K8S_AUTH_HOST: somewhere + register: _helm_result + failed_when: '"http://somewhere/version" not in _helm_result.stderr' diff --git a/plugins/doc_fragments/helm_common_options.py b/plugins/doc_fragments/helm_common_options.py index f13bc1e3..046e11b1 100644 --- a/plugins/doc_fragments/helm_common_options.py +++ b/plugins/doc_fragments/helm_common_options.py @@ -31,4 +31,29 @@ options: - If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead. type: path aliases: [ kubeconfig_path ] + host: + description: + - Provide a URL for accessing the API. Can also be specified via C(K8S_AUTH_HOST) environment variable. + type: str + version_added: "1.2.0" + api_key: + description: + - Token used to authenticate with the API. Can also be specified via C(K8S_AUTH_API_KEY) environment variable. + type: str + version_added: "1.2.0" + validate_certs: + description: + - Whether or not to verify the API server's SSL certificates. Can also be specified via C(K8S_AUTH_VERIFY_SSL) + environment variable. + type: bool + aliases: [ verify_ssl ] + default: True + version_added: "1.2.0" + ca_cert: + description: + - Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to + avoid certificate validation errors. Can also be specified via C(K8S_AUTH_SSL_CA_CERT) environment variable. + type: path + aliases: [ ssl_ca_cert ] + version_added: "1.2.0" ''' diff --git a/plugins/module_utils/helm.py b/plugins/module_utils/helm.py new file mode 100644 index 00000000..e5a7e901 --- /dev/null +++ b/plugins/module_utils/helm.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, Ansible Project +# 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 + + +from contextlib import contextmanager +import os +import tempfile +import traceback + +from ansible.module_utils.basic import missing_required_lib + + +try: + import yaml + HAS_YAML = True +except ImportError: + YAML_IMP_ERR = traceback.format_exc() + HAS_YAML = False + + +@contextmanager +def prepare_helm_environ_update(module): + environ_update = {} + file_to_cleam_up = None + kubeconfig_path = module.params.get('kubeconfig') + if module.params.get('kube_context') is not None: + environ_update["HELM_KUBECONTEXT"] = module.params.get('kube_context') + if module.params.get('release_namespace'): + environ_update["HELM_NAMESPACE"] = module.params.get('release_namespace') + if module.params.get("api_key"): + environ_update["HELM_KUBETOKEN"] = module.params["api_key"] + if module.params.get("host"): + environ_update["HELM_KUBEAPISERVER"] = module.params["host"] + if module.params.get("validate_certs") is False or module.params.get("ca_cert"): + kubeconfig_path = write_temp_kubeconfig( + module.params["host"], + validate_certs=module.params["validate_certs"], + ca_cert=module.params["ca_cert"]) + file_to_cleam_up = kubeconfig_path + if kubeconfig_path is not None: + environ_update["KUBECONFIG"] = kubeconfig_path + + try: + yield environ_update + finally: + if file_to_cleam_up: + os.remove(file_to_cleam_up) + + +def run_helm(module, command, fails_on_error=True): + if not HAS_YAML: + module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR) + + with prepare_helm_environ_update(module) as environ_update: + rc, out, err = module.run_command(command, environ_update=environ_update) + if fails_on_error and rc != 0: + module.fail_json( + msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err), + stdout=out, + stderr=err, + command=command, + ) + return rc, out, err + + +def get_values(module, command, release_name): + """ + Get Values from deployed release + """ + if not HAS_YAML: + module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR) + + get_command = command + " get values --output=yaml " + release_name + + rc, out, err = run_helm(module, get_command) + # Helm 3 return "null" string when no values are set + if out.rstrip("\n") == "null": + return {} + return yaml.safe_load(out) + + +def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None): + # Workaround until https://github.com/helm/helm/pull/8622 is merged + content = { + "apiVersion": "v1", + "kind": "Config", + "clusters": [ + { + "cluster": { + "server": server, + }, + "name": "generated-cluster" + } + ], + "contexts": [ + { + "context": { + "cluster": "generated-cluster" + }, + "name": "generated-context" + } + ], + "current-context": "generated-context" + } + + if not validate_certs: + content["clusters"][0]["cluster"]["insecure-skip-tls-verify"] = True + if ca_cert: + content["clusters"][0]["cluster"]["certificate-authority"] = ca_cert + + _fd, file_name = tempfile.mkstemp() + with os.fdopen(_fd, 'w') as fp: + yaml.dump(content, fp) + return file_name diff --git a/plugins/modules/helm.py b/plugins/modules/helm.py index ed7938c0..5bdc18af 100644 --- a/plugins/modules/helm.py +++ b/plugins/modules/helm.py @@ -266,34 +266,7 @@ except ImportError: IMP_YAML = False from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback - -module = None - - -def exec_command(command): - rc, out, err = module.run_command(command) - if rc != 0: - module.fail_json( - msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err), - stdout=out, - stderr=err, - command=command, - ) - return rc, out, err - - -def get_values(command, release_name): - """ - Get Values from deployed release - """ - - get_command = command + " get values --output=yaml " + release_name - - rc, out, err = exec_command(get_command) - # Helm 3 return "null" string when no values are set - if out.rstrip("\n") == "null": - return {} - return yaml.safe_load(out) +from ansible_collections.community.kubernetes.plugins.module_utils.helm import run_helm, get_values def get_release(state, release_name): @@ -308,40 +281,40 @@ def get_release(state, release_name): return None -def get_release_status(command, release_name): +def get_release_status(module, command, release_name): """ Get Release state from deployed release """ list_command = command + " list --output=yaml --filter " + release_name - rc, out, err = exec_command(list_command) + rc, out, err = run_helm(module, list_command) release = get_release(yaml.safe_load(out), release_name) if release is None: # not install return None - release['values'] = get_values(command, release_name) + release['values'] = get_values(module, command, release_name) return release -def run_repo_update(command): +def run_repo_update(module, command): """ Run Repo update """ repo_update_command = command + " repo update" - rc, out, err = exec_command(repo_update_command) + rc, out, err = run_helm(module, repo_update_command) -def fetch_chart_info(command, chart_ref): +def fetch_chart_info(module, command, chart_ref): """ Get chart info """ inspect_command = command + " show chart " + chart_ref - rc, out, err = exec_command(inspect_command) + rc, out, err = run_helm(module, inspect_command) return yaml.safe_load(out) @@ -440,11 +413,23 @@ def main(): atomic=dict(type='bool', default=False), create_namespace=dict(type='bool', default=False), replace=dict(type='bool', default=False), + + # Generic auth key + host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])), + ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])), + validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])), + api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY'])) ), required_if=[ ('release_state', 'present', ['release_name', 'chart_ref']), ('release_state', 'absent', ['release_name']) ], + mutually_exclusive=[ + ("context", "ca_cert"), + ("context", "validate_certs"), + ("kubeconfig", "ca_cert"), + ("kubeconfig", "validate_certs") + ], supports_check_mode=True, ) @@ -458,7 +443,6 @@ def main(): chart_repo_url = module.params.get('chart_repo_url') chart_version = module.params.get('chart_version') release_name = module.params.get('release_name') - release_namespace = module.params.get('release_namespace') release_state = module.params.get('release_state') release_values = module.params.get('release_values') values_files = module.params.get('values_files') @@ -467,8 +451,6 @@ def main(): # Helm options disable_hook = module.params.get('disable_hook') force = module.params.get('force') - kube_context = module.params.get('context') - kubeconfig_path = module.params.get('kubeconfig') purge = module.params.get('purge') wait = module.params.get('wait') wait_timeout = module.params.get('wait_timeout') @@ -481,19 +463,11 @@ def main(): else: helm_cmd_common = module.get_bin_path('helm', required=True) - if kube_context is not None: - helm_cmd_common += " --kube-context " + kube_context - - if kubeconfig_path is not None: - helm_cmd_common += " --kubeconfig " + kubeconfig_path - if update_repo_cache: - run_repo_update(helm_cmd_common) - - helm_cmd_common += " --namespace=" + release_namespace + run_repo_update(module, helm_cmd_common) # Get real/deployed release status - release_status = get_release_status(helm_cmd_common, release_name) + release_status = get_release_status(module, helm_cmd_common, release_name) # keep helm_cmd_common for get_release_status in module_exit_json helm_cmd = helm_cmd_common @@ -512,7 +486,7 @@ def main(): helm_cmd += " --repo=" + chart_repo_url # Fetch chart info to have real version and real name for chart_ref from archive, folder or url - chart_info = fetch_chart_info(helm_cmd, chart_ref) + chart_info = fetch_chart_info(module, helm_cmd, chart_ref) if release_status is None: # Not installed helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout, @@ -563,13 +537,13 @@ def main(): command=helm_cmd, ) - rc, out, err = exec_command(helm_cmd) + rc, out, err = run_helm(module, helm_cmd) module.exit_json( changed=changed, stdout=out, stderr=err, - status=get_release_status(helm_cmd_common, release_name), + status=get_release_status(module, helm_cmd_common, release_name), command=helm_cmd, ) diff --git a/plugins/modules/helm_info.py b/plugins/modules/helm_info.py index e3999cf7..0f649fe1 100644 --- a/plugins/modules/helm_info.py +++ b/plugins/modules/helm_info.py @@ -99,27 +99,7 @@ except ImportError: IMP_YAML = False from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback - -module = None - - -# Get Values from deployed release -def get_values(command, release_name): - get_command = command + " get values --output=yaml " + release_name - - rc, out, err = module.run_command(get_command) - - if rc != 0: - module.fail_json( - msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err), - command=get_command - ) - - # Helm 3 return "null" string when no values are set - if out.rstrip("\n") == "null": - return {} - else: - return yaml.safe_load(out) +from ansible_collections.community.kubernetes.plugins.module_utils.helm import run_helm, get_values # Get Release from all deployed releases @@ -132,10 +112,10 @@ def get_release(state, release_name): # Get Release state from deployed release -def get_release_status(command, release_name): +def get_release_status(module, command, release_name): list_command = command + " list --output=yaml --filter " + release_name - rc, out, err = module.run_command(list_command) + rc, out, err = run_helm(module, list_command) if rc != 0: module.fail_json( @@ -148,7 +128,7 @@ def get_release_status(command, release_name): if release is None: # not install return None - release['values'] = get_values(command, release_name) + release['values'] = get_values(module, command, release_name) return release @@ -165,7 +145,19 @@ def main(): # Helm options context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])), kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])), + + # Generic auth key + host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])), + ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])), + validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])), + api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY'])) ), + mutually_exclusive=[ + ("context", "ca_cert"), + ("context", "validate_certs"), + ("kubeconfig", "ca_cert"), + ("kubeconfig", "validate_certs") + ], supports_check_mode=True, ) @@ -174,26 +166,13 @@ def main(): bin_path = module.params.get('binary_path') release_name = module.params.get('release_name') - release_namespace = module.params.get('release_namespace') - - # Helm options - kube_context = module.params.get('context') - kubeconfig_path = module.params.get('kubeconfig') if bin_path is not None: helm_cmd_common = bin_path else: helm_cmd_common = module.get_bin_path('helm', required=True) - if kube_context is not None: - helm_cmd_common += " --kube-context " + kube_context - - if kubeconfig_path is not None: - helm_cmd_common += " --kubeconfig " + kubeconfig_path - - helm_cmd_common += " --namespace=" + release_namespace - - release_status = get_release_status(helm_cmd_common, release_name) + release_status = get_release_status(module, helm_cmd_common, release_name) if release_status is not None: module.exit_json(changed=False, status=release_status) diff --git a/plugins/modules/helm_plugin.py b/plugins/modules/helm_plugin.py index e5e28a4b..e6d55795 100644 --- a/plugins/modules/helm_plugin.py +++ b/plugins/modules/helm_plugin.py @@ -96,6 +96,7 @@ rc: ''' from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.community.kubernetes.plugins.module_utils.helm import run_helm def main(): @@ -109,6 +110,12 @@ def main(): # Helm options context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])), kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])), + + # Generic auth key + host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])), + ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])), + validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])), + api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY'])) ), supports_check_mode=True, required_if=[ @@ -116,18 +123,17 @@ def main(): ("state", "absent", ("plugin_name",)), ], mutually_exclusive=[ - ['plugin_name', 'plugin_path'], + ('plugin_name', 'plugin_path'), + ("context", "ca_cert"), + ("context", "validate_certs"), + ("kubeconfig", "ca_cert"), + ("kubeconfig", "validate_certs") ], ) bin_path = module.params.get('binary_path') - release_namespace = module.params.get('release_namespace') state = module.params.get('state') - # Helm options - kube_context = module.params.get('context') - kubeconfig_path = module.params.get('kubeconfig') - if bin_path is not None: helm_cmd_common = bin_path else: @@ -137,18 +143,10 @@ def main(): helm_cmd_common += " plugin" - if kube_context is not None: - helm_cmd_common += " --kube-context " + kube_context - - if kubeconfig_path is not None: - helm_cmd_common += " --kubeconfig " + kubeconfig_path - - helm_cmd_common += " --namespace=" + release_namespace - if state == 'present': helm_cmd_common += " install %s" % module.params.get('plugin_path') if not module.check_mode: - rc, out, err = module.run_command(helm_cmd_common) + rc, out, err = run_helm(module, helm_cmd_common, fails_on_error=False) else: rc, out, err = (0, '', '') @@ -183,7 +181,7 @@ def main(): elif state == 'absent': plugin_name = module.params.get('plugin_name') helm_plugin_list = helm_cmd_common + " list" - rc, out, err = module.run_command(helm_plugin_list) + rc, out, err = run_helm(module, helm_plugin_list) if rc != 0 or (out == '' and err == ''): module.fail_json( msg="Failed to get Helm plugin info", @@ -206,7 +204,7 @@ def main(): if found: helm_uninstall_cmd = "%s uninstall %s" % (helm_cmd_common, plugin_name) if not module.check_mode: - rc, out, err = module.run_command(helm_uninstall_cmd) + rc, out, err = run_helm(module, helm_uninstall_cmd, fails_on_error=False) else: rc, out, err = (0, '', '') diff --git a/plugins/modules/helm_plugin_info.py b/plugins/modules/helm_plugin_info.py index 26664b43..4746643f 100644 --- a/plugins/modules/helm_plugin_info.py +++ b/plugins/modules/helm_plugin_info.py @@ -77,6 +77,7 @@ rc: ''' from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.community.kubernetes.plugins.module_utils.helm import run_helm def main(): @@ -88,16 +89,23 @@ def main(): # Helm options context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])), kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])), + + # Generic auth key + host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])), + ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])), + validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])), + api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY'])) ), + mutually_exclusive=[ + ("context", "ca_cert"), + ("context", "validate_certs"), + ("kubeconfig", "ca_cert"), + ("kubeconfig", "validate_certs") + ], supports_check_mode=True, ) bin_path = module.params.get('binary_path') - release_namespace = module.params.get('release_namespace') - - # Helm options - kube_context = module.params.get('context') - kubeconfig_path = module.params.get('kubeconfig') if bin_path is not None: helm_cmd_common = bin_path @@ -108,17 +116,9 @@ def main(): helm_cmd_common += " plugin" - if kube_context is not None: - helm_cmd_common += " --kube-context " + kube_context - - if kubeconfig_path is not None: - helm_cmd_common += " --kubeconfig " + kubeconfig_path - - helm_cmd_common += " --namespace=" + release_namespace - plugin_name = module.params.get('plugin_name') helm_plugin_list = helm_cmd_common + " list" - rc, out, err = module.run_command(helm_plugin_list) + rc, out, err = run_helm(module, helm_plugin_list) if rc != 0 or (out == '' and err == ''): module.fail_json( msg="Failed to get Helm plugin info", diff --git a/plugins/modules/helm_repository.py b/plugins/modules/helm_repository.py index 7e7b5260..bb0eccc3 100644 --- a/plugins/modules/helm_repository.py +++ b/plugins/modules/helm_repository.py @@ -121,8 +121,7 @@ except ImportError: IMP_YAML = False from ansible.module_utils.basic import AnsibleModule, missing_required_lib - -module = None +from ansible_collections.community.kubernetes.plugins.module_utils.helm import run_helm # Get repository from all repositories added @@ -135,10 +134,10 @@ def get_repository(state, repo_name): # Get repository status -def get_repository_status(command, repository_name): +def get_repository_status(module, command, repository_name): list_command = command + " repo list --output=yaml" - rc, out, err = module.run_command(list_command) + rc, out, err = run_helm(module, list_command, fails_on_error=False) # no repo => rc=1 and 'no repositories to show' in output if rc == 1 and "no repositories to show" in err: @@ -208,7 +207,7 @@ def main(): else: helm_cmd = module.get_bin_path('helm', required=True) - repository_status = get_repository_status(helm_cmd, repo_name) + repository_status = get_repository_status(module, helm_cmd, repo_name) if repo_state == "absent" and repository_status is not None: helm_cmd = delete_repository(helm_cmd, repo_name) @@ -225,7 +224,7 @@ def main(): elif not changed: module.exit_json(changed=False, repo_name=repo_name, repo_url=repo_url) - rc, out, err = module.run_command(helm_cmd) + rc, out, err = run_helm(module, helm_cmd) if repo_password is not None: helm_cmd = helm_cmd.replace(repo_password, '******') diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..038d73d0 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +kubernetes-validate diff --git a/tests/unit/module_utils/test_helm.py b/tests/unit/module_utils/test_helm.py new file mode 100644 index 00000000..4f23f486 --- /dev/null +++ b/tests/unit/module_utils/test_helm.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, Ansible Project +# 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 + +import os.path + +import yaml + + +from ansible_collections.community.kubernetes.plugins.module_utils.helm import ( + run_helm, + write_temp_kubeconfig, +) + + +class MockedModule: + def __init__(self): + + self.params = { + "api_key": None, + "ca_cert": None, + "host": None, + "kube_context": None, + "kubeconfig": None, + "release_namespace": None, + "validate_certs": None, + } + + self.r = {} + + def run_command(self, command, environ_update=None): + self.r = {"command": command, "environ_update": environ_update} + return 0, "", "" + + +def test_write_temp_kubeconfig_server_only(): + file_name = write_temp_kubeconfig("ff") + try: + with open(file_name, "r") as fd: + content = yaml.load(fd) + finally: + os.remove(file_name) + + assert content == { + "apiVersion": "v1", + "clusters": [{"cluster": {"server": "ff"}, "name": "generated-cluster"}], + "contexts": [ + {"context": {"cluster": "generated-cluster"}, "name": "generated-context"} + ], + "current-context": "generated-context", + "kind": "Config", + } + + +def test_write_temp_kubeconfig_server_inscure_certs(): + file_name = write_temp_kubeconfig("ff", False, "my-certificate") + try: + with open(file_name, "r") as fd: + content = yaml.load(fd) + finally: + os.remove(file_name) + + assert content["clusters"][0]["cluster"]["insecure-skip-tls-verify"] is True + assert ( + content["clusters"][0]["cluster"]["certificate-authority"] == "my-certificate" + ) + + +def test_run_helm_naked(): + module = MockedModule() + run_helm(module, "helm foo") + + assert module.r["command"] == "helm foo" + assert module.r["environ_update"] == {} + + +def test_run_helm_with_params(): + module = MockedModule() + module.params = { + "api_key": "my-api-key", + "ca_cert": "my-ca-cert", + "host": "some-host", + "kube_context": "my-context", + "release_namespace": "a-release-namespace", + "validate_certs": False, + } + + run_helm(module, "helm foo") + + assert module.r["command"] == "helm foo" + assert module.r["environ_update"]["HELM_KUBEAPISERVER"] == "some-host" + assert module.r["environ_update"]["HELM_KUBECONTEXT"] == "my-context" + assert module.r["environ_update"]["HELM_KUBETOKEN"] == "my-api-key" + assert module.r["environ_update"]["HELM_NAMESPACE"] == "a-release-namespace" + assert module.r["environ_update"]["KUBECONFIG"] + assert not os.path.exists(module.r["environ_update"]["KUBECONFIG"]) diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt new file mode 100644 index 00000000..33438ef5 --- /dev/null +++ b/tests/unit/requirements.txt @@ -0,0 +1,2 @@ +pytest +PyYAML