Merge pull request #703 from munnerz/chart-verify
Use kubernetes-helm/chart-testing scripts to verify helm chart
This commit is contained in:
commit
959aba219f
@ -1,100 +1,33 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2017 The Kubernetes Authors All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
semvercompareOldVer=""
|
||||
semvercompareNewVer=""
|
||||
readonly REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
readonly UPSTREAM_REPO="https://github.com/jetstack/cert-manager.git"
|
||||
|
||||
if [ -z "${PULL_BASE_SHA+a}" ]; then
|
||||
echo "PULL_BASE_SHA must be set"
|
||||
if [ -z "${PULL_BASE_REF:-}" ]; then
|
||||
echo "PULL_BASE_REF must be set to a target branch name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! git remote get-url jetstack; then
|
||||
git remote add jetstack https://github.com/jetstack/cert-manager
|
||||
if [ -z "${REMOTE:-}" ]; then
|
||||
echo "+++ REMOTE not set - defaulting to 'upstream'"
|
||||
export REMOTE="upstream"
|
||||
fi
|
||||
|
||||
git fetch jetstack "${PULL_BASE_SHA}:refs/remotes/jetstack/pull-base"
|
||||
if ! git remote get-url "${REMOTE}" > /dev/null 2>&1; then
|
||||
echo "+++ Remote '${REMOTE}' does not exist. Setting to ${UPSTREAM_REPO}"
|
||||
git remote add "${REMOTE}" "${UPSTREAM_REPO}"
|
||||
fi
|
||||
|
||||
SCRIPT_ROOT="$(dirname "${BASH_SOURCE}")/.."
|
||||
git fetch "${REMOTE}"
|
||||
|
||||
CHANGED_FOLDERS=`git diff --find-renames --name-only $(git merge-base jetstack/pull-base HEAD) "${SCRIPT_ROOT}/contrib/charts/" | awk -F/ '{print $1"/"$2"/"$3}' | uniq`
|
||||
|
||||
# Verify that the semver for the chart was increased
|
||||
semvercompare() {
|
||||
set +e
|
||||
printf "\nChecking the Chart version has increased for the chart at ${1}\n"
|
||||
|
||||
# Checkout the Chart.yaml file on master to read the version for comparison
|
||||
# Sending the output to a file and the error to /dev/null so that these
|
||||
# messages do not clutter up the end user output
|
||||
$(git show jetstack/pull-base:$1/Chart.yaml 1> /tmp/Chart.yaml 2> /dev/null)
|
||||
|
||||
## If the chart is new git cannot checkout the chart. In that case return
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Unable to find Chart on master. New chart detected."
|
||||
return
|
||||
fi
|
||||
|
||||
semvercompareOldVer=`yaml r /tmp/Chart.yaml version`
|
||||
semvercompareNewVer=`yaml r $1/Chart.yaml version`
|
||||
|
||||
# Pre-releases may not be API compatible. So, when tools compare versions
|
||||
# they often skip pre-releases. vert can force looking at pre-releases by
|
||||
# adding a dash on the end followed by pre-release. -0 on the end will force
|
||||
# looking for all valid pre-releases since a prerelease cannot start with a 0.
|
||||
# For example, 1.2.3-0 will include looking for pre-releases.
|
||||
local ret
|
||||
local out
|
||||
if [[ $semvercompareOldVer == *"-"* ]]; then # Found the - to denote it has a pre-release
|
||||
out=$(vert ">$semvercompareOldVer" $semvercompareNewVer)
|
||||
ret=$?
|
||||
else
|
||||
# No pre-release was found so we increment the patch version and attach a
|
||||
# -0 to enable pre-releases being found.
|
||||
local ov=( ${semvercompareOldVer//./ } ) # Turn the version into an array
|
||||
((ov[2]+=1)) # Increment the patch release
|
||||
out=$(vert ">${ov[0]}.${ov[1]}.${ov[2]}-0" $semvercompareNewVer)
|
||||
ret=$?
|
||||
fi
|
||||
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "Error please increment the new chart version to be greater than the existing version of $semvercompareOldVer"
|
||||
exitCode=1
|
||||
else
|
||||
echo "New higher version $semvercompareNewVer found"
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
rm /tmp/Chart.yaml
|
||||
}
|
||||
|
||||
exitCode=0
|
||||
|
||||
for directory in ${CHANGED_FOLDERS}; do
|
||||
if [ "${directory}" == "contrib/charts" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ ! -d "${directory}" ]; then
|
||||
echo "Directory ${directory} has been deleted. Skipping version check..."
|
||||
continue
|
||||
fi
|
||||
semvercompare "${directory}"
|
||||
done
|
||||
|
||||
exit "${exitCode}"
|
||||
docker run --rm -v "${REPO_ROOT}:/workdir" --workdir /workdir \
|
||||
-e REMOTE="${REMOTE}" \
|
||||
-e TARGET_BRANCH="${PULL_BASE_REF}" \
|
||||
gcr.io/kubernetes-charts-ci/chart-testing:v1.0.2 \
|
||||
/workdir/test/chart/chart_test.sh \
|
||||
--no-install \
|
||||
--config test/chart/.testenv
|
||||
|
||||
25
test/chart/.testenv
Normal file
25
test/chart/.testenv
Normal file
@ -0,0 +1,25 @@
|
||||
# The name of the Git remote
|
||||
# Omitted to ensure it is provided on the CLI
|
||||
# REMOTE=upstream
|
||||
|
||||
# The name of the Git target branch
|
||||
# Omitted to ensure it is provided on the CLI
|
||||
# TARGET_BRANCH=master
|
||||
|
||||
# Chart directories separated by a space
|
||||
CHART_DIRS=(
|
||||
contrib/charts
|
||||
)
|
||||
|
||||
# Charts that should be skipped
|
||||
EXCLUDED_CHARTS=(
|
||||
pebble
|
||||
vault
|
||||
)
|
||||
|
||||
# Additional chart repos to add (<name>=<url>), separated by a space
|
||||
CHART_REPOS=(
|
||||
incubator=https://kubernetes-charts-incubator.storage.googleapis.com/
|
||||
)
|
||||
|
||||
TIMEOUT=600
|
||||
165
test/chart/chart_test.sh
Executable file
165
test/chart/chart_test.sh
Executable file
@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
## This script has been taken from https://github.com/kubernetes-helm/chart-testing
|
||||
## It has the same dependencies as described in that repo, and should ideally be run
|
||||
## within the docker image published by that same repository in order to make sure
|
||||
## the correct dependencies are included.
|
||||
##
|
||||
## Run from within the root of the repository with:
|
||||
##
|
||||
## docker run --rm -v "$(pwd):/workdir" --workdir /workdir \
|
||||
## gcr.io/kubernetes-charts-ci/chart-testing:v1.0.2 \
|
||||
## /workdir/test/chart_test.sh \
|
||||
## --no-install \
|
||||
## --config test/.testenv
|
||||
|
||||
# Copyright 2018 The Helm Authors. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
readonly REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
readonly SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") <options>
|
||||
Lint, install, and test Helm charts.
|
||||
-h, --help Display help
|
||||
--verbose Display verbose output
|
||||
--no-lint Skip chart linting
|
||||
--no-install Skip chart installation
|
||||
--config Path to the config file (optional)
|
||||
-- End of all options
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
local no_lint=
|
||||
local no_install=
|
||||
local config=
|
||||
local verbose=
|
||||
|
||||
while :; do
|
||||
case "${1:-}" in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit
|
||||
;;
|
||||
--verbose)
|
||||
verbose=true
|
||||
;;
|
||||
--no-install)
|
||||
no_install=true
|
||||
;;
|
||||
--no-lint)
|
||||
no_lint=true
|
||||
;;
|
||||
--config)
|
||||
if [ -n "$2" ]; then
|
||||
config="$2"
|
||||
shift
|
||||
else
|
||||
echo "ERROR: '--config' cannot be empty." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
-?*)
|
||||
echo "WARN: Unknown option (ignored): $1" >&2
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ -n "$config" ]]; then
|
||||
if [[ -f "$config" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$config"
|
||||
else
|
||||
echo "ERROR: Specified config file does not exist: $config" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# shellcheck source=lib/chartlib.sh
|
||||
source "$SCRIPT_DIR/lib/chartlib.sh"
|
||||
|
||||
[[ -n "$verbose" ]] && set -o xtrace
|
||||
|
||||
pushd "$REPO_ROOT" > /dev/null
|
||||
|
||||
local exit_code=0
|
||||
|
||||
read -ra changed_dirs <<< "$(chartlib::detect_changed_directories)"
|
||||
|
||||
if [[ -n "${changed_dirs[*]}" ]]; then
|
||||
echo "Charts to be installed and tested: ${changed_dirs[*]}"
|
||||
|
||||
chartlib::init_helm
|
||||
|
||||
local summary=()
|
||||
|
||||
for chart_dir in "${changed_dirs[@]}"; do
|
||||
echo ''
|
||||
echo '--------------------------------------------------------------------------------'
|
||||
echo " Processing chart '$chart_dir'..."
|
||||
echo '--------------------------------------------------------------------------------'
|
||||
echo ''
|
||||
|
||||
local error=
|
||||
|
||||
if [[ -z "$no_lint" ]]; then
|
||||
if ! chartlib::validate_chart "$chart_dir"; then
|
||||
error=true
|
||||
fi
|
||||
if ! chartlib::lint_chart_with_all_configs "$chart_dir"; then
|
||||
error=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$no_install" && -z "$error" ]]; then
|
||||
if ! chartlib::install_chart_with_all_configs "$chart_dir"; then
|
||||
error=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$error" ]]; then
|
||||
summary+=(" ✔︎ $chart_dir")
|
||||
else
|
||||
summary+=(" ✖︎ $chart_dir")
|
||||
exit_code=1
|
||||
fi
|
||||
done
|
||||
else
|
||||
summary+=('No chart changes detected.')
|
||||
fi
|
||||
|
||||
echo '--------------------------------------------------------------------------------'
|
||||
for line in "${summary[@]}"; do
|
||||
echo "$line"
|
||||
done
|
||||
echo '--------------------------------------------------------------------------------'
|
||||
|
||||
popd > /dev/null
|
||||
|
||||
exit "$exit_code"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
20
test/chart/etc/chart_schema.yaml
Normal file
20
test/chart/etc/chart_schema.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
name: str()
|
||||
home: str()
|
||||
version: str()
|
||||
appVersion: any(str(), num())
|
||||
description: str()
|
||||
keywords: list(str(), required=False)
|
||||
sources: list(str(), required=False)
|
||||
maintainers: list(include('maintainer'), required=False)
|
||||
icon: str(required=False)
|
||||
engine: str(required=False)
|
||||
condition: str(required=False)
|
||||
tags: str(required=False)
|
||||
deprecated: bool(required=False)
|
||||
kubeVersion: str(required=False)
|
||||
annotations: map(str(), str(), required=False)
|
||||
---
|
||||
maintainer:
|
||||
name: str()
|
||||
email: str(required=False)
|
||||
url: str(required=False)
|
||||
42
test/chart/etc/lintconf.yaml
Normal file
42
test/chart/etc/lintconf.yaml
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
rules:
|
||||
braces:
|
||||
min-spaces-inside: 0
|
||||
max-spaces-inside: 0
|
||||
min-spaces-inside-empty: -1
|
||||
max-spaces-inside-empty: -1
|
||||
brackets:
|
||||
min-spaces-inside: 0
|
||||
max-spaces-inside: 0
|
||||
min-spaces-inside-empty: -1
|
||||
max-spaces-inside-empty: -1
|
||||
colons:
|
||||
max-spaces-before: 0
|
||||
max-spaces-after: 1
|
||||
commas:
|
||||
max-spaces-before: 0
|
||||
min-spaces-after: 1
|
||||
max-spaces-after: 1
|
||||
comments:
|
||||
require-starting-space: true
|
||||
min-spaces-from-content: 2
|
||||
document-end: disable
|
||||
document-start: disable # No --- to start a file
|
||||
empty-lines:
|
||||
max: 2
|
||||
max-start: 0
|
||||
max-end: 0
|
||||
hyphens:
|
||||
max-spaces-after: 1
|
||||
indentation:
|
||||
spaces: consistent
|
||||
indent-sequences: whatever # - list indentation will handle both indentation and without
|
||||
check-multi-line-strings: false
|
||||
key-duplicates: enable
|
||||
line-length: disable # Lines can be any length
|
||||
new-line-at-end-of-file: enable
|
||||
new-lines:
|
||||
type: unix
|
||||
trailing-spaces: enable
|
||||
truthy:
|
||||
level: warning
|
||||
477
test/chart/lib/chartlib.sh
Normal file
477
test/chart/lib/chartlib.sh
Normal file
@ -0,0 +1,477 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2018 The Helm Authors. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
|
||||
readonly REMOTE="${REMOTE:-origin}"
|
||||
readonly TARGET_BRANCH="${TARGET_BRANCH:-master}"
|
||||
readonly TIMEOUT="${TIMEOUT:-300}"
|
||||
readonly LINT_CONF="${LINT_CONF:-/testing/etc/lintconf.yaml}"
|
||||
readonly CHART_YAML_SCHEMA="${CHART_YAML_SCHEMA:-/testing/etc/chart_schema.yaml}"
|
||||
readonly VALIDATE_MAINTAINERS="${VALIDATE_MAINTAINERS:-true}"
|
||||
|
||||
# Special handling for arrays
|
||||
[[ -z "${CHART_DIRS[*]}" ]] && CHART_DIRS=(charts); readonly CHART_DIRS
|
||||
[[ -z "${EXCLUDED_CHARTS[*]}" ]] && EXCLUDED_CHARTS=(); readonly EXCLUDED_CHARTS
|
||||
[[ -z "${CHART_REPOS[*]}" ]] && CHART_REPOS=(); readonly CHART_REPOS
|
||||
|
||||
echo
|
||||
echo '--------------------------------------------------------------------------------'
|
||||
echo ' Environment:'
|
||||
echo " REMOTE=$REMOTE"
|
||||
echo " TARGET_BRANCH=$TARGET_BRANCH"
|
||||
echo " CHART_DIRS=${CHART_DIRS[*]}"
|
||||
echo " EXCLUDED_CHARTS=${EXCLUDED_CHARTS[*]}"
|
||||
echo " CHART_REPOS=${CHART_REPOS[*]}"
|
||||
echo " TIMEOUT=$TIMEOUT"
|
||||
echo " LINT_CONF=$LINT_CONF"
|
||||
echo " CHART_YAML_SCHEMA=$CHART_YAML_SCHEMA"
|
||||
echo " VALIDATE_MAINTAINERS=$VALIDATE_MAINTAINERS"
|
||||
echo '--------------------------------------------------------------------------------'
|
||||
echo
|
||||
|
||||
|
||||
# Detects chart directories that have changes against the
|
||||
# target branch ("$REMOTE/$TARGET_BRANCH").
|
||||
chartlib::detect_changed_directories() {
|
||||
local merge_base
|
||||
merge_base="$(git merge-base "$REMOTE/$TARGET_BRANCH" HEAD)"
|
||||
|
||||
local changed_dirs=()
|
||||
local dir
|
||||
|
||||
while read -r dir; do
|
||||
local excluded=
|
||||
for excluded_dir in "${EXCLUDED_CHARTS[@]}"; do
|
||||
if [[ "$dir" == "$excluded_dir" ]]; then
|
||||
excluded=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$excluded" && -d "$dir" ]]; then
|
||||
changed_dirs=("${changed_dirs[@]}" "$dir")
|
||||
fi
|
||||
|
||||
## @munnerz: because the cert-manager repository stores charts in the contrib/
|
||||
## subdirectory, we must modify the below line from $1/$2 to be $1/$2/$3.
|
||||
## In future, we should PR upstream so we no longer hardcode the depth of
|
||||
## directories required for this script.
|
||||
done < <(git diff --find-renames --name-only "$merge_base" "${CHART_DIRS[@]}" | awk -F/ '{ print $1"/"$2"/"$3 }' | uniq)
|
||||
|
||||
echo "${changed_dirs[@]}"
|
||||
}
|
||||
|
||||
# Initializes the Helm client and add configured repos.
|
||||
chartlib::init_helm() {
|
||||
echo 'Initializing Helm client...'
|
||||
|
||||
helm init --client-only
|
||||
|
||||
for repo in "${CHART_REPOS[@]}"; do
|
||||
local name="${repo%=*}"
|
||||
local url="${repo#*=}"
|
||||
|
||||
helm repo add "$name" "$url"
|
||||
done
|
||||
}
|
||||
|
||||
# Checks a chart for a version bump comparing the version from Chart.yaml
|
||||
# with that from the target branch.
|
||||
# Args:
|
||||
# $1 The chart directory
|
||||
chartlib::check_for_version_bump() {
|
||||
local chart_dir="${1?Chart directory is required}"
|
||||
|
||||
echo "Checking chart '$chart_dir' for a version bump..."
|
||||
|
||||
# Check if chart exists on taget branch
|
||||
if ! git cat-file -e "$REMOTE/$TARGET_BRANCH:$chart_dir/Chart.yaml" > /dev/null 2>&1; then
|
||||
echo "Unable to find chart on master. New chart detected."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Compare version of chart under test with that on the target branch
|
||||
|
||||
local old_version
|
||||
old_version=$(yq -r .version <(git show "$REMOTE/$TARGET_BRANCH:$chart_dir/Chart.yaml"))
|
||||
echo "Chart version on" "$REMOTE/$TARGET_BRANCH" ":" "$old_version"
|
||||
|
||||
local new_version
|
||||
new_version=$(yq -r .version "$chart_dir/Chart.yaml")
|
||||
echo "New chart version: " "$new_version"
|
||||
|
||||
# Pre-releases may not be API compatible. So, when tools compare versions
|
||||
# they often skip pre-releases. vert can force looking at pre-releases by
|
||||
# adding a dash on the end followed by pre-release. -0 on the end will force
|
||||
# looking for all valid pre-releases since a pre-release cannot start with a 0.
|
||||
# For example, 1.2.3-0 will include looking for pre-releases.
|
||||
if [[ $old_version == *-* ]]; then # Found the - to denote it has a pre-release
|
||||
if vert ">$old_version" "$new_version"; then
|
||||
echo "Chart version ok. Version bumped."
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
# No pre-release was found so we increment the patch version and attach a
|
||||
# -0 to enable pre-releases being found.
|
||||
local old_version_array
|
||||
read -ra old_version_array <<< "${old_version//./ }" # Turn the version into an array
|
||||
|
||||
(( old_version_array[2] += 1 )) # Increment the patch release
|
||||
if vert ">${old_version_array[0]}.${old_version_array[1]}.${old_version_array[2]}-0" "$new_version"; then
|
||||
echo "Chart version ok. Version bumped."
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
chartlib::error "Chart version not ok. Needs a version bump."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Validates the Chart.yaml against a YAML schema.
|
||||
# Args:
|
||||
# $1 The chart directory
|
||||
chartlib::validate_chart_yaml() {
|
||||
local chart_dir="${1?Chart directory is required}"
|
||||
|
||||
echo "Validating Chart.yaml"
|
||||
yamale --schema "$CHART_YAML_SCHEMA" "$chart_dir/Chart.yaml"
|
||||
}
|
||||
|
||||
# Validates maintainer names in Chart.yaml to be valid Github users.
|
||||
# Args:
|
||||
# $1 The chart directory
|
||||
chartlib::validate_maintainers() {
|
||||
local chart_dir="${1?Chart directory is required}"
|
||||
|
||||
echo "Validating maintainers"
|
||||
|
||||
# We require maintainers for non-deprecated charts
|
||||
local deprecated
|
||||
deprecated=$(yq -r '.deprecated // empty' "$chart_dir/Chart.yaml")
|
||||
|
||||
local maintainers
|
||||
maintainers=$(yq -r '.maintainers // empty' "$chart_dir/Chart.yaml")
|
||||
|
||||
if [[ -n "$deprecated" ]]; then
|
||||
if [[ -n "$maintainers" ]]; then
|
||||
chartlib::error "Deprecated charts must not have any maintainers in 'Chart.yaml'."
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
if [[ -z "$maintainers" ]]; then
|
||||
echo "No maintainers found in 'Chart.yaml'."
|
||||
fi
|
||||
fi
|
||||
|
||||
while read -r name; do
|
||||
echo "Verifying maintainer '$name'..."
|
||||
if [[ $(curl --silent --output /dev/null --write-out "%{http_code}" --fail --head "https://github.com/$name") -ne 200 ]]; then
|
||||
chartlib::error "'$name' is not a valid GitHub account. Please use a valid Github account to help us communicate with maintainers in PRs/issues."
|
||||
return 1
|
||||
fi
|
||||
done < <(yq -r '.maintainers[].name' "$chart_dir/Chart.yaml")
|
||||
}
|
||||
|
||||
# Lints a YAML file.
|
||||
# Args:
|
||||
# $1 The YAML file to lint
|
||||
chartlib::lint_yaml_file() {
|
||||
local file="${1?Specify YAML file for linting}"
|
||||
|
||||
echo "Linting '$file'..."
|
||||
|
||||
if [[ -f "$file" ]]; then
|
||||
yamllint --config-file "$LINT_CONF" "$file"
|
||||
else
|
||||
chartlib::error "File '$file' does not exist."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Validates a chart:
|
||||
# - Checks for a version bump
|
||||
# - Lints Chart.yaml and values.yaml
|
||||
# - Validates Chart.yaml against schema
|
||||
# - Validates maintainers
|
||||
# Args:
|
||||
# $1 The chart directory
|
||||
chartlib::validate_chart() {
|
||||
local chart_dir="${1?Chart directory is required}"
|
||||
local error=
|
||||
|
||||
echo "Validating chart '$chart_dir'..."
|
||||
|
||||
chartlib::check_for_version_bump "$chart_dir" || error=true
|
||||
chartlib::lint_yaml_file "$chart_dir/Chart.yaml" || error=true
|
||||
chartlib::lint_yaml_file "$chart_dir/values.yaml" || error=true
|
||||
chartlib::validate_chart_yaml "$chart_dir" || error=true
|
||||
|
||||
if [[ "$VALIDATE_MAINTAINERS" == true ]]; then
|
||||
chartlib::validate_maintainers "$chart_dir" || error=true
|
||||
fi
|
||||
|
||||
if [[ -n "$error" ]]; then
|
||||
chartlib::error 'Chart validation failed.'
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Lints a chart.
|
||||
# Args:
|
||||
# $1 The chart directory
|
||||
# $2 A custom values file for the chart installation (optional)
|
||||
chartlib::lint_chart_with_single_config() {
|
||||
local chart_dir="${1?Chart directory is required}"
|
||||
local values_file="${2:-}"
|
||||
|
||||
echo "Building dependencies for chart '$chart_dir'..."
|
||||
helm dependency build "$chart_dir"
|
||||
|
||||
if [[ -n "$values_file" ]]; then
|
||||
echo "Using custom values file '$values_file'..."
|
||||
|
||||
echo "Linting chart '$chart_dir'..."
|
||||
helm lint "$chart_dir" --values "$values_file"
|
||||
else
|
||||
echo "Chart does not provide test values. Using defaults..."
|
||||
|
||||
echo "Linting chart '$chart_dir'..."
|
||||
helm lint "$chart_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
# Installs and tests a chart. The release and the namespace are
|
||||
# automatically deleted afterwards.
|
||||
# Args:
|
||||
# $1 The chart directory
|
||||
# $2 The release name for the chart to be installed
|
||||
# $3 The namespace to install the chart in
|
||||
# $4 A custom values file for the chart installation (optional)
|
||||
chartlib::install_chart_with_single_config() {
|
||||
local chart_dir="${1?Chart directory is required}"
|
||||
local release="${2?Release is required}"
|
||||
local namespace="${3?Namespace is required}"
|
||||
local values_file="${4:-}"
|
||||
|
||||
# Capture subshell output
|
||||
exec 3>&1
|
||||
|
||||
if ! (
|
||||
set -o errexit
|
||||
|
||||
# Run in subshell so we can use a trap within the function.
|
||||
trap 'chartlib::print_pod_details_and_logs "$namespace" || true; chartlib::delete_release "$release" || true; chartlib::delete_namespace "$namespace" || true' EXIT
|
||||
|
||||
echo "Building dependencies for chart '$chart_dir'..."
|
||||
helm dependency build "$chart_dir"
|
||||
|
||||
echo "Installing chart '$chart_dir' into namespace '$namespace'..."
|
||||
|
||||
if [[ -n "$values_file" ]]; then
|
||||
echo "Using custom values file '$values_file'..."
|
||||
helm install "$chart_dir" --name "$release" --namespace "$namespace" --wait --timeout "$TIMEOUT" --values "$values_file"
|
||||
else
|
||||
echo "Chart does not provide test values. Using defaults..."
|
||||
helm install "$chart_dir" --name "$release" --namespace "$namespace" --wait --timeout "$TIMEOUT"
|
||||
fi
|
||||
|
||||
# For deployments --wait may not be sufficient because it looks at 'maxUnavailable' which is 0 by default.
|
||||
for deployment in $(kubectl get deployment --namespace "$namespace" --output jsonpath='{.items[*].metadata.name}'); do
|
||||
kubectl rollout status "deployment/$deployment" --namespace "$namespace"
|
||||
done
|
||||
|
||||
echo "Testing chart '$chart_dir' in namespace '$namespace'..."
|
||||
helm test "$release" --cleanup --timeout "$TIMEOUT"
|
||||
|
||||
) >&3; then
|
||||
|
||||
chartlib::error "Chart installation failed: $chart_dir"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Lints a chart for all custom values files matching '*.values.yaml'
|
||||
# in the 'ci' subdirectory.
|
||||
# Args:
|
||||
# $1 The chart directory
|
||||
chartlib::lint_chart_with_all_configs() {
|
||||
local chart_dir="${1?Chart directory is required}"
|
||||
|
||||
local has_test_values=
|
||||
for values_file in "$chart_dir"/ci/*-values.yaml; do
|
||||
has_test_values=true
|
||||
chartlib::lint_chart_with_single_config "$chart_dir" "$values_file"
|
||||
done
|
||||
|
||||
if [[ -z "$has_test_values" ]]; then
|
||||
chartlib::lint_chart_with_single_config "$chart_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
# Installs a chart for all custom values files matching '*.values.yaml'
|
||||
# in the 'ci' subdirectory. If no custom values files are found, the chart
|
||||
# is installed with defaults. If $BUILD_ID is set, it is used as
|
||||
# name for the namespace to install the chart in. Otherwise, the chart
|
||||
# name is taken as the namespace name. Namespace and release are suffixed with
|
||||
# an index. Releases and namespaces are automatically deleted afterwards.
|
||||
# Args:
|
||||
# $1 The chart directory
|
||||
chartlib::install_chart_with_all_configs() {
|
||||
local chart_dir="${1?Chart directory is required}"
|
||||
local index=0
|
||||
|
||||
local release
|
||||
release=$(yq -r .name < "$chart_dir/Chart.yaml")
|
||||
|
||||
local random_suffix
|
||||
random_suffix=$(tr -dc a-z0-9 < /dev/urandom | fold -w 16 | head -n 1)
|
||||
|
||||
local namespace="${BUILD_ID:-"$release"}-$random_suffix"
|
||||
local release="$release-$random_suffix"
|
||||
|
||||
local has_test_values=
|
||||
for values_file in "$chart_dir"/ci/*-values.yaml; do
|
||||
has_test_values=true
|
||||
chartlib::install_chart_with_single_config "$chart_dir" "$release-$index" "$namespace-$index" "$values_file"
|
||||
((index += 1))
|
||||
done
|
||||
|
||||
if [[ -z "$has_test_values" ]]; then
|
||||
chartlib::install_chart_with_single_config "$chart_dir" "$release" "$namespace"
|
||||
fi
|
||||
}
|
||||
|
||||
# Prints log for all pods in the specified namespace.
|
||||
# Args:
|
||||
# $1 The namespace
|
||||
chartlib::print_pod_details_and_logs() {
|
||||
local namespace="${1?Namespace is required}"
|
||||
|
||||
kubectl get pods --show-all --no-headers --namespace "$namespace" | awk '{ print $1 }' | while read -r pod; do
|
||||
if [[ -n "$pod" ]]; then
|
||||
printf '\n================================================================================\n'
|
||||
printf ' Details from pod %s\n' "$pod"
|
||||
printf '================================================================================\n'
|
||||
|
||||
printf '\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n'
|
||||
printf ' Description of pod %s\n' "$pod"
|
||||
printf '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n'
|
||||
|
||||
kubectl describe pod --namespace "$namespace" "$pod" || true
|
||||
|
||||
printf '\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n'
|
||||
printf ' End of description for pod %s\n' "$pod"
|
||||
printf '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n'
|
||||
|
||||
local init_containers
|
||||
init_containers=$(kubectl get pods --show-all --output jsonpath="{.spec.initContainers[*].name}" --namespace "$namespace" "$pod")
|
||||
for container in $init_containers; do
|
||||
printf -- '\n--------------------------------------------------------------------------------\n'
|
||||
printf ' Logs of init container %s in pod %s\n' "$container" "$pod"
|
||||
printf -- '--------------------------------------------------------------------------------\n\n'
|
||||
|
||||
kubectl logs --namespace "$namespace" --container "$container" "$pod" || true
|
||||
|
||||
printf -- '\n--------------------------------------------------------------------------------\n'
|
||||
printf ' End of logs of init container %s in pod %s\n' "$container" "$pod"
|
||||
printf -- '--------------------------------------------------------------------------------\n'
|
||||
done
|
||||
|
||||
local containers
|
||||
containers=$(kubectl get pods --show-all --output jsonpath="{.spec.containers[*].name}" --namespace "$namespace" "$pod")
|
||||
for container in $containers; do
|
||||
printf '\n--------------------------------------------------------------------------------\n'
|
||||
printf -- ' Logs of container %s in pod %s\n' "$container" "$pod"
|
||||
printf -- '--------------------------------------------------------------------------------\n\n'
|
||||
|
||||
kubectl logs --namespace "$namespace" --container "$container" "$pod" || true
|
||||
|
||||
printf -- '\n--------------------------------------------------------------------------------\n'
|
||||
printf ' End of logs of container %s in pod %s\n' "$container" "$pod"
|
||||
printf -- '--------------------------------------------------------------------------------\n'
|
||||
done
|
||||
|
||||
printf '\n================================================================================\n'
|
||||
printf ' End of details for pod %s\n' "$pod"
|
||||
printf '================================================================================\n\n'
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Deletes a release.
|
||||
# Args:
|
||||
# $1 The name of the release to delete
|
||||
chartlib::delete_release() {
|
||||
local release="${1?Release is required}"
|
||||
|
||||
echo "Deleting release '$release'..."
|
||||
helm delete --purge "$release" --timeout "$TIMEOUT"
|
||||
}
|
||||
|
||||
# Deletes a namespace.
|
||||
# Args:
|
||||
# $1 The namespace to delete
|
||||
chartlib::delete_namespace() {
|
||||
local namespace="${1?Namespace is required}"
|
||||
|
||||
echo "Deleting namespace '$namespace'..."
|
||||
kubectl delete namespace "$namespace"
|
||||
|
||||
echo -n "Waiting for namespace '$namespace' to terminate..."
|
||||
|
||||
local max_retries=30
|
||||
local retry=0
|
||||
local sleep_time_sec=3
|
||||
while ((retry < max_retries)); do
|
||||
sleep "$sleep_time_sec"
|
||||
((retry++))
|
||||
|
||||
if ! kubectl get namespace "$namespace" &> /dev/null; then
|
||||
echo
|
||||
echo "Namespace '$namespace' terminated."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -n '.'
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
chartlib::error "Namespace '$namespace' not terminated after $((max_retries * sleep_time_sec)) s."
|
||||
|
||||
echo "Force-deleting pods..."
|
||||
kubectl delete pods --namespace "$namespace" --all --force --grace-period 0 || true
|
||||
|
||||
sleep 3
|
||||
|
||||
if ! kubectl get namespace "$namespace" &> /dev/null; then
|
||||
echo "Force-deleting namespace '$namespace'..."
|
||||
kubectl delete namespace "$namespace" --ignore-not-found --force --grace-period 0 || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Logs an error.
|
||||
# Args:
|
||||
# $1 The error message
|
||||
chartlib::error() {
|
||||
printf '\e[31mERROR: %s\n\e[39m' "$1" >&2
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user