helm - add support for in-memory kubeconfig (#497)

helm - add support for in-memory kubeconfig

SUMMARY

closes #492

ISSUE TYPE


Feature Pull Request

Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: Bikouo Aubin <None>
This commit is contained in:
Bikouo Aubin
2022-09-12 11:13:19 +02:00
committed by GitHub
parent 5ff3566f30
commit a3a5f3cf4b
15 changed files with 478 additions and 240 deletions

View File

@@ -0,0 +1,2 @@
minor_changes:
- helm, helm_plugin, helm_info, helm_plugin_info, kubectl - add support for in-memory kubeconfig. (https://github.com/ansible-collections/kubernetes.core/issues/492).

View File

@@ -75,6 +75,7 @@ DOCUMENTATION = r"""
kubectl_kubeconfig:
description:
- Path to a kubectl config file. Defaults to I(~/.kube/config)
- The configuration can be provided as dictionary. Added in version 2.4.0.
default: ''
vars:
- name: ansible_kubectl_kubeconfig
@@ -175,6 +176,8 @@ import os
import os.path
import shutil
import subprocess
import tempfile
import json
from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.errors import AnsibleError, AnsibleFileNotFound
@@ -222,6 +225,12 @@ class Connection(ConnectionBase):
self.transport_cmd = kwargs.get(cmd_arg, shutil.which(self.transport))
if not self.transport_cmd:
raise AnsibleError("{0} command not found in PATH".format(self.transport))
self._file_to_delete = None
def delete_temporary_file(self):
if self._file_to_delete is not None:
os.remove(self._file_to_delete)
self._file_to_delete = None
def _build_exec_cmd(self, cmd):
"""Build the local kubectl exec command to run cmd on remote_host"""
@@ -244,6 +253,18 @@ class Connection(ConnectionBase):
self.connection_options[key], str(skip_verify_ssl).lower()
)
)
elif key.endswith("kubeconfig") and self.get_option(key) != "":
kubeconfig_path = self.get_option(key)
if isinstance(kubeconfig_path, dict):
fd, tmpfile = tempfile.mkstemp()
with os.fdopen(fd, "w") as fp:
json.dump(kubeconfig_path, fp)
kubeconfig_path = tmpfile
self._file_to_delete = tmpfile
cmd_arg = self.connection_options[key]
local_cmd += [cmd_arg, kubeconfig_path]
censored_local_cmd += [cmd_arg, kubeconfig_path]
elif (
not key.endswith("container")
and self.get_option(key)
@@ -311,6 +332,7 @@ class Connection(ConnectionBase):
)
stdout, stderr = p.communicate(in_data)
self.delete_temporary_file()
return (p.returncode, stdout, stderr)
def _prefix_login_path(self, remote_path):
@@ -363,6 +385,7 @@ class Connection(ConnectionBase):
"kubectl connection requires dd command in the container to put files"
)
stdout, stderr = p.communicate()
self.delete_temporary_file()
if p.returncode != 0:
raise AnsibleError(
@@ -401,6 +424,7 @@ class Connection(ConnectionBase):
)
)
stdout, stderr = p.communicate()
self.delete_temporary_file()
if p.returncode != 0:
raise AnsibleError(

View File

@@ -30,7 +30,8 @@ options:
description:
- Helm option to specify kubeconfig path to use.
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
type: path
- The configuration can be provided as dictionary. Added in version 2.4.0.
type: raw
aliases: [ kubeconfig_path ]
host:
description:

View File

@@ -7,14 +7,14 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
from contextlib import contextmanager
import os
import tempfile
import traceback
import re
import json
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.six import string_types
try:
import yaml
@@ -25,11 +25,18 @@ except ImportError:
HAS_YAML = False
@contextmanager
def prepare_helm_environ_update(module):
environ_update = {}
file_to_cleam_up = None
kubeconfig_path = module.params.get("kubeconfig")
kubeconfig_path = None
if module.params.get("kubeconfig") is not None:
kubeconfig = module.params.get("kubeconfig")
if isinstance(kubeconfig, string_types):
kubeconfig_path = kubeconfig
elif isinstance(kubeconfig, dict):
fd, kubeconfig_path = tempfile.mkstemp()
with os.fdopen(fd, "w") as fp:
json.dump(kubeconfig, fp)
module.add_cleanup_file(kubeconfig_path)
if module.params.get("context") is not None:
environ_update["HELM_KUBECONTEXT"] = module.params.get("context")
if module.params.get("release_namespace"):
@@ -44,23 +51,19 @@ def prepare_helm_environ_update(module):
validate_certs=module.params["validate_certs"],
ca_cert=module.params["ca_cert"],
)
file_to_cleam_up = kubeconfig_path
module.add_cleanup_file(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)
return environ_update
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)
environ_update = prepare_helm_environ_update(module)
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(
@@ -118,7 +121,7 @@ def get_helm_plugin_list(module, helm_bin=None):
"""
if not helm_bin:
return []
helm_plugin_list = helm_bin + " list"
helm_plugin_list = helm_bin + " plugin list"
rc, out, err = run_helm(module, helm_plugin_list)
if rc != 0 or (out == "" and err == ""):
module.fail_json(
@@ -162,3 +165,9 @@ def get_helm_version(module, helm_bin):
if m:
return m.group(1)
return None
def get_helm_binary(module):
return module.params.get("binary_path") or module.get_bin_path(
"helm", required=True
)

View File

@@ -0,0 +1,44 @@
from __future__ import absolute_import, division, print_function
from ansible.module_utils.basic import env_fallback
__metaclass__ = type
HELM_AUTH_ARG_SPEC = dict(
binary_path=dict(type="path"),
context=dict(
type="str",
aliases=["kube_context"],
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
),
kubeconfig=dict(
type="raw",
aliases=["kubeconfig_path"],
fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]),
),
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"]),
),
)
HELM_AUTH_MUTUALLY_EXCLUSIVE = [
("context", "ca_cert"),
("context", "validate_certs"),
("kubeconfig", "ca_cert"),
("kubeconfig", "validate_certs"),
]

View File

@@ -336,6 +336,7 @@ command:
import re
import tempfile
import traceback
import copy
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
LooseVersion,
)
@@ -348,13 +349,17 @@ except ImportError:
IMP_YAML_ERR = traceback.format_exc()
IMP_YAML = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
run_helm,
get_values,
get_helm_plugin_list,
parse_helm_plugin_list,
get_helm_version,
get_helm_binary,
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
)
@@ -531,13 +536,12 @@ def load_values_files(values_files):
return values
def get_plugin_version(command, plugin):
def get_plugin_version(helm_bin, plugin):
"""
Check if helm plugin is installed and return corresponding version
"""
cmd = command + " plugin"
rc, output, err = get_helm_plugin_list(module, helm_bin=cmd)
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_bin)
out = parse_helm_plugin_list(module, output=output.splitlines())
if not out:
@@ -612,11 +616,10 @@ def default_check(release_status, chart_info, values=None, values_files=None):
)
def main():
global module
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type="path"),
def argument_spec():
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
arg_spec.update(
dict(
chart_ref=dict(type="path"),
chart_repo_url=dict(type="str"),
chart_version=dict(type="str"),
@@ -629,19 +632,8 @@ def main():
release_values=dict(type="dict", default={}, aliases=["values"]),
values_files=dict(type="list", default=[], elements="str"),
update_repo_cache=dict(type="bool", default=False),
# Helm options
disable_hook=dict(type="bool", default=False),
force=dict(type="bool", default=False),
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"]),
),
purge=dict(type="bool", default=True),
wait=dict(type="bool", default=False),
wait_timeout=dict(type="str"),
@@ -652,23 +644,15 @@ def main():
replace=dict(type="bool", default=False),
skip_crds=dict(type="bool", default=False),
history_max=dict(type="int"),
# 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"])
),
),
)
)
return arg_spec
def main():
global module
module = AnsibleModule(
argument_spec=argument_spec(),
required_if=[
("release_state", "present", ["release_name", "chart_ref"]),
("release_state", "absent", ["release_name"]),
@@ -687,7 +671,6 @@ def main():
changed = False
bin_path = module.params.get("binary_path")
chart_ref = module.params.get("chart_ref")
chart_repo_url = module.params.get("chart_repo_url")
chart_version = module.params.get("chart_version")
@@ -712,10 +695,8 @@ def main():
history_max = module.params.get("history_max")
timeout = module.params.get("timeout")
if bin_path is not None:
helm_cmd_common = bin_path
else:
helm_cmd_common = module.get_bin_path("helm", required=True)
helm_cmd_common = get_helm_binary(module)
helm_bin = helm_cmd_common
if update_repo_cache:
run_repo_update(module, helm_cmd_common)
@@ -808,7 +789,7 @@ def main():
else:
helm_diff_version = get_plugin_version(helm_cmd_common, "diff")
helm_diff_version = get_plugin_version(helm_bin, "diff")
if helm_diff_version and (
not chart_repo_url
or (

View File

@@ -112,6 +112,7 @@ status:
"""
import traceback
import copy
try:
import yaml
@@ -121,10 +122,15 @@ except ImportError:
IMP_YAML_ERR = traceback.format_exc()
IMP_YAML = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
run_helm,
get_values,
get_helm_binary,
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
HELM_AUTH_MUTUALLY_EXCLUSIVE,
)
@@ -176,63 +182,34 @@ def get_release_status(module, command, release_name, release_state):
return release
def argument_spec():
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
arg_spec.update(
dict(
release_name=dict(type="str", required=True, aliases=["name"]),
release_namespace=dict(type="str", required=True, aliases=["namespace"]),
release_state=dict(type="list", default=[], elements="str"),
)
)
return arg_spec
def main():
global module
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type="path"),
release_name=dict(type="str", required=True, aliases=["name"]),
release_namespace=dict(type="str", required=True, aliases=["namespace"]),
# 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"])
),
release_state=dict(type="list", default=[], elements="str"),
),
mutually_exclusive=[
("context", "ca_cert"),
("context", "validate_certs"),
("kubeconfig", "ca_cert"),
("kubeconfig", "validate_certs"),
],
argument_spec=argument_spec(),
mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
supports_check_mode=True,
)
if not IMP_YAML:
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
bin_path = module.params.get("binary_path")
release_name = module.params.get("release_name")
release_state = module.params.get("release_state")
if bin_path is not None:
helm_cmd_common = bin_path
else:
helm_cmd_common = module.get_bin_path("helm", required=True)
helm_cmd_common = get_helm_binary(module)
release_status = get_release_status(
module, helm_cmd_common, release_name, release_state

View File

@@ -108,21 +108,24 @@ rc:
sample: 1
"""
from ansible.module_utils.basic import AnsibleModule, env_fallback
import copy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
run_helm,
get_helm_plugin_list,
parse_helm_plugin_list,
get_helm_binary,
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
HELM_AUTH_MUTUALLY_EXCLUSIVE,
)
def main():
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type="path"),
state=dict(
type="str", default="present", choices=["present", "absent", "latest"]
),
def argument_spec():
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
arg_spec.update(
dict(
plugin_path=dict(
type="str",
),
@@ -132,60 +135,38 @@ def main():
plugin_version=dict(
type="str",
),
# Helm options
context=dict(
state=dict(
type="str",
aliases=["kube_context"],
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
default="present",
choices=["present", "absent", "latest"],
),
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"])
),
),
)
)
return arg_spec
def mutually_exclusive():
mutually_ex = copy.deepcopy(HELM_AUTH_MUTUALLY_EXCLUSIVE)
mutually_ex.append(("plugin_name", "plugin_path"))
return mutually_ex
def main():
module = AnsibleModule(
argument_spec=argument_spec(),
supports_check_mode=True,
required_if=[
("state", "present", ("plugin_path",)),
("state", "absent", ("plugin_name",)),
("state", "latest", ("plugin_name",)),
],
mutually_exclusive=[
("plugin_name", "plugin_path"),
("context", "ca_cert"),
("context", "validate_certs"),
("kubeconfig", "ca_cert"),
("kubeconfig", "validate_certs"),
],
mutually_exclusive=mutually_exclusive(),
)
bin_path = module.params.get("binary_path")
state = module.params.get("state")
helm_bin = get_helm_binary(module)
if bin_path is not None:
helm_cmd_common = bin_path
else:
helm_cmd_common = "helm"
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
helm_cmd_common += " plugin"
helm_cmd_common = helm_bin + " plugin"
if state == "present":
helm_cmd_common += " install %s" % module.params.get("plugin_path")
@@ -227,7 +208,7 @@ def main():
)
elif state == "absent":
plugin_name = module.params.get("plugin_name")
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_bin)
out = parse_helm_plugin_list(module, output=output.splitlines())
if not out:
@@ -281,7 +262,7 @@ def main():
)
elif state == "latest":
plugin_name = module.params.get("plugin_name")
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_bin)
out = parse_helm_plugin_list(module, output=output.splitlines())
if not out:

View File

@@ -70,73 +70,43 @@ rc:
sample: 1
"""
from ansible.module_utils.basic import AnsibleModule, env_fallback
import copy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
get_helm_plugin_list,
parse_helm_plugin_list,
get_helm_binary,
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
HELM_AUTH_MUTUALLY_EXCLUSIVE,
)
def main():
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type="path"),
argument_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
argument_spec.update(
dict(
plugin_name=dict(
type="str",
),
# 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"),
],
)
)
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
supports_check_mode=True,
)
bin_path = module.params.get("binary_path")
if bin_path is not None:
helm_cmd_common = bin_path
else:
helm_cmd_common = "helm"
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
helm_cmd_common += " plugin"
helm_bin = get_helm_binary(module)
plugin_name = module.params.get("plugin_name")
plugin_list = []
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_bin)
out = parse_helm_plugin_list(module, output=output.splitlines())
@@ -155,7 +125,7 @@ def main():
module.exit_json(
changed=True,
command=helm_cmd_common + " list",
command=helm_bin + " plugin list",
stdout=output,
stderr=err,
rc=rc,

View File

@@ -97,6 +97,21 @@ options:
type: path
aliases: [ ssl_ca_cert ]
version_added: "2.3.0"
context:
description:
- Helm option to specify which kubeconfig context to use.
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_CONTEXT) will be used instead.
type: str
aliases: [ kube_context ]
version_added: "2.4.0"
kubeconfig:
description:
- Helm option to specify kubeconfig path to use.
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
- The configuration can be provided as dictionary.
type: raw
aliases: [ kubeconfig_path ]
version_added: "2.4.0"
"""
EXAMPLES = r"""
@@ -145,6 +160,7 @@ msg:
"""
import traceback
import copy
try:
import yaml
@@ -154,8 +170,15 @@ except ImportError:
IMP_YAML_ERR = traceback.format_exc()
IMP_YAML = False
from ansible.module_utils.basic import AnsibleModule, env_fallback, missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
run_helm,
get_helm_binary,
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
HELM_AUTH_MUTUALLY_EXCLUSIVE,
)
# Get repository from all repositories added
@@ -215,12 +238,10 @@ def delete_repository(command, repository_name):
return remove_command
def main():
global module
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type="path"),
def argument_spec():
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
arg_spec.update(
dict(
repo_name=dict(type="str", aliases=["name"], required=True),
repo_url=dict(type="str", aliases=["url"]),
repo_username=dict(type="str", aliases=["username"]),
@@ -229,25 +250,19 @@ def main():
default="present", choices=["present", "absent"], aliases=["state"]
),
pass_credentials=dict(type="bool", default=False, no_log=True),
# 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"])
),
),
)
)
return arg_spec
def main():
global module
module = AnsibleModule(
argument_spec=argument_spec(),
required_together=[["repo_username", "repo_password"]],
required_if=[("repo_state", "present", ["repo_url"])],
mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
supports_check_mode=True,
)
@@ -256,7 +271,6 @@ def main():
changed = False
bin_path = module.params.get("binary_path")
repo_name = module.params.get("repo_name")
repo_url = module.params.get("repo_url")
repo_username = module.params.get("repo_username")
@@ -264,10 +278,7 @@ def main():
repo_state = module.params.get("repo_state")
pass_credentials = module.params.get("pass_credentials")
if bin_path is not None:
helm_cmd = bin_path
else:
helm_cmd = module.get_bin_path("helm", required=True)
helm_cmd = get_helm_binary(module)
repository_status = get_repository_status(module, helm_cmd, repo_name)

View File

@@ -181,7 +181,10 @@ except ImportError:
IMP_YAML = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
run_helm,
get_helm_binary,
)
def template(
@@ -266,7 +269,6 @@ def main():
)
check_mode = module.check_mode
bin_path = module.params.get("binary_path")
chart_ref = module.params.get("chart_ref")
chart_repo_url = module.params.get("chart_repo_url")
chart_version = module.params.get("chart_version")
@@ -284,7 +286,7 @@ def main():
if not IMP_YAML:
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
helm_cmd = bin_path or module.get_bin_path("helm", required=True)
helm_cmd = get_helm_binary(module)
if update_repo_cache:
update_cmd = helm_cmd + " repo update"

View File

@@ -26,3 +26,4 @@ test_namespace:
- "helm-local-path-002"
- "helm-local-path-003"
- "helm-dep"
- "helm-kubeconfig"

View File

@@ -43,6 +43,9 @@
- name: Test Skip CRDS feature in helm chart install
include_tasks: test_crds.yml
- name: Test in-memory kubeconfig
include_tasks: tests_in_memory_kubeconfig.yml
- name: Clean helm install
file:
path: "{{ item }}"

View File

@@ -0,0 +1,164 @@
---
- set_fact:
custom_kubeconfig_path: "~/.kube/customconfig"
default_kubeconfig_path: "~/.kube/config"
helm_in_mem_kubeconf_ns: "{{ test_namespace[11] }}"
- block:
- name: Copy default kubeconfig
copy:
remote_src: true
src: "{{ default_kubeconfig_path }}"
dest: "{{ custom_kubeconfig_path }}"
- name: Delete default kubeconfig
file:
path: "{{ default_kubeconfig_path }}"
state: absent
- set_fact:
custom_kubeconfig: "{{ lookup('file', custom_kubeconfig_path) | from_yaml }}"
no_log: true
# helm_plugin and helm_plugin_info
- name: Install subenv plugin
helm_plugin:
binary_path: "{{ helm_binary }}"
kubeconfig: "{{ custom_kubeconfig }}"
state: present
plugin_path: https://github.com/hydeenoble/helm-subenv
register: plugin
- assert:
that:
- plugin is changed
- name: Gather info about all plugin
helm_plugin_info:
binary_path: "{{ helm_binary }}"
kubeconfig: "{{ custom_kubeconfig }}"
register: plugin_info
- assert:
that:
- '"plugin_list" in plugin_info'
- plugin_info.plugin_list != []
# helm_repository, helm, helm_info
- name: Add test_bitnami chart repository
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_bitnami
kubeconfig: "{{ custom_kubeconfig }}"
repo_url: https://charts.bitnami.com/bitnami
register: repository
- name: Assert that repository was added
assert:
that:
- repository is changed
- name: Install chart from repository added before
helm:
binary_path: "{{ helm_binary }}"
name: rabbitmq
chart_ref: test_bitnami/rabbitmq
namespace: "{{ helm_in_mem_kubeconf_ns }}"
update_repo_cache: true
kubeconfig: "{{ custom_kubeconfig }}"
create_namespace: true
register: deploy
- name: Assert chart was successfully deployed
assert:
that:
- deploy is changed
- name: Get chart content
helm_info:
binary_path: "{{ helm_binary }}"
kubeconfig: "{{ custom_kubeconfig }}"
name: "rabbitmq"
namespace: "{{ helm_in_mem_kubeconf_ns }}"
register: chart_info
- name: Assert chart was successfully deployed
assert:
that:
- '"status" in chart_info'
- chart_info.status.status is defined
- chart_info.status.status == "deployed"
- name: Remove chart
helm:
binary_path: "{{ helm_binary }}"
name: rabbitmq
namespace: "{{ helm_in_mem_kubeconf_ns }}"
kubeconfig: "{{ custom_kubeconfig }}"
state: absent
register: remove_chart
- name: Assert chart was successfully removed
assert:
that:
- remove_chart is changed
- name: Get chart content
helm_info:
binary_path: "{{ helm_binary }}"
kubeconfig: "{{ custom_kubeconfig }}"
name: "rabbitmq"
namespace: "{{ helm_in_mem_kubeconf_ns }}"
register: chart_info
- name: Assert chart was successfully deployed
assert:
that:
- '"status" not in chart_info'
- name: Remove chart repository
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_bitnami
kubeconfig: "{{ custom_kubeconfig }}"
state: absent
register: remove
- name: Assert that repository was removed
assert:
that:
- remove is changed
always:
- name: Return kubeconfig
copy:
remote_src: true
src: "{{ custom_kubeconfig_path }}"
dest: "{{ default_kubeconfig_path }}"
ignore_errors: true
- name: Delete custom config
file:
path: "{{ custom_kubeconfig_path }}"
state: absent
ignore_errors: true
- name: Remove subenv plugin
helm_plugin:
binary_path: "{{ helm_binary }}"
plugin_name: subenv
state: absent
ignore_errors: true
- name: Delete namespace
k8s:
kind: Namespace
name: "{{ helm_in_mem_kubeconf_ns }}"
ignore_errors: true
- name: Delete helm repository
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_bitnami
state: absent
ignore_errors: true

View File

@@ -7,8 +7,9 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import yaml
import tempfile
import json
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
@@ -31,11 +32,22 @@ class MockedModule:
}
self.r = {}
self.files_to_delete = []
def run_command(self, command, environ_update=None):
self.r = {"command": command, "environ_update": environ_update}
return 0, "", ""
def add_cleanup_file(self, file_path):
self.files_to_delete.append(file_path)
def do_cleanup_files(self):
for file in self.files_to_delete:
try:
os.remove(file)
except Exception:
pass
def test_write_temp_kubeconfig_server_only():
file_name = write_temp_kubeconfig("ff")
@@ -97,4 +109,60 @@ def test_run_helm_with_params():
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"])
assert os.path.exists(module.r["environ_update"]["KUBECONFIG"])
module.do_cleanup_files()
def test_run_helm_with_kubeconfig():
custom_config = {
"apiVersion": "v1",
"clusters": [
{
"cluster": {
"certificate-authority-data": "LS0tLS1CRUdJTiBDRV",
"server": "https://api.cluster.testing:6443",
},
"name": "api-cluster-testing:6443",
}
],
"contexts": [
{
"context": {
"cluster": "api-cluster-testing:6443",
"namespace": "default",
"user": "kubeadmin",
},
"name": "context-1",
}
],
"current-context": "context-1",
"kind": "Config",
"users": [
{
"name": "developer",
"user": {"token": "sha256~jbIvVieBC_8W6Pb-iH5vqC_BvvPHIxQMxUPLDnYvHYM"},
}
],
}
# kubeconfig defined as path
_fd, tmpfile_name = tempfile.mkstemp()
with os.fdopen(_fd, "w") as fp:
yaml.dump(custom_config, fp)
k1_module = MockedModule()
k1_module.params = {"kubeconfig": tmpfile_name}
run_helm(k1_module, "helm foo")
assert k1_module.r["environ_update"] == {"KUBECONFIG": tmpfile_name}
os.remove(tmpfile_name)
# kubeconfig defined as string
k2_module = MockedModule()
k2_module.params = {"kubeconfig": custom_config}
run_helm(k2_module, "helm foo")
assert os.path.exists(k2_module.r["environ_update"]["KUBECONFIG"])
with open(k2_module.r["environ_update"]["KUBECONFIG"]) as f:
assert json.loads(f.read()) == custom_config
k2_module.do_cleanup_files()