Skip to content

Instantly share code, notes, and snippets.

@darki73
Last active December 12, 2023 15:39
Show Gist options
  • Save darki73/d578b8bf6fc9e11760820ac2d27e0db0 to your computer and use it in GitHub Desktop.
Save darki73/d578b8bf6fc9e11760820ac2d27e0db0 to your computer and use it in GitHub Desktop.
Kubernetes Helm Chart Manager

This script will help you to manage your Helm chart deployments.
The main idea is to provide a single command to install / upgrade the Helm chart.

There are 2 types of variables:

  • General - these are the variables that you MUST to edit in order to make this script work
  • Advanced - these are the variables that could be left as is

The General variables include the following:

  • GITHUB_OWNER - the name of the owner of the repository (OWNER/REPOSITORY)
  • GITHUB_REPOSITORY - the name of the repository itself (OWNER/REPOSITORY)
  • HELM_REPOSITORY_URL - the URL for the Helm charts repository
  • HELM_REPOSITORY_NAME - the name under which this repository will be linked locally
  • HELM_RELEASE_NAME - the release name of the Helm chart
  • KUBERNETES_NAMESPACE - namespace in Kubernetes to which this chart will be installed
  • KUBERNETES_APPLICATION_NAME - the name of the deployment inside Kubernetes
  • ADDITIONAL_MANIFESTS - list of additional manifests to be applied while installing / upgrading the chart.

Here is the example for the Cert Manager:

####################################
######## General Variables #########
####################################

# The name of the owner of the github repository
GITHUB_OWNER="cert-manager"
# The name of the github repository
GITHUB_REPOSITORY="cert-manager"
# Helm charts repository URL
HELM_REPOSITORY_URL="https://charts.jetstack.io"
# The name of the Helm repository to use to add charts repository
HELM_REPOSITORY_NAME="jetstack"
# The name of the Helm release
HELM_RELEASE_NAME="cert-manager"
# Namespace we are going to use in kubernetes for deployment
KUBERNETES_NAMESPACE="cert-manager"
# The name of the application under which deployment will be created
KUBERNETES_APPLICATION_NAME="cert-manager"
# List of YAML files to be applied with kubectl
# Add your file URLs or local paths to this list
# For versions, substitute the version with the {version} placeholder
ADDITIONAL_MANIFESTS=(
    "https://github.com/cert-manager/cert-manager/releases/download/{version}/cert-manager.crds.yaml"
)

And here is another example, this time for Longhorn:

####################################
######## General Variables #########
####################################

# The name of the owner of the github repository
GITHUB_OWNER="longhorn"
# The name of the github repository
GITHUB_REPOSITORY="longhorn"
# Helm charts repository URL
HELM_REPOSITORY_URL="https://charts.longhorn.io"
# The name of the Helm repository to use to add charts repository
HELM_REPOSITORY_NAME="longhorn"
# The name of the Helm release
HELM_RELEASE_NAME="longhorn"
# Namespace we are going to use in kubernetes for deployment
KUBERNETES_NAMESPACE="longhorn-system"
# The name of the application under which deployment will be created
KUBERNETES_APPLICATION_NAME="longhorn"
# List of YAML files to be applied with kubectl
# Add your file URLs or local paths to this list
# For versions, substitute the version with the {version} placeholder
ADDITIONAL_MANIFESTS=()

You simply need to run ./manage.sh and it will either install or upgrade the installed chart.
This script also provides basic sanity check - it will only update the patch versions of the chart (MAJOR.MINOR.PATCH), and will throw an error if you are trying to jump from one MINOR to another (same for major).

Script will also automatically pickup the values.yaml file if there is one near it, you can also change the name of the values in the VALUES_FILE_NAME variable. It is important to note that script expects this file to be at the same level as the script itself.

#!/usr/bin/env bash
####################################
######## General Variables #########
####################################
# The name of the owner of the github repository
GITHUB_OWNER=""
# The name of the github repository
GITHUB_REPOSITORY=""
# Helm charts repository URL
HELM_REPOSITORY_URL=""
# The name of the Helm repository to use to add charts repository
HELM_REPOSITORY_NAME=""
# The name of the Helm release
HELM_RELEASE_NAME=""
# Namespace we are going to use in kubernetes for deployment
KUBERNETES_NAMESPACE=""
# The name of the application under which deployment will be created
KUBERNETES_APPLICATION_NAME=""
# List of YAML files to be applied with kubectl
# Add your file URLs or local paths to this list
# For versions, substitute the version with the {version} placeholder
ADDITIONAL_MANIFESTS=()
####################################
######## Advanced Variables ########
####################################
# The script source directory (leave it as is)
SCRIPT_DIRECTORY=$(dirname "$BASH_SOURCE")
# The expected Helm version to be installed
HELM_EXPECTED_VERSION="3"
# The Kubernetes context we want to use
KUBERNETES_CONTEXT="default"
# The name of the values file to also be used during the install/upgrade process
VALUES_FILE_NAME="values.yaml"
# Function to fetch the latest release from a GitHub repository
fetch_latest_release() {
local owner_name="$1"
local owner_repository="$2"
local url="https://api.github.com/repos/${owner_name}/${owner_repository}/releases/latest"
local data
data=$(curl -s --fail "$url")
if [[ $? -ne 0 ]]; then
echo "Error fetching release data." >&2
return 1
fi
local tag_name
tag_name=$(echo "$data" | jq -r '.tag_name')
if [[ $tag_name == v* ]]; then
echo "$tag_name"
else
echo ""
fi
}
# Function to check if there is a valid release
has_valid_release() {
local release_tag="$1"
if [[ -z "$release_tag" ]]; then
echo "No valid release found. Exiting."
exit 1
fi
}
# Function to get the installed Helm chart version
get_installed_release() {
local namespace="$1"
local release_name="$2"
# Try to get the app_version
local version=$(helm list -n "$namespace" --filter "$release_name" -o json | jq -r '.[0].app_version')
# If app_version is empty or not found, fall back to extracting from chart
if [[ -z "$version" || "$version" == "null" ]]; then
version=$(helm list -n "$namespace" --filter "$release_name" -o json | jq -r '.[0].chart' | grep -oE "v?[0-9]+\.[0-9]+\.[0-9]+")
fi
version=${version#v}
echo "$version"
}
# Function to check whether Helm chart is actually installed
is_chart_installed() {
local namespace="$1"
local release_name="$2"
if helm list -n "$namespace" --filter "$release_name" | grep -q "$release_name"; then
return 0 # Chart is installed
else
return 1 # Chart is not installed
fi
}
# Function to compare installed version with the latest release version
compare_versions() {
local installed_version="$1"
local latest_version="${2#v}"
if [[ "$installed_version" == "$latest_version" ]]; then
echo "Installed version ($installed_version) is the latest. Exiting."
exit 0
else
echo "Installed version ($installed_version) differs from the latest ($latest_version)."
fi
}
# Function to compare versions and check for patch update
is_patch_update() {
local installed_version="$1"
local latest_version="$2"
installed_version=${installed_version#v}
latest_version=${latest_version#v}
IFS='.' read -ra INSTALLED_VERSION_PARTS <<< "$installed_version"
IFS='.' read -ra LATEST_VERSION_PARTS <<< "$latest_version"
if [[ ${INSTALLED_VERSION_PARTS[0]} -eq ${LATEST_VERSION_PARTS[0]} ]] && [[ ${INSTALLED_VERSION_PARTS[1]} -eq ${LATEST_VERSION_PARTS[1]} ]]; then
if [[ ${INSTALLED_VERSION_PARTS[2]} -eq ${LATEST_VERSION_PARTS[2]} ]]; then
echo "No update required. Exiting."
exit 0
fi
else
echo "Update is not a patch update. Potentially breaking changes. Exiting."
exit 1
fi
}
# Function to check and add Helm repository if it doesn't exist
check_and_add_helm_repository() {
local repository_url="$1"
local repository_name="$2"
# Check if the Helm repository exists
if ! helm repo list | grep -q "$repository_name"; then
echo "Adding missing repository: $repository_name ($repository_url)."
helm repo add "$repository_name" "$repository_url" > /dev/null 2>&1
fi
}
# Function to update Helm repositories
update_helm_repository() {
helm repo update > /dev/null 2>&1
}
# Function to ensure a Kubernetes namespace exists
ensure_namespace_exists() {
local namespace="$1"
if ! kubectl get namespace "$namespace" > /dev/null 2>&1; then
echo "Creating namespace '$namespace'."
kubectl create namespace "$namespace" > /dev/null 2>&1
fi
}
# Function to apply YAML manifests
apply_additional_manifests() {
for file in "${ADDITIONAL_MANIFESTS[@]}"; do
echo "Applying $file"
kubectl apply -f "$file"
done
}
# Ensure we are in a valid Kubernetes context
ensure_kubernetes_context() {
local context="$1"
current_context=$(kubectl config current-context)
if [[ "$current_context" != "$context" ]]; then
echo "Not in the expected Kubernetes context."
echo "Expected to be in '${context}', but currently in '${current_context}'"
echo "Exiting."
exit 1
fi
}
# Ensure that we have the correct Helm version installed
ensure_helm_version() {
local required_version="$1"
helm_version=$(helm version --short | cut -d '.' -f 1 | tr -dc '0-9')
if [[ $helm_version -lt $required_version ]]; then
echo "Requires Helm version $required_version or newer. Exiting."
exit 1
fi
}
# Function to perform the Helm upgrade
upgrade() {
local release_name="$1"
local chart="$2"
local namespace="$3"
local version="$4"
local values_file="$SCRIPT_DIRECTORY/$VALUES_FILE_NAME"
if [ -f "$values_file" ]; then
has_values_file=true
else
has_values_file=false
fi
local upgrade_cmd="helm upgrade --version $version $release_name $chart --namespace $namespace"
if [[ "$has_values_file" == "true" ]]; then
upgrade_cmd+=" --values=$values_file"
fi
$upgrade_cmd
}
# Function to perform a Helm install
install() {
local release_name="$1"
local chart="$2"
local namespace="$3"
local version="$4"
local values_file="$SCRIPT_DIRECTORY/$VALUES_FILE_NAME"
if [ -f "$values_file" ]; then
has_values_file=true
else
has_values_file=false
fi
local install_cmd="helm install $release_name $chart --namespace $namespace --version $version"
if [[ "$has_values_file" == "true" ]]; then
install_cmd+=" --values=$values_file"
fi
$install_cmd
}
# Get the latest release version from Github
latest_version=$(fetch_latest_release "$GITHUB_OWNER" "$GITHUB_REPOSITORY") || exit 1
# Loop through additional manifests and replace the version placeholder with actual vesion
for i in "${!ADDITIONAL_MANIFESTS[@]}"; do
ADDITIONAL_MANIFESTS[$i]=${ADDITIONAL_MANIFESTS[$i]//\{version\}/$latest_version}
done
# Check that we were able to successfully retrieve the latest release tag
has_valid_release "$latest_version"
# Check that we are working in a valid Kubernetes context
ensure_kubernetes_context "$KUBERNETES_CONTEXT"
# Check that we have required version of Helm installed
ensure_helm_version "$HELM_EXPECTED_VERSION"
# Get the installed chart version
installed_version=$(get_installed_release "$KUBERNETES_NAMESPACE" "$KUBERNETES_APPLICATION_NAME")
# Check if helm repository exists and add if not
check_and_add_helm_repository "$HELM_REPOSITORY_URL" "$HELM_REPOSITORY_NAME"
# Update helm repositories
update_helm_repository
# Check that Helm chart is actually installed
if is_chart_installed "$KUBERNETES_NAMESPACE" "$KUBERNETES_APPLICATION_NAME"; then
# Compare the installed chart version and latest version from Github
compare_versions "$installed_version" "$latest_version"
# Ensure that update is for the patch version, otherwise prompt user to check for breaking changes
is_patch_update "$installed_version" "$latest_version"
# Call the function to apply the YAML manifests
apply_additional_manifests
# Actually perform the upgrade of the Helm chart
upgrade "$HELM_RELEASE_NAME" "$HELM_REPOSITORY_NAME/$GITHUB_REPOSITORY" "$KUBERNETES_NAMESPACE" "$latest_version"
else
# Ensure that kubernetes namespace exists
ensure_namespace_exists "$KUBERNETES_NAMESPACE"
# Call the function to apply the YAML manifests
apply_additional_manifests
# Actually perform the installation of the Helm chart
install "$HELM_RELEASE_NAME" "$HELM_REPOSITORY_NAME/$GITHUB_REPOSITORY" "$KUBERNETES_NAMESPACE" "$latest_version"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment