blob: 3d365957f5873bfd82dacdb874c83bf4bf1ddc4c [file] [log] [blame]
#!/usr/bin/env bash
# Copyright 2018-2024 Open Networking Foundation (ONF) and the ONF Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# helmrepo.sh
# creates or updates a helm repo for publishing on the guide website
# Reference: https://github.com/helm/charts/blob/master/test/repo-sync.sh
set -eu -o pipefail
##-------------------##
##---] GLOBALS [---##
##-------------------##
# when not running under Jenkins, use current dir as workspace
WORKSPACE=${WORKSPACE:-.}
# directory to compare against, doesn't need to be present
OLD_REPO_DIR="${OLD_REPO_DIR:-cord-charts-repo}"
NEW_REPO_DIR="${NEW_REPO_DIR:-chart_repo}"
PUBLISH_URL="${PUBLISH_URL:-charts.opencord.org}"
## -----------------------------------------------------------------------
## Intent: Dispay called function with given output
## -----------------------------------------------------------------------
function func_echo()
{
echo "** ${FUNCNAME[1]}: $*"
return
}
## -----------------------------------------------------------------------
## Intent: Display given text and exit with shell error status.
## -----------------------------------------------------------------------
function error()
{
echo -e "** ${BASH_SOURCE[0]##*/}::${FUNCNAME[1]} ERROR: $*"
exit 1
}
## -----------------------------------------------------------------------
## Intent: Detect pre-existing versioned packages.
## -----------------------------------------------------------------------
function check_packages()
{
local dir="$1"; shift
readarray -t package_paths < <(find "${dir}" -name '*.tgz' -print)
declare -p package_paths
# ---------------------------------------------
# Check for versioned package collision.
# ---------------------------------------------
for package_path in "${package_paths[@]}";
do
package="${package_path##*/}" # basename
if [ -f "${OLD_REPO_DIR}/${package}" ]; then
echo
echo "PACKAGE: $package"
/bin/ls -l "$package_path"
/bin/ls -l "${OLD_REPO_DIR}/${package}"
error "Package: ${package} with same version already exists in ${OLD_REPO_DIR}"
fi
done
return
}
## -----------------------------------------------------------------------
## Intent: Gather a list of Chart.yaml files from the filesystem.
## -----------------------------------------------------------------------
function get_chart_yaml()
{
local dir="$1" ; shift
declare -n ref=$1 ; shift
readarray -t _charts < <(find "$dir" -name Chart.yaml -print | sort)
ref=("${_charts[@]}")
return
}
## -----------------------------------------------------------------------
## Intent: Given a helm chart line extract and return *version.
## -----------------------------------------------------------------------
function getVersion()
{
# shellcheck disable=SC2178
local -n ref=$1; shift # declare -A
local line="$1"; shift
[[ -v debug ]] && func_echo "LINE: $line"
# foo=${string#"$prefix"}
line="${line%\#*}" # Snip comments
line="${line//[[:blank:]]}" # Prune whitespace
# version : x.y.z
readarray -d':' -t _fields < <(printf '%s' "$line")
local key="${_fields[0]}"
local val="${_fields[1]}"
# shellcheck disable=SC2004
ref[$key]="$val"
return
}
## -----------------------------------------------------------------------
## Intent: Update helm package dependencies
## -----------------------------------------------------------------------
function helm_deps_update()
{
local dest="$1"; shift # helm --destination
if [[ -v dry_run ]]; then
func_echo "helm package --dependency-update --destination $dest $chartdir"
else
helm package --dependency-update --destination "$dest" "$chartdir"
fi
return
}
## -----------------------------------------------------------------------
## Intent: Update helm package index
## -----------------------------------------------------------------------
function helm_index_publish()
{
local repo_dir="$1"; shift # helm --destination
if [[ -v dry_run ]]; then
func_echo "helm repo index $repo_dir --url https://${PUBLISH_URL}"
elif [[ -v no_publish ]]; then
func_echo "[SKIP] helm publishing due to --no-publish"
else
## ------------------------------------------------
## Helm updates are guarded by jenkins
## Revision control should reinforce that assertion
## ------------------------------------------------
case "$USER" in
jenkins)
helm repo index "$repo_dir" --url https://"${PUBLISH_URL}"
;;
*)
func_echo "[SKIP] helm publishing due to ($USER != jenkins)"
;;
esac
fi
return
}
## -----------------------------------------------------------------------
## Intent: Update helm package index
## -----------------------------------------------------------------------
function helm_index_merge()
{
local old_repo="$1" ; shift
local new_repo="$1" ; shift
declare -a cmd=()
cmd+=('helm' 'repo' 'index')
cmd+=('--url' "https://${PUBLISH_URL}")
cmd+=('--merge' "${old_repo}/index.yaml" "$new_repo")
if [[ -v dry_run ]]; then
func_echo "${cmd[@]}"
else
"${cmd[@]}"
fi
return
}
## -----------------------------------------------------------------------
## Intent: Given a Chart.yaml file path return test directory where stored
## -----------------------------------------------------------------------
function chart_path_to_test_dir()
{
local val="$1" ; shift
# shellcheck disable=SC2178
declare -n ref=$1 ; shift # indirect var
val="${val%/Chart.yaml}" # dirname: prune /Chart.yaml
val="${val##*/}" # basename: test directory
# shellcheck disable=SC2034,SC2178
ref="$val" # Return value to caller
return
}
## -----------------------------------------------------------------------
## Intent: Given Chart.yaml files create a new indexed chart repository
## -----------------------------------------------------------------------
function create_helm_repo_new()
{
local repo_dir="$1"; shift # NEW_REPO_DIR
local work_dir="$1"; shift # WORKSPACE
echo "Creating new helm repo: ${repo_dir}"
declare -a charts=()
get_chart_yaml "$work_dir" charts
local chart
for chart in "${charts[@]}";
do
echo
func_echo "Chart.yaml: $chart"
chartdir=''
chart_path_to_test_dir "$chart" chartdir
func_echo " Chart.dir: $chartdir"
helm_deps_update "${repo_dir}"
done
helm_index_publish "${repo_dir}"
return
}
## -----------------------------------------------------------------------
## Intent: Compare version stings extracted from Chart.yaml delta.
## o attribute version:x.y.z must be changed to enable change
## detection and chart loading.
## -----------------------------------------------------------------------
function validate_changes()
{
local chart="$1"; shift
# shellcheck disable=SC2178
local -n ref=$1; shift
local msg
## -------------------------------------------
## Validation logic: all keys collected exist
## Chart version must change to enable loading
## -------------------------------------------
local key0
for key0 in "${!ref[@]}";
do
local key="${key0:1}"
# shellcheck disable=SC2034
local old="-${key}"
local new="+${key}"
## Key/val paris are diff deltas:
## -version : 1.2.3
## +version : 4.5.6
if [[ ! -v ref['-version'] ]]; then
msg='Modify version to publish chart changes'
elif [[ ! -v ref["$new"] ]]; then
msg="Failed to detect +${key} change in attributes"
else
continue
fi
local -i failed=1
cat <<ERR
** -----------------------------------------------------------------------
** Chart dir: $chartdir
** Chart.yml: $chart
** Error: $msg
** -----------------------------------------------------------------------
ERR
func_echo "$(declare -p versions | sed -e 's/\[/\n\[/g')"
done
if [[ -v failed ]]; then
false
else
true
fi
return
}
##----------------##
##---] MAIN [---##
##----------------##
while [ $# -gt 0 ]; do
arg="$1"; shift
case "$arg" in
-*debug) declare -g -i debug=1 ;;
-*dry*) declare -g -i dry_run=1 ;;
-*no-publish) declare -g -i no_publish=1 ;;
-*help)
cat <<EOH
Usage: $0
--debug Enable debug mode
--dry-run Simulate helm calls
EOH
;;
-*) echo "[SKIP] unknown switch [$arg]" ;;
*) echo "[SKIP] unknown argument [$arg]" ;;
esac
done
echo "# helmrepo.sh, using helm: $(helm version -c) #"
# create and clean NEW_REPO_DIR
mkdir -p "${NEW_REPO_DIR}"
rm -f "${NEW_REPO_DIR}"/*
# if OLD_REPO_DIR doesn't exist, generate packages and index in NEW_REPO_DIR
if [ ! -d "${OLD_REPO_DIR}" ]
then
create_helm_repo_new "$NEW_REPO_DIR" "$WORKSPACE"
echo
echo "# helmrepo.sh Success! Generated new repo index in ${NEW_REPO_DIR}"
else
# OLD_REPO_DIR exists, check for new charts and update only with changes
echo "Found existing helm repo: ${OLD_REPO_DIR}, attempting update"
# Loop and create chart packages, only if changed
declare -a charts=()
get_chart_yaml "$WORKSPACE" charts
for chart in "${charts[@]}";
do
echo
func_echo "Chart.yaml: $chart"
chartdir=''
chart_path_to_test_dir "$chart" chartdir
func_echo " Chart.dir: $chartdir"
# See if chart version changed from previous HEAD commit
readarray -t chart_yaml_diff < <(git diff -p HEAD^ -- "$chart")
if [[ ! -v chart_yaml_diff ]]; then
echo "Chart unchanged, not packaging: '${chartdir}'"
# -------------------------------------------------------------------
# Assumes that helmlint.sh and chart_version_check.sh have been run
# pre-merge, which ensures that all charts are valid and have their
# version updated in Chart.yaml
# -------------------------------------------------------------------
elif [ ${#chart_yaml_diff} -gt 0 ]; then
declare -A versions=()
for line in "${chart_yaml_diff[@]}";
do
[[ -v debug ]] && func_echo "$line"
case "$line" in
# appVersion: "1.0.3"
# version: 1.2.3
[-+]*[vV]ersion*:*) getVersion versions "$line" ;;
esac
done
# ---------------------------------------------------------------
# [TODO] -- versions['-version']='version string change required'
# ---------------------------------------------------------------
# version: string change initiates a delta forcing helm to update.
# Should it be required by every checkin ? For ex: release may
# accumulate several version edits then publish all when finished.
#
# Danger would be chart changes are not published/tested when
# a dev forgets to update the chart version string.
# ---------------------------------------------------------------
## ---------------------------------------------------------------
## Check for required version change and stray attribute deletions
## We are comparing diff output [-+]verison : x.y
## +{key} indicates a required attribute exists and was modified
## ---------------------------------------------------------------
if ! validate_changes "$chart" versions; then
declare -g -i failed=1
continue
fi
# Always query, version string may not have changed
readarray -t ver < <(grep -oP '(?<= version: )\S+' "$chart")
declare -p ver
echo "Detected new version of chart ${chartdir}, creating package: ${ver[*]}"
helm_deps_update "${NEW_REPO_DIR}"
else
echo "Chart unchanged, not packaging: '${chartdir}'"
fi
done
check_packages "$NEW_REPO_DIR"
## -----------------------------------------------------------------------
## -----------------------------------------------------------------------
# only update index when new charts are added
if [ ${#package_paths[@]} -gt 0 ]; then
# Create updated index.yaml (new version created in NEW_REPO_DIR)
helm_index_merge "${OLD_REPO_DIR}" "${NEW_REPO_DIR}"
# move over packages and index.yaml
mv "${NEW_REPO_DIR}"/*.tgz "${OLD_REPO_DIR}/"
mv "${NEW_REPO_DIR}/index.yaml" "${OLD_REPO_DIR}/index.yaml"
echo "# helmrepo.sh Success! Updated existing repo index in ${OLD_REPO_DIR}"
else
echo "# helmrepo.sh Success! No new charts added."
fi
fi
exit 0
# [EOF]