from __future__ import print_function
from collections import namedtuple
from os import path
from time import sleep
from blessings import Terminal
from nephos.helpers.k8s import pod_check, secret_read
from nephos.helpers.misc import execute
TERM = Terminal()
HelmPreserve = namedtuple("HelmPreserve", ("secret_name", "data_item", "values_path"))
# noinspection PyArgumentList
HelmSet = namedtuple("HelmSet", ("key", "value", "set_string"), defaults=(False,))
CURRENT_DIR = path.abspath(path.split(__file__)[0])
# TODO: We should be able to get the namespace from the Helm release...
[docs]def helm_check(app, release, namespace, pod_num=None):
"""Check if a Helm release exists and is functional.
Args:
app (str): Helm application name.
release (str): Release name on K8S.
namespace (str): Namespace where Helm deployment is located.
pod_num (int): Number of pods expected to exist in the release.
"""
identifier = '-l "app={app},release={name}"'.format(
app=app, name=release
)
pod_check(namespace, identifier, pod_num=pod_num)
[docs]def helm_init():
"""Initialise Helm on cluster, using RBAC."""
res, _ = execute("helm list")
if res is not None:
print(TERM.green("Helm is already installed!"))
else:
execute("kubectl create -f {}/../extras/helm-rbac.yaml".format(CURRENT_DIR))
execute("helm init --service-account tiller")
# Fix issue with automountServiceToken
res, _ = execute(
"kubectl -n kube-system get deployment tiller-deploy "
+ '-o jsonpath="{.spec.template.spec.automountServiceAccountToken}"'
)
if res == "false":
execute(
"kubectl -n kube-system patch deployment tiller-deploy "
+ '-p \'{"spec": {"template": {"spec": {"automountServiceAccountToken": true}}}}\''
)
# We keep checking the state of helm until everything is running
running = False
while not running:
res, _ = execute("helm list")
if res is not None:
running = True
else:
print(TERM.red("."), end="", flush=True)
sleep(15)
[docs]def helm_env_vars(env_vars):
"""Convert environmental variables to a "--set" string for Helm deployments.
Args:
env_vars (tuple): Environmental variables we wish to store in Helm.
Returns:
str: String containing variables to be set with Helm release.
"""
if not env_vars:
env_vars = []
else:
env_vars = list(env_vars)
for i, item in enumerate(env_vars):
if isinstance(item, tuple):
item = HelmSet(*item)
elif not isinstance(item, HelmSet):
raise TypeError("Items in env_vars array must be HelmSet named tuples")
env_vars[i] = item
# Environmental variables
# TODO: This may well be its own subfunction
env_vars_string = "".join(
[
" --set{} {}={}".format(
"-string" if item.set_string else "", item.key, item.value
)
for item in env_vars
]
)
return env_vars_string
[docs]def helm_preserve(namespace, preserve, verbose=False):
"""Convert secret data to a "--set" string for Helm deployments.
Args:
namespace (str): Namespace where preserved secrets are located.
preserve (tuple): Set of secrets we wish to get data from to assign to the Helm Chart.
verbose (bool): Verbosity. False by default.
Returns:
str: String containing variables to be set with Helm release.
"""
# Any data we need to preserve during upgrade?
if not preserve:
return ""
env_vars = []
for item in preserve:
if isinstance(item, tuple):
item = HelmPreserve(*item)
elif not isinstance(item, HelmPreserve):
raise TypeError("Items in preserve array must be HelmPerserve named tuples")
secret_data = secret_read(item.secret_name, namespace, verbose=verbose)
env_vars.append(HelmSet(item.values_path, secret_data[item.data_item]))
# Environmental variables
# TODO: This may well be its own subfunction
env_vars_string = "".join(
[
" --set{} {}={}".format(
"-string" if item.set_string else "", item.key, item.value
)
for item in env_vars
]
)
return env_vars_string
# TODO: Too many parameters - SQ Code Smell
# TODO: Cleanest way of fixing parameter issues is via a Helm class
# TODO: We should ideally auto-detect number of pods
[docs]def helm_install(
repo,
app,
release,
namespace,
config_yaml=None,
env_vars=None,
verbose=False,
pod_num=1,
):
"""Install Helm chart.
Args:
repo (str): Repository or folder from which to install Helm chart.
app (str): Helm application name.
release (str): Release name on K8S.
namespace (str): Namespace where to deploy Helm Chart.
config_yaml (str): Values file to ovverride defaults.
env_vars (tuple): List of env vars we want to set.
verbose (bool): Verbosity. False by default.
pod_num (int): Number of pods we wish to have.
"""
ls_res, _ = execute("helm status {release}".format(release=release))
# Get Helm Env-Vars
env_vars_string = helm_env_vars(env_vars)
if not ls_res:
command = "helm install {repo}/{app} -n {name} --namespace {ns}".format(
app=app, name=release, ns=namespace, repo=repo
)
if config_yaml:
command += " -f {}".format(config_yaml)
command += env_vars_string
# Execute
execute(command, verbose=verbose)
helm_check(app, release, namespace, pod_num)
# TODO: Too many parameters - SQ Code Smell
# TODO: We should ideally auto-detect number of pods
[docs]def helm_upgrade(
repo,
app,
release,
namespace,
config_yaml=None,
env_vars=None,
preserve=None,
verbose=False,
pod_num=1,
):
"""Upgrade Helm chart.
Args:
repo (str): Repository or folder from which to install Helm chart.
app (str): Helm application name.
release (str): Release name on K8S.
namespace (str): Namespace where to deploy Helm Chart.
config_yaml (str): Values file to ovverride defaults.
env_vars (tuple): Environmental variables we wish to store in Helm.
preserve (tuple): Set of secrets we wish to get data from to assign to the Helm Chart.
verbose (bool): Verbosity. False by default.
pod_num (int): Number of pods we wish to have.
"""
ls_res, _ = execute("helm status {release}".format(release=release))
# Get Helm Env-Vars
env_vars_string = helm_env_vars(env_vars)
env_vars_string += helm_preserve(namespace, preserve, verbose=verbose)
if ls_res:
command = "helm upgrade {name} {repo}/{app}".format(
app=app, name=release, repo=repo
)
if config_yaml:
command += " -f {}".format(config_yaml)
command += env_vars_string
# Execute
execute(command, verbose=verbose)
else:
raise Exception("Cannot update a Helm release that is not running")
helm_check(app, release, namespace, pod_num)