Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Add this to your .pre-commit-config.yaml:

```yaml
- repo: https://github.com/jordanopensource/pre-commit-hooks
rev: v0.1.0 # Use the ref you want to point at
rev: v0.1.1 # Use the ref you want to point at
hooks:
- id: validate-flux
# - id: ...
Expand Down
360 changes: 334 additions & 26 deletions scripts/validate-flux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,300 @@
# - kustomize v5.0
# - kubeconform v0.6

# General variables
numberOfFilesChanged=0
numberOfClusterFilesChanged=0
numberOfKsFilesChanged=0
numberOfKsChecksFailed=0
numberOfClustersChecksFailed=0
DEBUG=0
# Define the color codes
GREEN='\033[0;32m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m'
# Define text decorations
UNDERLINE='\033[4m'
NOUNDERLINE='\033[0m'

# Define the help message
show_help() {
echo "Usage: $(basename "$0") [options]"
echo
echo "Options:"
echo " --help, -h Show this help message and exit"
echo " --validate, -v Validate specified items (clstr=clusters, ks=kustomize) -- NOTE: YAML file check will always run"
echo " --debug, -d Enable debug mode showing more information"
}

# Check for --help or -h flag
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
show_help
exit 0
fi

# Initialize an array to store validation options
validate_options=()

# Process command line arguments
while [[ "$1" != "" ]]; do
case $1 in
-v | --validate)
shift
while [[ "$1" != "" && "$1" != -* ]]; do
case $1 in
clusters | clstr)
validate_options+=("clusters")
;;
kustomize | ks)
validate_options+=("kustomize")
;;
*)
echo "Unknown validation option: $1"
show_help
exit 1
;;
esac
shift
done
validate_options+=("files") # always validate YAML files
;;
-d | --debug)
DEBUG=1
shift
;;
*)
echo "Unknown option: $1"
show_help
exit 1
;;
esac
done

## Function: check_package
#
### Description
# The `check_package` function is a Bash script that checks if a specified command-line package is installed on the system. If the package is not installed, the function provides instructions on how to install it, depending on the package name.
#
### Parameters
# - `$1` (String): The name of the package to check.
#
### Usage
# ```bash
# check_package <package_name>

check_package() {
if ! command -v $1 &>/dev/null; then
echo -e "${YELLOW}WARN${NC} - Package $1 is not installed."
if [ "$1" == "yq" ]; then
echo -e "You can download it from the GitHub page: ${CYAN}${UNDERLINE}https://github.com/mikefarah/yq?tab=readme-ov-file#install${NOUNDERLINE}${NC}"
elif [ "$1" == "kustomize" ]; then
echo -e "You can download it from the GitHub page: ${CYAN}${UNDERLINE}https://github.com/kubernetes-sigs/kustomize${NOUNDERLINE}${NC}"
elif [ "$1" == "kubeconform" ]; then
echo -e "You can download it from the GitHub page: ${CYAN}${UNDERLINE}https://github.com/yannh/kubeconform${NOUNDERLINE}${NC}"
else
echo -e "Please install ${YELLOW}${UNDERLINE}$1${NOUNDERLINE}${NC} and try again."
fi
exit 1
fi
}

## Function: get_git_diff_files
#
### Description
# The `get_git_diff_files` function is a Bash script that retrieves the list of modified files in a Git repository, filtered by a specified directory and pattern. This function is particularly useful for identifying specific types of files that have been staged for commit.
#
### Parameters
# - `dir` (String): The directory to filter the git diff output. This parameter limits the scope of the files to be checked within the specified directory.
# - `pattern` (String): The pattern to filter the file names using grep. This parameter allows you to specify a regular expression to match certain file types or naming conventions.
#
### Usage
# ```bash
# get_git_diff_files <dir> <pattern>

get_git_diff_files() {
local dir="$1"
local pattern="$2"
git diff --diff-filter=d --cached --name-only -- "$dir" | grep "$pattern"
}

# Define the error handling function
handle_error() {
local message="$1"
echo -e "\n${RED}ERROR${NC} - ${message}"
}

## Function: download_schemas

### Description
# The `download_schemas` function downloads the latest Flux OpenAPI schemas and extracts them to a specified directory. It also retrieves and stores the latest version tag of the schemas for future reference.
#
### Usage
# ```bash
# download_schemas

download_schemas() {
[[ $DEBUG -eq 1 ]] && echo -e "${CYAN}INFO${NC} - Downloading Flux OpenAPI schemas"
mkdir -p "$SCHEMA_DIR"
curl -sL https://github.com/fluxcd/flux2/releases/latest/download/crd-schemas.tar.gz -o "$SCHEMA_TAR"
tar zxf "$SCHEMA_TAR" -C "$SCHEMA_DIR"
curl -sL $LATEST_VERSION_URL | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' >"$LATEST_VERSION_FILE"
}

## Function: check_latest_schemas
#
### Description
# The `check_latest_schemas` function checks if the downloaded Flux OpenAPI schemas are the latest available version. It compares the version tag of the currently downloaded schemas with the latest version available on GitHub.
#
### Usage
# ```bash
# check_latest_schemas

check_latest_schemas() {
local latest_version
latest_version=$(curl -sL $LATEST_VERSION_URL | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
if [[ -f "$LATEST_VERSION_FILE" ]]; then
local current_version
current_version=$(cat "$LATEST_VERSION_FILE")
if [[ "$latest_version" == "$current_version" ]]; then
[[ $DEBUG -eq 1 ]] && echo -e "${CYAN}INFO${NC} - Flux OpenAPI schemas are already up-to-date."
return 0
fi
fi
return 1
}

print_initialization() {
echo "----------------------------------"
if [ $DEBUG -eq 1 ]; then
echo -e "# ${MAGENTA}Running validation checks in Debug mode${NC} "
else
echo -e "# ${MAGENTA}Running validation checks in Regular mode${NC} "
fi
echo -e "----------------------------------"
}

print_summary() {

echo -e "----------------------------------"
echo -e "# ${YELLOW}Validation Summary${NC} "
echo -e "Total YAML files validated: ${CYAN}$numberOfFilesChanged${NC}"
echo -e "Validated Clusters: ${CYAN}$numberOfClusterFilesChanged${NC}"
echo -e "Validated Kustomizations: ${CYAN}$numberOfKsFilesChanged${NC}"
if [[ ! $numberOfClustersChecksFailed -eq 0 || ! $numberOfKsChecksFailed -eq 0 ]]; then
echo -e "-------------------------"
echo -e "Failed Clusters validation checks: ${RED}$numberOfClustersChecksFailed${NC}"
echo -e "Failed Kustomizations checks: ${RED}$numberOfKsChecksFailed${NC}"
echo "----------------------------------"
exit 1
fi
echo "----------------------------------"
}

## Function: validate_files
#
### Description
# The `validate_files` function is a Bash script that identifies and validates YAML files that have been modified and staged for commit in a Git repository.
# It uses the `yq` tool to check the syntax of each file.
#
### Usage
# ```bash
# validate_files

validate_files() {
mapfile -t fileList < <(get_git_diff_files "./" ".*\.ya\?ml$")
if [ ${#fileList[@]} -eq 0 ]; then
echo "No files to validate"
exit 0
fi
echo -n "VALIDATING YAML FILES ..." # validates the yaml convention,
[[ $DEBUG -eq 1 ]] && echo -e "\n----------------------------------\n${YELLOW}Files changed:${NC}"
for file in "${fileList[@]}"; do
[[ $DEBUG -eq 1 ]] && echo "$file"
[[ $DEBUG -eq 1 ]] && echo -e "${CYAN}INFO${NC} - Validating $file"
yq e 'true' "$file" >/dev/null
((++numberOfFilesChanged))
done
echo -e "[ ${GREEN}Done${NC} ]"
}

## Function: validate_clusters
#
### Description
# The `validate_clusters` function is a Bash script that validates YAML files related to clusters that have been modified and staged for commit in a Git repository.
# It uses the `kubeconform` tool to perform validation on these files.
#
### Usage
# ```bash
# validate_clusters

validate_clusters() {

echo -n "VALIDATING CLUSTERS ..."
[[ $DEBUG -eq 1 ]] && echo -e "\n${CYAN}INFO${NC} - Validating clusters"
mapfile -t clusterList < <(get_git_diff_files "./clusters" ".*\.ya\?ml$")
for file in "${clusterList[@]}"; do
local failed_checks
[[ $DEBUG -eq 1 ]] && echo -e "${CYAN}INFO${NC} - Validating ${file}"
kubeconform "${kubeconform_flags[@]}" "${kubeconform_config[@]}" "${file}" || ((failed_checks = 1))

if [[ ! $failed_checks -eq 0 ]]; then
handle_error "kubeconform checks failed for: ${file}"
((++numberOfClustersChecksFailed))
else
((++numberOfClusterFilesChanged))
fi
done
if [[ $numberOfClustersChecksFailed -eq 0 ]]; then
echo -e "[ ${GREEN}Done${NC} ]"
else
echo -e "[ ${RED}Failed${NC} ]"
fi
}

## Function: validate_kustomize
#
### Description
# The `validate_kustomize` function is a Bash script that identifies and validates kustomize overlays that have been modified and staged for commit in a Git repository.
# It uses the `kustomize` and `kubeconform` tools to perform validation on these overlays.
#
### Usage
# ```bash
# validate_kustomize

validate_kustomize() {
echo -n "VALIDATING KUSTOMIZE ..."
[[ $DEBUG -eq 1 ]] && echo -e "\n${CYAN}INFO${NC} - Validating kustomize overlays"
mapfile -t kustomizeList < <(get_git_diff_files "./" "$kustomize_config")
for file in "${kustomizeList[@]}"; do
local failed_checks=0
if [[ "$file" == *"$kustomize_config" ]]; then
[[ $DEBUG -eq 1 ]] && echo -e "${CYAN}INFO${NC} - Validating kustomization ${file/%$kustomize_config/}"
kustomize build "${file/%$kustomize_config/}" "${kustomize_flags[@]}" | kubeconform "${kubeconform_flags[@]}" "${kubeconform_config[@]}" || ((failed_checks = 1))

# if [[ ${PIPESTATUS[0]} != 0 ]]; then
if [[ ! $failed_checks -eq 0 ]]; then
handle_error "kustomize checks failed for: ${file}"
((++numberOfKsChecksFailed))
else
((++numberOfKsFilesChanged))
fi
fi
done

if [[ $numberOfKsChecksFailed -eq 0 ]]; then
echo -e "[ ${GREEN}Done${NC} ]"
else
echo -e "[ ${RED}Failed${NC} ]"
fi
}

check_package "yq"
check_package "kustomize"
check_package "kubeconform"

set -o errexit
set -o pipefail

Expand All @@ -33,34 +327,48 @@ kustomize_config="kustomization.yaml"

# skip Kubernetes Secrets due to SOPS fields failing validation
kubeconform_flags=("-skip=Secret")
kubeconform_config=("-strict" "-ignore-missing-schemas" "-schema-location" "default" "-schema-location" "/tmp/flux-crd-schemas" "-verbose")
kubeconform_config=("-strict" "-ignore-missing-schemas" "-schema-location" "default" "-schema-location" "/tmp/flux-crd-schemas")
[[ $DEBUG -eq 1 ]] && kubeconform_config+=("-verbose")

echo "INFO - Downloading Flux OpenAPI schemas"
mkdir -p /tmp/flux-crd-schemas/master-standalone-strict
curl -sL https://github.com/fluxcd/flux2/releases/latest/download/crd-schemas.tar.gz | tar zxf - -C /tmp/flux-crd-schemas/master-standalone-strict
# Define the directory and file paths
SCHEMA_DIR="/tmp/flux-crd-schemas/master-standalone-strict"
SCHEMA_TAR="$SCHEMA_DIR/crd-schemas.tar.gz"
LATEST_VERSION_URL="https://api.github.com/repos/fluxcd/flux2/releases/latest"
LATEST_VERSION_FILE="$SCHEMA_DIR/latest_version"

find . -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file;
do
echo "INFO - Validating $file"
yq e 'true' "$file" > /dev/null
done
# Check if the directory exists and contains the schema files
if [[ -d "$SCHEMA_DIR" && $(ls -A "$SCHEMA_DIR" 2>/dev/null) ]] && check_latest_schemas; then
[[ $DEBUG -eq 1 ]] && echo -e "${CYAN}INFO${NC} - Flux OpenAPI schemas are already cached."
else
download_schemas
fi

echo "INFO - Validating clusters"
find ./clusters -maxdepth 2 -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file;
do
kubeconform "${kubeconform_flags[@]}" "${kubeconform_config[@]}" "${file}"
if [[ ${PIPESTATUS[0]} != 0 ]]; then
exit 1
fi
done
print_initialization

echo "INFO - Validating kustomize overlays"
find . -type f -name $kustomize_config -print0 | while IFS= read -r -d $'\0' file;
do
echo "INFO - Validating kustomization ${file/%$kustomize_config}"
kustomize build "${file/%$kustomize_config}" "${kustomize_flags[@]}" | \
kubeconform "${kubeconform_flags[@]}" "${kubeconform_config[@]}"
if [[ ${PIPESTATUS[0]} != 0 ]]; then
# Run validations based on user input
if [[ ${#validate_options[@]} -eq 0 ]]; then
validate_files
validate_clusters
validate_kustomize
else
for option in "${validate_options[@]}"; do
case $option in
files)
validate_files
;;
clusters)
validate_clusters
;;
kustomize)
validate_kustomize
;;
*)
echo "Unknown validation option: $option"
show_help
exit 1
fi
done
;;
esac
done
fi

print_summary