mirror of
				https://github.com/kata-containers/kata-containers.git
				synced 2025-10-31 01:13:02 +00:00 
			
		
		
		
	Ensure rego policies in tree can be parsed using opa and regorus. Signed-off-by: Paul Meyer <katexochen0@gmail.com>
		
			
				
	
	
		
			1599 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1599 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env bash
 | |
| 
 | |
| # Copyright (c) 2017-2023 Intel Corporation
 | |
| #
 | |
| # SPDX-License-Identifier: Apache-2.0
 | |
| #
 | |
| # Description: Central script to run all static checks.
 | |
| #   This script should be called by all other repositories to ensure
 | |
| #   there is only a single source of all static checks.
 | |
| 
 | |
| set -e
 | |
| 
 | |
| [ -n "$DEBUG" ] && set -x
 | |
| 
 | |
| cidir=$(realpath $(dirname "$0"))
 | |
| source "${cidir}/common.bash"
 | |
| 
 | |
| # By default in Golang >= 1.16 GO111MODULE is set to "on",
 | |
| # some subprojects in this repo may not support "go modules",
 | |
| # set GO111MODULE to "auto" to enable module-aware mode only when
 | |
| # a go.mod file is present in the current directory.
 | |
| export GO111MODULE="auto"
 | |
| export test_path="${test_path:-github.com/kata-containers/kata-containers/tests}"
 | |
| export test_dir="${GOPATH}/src/${test_path}"
 | |
| 
 | |
| # List of files to delete on exit
 | |
| files_to_remove=()
 | |
| 
 | |
| script_name=${0##*/}
 | |
| 
 | |
| # Static check functions must follow the following naming conventions:
 | |
| #
 | |
| 
 | |
| # All static check function names must match this pattern.
 | |
| typeset -r check_func_regex="^static_check_"
 | |
| 
 | |
| # All architecture-specific static check functions must match this pattern.
 | |
| typeset -r arch_func_regex="_arch_specific$"
 | |
| 
 | |
| repo=""
 | |
| repo_path=""
 | |
| specific_branch="false"
 | |
| force="false"
 | |
| branch=${branch:-main}
 | |
| 
 | |
| # Which static check functions to consider.
 | |
| handle_funcs="all"
 | |
| 
 | |
| single_func_only="false"
 | |
| list_only="false"
 | |
| 
 | |
| # number of seconds to wait for curl to check a URL
 | |
| typeset url_check_timeout_secs="${url_check_timeout_secs:-60}"
 | |
| 
 | |
| # number of attempts that will be made to check an individual URL.
 | |
| typeset url_check_max_tries="${url_check_max_tries:-3}"
 | |
| 
 | |
| typeset -A long_options
 | |
| 
 | |
| # Generated code
 | |
| ignore_clh_generated_code="virtcontainers/pkg/cloud-hypervisor/client"
 | |
| 
 | |
| paths_to_skip=(
 | |
| 	"${ignore_clh_generated_code}"
 | |
| 	"vendor"
 | |
| )
 | |
| 
 | |
| # Skip paths that are not statically checked
 | |
| # $1 : List of paths to check, space separated list
 | |
| # If you have a list in a bash array call in this way:
 | |
| # list=$(skip_paths "${list[@]}")
 | |
| # If you still want to use it as an array do:
 | |
| # list=(${list})
 | |
| skip_paths(){
 | |
| 	local list_param="${1}"
 | |
| 	[ -z "$list_param" ] && return
 | |
| 	local list=(${list_param})
 | |
| 
 | |
| 	for p in "${paths_to_skip[@]}"; do
 | |
| 		new_list=()
 | |
| 		for l in "${list[@]}"; do
 | |
| 			if echo "${l}" | grep -qv "${p}"; then
 | |
| 				new_list=("${new_list[@]}" "${l}")
 | |
| 			fi
 | |
| 		done
 | |
| 		list=("${new_list[@]}")
 | |
| 	done
 | |
| 	echo "${list[@]}"
 | |
| }
 | |
| 
 | |
| 
 | |
| long_options=(
 | |
| 	[all]="Force checking of all changes, including files in the base branch"
 | |
| 	[branch]="Specify upstream branch to compare against (default '$branch')"
 | |
| 	[docs]="Check document files"
 | |
| 	[dockerfiles]="Check dockerfiles"
 | |
| 	[files]="Check files"
 | |
| 	[force]="Force a skipped test to run"
 | |
| 	[golang]="Check '.go' files"
 | |
| 	[help]="Display usage statement"
 | |
| 	[json]="Check JSON files"
 | |
| 	[labels]="Check labels databases"
 | |
| 	[licenses]="Check licenses"
 | |
| 	[list]="List tests that would run"
 | |
| 	[no-arch]="Run/list all tests except architecture-specific ones"
 | |
| 	[only-arch]="Only run/list architecture-specific tests"
 | |
| 	[repo:]="Specify GitHub URL of repo to use (github.com/user/repo)"
 | |
| 	[scripts]="Check script files"
 | |
| 	[vendor]="Check vendor files"
 | |
| 	[versions]="Check versions files"
 | |
| 	[xml]="Check XML files"
 | |
| )
 | |
| 
 | |
| yamllint_cmd="yamllint"
 | |
| have_yamllint_cmd=$(command -v "$yamllint_cmd" || true)
 | |
| 
 | |
| chronic=chronic
 | |
| 
 | |
| # Disable chronic on OSX to avoid having to update the Travis config files
 | |
| # for additional packages on that platform.
 | |
| [ "$(uname -s)" == "Darwin" ] && chronic=
 | |
| 
 | |
| usage()
 | |
| {
 | |
| 	cat <<EOF
 | |
| 
 | |
| Usage: $script_name help
 | |
|        $script_name [options] repo-name [true]
 | |
| 
 | |
| Options:
 | |
| 
 | |
| EOF
 | |
| 
 | |
| 	local option
 | |
| 	local description
 | |
| 
 | |
| 	local long_option_names="${!long_options[@]}"
 | |
| 
 | |
| 	# Sort space-separated list by converting to newline separated list
 | |
| 	# and back again.
 | |
| 	long_option_names=$(echo "$long_option_names"|tr ' ' '\n'|sort|tr '\n' ' ')
 | |
| 
 | |
| 	# Display long options
 | |
| 	for option in ${long_option_names}
 | |
| 	do
 | |
| 		description=${long_options[$option]}
 | |
| 
 | |
| 		# Remove any trailing colon which is for getopt(1) alone.
 | |
| 		option=$(echo "$option"|sed 's/:$//g')
 | |
| 
 | |
| 		printf "    --%-10.10s # %s\n" "$option" "$description"
 | |
| 	done
 | |
| 
 | |
| 	cat <<EOF
 | |
| 
 | |
| Parameters:
 | |
| 
 | |
|   help      : Show usage.
 | |
|   repo-name : GitHub URL of repo to check in form "github.com/user/repo"
 | |
|               (equivalent to "--repo \$URL").
 | |
|   true      : Specify as "true" if testing a specific branch, else assume a
 | |
|               PR branch (equivalent to "--all").
 | |
| 
 | |
| Notes:
 | |
| 
 | |
| - If no options are specified, all non-skipped tests will be run.
 | |
| - Some required tools may be installed in \$GOPATH/bin, so you should ensure
 | |
|   that it is in your \$PATH.
 | |
| 
 | |
| Examples:
 | |
| 
 | |
| - Run all tests on a specific branch (stable or main) of kata-containers repo:
 | |
| 
 | |
|   $ $script_name github.com/kata-containers/kata-containers true
 | |
| 
 | |
| - Auto-detect repository and run golang tests for current repository:
 | |
| 
 | |
|   $ KATA_DEV_MODE=true $script_name --golang
 | |
| 
 | |
| - Run all tests on the kata-containers repository, forcing the tests to
 | |
|   consider all files, not just those changed by a PR branch:
 | |
| 
 | |
|   $ $script_name github.com/kata-containers/kata-containers --all
 | |
| 
 | |
| 
 | |
| EOF
 | |
| }
 | |
| 
 | |
| # Calls die() if the specified function is not valid.
 | |
| func_is_valid() {
 | |
| 	local name="$1"
 | |
| 
 | |
| 	type -t "$name" &>/dev/null || die "function '$name' does not exist"
 | |
| }
 | |
| 
 | |
| # Calls die() if the specified function is not valid or not a check function.
 | |
| ensure_func_is_check_func() {
 | |
| 	local name="$1"
 | |
| 
 | |
| 	func_is_valid "$name"
 | |
| 
 | |
| 	{ echo "$name" | grep -q "${check_func_regex}"; ret=$?; }
 | |
| 
 | |
| 	[ "$ret" = 0 ] || die "function '$name' is not a check function"
 | |
| }
 | |
| 
 | |
| # Returns "yes" if the specified function needs to run on all architectures,
 | |
| # else "no".
 | |
| func_is_arch_specific() {
 | |
| 	local name="$1"
 | |
| 
 | |
| 	ensure_func_is_check_func "$name"
 | |
| 
 | |
| 	{ echo "$name" | grep -q "${arch_func_regex}"; ret=$?; }
 | |
| 
 | |
| 	if [ "$ret" = 0 ]; then
 | |
| 		echo "yes"
 | |
| 	else
 | |
| 		echo "no"
 | |
| 	fi
 | |
| }
 | |
| 
 | |
| function remove_tmp_files() {
 | |
| 	rm -rf "${files_to_remove[@]}"
 | |
| }
 | |
| 
 | |
| # Convert a golang package to a full path
 | |
| pkg_to_path()
 | |
| {
 | |
| 	local pkg="$1"
 | |
| 
 | |
| 	go list -f '{{.Dir}}' "$pkg"
 | |
| }
 | |
| 
 | |
| # Check that chronic is installed, otherwise die.
 | |
| need_chronic() {
 | |
| 	local first_word
 | |
| 	[ -z "$chronic" ] && return
 | |
| 	first_word="${chronic%% *}"
 | |
| 	command -v chronic &>/dev/null || \
 | |
| 		die "chronic command not found. You must have it installed to run this check." \
 | |
| 		"Usually it is distributed with the 'moreutils' package of your Linux distribution."
 | |
| }
 | |
| 
 | |
| 
 | |
| static_check_go_arch_specific()
 | |
| {
 | |
| 	local go_packages
 | |
| 	local submodule_packages
 | |
| 	local all_packages
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	# List of all golang packages found in all submodules
 | |
| 	#
 | |
| 	# These will be ignored: since they are references to other
 | |
| 	# repositories, we assume they are tested independently in their
 | |
| 	# repository so do not need to be re-tested here.
 | |
| 	submodule_packages=$(mktemp)
 | |
| 	git submodule -q foreach "go list ./..." | sort > "$submodule_packages" || true
 | |
| 
 | |
| 	# all packages
 | |
| 	all_packages=$(mktemp)
 | |
| 	go list ./... | sort > "$all_packages" || true
 | |
| 
 | |
| 	files_to_remove+=("$submodule_packages" "$all_packages")
 | |
| 
 | |
| 	# List of packages to consider which is defined as:
 | |
| 	#
 | |
| 	#   "all packages" - "submodule packages"
 | |
| 	#
 | |
| 	# Note: the vendor filtering is required for versions of go older than 1.9
 | |
| 	go_packages=$(comm -3 "$all_packages" "$submodule_packages" || true)
 | |
| 	go_packages=$(skip_paths "${go_packages[@]}")
 | |
| 
 | |
| 	# No packages to test
 | |
| 	[ -z "$go_packages" ] && popd && return
 | |
| 
 | |
| 	local linter="golangci-lint"
 | |
| 
 | |
| 	# Run golang checks
 | |
| 	if [ ! "$(command -v $linter)" ]
 | |
| 	then
 | |
| 		info "Installing ${linter}"
 | |
| 
 | |
| 		local linter_url=$(get_test_version "languages.golangci-lint.url")
 | |
| 		local linter_version=$(get_test_version "languages.golangci-lint.version")
 | |
| 
 | |
| 		info "Forcing ${linter} version ${linter_version}"
 | |
| 		curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin "v${linter_version}"
 | |
| 		command -v $linter &>/dev/null || \
 | |
| 			die "$linter command not found. Ensure that \"\$GOPATH/bin\" is in your \$PATH."
 | |
| 	fi
 | |
| 
 | |
| 	local linter_args="run -c ${cidir}/.golangci.yml"
 | |
| 
 | |
| 	# Non-option arguments other than "./..." are
 | |
| 	# considered to be directories by $linter, not package names.
 | |
| 	# Hence, we need to obtain a list of package directories to check,
 | |
| 	# excluding any that relate to submodules.
 | |
| 	local dirs
 | |
| 
 | |
| 	for pkg in $go_packages
 | |
| 	do
 | |
| 		path=$(pkg_to_path "$pkg")
 | |
| 
 | |
| 		makefile="${path}/Makefile"
 | |
| 
 | |
| 		# perform a basic build since some repos generate code which
 | |
| 		# is required for the package to be buildable (and thus
 | |
| 		# checkable).
 | |
| 		[ -f "$makefile" ] && (cd "$path" && make)
 | |
| 
 | |
| 		dirs+=" $path"
 | |
| 	done
 | |
| 
 | |
| 	info "Running $linter checks on the following packages:\n"
 | |
| 	echo "$go_packages"
 | |
| 	echo
 | |
| 	info "Package paths:\n"
 | |
| 	echo "$dirs" | sed 's/^ *//g' | tr ' ' '\n'
 | |
| 	for d in ${dirs};do
 | |
| 		info "Running $linter on $d"
 | |
| 		(cd $d && GO111MODULE=auto eval "$linter" "${linter_args}" ".")
 | |
| 	done
 | |
| 	popd
 | |
| 
 | |
| }
 | |
| 
 | |
| # Install yamllint in the different Linux distributions
 | |
| install_yamllint()
 | |
| {
 | |
| 	package="yamllint"
 | |
| 
 | |
| 	case "$ID" in
 | |
| 		centos|rhel) sudo yum -y install $package ;;
 | |
| 		ubuntu) sudo apt-get -y install $package ;;
 | |
| 		fedora) sudo dnf -y install $package ;;
 | |
| 		*) die "Please install yamllint on $ID" ;;
 | |
| 	esac
 | |
| 
 | |
| 	have_yamllint_cmd=$(command -v "$yamllint_cmd" || true)
 | |
| 
 | |
| 	if [ -z "$have_yamllint_cmd" ]; then
 | |
| 		info "Cannot install $package" && return
 | |
| 	fi
 | |
| }
 | |
| 
 | |
| # Check the "versions database".
 | |
| #
 | |
| # Some repositories use a versions database to maintain version information
 | |
| # about non-golang dependencies. If found, check it for validity.
 | |
| static_check_versions()
 | |
| {
 | |
| 	local db="versions.yaml"
 | |
| 
 | |
| 	if [ -z "$have_yamllint_cmd" ]; then
 | |
| 		info "Installing yamllint"
 | |
| 		install_yamllint
 | |
| 	fi
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	[ ! -e "$db" ] && popd && return
 | |
| 
 | |
| 	if [ -n "$have_yamllint_cmd" ]; then
 | |
| 		eval "$yamllint_cmd" "$db"
 | |
| 	else
 | |
| 		info "Cannot check versions as $yamllint_cmd not available"
 | |
| 	fi
 | |
| 
 | |
| 	popd
 | |
| }
 | |
| 
 | |
| static_check_labels()
 | |
| {
 | |
| 	[ $(uname -s) != Linux ] && info "Can only check labels under Linux" && return
 | |
| 
 | |
| 	# Handle SLES which doesn't provide the required command.
 | |
| 	[ -z "$have_yamllint_cmd" ] && info "Cannot check labels as $yamllint_cmd not available" && return
 | |
| 
 | |
| 	# Since this script is called from another repositories directory,
 | |
| 	# ensure the utility is built before the script below (which uses it) is run.
 | |
| 	(cd "${test_dir}/cmd/github-labels" && make)
 | |
| 
 | |
| 	tmp=$(mktemp)
 | |
| 
 | |
| 	files_to_remove+=("${tmp}")
 | |
| 
 | |
| 	info "Checking labels for repo ${repo} using temporary combined database ${tmp}"
 | |
| 
 | |
| 	bash -f "${test_dir}/cmd/github-labels/github-labels.sh" "generate" "${repo}" "${tmp}"
 | |
| }
 | |
| 
 | |
| # Ensure all files (where possible) contain an SPDX license header
 | |
| static_check_license_headers()
 | |
| {
 | |
| 	# The branch is the baseline - ignore it.
 | |
| 	[ "$specific_branch" = "true" ] && return
 | |
| 
 | |
| 	# See: https://spdx.org/licenses/Apache-2.0.html
 | |
| 	local -r spdx_tag="SPDX-License-Identifier"
 | |
| 	local -r spdx_license="Apache-2.0"
 | |
| 	local -r license_pattern="${spdx_tag}: ${spdx_license}"
 | |
| 	local -r copyright_pattern="Copyright"
 | |
| 
 | |
| 	local header_checks=()
 | |
| 
 | |
| 	header_checks+=("SPDX license header::${license_pattern}")
 | |
| 	header_checks+=("Copyright header:-i:${copyright_pattern}")
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	files=$(get_pr_changed_file_details || true)
 | |
| 
 | |
| 	# Strip off status and convert to array
 | |
| 	files=($(echo "$files"|awk '{print $NF}'))
 | |
| 
 | |
| 	text_files=()
 | |
| 	# Filter out non-text files
 | |
| 	for file in "${files[@]}"; do
 | |
| 		if [[ -f "$file" ]] && file --mime-type "$file" | grep -q "text/"; then
 | |
| 			text_files+=("$file")
 | |
| 		else
 | |
| 			info "Ignoring non-text file: $file"
 | |
| 		fi
 | |
| 	done
 | |
| 	files="${text_files[*]}"
 | |
| 
 | |
| 	# no text files were changed
 | |
| 	[ -z "$files" ] && info "No files found" && popd && return
 | |
| 
 | |
| 	local header_check
 | |
| 
 | |
| 	for header_check in "${header_checks[@]}"
 | |
| 	do
 | |
| 		local desc=$(echo "$header_check"|cut -d: -f1)
 | |
| 		local extra_args=$(echo "$header_check"|cut -d: -f2)
 | |
| 		local pattern=$(echo "$header_check"|cut -d: -f3-)
 | |
| 
 | |
| 		info "Checking $desc"
 | |
| 
 | |
| 		local missing=$(grep \
 | |
| 			--exclude=".git/*" \
 | |
| 			--exclude=".gitignore" \
 | |
| 			--exclude=".dockerignore" \
 | |
| 			--exclude="Gopkg.lock" \
 | |
| 			--exclude="*.gpl.c" \
 | |
| 			--exclude="*.ipynb" \
 | |
| 			--exclude="*.jpg" \
 | |
| 			--exclude="*.json" \
 | |
| 			--exclude="LICENSE*" \
 | |
| 			--exclude="THIRD-PARTY" \
 | |
| 			--exclude="*.md" \
 | |
| 			--exclude="*.pb.go" \
 | |
| 			--exclude="*pb_test.go" \
 | |
| 			--exclude="*.bin" \
 | |
| 			--exclude="*.png" \
 | |
| 			--exclude="*.pub" \
 | |
| 			--exclude="*.service" \
 | |
| 			--exclude="*.svg" \
 | |
| 			--exclude="*.drawio" \
 | |
| 			--exclude="*.toml" \
 | |
| 			--exclude="*.txt" \
 | |
| 			--exclude="*.dtd" \
 | |
| 			--exclude="vendor/*" \
 | |
| 			--exclude="VERSION" \
 | |
| 			--exclude="kata_config_version" \
 | |
| 			--exclude="tools/packaging/kernel/configs/*" \
 | |
| 			--exclude="virtcontainers/pkg/firecracker/*" \
 | |
| 			--exclude="${ignore_clh_generated_code}*" \
 | |
| 			--exclude="*.xml" \
 | |
| 			--exclude="*.yaml" \
 | |
| 			--exclude="*.yml" \
 | |
| 			--exclude="go.mod" \
 | |
| 			--exclude="go.sum" \
 | |
| 			--exclude="*.lock" \
 | |
| 			--exclude="grpc-rs/*" \
 | |
| 			--exclude="target/*" \
 | |
| 			--exclude="*.patch" \
 | |
| 			--exclude="*.diff" \
 | |
| 			--exclude="tools/packaging/static-build/qemu.blacklist" \
 | |
| 			--exclude="tools/packaging/qemu/default-configs/*" \
 | |
| 			--exclude="src/libs/protocols/protos/gogo/*.proto" \
 | |
| 			--exclude="src/libs/protocols/protos/google/*.proto" \
 | |
| 			--exclude="src/libs/protocols/protos/cri-api/api.proto" \
 | |
| 			--exclude="src/mem-agent/example/protocols/protos/google/protobuf/*.proto" \
 | |
| 			--exclude="src/libs/*/test/texture/*" \
 | |
| 			--exclude="*.dic" \
 | |
| 			-EL $extra_args -E "\<${pattern}\>" \
 | |
| 			$files || true)
 | |
| 
 | |
| 		if [ -n "$missing" ]; then
 | |
| 			cat >&2 <<-EOF
 | |
| 		ERROR: Required $desc check ('$pattern') failed for the following files:
 | |
| 
 | |
| 		$missing
 | |
| 
 | |
| EOF
 | |
| 			exit 1
 | |
| 		fi
 | |
| 	done
 | |
| 	popd
 | |
| }
 | |
| 
 | |
| run_url_check_cmd()
 | |
| {
 | |
| 	local url="${1:-}"
 | |
| 	[ -n "$url" ] || die "need URL"
 | |
| 
 | |
| 	local out_file="${2:-}"
 | |
| 	[ -n "$out_file" ] || die "need output file"
 | |
| 
 | |
| 	# Can be blank
 | |
| 	local extra_args="${3:-}"
 | |
| 
 | |
| 	local curl_extra_args=()
 | |
| 
 | |
| 	curl_extra_args+=("$extra_args")
 | |
| 
 | |
| 	# Authenticate for github to increase threshold for rate limiting
 | |
| 	if [[ "$url" =~ github\.com && -n "$GITHUB_USER" && -n "$GITHUB_TOKEN" ]]; then
 | |
| 		curl_extra_args+=("-u ${GITHUB_USER}:${GITHUB_TOKEN}")
 | |
| 	fi
 | |
| 
 | |
| 	# Some endpoints return 403 to HEAD but 200 for GET,
 | |
| 	# so perform a GET but only read headers.
 | |
| 	curl \
 | |
| 		${curl_extra_args[*]} \
 | |
| 		-sIL \
 | |
| 		-X GET \
 | |
| 		-c - \
 | |
| 		-H "Accept-Encoding: zstd, none, gzip, deflate" \
 | |
| 		--max-time "$url_check_timeout_secs" \
 | |
| 		--retry "$url_check_max_tries" \
 | |
| 		"$url" \
 | |
| 		&>"$out_file"
 | |
| }
 | |
| 
 | |
| check_url()
 | |
| {
 | |
| 	local url="${1:-}"
 | |
| 	[ -n "$url" ] || die "need URL to check"
 | |
| 
 | |
| 	local invalid_urls_dir="${2:-}"
 | |
| 	[ -n "$invalid_urls_dir" ] || die "need invalid URLs directory"
 | |
| 
 | |
| 	local curl_out
 | |
| 	curl_out=$(mktemp)
 | |
| 
 | |
| 	files_to_remove+=("${curl_out}")
 | |
| 
 | |
| 	# Process specific file to avoid out-of-order writes
 | |
| 	local invalid_file
 | |
| 	invalid_file=$(printf "%s/%d" "$invalid_urls_dir" "$$")
 | |
| 
 | |
| 	local ret
 | |
| 
 | |
| 	local -a errors=()
 | |
| 
 | |
| 	local -a user_agents=()
 | |
| 
 | |
| 	# Test an unspecified UA (curl default)
 | |
| 	user_agents+=('')
 | |
| 
 | |
| 	# Test an explictly blank UA
 | |
| 	user_agents+=('""')
 | |
| 
 | |
| 	# Single space
 | |
| 	user_agents+=(' ')
 | |
| 
 | |
| 	# CLI HTTP tools
 | |
| 	user_agents+=('Wget')
 | |
| 	user_agents+=('curl')
 | |
| 
 | |
| 	# console based browsers
 | |
| 	# Hopefully, these will always be supported for a11y.
 | |
| 	user_agents+=('Lynx')
 | |
| 	user_agents+=('Elinks')
 | |
| 
 | |
| 	# Emacs' w3m browser
 | |
| 	user_agents+=('Emacs')
 | |
| 
 | |
| 	# The full craziness
 | |
| 	user_agents+=('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36')
 | |
| 
 | |
| 	local user_agent
 | |
| 
 | |
| 	# Cycle through the user agents until we find one that works.
 | |
| 	#
 | |
| 	# Note that we also test an unspecified user agent
 | |
| 	# (no '-A <value>').
 | |
| 	for user_agent in "${user_agents[@]}"
 | |
| 	do
 | |
| 		info "Checking URL $url with User Agent '$user_agent'"
 | |
| 
 | |
| 		local curl_ua_args
 | |
| 		[ -n "$user_agent" ] && curl_ua_args="-A '$user_agent'"
 | |
| 
 | |
| 		{ run_url_check_cmd "$url" "$curl_out" "$curl_ua_args"; ret=$?; } || true
 | |
| 
 | |
| 		# A transitory error, or the URL is incorrect,
 | |
| 		# but capture either way.
 | |
| 		if [ "$ret" -ne 0 ]; then
 | |
| 			errors+=("Failed to check URL '$url' (user agent: '$user_agent', return code $ret)")
 | |
| 
 | |
| 			# Try again with another UA since it appears that some return codes
 | |
| 			# indicate the server was unhappy with the details
 | |
| 			# presented by the client.
 | |
| 			continue
 | |
| 		fi
 | |
| 
 | |
| 		local http_statuses
 | |
| 
 | |
| 		http_statuses=$(grep -E "^HTTP" "$curl_out" |\
 | |
| 			awk '{print $2}' || true)
 | |
| 
 | |
| 		if [ -z "$http_statuses" ]; then
 | |
| 			errors+=("no HTTP status codes for URL '$url' (user agent: '$user_agent')")
 | |
| 
 | |
| 			continue
 | |
| 		fi
 | |
| 
 | |
| 		local status
 | |
| 
 | |
| 		local -i fail_count=0
 | |
| 
 | |
| 		# Check all HTTP status codes
 | |
| 		for status in $http_statuses
 | |
| 		do
 | |
| 			# Ignore the following ranges of status codes:
 | |
| 			#
 | |
| 			# - 1xx: Informational codes.
 | |
| 			# - 2xx: Success codes.
 | |
| 			# - 3xx: Redirection codes.
 | |
| 			# - 405: Specifically to handle some sites
 | |
| 			#   which get upset by "curl -L" when the
 | |
| 			#   redirection is not required.
 | |
| 			#
 | |
| 			# Anything else is considered an error.
 | |
| 			#
 | |
| 			# See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
 | |
| 
 | |
| 			{ grep -qE "^(1[0-9][0-9]|2[0-9][0-9]|3[0-9][0-9]|405)" <<< "$status"; ret=$?; } || true
 | |
| 
 | |
| 			[ "$ret" -eq 0 ] && continue
 | |
| 
 | |
| 			fail_count+=1
 | |
| 		done
 | |
| 
 | |
| 		# If we didn't receive any unexpected HTTP status codes for
 | |
| 		# this UA, the URL is valid so we don't need to check with any
 | |
| 		# further UAs, so clear any (transitory) errors we've
 | |
| 		# recorded.
 | |
| 		[ "$fail_count" -eq 0 ] && errors=() && break
 | |
| 
 | |
| 		echo "$url" >> "$invalid_file"
 | |
| 		errors+=("found HTTP error status codes for URL $url (status: '$status', user agent: '$user_agent')")
 | |
| 	done
 | |
| 
 | |
| 	[ "${#errors}" = 0 ] && return 0
 | |
| 
 | |
| 	die "failed to check URL '$url': errors: '${errors[*]}'"
 | |
| }
 | |
| 
 | |
| # Perform basic checks on documentation files
 | |
| static_check_docs()
 | |
| {
 | |
| 	local cmd="xurls"
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	if [ ! "$(command -v $cmd)" ]
 | |
| 	then
 | |
| 		info "Installing $cmd utility"
 | |
| 
 | |
| 		local version
 | |
| 		local url
 | |
| 
 | |
| 		version=$(get_test_version "externals.xurls.version")
 | |
| 		url=$(get_test_version "externals.xurls.url")
 | |
| 
 | |
| 		# xurls is very fussy about how it's built.
 | |
| 		go install "${url}@${version}"
 | |
| 
 | |
| 		command -v xurls &>/dev/null ||
 | |
| 			die 'xurls not found. Ensure that "$GOPATH/bin" is in your $PATH'
 | |
| 	fi
 | |
| 
 | |
| 	info "Checking documentation"
 | |
| 
 | |
| 	local doc
 | |
| 	local all_docs
 | |
| 	local docs
 | |
| 	local docs_status
 | |
| 	local new_docs
 | |
| 	local new_urls
 | |
| 	local url
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	all_docs=$(git ls-files "*.md" | grep -Ev "(grpc-rs|target)/" | sort || true)
 | |
| 	all_docs=$(skip_paths "${all_docs[@]}")
 | |
| 
 | |
| 	if [ "$specific_branch" = "true" ]
 | |
| 	then
 | |
| 		info "Checking all documents in $branch branch"
 | |
| 		docs="$all_docs"
 | |
| 	else
 | |
| 		info "Checking local branch for changed documents only"
 | |
| 
 | |
| 		docs_status=$(get_pr_changed_file_details || true)
 | |
| 		docs_status=$(echo "$docs_status" | grep "\.md$" || true)
 | |
| 
 | |
| 		docs=$(echo "$docs_status" | awk '{print $NF}' | sort)
 | |
| 		docs=$(skip_paths "${docs[@]}")
 | |
| 
 | |
| 		# Newly-added docs
 | |
| 		new_docs=$(echo "$docs_status" | awk '/^A/ {print $NF}' | sort)
 | |
| 		new_docs=$(skip_paths "${new_docs[@]}")
 | |
| 
 | |
| 		for doc in $new_docs
 | |
| 		do
 | |
| 			# A new document file has been added. If that new doc
 | |
| 			# file is referenced by any files on this PR, checking
 | |
| 			# its URL will fail since the PR hasn't been merged
 | |
| 			# yet. We could construct the URL based on the users
 | |
| 			# original PR branch and validate that. But it's
 | |
| 			# simpler to just construct the URL that the "pending
 | |
| 			# document" *will* result in when the PR has landed
 | |
| 			# and then check docs for that new URL and exclude
 | |
| 			# them from the real URL check.
 | |
| 			url="https://${repo}/blob/${branch}/${doc}"
 | |
| 
 | |
| 			new_urls+=" ${url}"
 | |
| 		done
 | |
| 	fi
 | |
| 
 | |
| 	[ -z "$docs" ] && info "No documentation to check" && return
 | |
| 
 | |
| 	local urls
 | |
| 	local url_map=$(mktemp)
 | |
| 	local invalid_urls=$(mktemp)
 | |
| 	local md_links=$(mktemp)
 | |
| 	files_to_remove+=("${url_map}" "${invalid_urls}" "${md_links}")
 | |
| 
 | |
| 	info "Checking document markdown references"
 | |
| 
 | |
| 	local md_docs_to_check
 | |
| 
 | |
| 	# All markdown docs are checked (not just those changed by a PR). This
 | |
| 	# is necessary to guarantee that all docs are referenced.
 | |
| 	md_docs_to_check="$all_docs"
 | |
| 
 | |
| 	command -v kata-check-markdown &>/dev/null ||\
 | |
| 		(cd "${test_dir}/cmd/check-markdown" && make)
 | |
| 
 | |
| 	command -v kata-check-markdown &>/dev/null || \
 | |
| 		die 'kata-check-markdown command not found. Ensure that "$GOPATH/bin" is in your $PATH.'
 | |
| 
 | |
| 	for doc in $md_docs_to_check
 | |
| 	do
 | |
| 		kata-check-markdown check "$doc"
 | |
| 
 | |
| 		# Get a link of all other markdown files this doc references
 | |
| 		kata-check-markdown list links --format tsv --no-header "$doc" |\
 | |
| 			grep "external-link" |\
 | |
| 			awk '{print $3}' |\
 | |
| 			sort -u >> "$md_links"
 | |
| 	done
 | |
| 
 | |
| 	# clean the list of links
 | |
| 	local tmp
 | |
| 	tmp=$(mktemp)
 | |
| 
 | |
| 	sort -u "$md_links" > "$tmp"
 | |
| 	mv "$tmp" "$md_links"
 | |
| 
 | |
| 	# A list of markdown files that do not have to be referenced by any
 | |
| 	# other markdown file.
 | |
| 	exclude_doc_regexs+=()
 | |
| 
 | |
| 	exclude_doc_regexs+=(^CODE_OF_CONDUCT\.md$)
 | |
| 	exclude_doc_regexs+=(^CONTRIBUTING\.md$)
 | |
| 
 | |
| 	# Magic github template files
 | |
| 	exclude_doc_regexs+=(^\.github/.*\.md$)
 | |
| 
 | |
| 	# The top level README doesn't need to be referenced by any other
 | |
| 	# since it displayed by default when visiting the repo.
 | |
| 	exclude_doc_regexs+=(^README\.md$)
 | |
| 
 | |
| 	# Exclude READMEs for test integration
 | |
| 	exclude_doc_regexs+=(^\tests/cmd/.*/README\.md$)
 | |
| 
 | |
| 	local exclude_pattern
 | |
| 
 | |
| 	# Convert the list of files into an grep(1) alternation pattern.
 | |
| 	exclude_pattern=$(echo "${exclude_doc_regexs[@]}"|sed 's, ,|,g')
 | |
| 
 | |
| 	# Every document in the repo (except a small handful of exceptions)
 | |
| 	# should be referenced by another document.
 | |
| 	for doc in $md_docs_to_check
 | |
| 	do
 | |
| 		# Check the ignore list for markdown files that do not need to
 | |
| 		# be referenced by others.
 | |
| 		echo "$doc"|grep -q -E "(${exclude_pattern})" && continue
 | |
| 
 | |
| 		grep -q "$doc" "$md_links" || die "Document $doc is not referenced"
 | |
| 	done
 | |
| 
 | |
| 	info "Checking document code blocks"
 | |
| 
 | |
| 	local doc_to_script_cmd="${cidir}/kata-doc-to-script.sh"
 | |
| 
 | |
| 	for doc in $docs
 | |
| 	do
 | |
| 		bash "${doc_to_script_cmd}" -csv "$doc"
 | |
| 
 | |
| 		# Look for URLs in the document
 | |
| 		urls=$("${doc_to_script_cmd}" -i "$doc" - | "$cmd")
 | |
| 
 | |
| 		# Gather URLs
 | |
| 		for url in $urls
 | |
| 		do
 | |
| 			printf "%s\t%s\n" "${url}" "${doc}" >> "$url_map"
 | |
| 		done
 | |
| 	done
 | |
| 
 | |
| 	# Get unique list of URLs
 | |
| 	urls=$(awk '{print $1}' "$url_map" | sort -u)
 | |
| 
 | |
| 	info "Checking all document URLs"
 | |
| 	local invalid_urls_dir=$(mktemp -d)
 | |
| 	files_to_remove+=("${invalid_urls_dir}")
 | |
| 
 | |
| 	for url in $urls
 | |
| 	do
 | |
| 		if [ "$specific_branch" != "true" ]
 | |
| 		then
 | |
| 			# If the URL is new on this PR, it cannot be checked.
 | |
| 			echo "$new_urls" | grep -q -E "\<${url}\>" && \
 | |
| 				info "ignoring new (but correct) URL: $url" && continue
 | |
| 		fi
 | |
| 
 | |
| 		# Ignore local URLs. The only time these are used is in
 | |
| 		# examples (meaning these URLs won't exist).
 | |
| 		echo "$url" | grep -q "^file://" && continue
 | |
| 		echo "$url" | grep -q "^http://localhost" && continue
 | |
| 
 | |
| 		# Ignore the install guide URLs that contain a shell variable
 | |
| 		echo "$url" | grep -q "\\$" && continue
 | |
| 
 | |
| 		# This prefix requires the client to be logged in to github, so ignore
 | |
| 		echo "$url" | grep -q 'https://github.com/pulls' && continue
 | |
| 
 | |
| 		# Sigh.
 | |
| 		echo "$url"|grep -q 'https://example.com' && continue
 | |
| 
 | |
| 		# Google APIs typically require an auth token.
 | |
| 		echo "$url"|grep -q 'https://www.googleapis.com' && continue
 | |
| 
 | |
| 		# Git repo URL check
 | |
| 		if echo "$url"|grep -q '^https.*git'
 | |
| 		then
 | |
| 			timeout "${KATA_NET_TIMEOUT}" git ls-remote "$url" > /dev/null 2>&1 && continue
 | |
| 		fi
 | |
| 
 | |
| 		# Check the URL, saving it if invalid
 | |
| 		#
 | |
| 		# Each URL is checked in a separate process as each unique URL
 | |
| 		# requires us to hit the network.
 | |
| 		check_url "$url" "$invalid_urls_dir" &
 | |
| 	done
 | |
| 
 | |
| 	# Synchronisation point
 | |
| 	wait
 | |
| 
 | |
| 	# Combine all the separate invalid URL files into one
 | |
| 	local invalid_files=$(ls "$invalid_urls_dir")
 | |
| 
 | |
| 	if [ -n "$invalid_files" ]; then
 | |
| 		pushd "$invalid_urls_dir" &>/dev/null
 | |
| 		cat $(echo "$invalid_files"|tr '\n' ' ') > "$invalid_urls"
 | |
| 		popd &>/dev/null
 | |
| 	fi
 | |
| 
 | |
| 	if [ -s "$invalid_urls" ]
 | |
| 	then
 | |
| 		local files
 | |
| 
 | |
| 		cat "$invalid_urls" | while read url
 | |
| 		do
 | |
| 			files=$(grep "^${url}" "$url_map" | awk '{print $2}' | sort -u)
 | |
| 			echo >&2 -e "ERROR: Invalid URL '$url' found in the following files:\n"
 | |
| 
 | |
| 			for file in $files
 | |
| 			do
 | |
| 				echo >&2 "$file"
 | |
| 			done
 | |
| 		done
 | |
| 
 | |
| 		exit 1
 | |
| 	fi
 | |
| 
 | |
| 	# Now, spell check the docs
 | |
| 	cmd="${test_dir}/cmd/check-spelling/kata-spell-check.sh"
 | |
| 
 | |
| 	local docs_failed=0
 | |
| 	for doc in $docs
 | |
| 	do
 | |
| 		"$cmd" check "$doc" || { info "spell check failed for document $doc" && docs_failed=1; }
 | |
| 
 | |
| 		static_check_eof "$doc"
 | |
| 	done
 | |
| 
 | |
| 	popd
 | |
| 
 | |
| 	[ $docs_failed -eq 0 ] || {
 | |
|         url='https://github.com/kata-containers/kata-containers/blob/main/docs/Documentation-Requirements.md#spelling'
 | |
|         die "spell check failed, See $url for more information."
 | |
|     }
 | |
| }
 | |
| 
 | |
| static_check_eof()
 | |
| {
 | |
| 	local file="$1"
 | |
| 	local anchor="EOF"
 | |
| 
 | |
| 
 | |
| 	[ -z "$file" ] && info "No files to check" && return
 | |
| 
 | |
| 	# Skip the itself
 | |
| 	[ "$file" == "$script_name" ] && return
 | |
| 
 | |
| 	# Skip the Vagrantfile
 | |
| 	[ "$file" == "Vagrantfile" ] && return
 | |
| 
 | |
| 	local invalid=$(cat "$file" |\
 | |
| 		grep -o -E '<<-* *\w*' |\
 | |
| 		sed -e 's/^<<-*//g' |\
 | |
| 		tr -d ' ' |\
 | |
| 		sort -u |\
 | |
| 		grep -v -E '^$' |\
 | |
| 		grep -v -E "$anchor" || true)
 | |
| 	[ -z "$invalid" ] || die "Expected '$anchor' here anchor, in $file found: $invalid"
 | |
| }
 | |
| 
 | |
| # Tests to apply to all files.
 | |
| #
 | |
| # Currently just looks for TODO/FIXME comments that should be converted to
 | |
| # (or annotated with) an Issue URL.
 | |
| static_check_files()
 | |
| {
 | |
| 	local file
 | |
| 	local files
 | |
| 
 | |
| 	if [ "$force" = "false" ]
 | |
| 	then
 | |
| 		info "Skipping check_files: see https://github.com/kata-containers/tests/issues/469"
 | |
| 		return
 | |
| 	else
 | |
| 		info "Force override of check_files skip"
 | |
| 	fi
 | |
| 
 | |
| 	info "Checking files"
 | |
| 
 | |
| 	if [ "$specific_branch" = "true" ]
 | |
| 	then
 | |
| 		info "Checking all files in $branch branch"
 | |
| 
 | |
| 		files=$(git ls-files | grep -v -E "/(.git|vendor|grpc-rs|target)/" || true)
 | |
| 	else
 | |
| 		info "Checking local branch for changed files only"
 | |
| 
 | |
| 		files=$(get_pr_changed_file_details || true)
 | |
| 
 | |
| 		# Strip off status
 | |
| 		files=$(echo "$files"|awk '{print $NF}')
 | |
| 	fi
 | |
| 
 | |
| 	[ -z "$files" ] && info "No files changed" && return
 | |
| 
 | |
| 	local matches=""
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	for file in $files
 | |
| 	do
 | |
| 		local match
 | |
| 
 | |
| 		# Look for files containing the specified comment tags but
 | |
| 		# which do not include a github URL.
 | |
| 		match=$(grep -H -E "\<FIXME\>|\<TODO\>" "$file" |\
 | |
| 			grep -v "https://github.com/.*/issues/[0-9]" |\
 | |
| 			cut -d: -f1 |\
 | |
| 			sort -u || true)
 | |
| 
 | |
| 		[ -z "$match" ] && continue
 | |
| 
 | |
| 		# Don't fail if this script contains the patterns
 | |
| 		# (as it is guaranteed to ;)
 | |
| 		echo "$file" | grep -q "${script_name}$" && info "Ignoring special file $file" && continue
 | |
| 
 | |
| 		# We really only care about comments in code. But to avoid
 | |
| 		# having to hard-code the list of file extensions to search,
 | |
| 		# invert the problem by simply ignoring document files and
 | |
| 		# considering all other file types.
 | |
| 		echo "$file" | grep -q ".md$" && info "Ignoring comment tag in document $file" && continue
 | |
| 
 | |
| 		matches+=" $match"
 | |
| 	done
 | |
| 
 | |
| 	popd
 | |
| 
 | |
| 	[ -z "$matches" ] && return
 | |
| 
 | |
| 	echo >&2 -n \
 | |
| 		"ERROR: The following files contain TODO/FIXME's that need "
 | |
| 	echo >&2 -e "converting to issues:\n"
 | |
| 
 | |
| 	for file in $matches
 | |
| 	do
 | |
| 		echo >&2 "$file"
 | |
| 	done
 | |
| 
 | |
| 	# spacer
 | |
| 	echo >&2
 | |
| 
 | |
| 	exit 1
 | |
| }
 | |
| 
 | |
| # Perform vendor checks:
 | |
| #
 | |
| # - Ensure that changes to vendored code are accompanied by an update to the
 | |
| #   vendor tooling config file. If not, the user simply hacked the vendor files
 | |
| #   rather than following the correct process:
 | |
| #
 | |
| #   https://github.com/kata-containers/community/blob/main/VENDORING.md
 | |
| #
 | |
| # - Ensure vendor metadata is valid.
 | |
| static_check_vendor()
 | |
| {
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	local files
 | |
| 	local files_arr=()
 | |
| 
 | |
| 	files=$(find . -type f -name "go.mod")
 | |
| 
 | |
| 	while IFS= read -r line; do
 | |
| 		files_arr+=("$line")
 | |
| 	done <<< "$files"
 | |
| 
 | |
| 	for file in "${files_arr[@]}"; do
 | |
| 	        local dir=$(echo $file | sed 's/go\.mod//')
 | |
| 
 | |
| 	        pushd $dir
 | |
| 
 | |
| 		# Check if directory has been changed to use go modules
 | |
| 		if [ -f "go.mod" ]; then
 | |
| 			info "go.mod file found in $dir, running go mod verify instead"
 | |
| 			# This verifies the integrity of modules in the local cache.
 | |
| 			# This does not really verify the integrity of vendored code:
 | |
| 			# https://github.com/golang/go/issues/27348
 | |
| 			# Once that is added we need to add an extra step to verify vendored code.
 | |
| 			go mod verify
 | |
| 		fi
 | |
| 		popd
 | |
| 	done
 | |
| 
 | |
| 	popd
 | |
| }
 | |
| 
 | |
| static_check_xml()
 | |
| {
 | |
| 	local all_xml
 | |
| 	local files
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	need_chronic
 | |
| 
 | |
| 	all_xml=$(git ls-files "*.xml" | grep -Ev "/(vendor|grpc-rs|target)/" | sort || true)
 | |
| 
 | |
| 	if [ "$specific_branch" = "true" ]
 | |
| 	then
 | |
| 		info "Checking all XML files in $branch branch"
 | |
| 		files="$all_xml"
 | |
| 	else
 | |
| 		info "Checking local branch for changed XML files only"
 | |
| 
 | |
| 		local xml_status
 | |
| 
 | |
| 		xml_status=$(get_pr_changed_file_details || true)
 | |
| 		xml_status=$(echo "$xml_status" | grep "\.xml$" || true)
 | |
| 
 | |
| 		files=$(echo "$xml_status" | awk '{print $NF}')
 | |
| 	fi
 | |
| 
 | |
| 	[ -z "$files" ] && info "No XML files to check" && popd && return
 | |
| 
 | |
| 	local file
 | |
| 
 | |
| 	for file in $files
 | |
| 	do
 | |
| 		info "Checking XML file '$file'"
 | |
| 
 | |
| 		local contents
 | |
| 
 | |
| 		# Most XML documents are specified as XML 1.0 since, with the
 | |
| 		# advent of XML 1.0 (Fifth Edition), XML 1.1 is "almost
 | |
| 		# redundant" due to XML 1.0 providing the majority of XML 1.1
 | |
| 		# features. xmllint doesn't support XML 1.1 seemingly for this
 | |
| 		# reason, so the only check we can do is to (crudely) force
 | |
| 		# the document to be an XML 1.0 one since XML 1.1 documents
 | |
| 		# can mostly be represented as XML 1.0.
 | |
| 		#
 | |
| 		# This is only really required since Jenkins creates XML 1.1
 | |
| 		# documents.
 | |
| 		contents=$(sed "s/xml version='1.1'/xml version='1.0'/g" "$file")
 | |
| 
 | |
| 		local ret
 | |
| 
 | |
| 		{ $chronic xmllint -format - <<< "$contents"; ret=$?; } || true
 | |
| 
 | |
| 		[ "$ret" -eq 0 ] || die "failed to check XML file '$file'"
 | |
| 	done
 | |
| 
 | |
| 	popd
 | |
| }
 | |
| 
 | |
| static_check_shell()
 | |
| {
 | |
| 	local all_scripts
 | |
| 	local scripts
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	need_chronic
 | |
| 
 | |
| 	all_scripts=$(git ls-files "*.sh" "*.bash" | grep -Ev "/(vendor|grpc-rs|target)/" | sort || true)
 | |
| 
 | |
| 	if [ "$specific_branch" = "true" ]
 | |
| 	then
 | |
| 		info "Checking all scripts in $branch branch"
 | |
| 		scripts="$all_scripts"
 | |
| 	else
 | |
| 		info "Checking local branch for changed scripts only"
 | |
| 
 | |
| 		local scripts_status
 | |
| 		scripts_status=$(get_pr_changed_file_details || true)
 | |
| 		scripts_status=$(echo "$scripts_status" | grep -E "\.(sh|bash)$" || true)
 | |
| 
 | |
| 		scripts=$(echo "$scripts_status" | awk '{print $NF}')
 | |
| 	fi
 | |
| 
 | |
| 	[ -z "$scripts" ] && info "No scripts to check" && popd && return 0
 | |
| 
 | |
| 	local script
 | |
| 
 | |
| 	for script in $scripts
 | |
| 	do
 | |
| 		info "Checking script file '$script'"
 | |
| 
 | |
| 		local ret
 | |
| 
 | |
| 		{ $chronic bash -n "$script"; ret=$?; } || true
 | |
| 
 | |
| 		[ "$ret" -eq 0 ] || die "check for script '$script' failed"
 | |
| 
 | |
| 		static_check_eof "$script"
 | |
| 	done
 | |
| 
 | |
| 	popd
 | |
| }
 | |
| 
 | |
| static_check_json()
 | |
| {
 | |
| 	local all_json
 | |
| 	local json_files
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	need_chronic
 | |
| 
 | |
| 	all_json=$(git ls-files "*.json" | grep -Ev "/(vendor|grpc-rs|target)/" | sort || true)
 | |
| 
 | |
| 	if [ "$specific_branch" = "true" ]
 | |
| 	then
 | |
| 		info "Checking all JSON in $branch branch"
 | |
| 		json_files="$all_json"
 | |
| 	else
 | |
| 		info "Checking local branch for changed JSON only"
 | |
| 
 | |
| 		local json_status
 | |
| 		json_status=$(get_pr_changed_file_details || true)
 | |
| 		json_status=$(echo "$json_status" | grep "\.json$" || true)
 | |
| 
 | |
| 		json_files=$(echo "$json_status" | awk '{print $NF}')
 | |
| 	fi
 | |
| 
 | |
| 	[ -z "$json_files" ] && info "No JSON files to check" && popd && return 0
 | |
| 
 | |
| 	local json
 | |
| 
 | |
| 	for json in $json_files
 | |
| 	do
 | |
| 		info "Checking JSON file '$json'"
 | |
| 
 | |
| 		local ret
 | |
| 
 | |
| 		{ $chronic jq -S . "$json"; ret=$?; } || true
 | |
| 
 | |
| 		[ "$ret" -eq 0 ] || die "failed to check JSON file '$json'"
 | |
| 	done
 | |
| 
 | |
| 	popd
 | |
| }
 | |
| 
 | |
| # The dockerfile checker relies on the hadolint tool. This function handle its
 | |
| # installation if it is not found on PATH.
 | |
| # Note that we need a specific version of the tool as it seems to not have
 | |
| # backward/forward compatibility between versions.
 | |
| has_hadolint_or_install()
 | |
| {
 | |
| 	# Global variable set by the caller. It might be overwritten here.
 | |
| 	linter_cmd=${linter_cmd:-"hadolint"}
 | |
| 	local linter_version=$(get_test_version "externals.hadolint.version")
 | |
| 	local linter_url=$(get_test_version "externals.hadolint.url")
 | |
| 	local linter_dest="${GOPATH}/bin/hadolint"
 | |
| 
 | |
| 	local has_linter=$(command -v "$linter_cmd")
 | |
| 	if [[ -z "$has_linter" && "$KATA_DEV_MODE" == "yes" ]]; then
 | |
| 		# Do not install if it is in development mode.
 | |
| 		die "$linter_cmd command not found. You must have the version $linter_version installed to run this check."
 | |
| 	elif [ -n "$has_linter" ]; then
 | |
| 		# Check if the expected linter version
 | |
| 		if $linter_cmd --version | grep -v "$linter_version" &>/dev/null; then
 | |
| 			warn "$linter_cmd command found but not the required version $linter_version"
 | |
| 			has_linter=""
 | |
| 		fi
 | |
| 	fi
 | |
| 
 | |
| 	if [ -z "$has_linter" ]; then
 | |
| 		local download_url="${linter_url}/releases/download/v${linter_version}/hadolint-Linux-x86_64"
 | |
| 		info "Installing $linter_cmd $linter_version at $linter_dest"
 | |
| 
 | |
| 		curl -sfL "$download_url" -o "$linter_dest" || \
 | |
| 			die "Failed to download $download_url"
 | |
| 		chmod +x "$linter_dest"
 | |
| 
 | |
| 		# Overwrite in case it cannot be found in PATH.
 | |
| 		linter_cmd="$linter_dest"
 | |
| 	fi
 | |
| }
 | |
| 
 | |
| static_check_dockerfiles()
 | |
| {
 | |
| 	local all_files
 | |
| 	local files
 | |
| 	local ignore_files
 | |
| 	# Put here a list of files which should be ignored.
 | |
|         local ignore_files=(
 | |
|         )
 | |
| 
 | |
| 	pushd $repo_path
 | |
| 
 | |
| 	local linter_cmd="hadolint"
 | |
| 
 | |
|         all_files=$(git ls-files "*/Dockerfile*" | grep -Ev "/(vendor|grpc-rs|target)/" | sort || true)
 | |
| 
 | |
|         if [ "$specific_branch" = "true" ]; then
 | |
|                 info "Checking all Dockerfiles in $branch branch"
 | |
| 		files="$all_files"
 | |
|         else
 | |
|                 info "Checking local branch for changed Dockerfiles only"
 | |
| 
 | |
|                 local files_status
 | |
| 		files_status=$(get_pr_changed_file_details || true)
 | |
| 		files_status=$(echo "$files_status" | grep -E "Dockerfile.*$" || true)
 | |
| 
 | |
| 		files=$(echo "$files_status" | awk '{print $NF}')
 | |
|         fi
 | |
| 
 | |
|         [ -z "$files" ] && info "No Dockerfiles to check" && popd && return 0
 | |
| 
 | |
| 	# As of this writing hadolint is only distributed for x86_64
 | |
| 	if [ "$(uname -m)" != "x86_64" ]; then
 | |
| 		info "Skip checking as $linter_cmd is not available for $(uname -m)"
 | |
| 		popd
 | |
| 		return 0
 | |
| 	fi
 | |
| 	has_hadolint_or_install
 | |
| 
 | |
| 	linter_cmd+=" --no-color"
 | |
| 
 | |
| 	# Let's not fail with INFO rules.
 | |
| 	linter_cmd+=" --failure-threshold warning"
 | |
| 
 | |
| 	# Some rules we don't want checked, below we ignore them.
 | |
| 	#
 | |
| 	# "DL3008 warning: Pin versions in apt get install"
 | |
| 	linter_cmd+=" --ignore DL3008"
 | |
| 	# "DL3041 warning: Specify version with `dnf install -y <package>-<version>`"
 | |
| 	linter_cmd+=" --ignore DL3041"
 | |
| 	# "DL3033 warning: Specify version with `yum install -y <package>-<version>`"
 | |
| 	linter_cmd+=" --ignore DL3033"
 | |
| 	# "DL3018 warning: Pin versions in apk add. Instead of `apk add <package>` use `apk add <package>=<version>`"
 | |
| 	linter_cmd+=" --ignore DL3018"
 | |
| 	# "DL3003 warning: Use WORKDIR to switch to a directory"
 | |
| 	# See https://github.com/hadolint/hadolint/issues/70
 | |
| 	linter_cmd+=" --ignore DL3003"
 | |
| 	# "DL3048 style: Invalid label key"
 | |
| 	linter_cmd+=" --ignore DL3048"
 | |
|         # DL3037 warning: Specify version with `zypper install -y <package>=<version>`.
 | |
| 	linter_cmd+=" --ignore DL3037"
 | |
| 
 | |
| 	# Temporary add to prevent failure for test migration
 | |
| 	# DL3040 warning: `dnf clean all` missing after dnf command.
 | |
| 	linter_cmd+=" --ignore DL3040"
 | |
| 
 | |
| 	local file
 | |
| 	for file in $files; do
 | |
| 		if echo "${ignore_files[@]}" | grep -q $file ; then
 | |
| 			info "Ignoring Dockerfile '$file'"
 | |
| 			continue
 | |
| 		fi
 | |
| 
 | |
| 		info "Checking Dockerfile '$file'"
 | |
| 		local ret
 | |
| 		# The linter generates an Abstract Syntax Tree (AST) from the
 | |
| 		# dockerfile. Some of our dockerfiles are actually templates
 | |
| 		# with special syntax, thus the linter might fail to build
 | |
| 		# the AST. Here we handle Dockerfile templates.
 | |
| 		if [[ "$file" =~ Dockerfile.*\.(in|template)$ ]]; then
 | |
| 			# In our templates, text with marker as @SOME_NAME@ is
 | |
| 			# replaceable. Usually it is used to replace in a
 | |
| 			# FROM command (e.g. `FROM @UBUNTU_REGISTRY@/ubuntu`)
 | |
| 			# but also to add an entire block of commands. Example
 | |
| 			# of later:
 | |
| 			# ```
 | |
| 			# RUN apt-get install -y package1
 | |
| 			# @INSTALL_MUSL@
 | |
| 			# @INSTALL_RUST@
 | |
| 			# ```
 | |
| 			# It's known that the linter will fail to parse lines
 | |
| 			# started with `@`. Also it might give false-positives
 | |
| 		        # on some cases. Here we remove all markers as a best
 | |
| 			# effort approach. If the template file is still
 | |
| 			# unparseable then it should be added in the
 | |
| 			# `$ignore_files` list.
 | |
| 			{ sed -e 's/^@[A-Z_]*@//' -e 's/@\([a-zA-Z_]*\)@/\1/g' "$file" | $linter_cmd -; ret=$?; }\
 | |
| 				|| true
 | |
| 		else
 | |
| 			# Non-template Dockerfile.
 | |
| 			{ $linter_cmd "$file"; ret=$?; } || true
 | |
| 		fi
 | |
| 
 | |
| 		[ "$ret" -eq 0 ] || die "failed to check Dockerfile '$file'"
 | |
| 	done
 | |
| 	popd
 | |
| }
 | |
| 
 | |
| static_check_rego()
 | |
| {
 | |
| 	local rego_files
 | |
| 	rego_files=$(git ls-files | grep -E '.*\.rego$')
 | |
| 
 | |
| 	interpreters=("opa" "regorus")
 | |
| 	for interpreter in "${interpreters[@]}"
 | |
| 	do
 | |
| 		if ! command -v "${interpreter}" &>/dev/null; then
 | |
| 			die "Required rego interpreter '${interpreter}' not found in PATH"
 | |
| 		fi
 | |
| 	done
 | |
| 
 | |
| 	found_unparsable=0
 | |
| 	for file in ${rego_files}
 | |
| 	do
 | |
| 		for interpreter in "${interpreters[@]}"
 | |
| 			do
 | |
| 			if ! ${interpreter} parse "${file}" > /dev/null; then
 | |
| 				info "Failed to parse Rego file '${file}' with ${interpreter}"
 | |
| 				found_unparsable=1
 | |
| 			else
 | |
| 				info "Successfully parsed Rego file '${file}' with ${interpreter}"
 | |
| 			fi
 | |
| 		done
 | |
| 	done
 | |
| 
 | |
| 	if [[ ${found_unparsable} -ne 0 ]]; then
 | |
| 		die "Unparsable rego files found"
 | |
| 	fi
 | |
| }
 | |
| 
 | |
| # Run the specified function (after first checking it is compatible with the
 | |
| # users architectural preferences), or simply list the function name if list
 | |
| # mode is active.
 | |
| run_or_list_check_function()
 | |
| {
 | |
| 	local name="$1"
 | |
| 
 | |
| 	func_is_valid "$name"
 | |
| 
 | |
| 	local arch_func
 | |
| 	local handler
 | |
| 
 | |
| 	arch_func=$(func_is_arch_specific "$name")
 | |
| 
 | |
| 	handler="info"
 | |
| 
 | |
| 	# If the user requested only a single function to run, we should die
 | |
| 	# if the function cannot be run due to the other options specified.
 | |
| 	#
 | |
| 	# Whereas if this script is running all functions, just display an
 | |
| 	# info message if a function cannot be run.
 | |
| 	[ "$single_func_only" = "true" ] && handler="die"
 | |
| 
 | |
| 	if [ "$handle_funcs" = "arch-agnostic" ] && [ "$arch_func" = "yes" ]; then
 | |
| 		if [ "$list_only" != "true" ]; then
 | |
| 			"$handler" "Not running '$func' as requested no architecture-specific functions"
 | |
| 		fi
 | |
| 
 | |
| 		return 0
 | |
| 	fi
 | |
| 
 | |
| 	if [ "$handle_funcs" = "arch-specific" ] && [ "$arch_func" = "no" ]; then
 | |
| 		if [ "$list_only" != "true" ]; then
 | |
| 			"$handler" "Not running architecture-agnostic function '$func' as requested only architecture specific functions"
 | |
| 		fi
 | |
| 
 | |
| 		return 0
 | |
| 	fi
 | |
| 
 | |
| 	if [ "$list_only" = "true" ]; then
 | |
| 		echo "$func"
 | |
| 		return 0
 | |
| 	fi
 | |
| 
 | |
| 	info "Running '$func' function"
 | |
| 	eval "$func"
 | |
| }
 | |
| 
 | |
| setup()
 | |
| {
 | |
| 	source /etc/os-release || source /usr/lib/os-release
 | |
| 
 | |
| 	trap remove_tmp_files EXIT
 | |
| }
 | |
| 
 | |
| # Display a message showing some system details.
 | |
| announce()
 | |
| {
 | |
| 	local arch
 | |
| 	arch=$(uname -m)
 | |
| 
 | |
| 	local file='/proc/cpuinfo'
 | |
| 
 | |
| 	local detail
 | |
| 	detail=$(grep -m 1 -E '\<vendor_id\>|\<cpu\> *	*:' "$file" \
 | |
| 		2>/dev/null |\
 | |
| 		cut -d: -f2- |\
 | |
| 		tr -d ' ' || true)
 | |
| 
 | |
| 	local arch="$arch"
 | |
| 
 | |
| 	[ -n "$detail" ] && arch+=" ('$detail')"
 | |
| 
 | |
| 	local kernel
 | |
| 	kernel=$(uname -r)
 | |
| 
 | |
| 	local distro_name
 | |
| 	local distro_version
 | |
| 
 | |
| 	distro_name="${NAME:-}"
 | |
| 	distro_version="${VERSION:-}"
 | |
| 
 | |
| 	local -a lines
 | |
| 
 | |
| 	local IFS=$'\n'
 | |
| 
 | |
|     lines=( $(cat <<-EOF
 | |
| 	Running static checks:
 | |
| 	  script: $script_name
 | |
| 	  architecture: $arch
 | |
| 	  kernel: $kernel
 | |
| 	  distro:
 | |
| 	    name: $distro_name
 | |
| 	    version: $distro_version
 | |
| 	EOF
 | |
| 	))
 | |
| 
 | |
| 	local line
 | |
| 
 | |
| 	for line in "${lines[@]}"
 | |
| 	do
 | |
| 		info "$line"
 | |
| 	done
 | |
| }
 | |
| 
 | |
| main()
 | |
| {
 | |
| 	setup
 | |
| 
 | |
| 	local long_option_names="${!long_options[@]}"
 | |
| 
 | |
| 	local args
 | |
| 
 | |
| 	args=$(getopt \
 | |
| 		-n "$script_name" \
 | |
| 		-a \
 | |
| 		--options="h" \
 | |
| 		--longoptions="$long_option_names" \
 | |
| 		-- "$@")
 | |
| 	[ $? -eq 0 ] || { usage >&2; exit 1; }
 | |
| 
 | |
| 	eval set -- "$args"
 | |
| 
 | |
| 	local func=
 | |
| 
 | |
| 	while [ $# -gt 1 ]
 | |
| 	do
 | |
| 		case "$1" in
 | |
| 			--all) specific_branch="true" ;;
 | |
| 			--branch) branch="$2"; shift ;;
 | |
| 			--commits) func=static_check_commits ;;
 | |
| 			--docs) func=static_check_docs ;;
 | |
| 			--dockerfiles) func=static_check_dockerfiles ;;
 | |
| 			--files) func=static_check_files ;;
 | |
| 			--force) force="true" ;;
 | |
| 			--golang) func=static_check_go_arch_specific ;;
 | |
| 			-h|--help) usage; exit 0 ;;
 | |
| 			--json) func=static_check_json ;;
 | |
| 			--labels) func=static_check_labels;;
 | |
| 			--licenses) func=static_check_license_headers ;;
 | |
| 			--list) list_only="true" ;;
 | |
| 			--no-arch) handle_funcs="arch-agnostic" ;;
 | |
| 			--only-arch) handle_funcs="arch-specific" ;;
 | |
| 			--rego) func=static_check_rego ;;
 | |
| 			--repo) repo="$2"; shift ;;
 | |
| 			--scripts) func=static_check_shell ;;
 | |
| 			--vendor) func=static_check_vendor;;
 | |
| 			--versions) func=static_check_versions ;;
 | |
| 			--xml) func=static_check_xml ;;
 | |
| 			--) shift; break ;;
 | |
| 		esac
 | |
| 
 | |
| 		shift
 | |
| 	done
 | |
| 
 | |
| 	# Consume getopt cruft
 | |
| 	[ "$1" = "--" ] && shift
 | |
| 
 | |
| 	[ "$1" = "help" ] && usage && exit 0
 | |
| 
 | |
| 	# Set if not already set by options
 | |
| 	[ -z "$repo" ] && repo="$1"
 | |
| 	[ "$specific_branch" = "false" ] && specific_branch="$2"
 | |
| 
 | |
| 	if [ -z "$repo" ]
 | |
| 	then
 | |
| 		if [ -n "$KATA_DEV_MODE" ]
 | |
| 		then
 | |
| 			# No repo param provided so assume it's the current
 | |
| 			# one to avoid developers having to specify one now
 | |
| 			# (backwards compatability).
 | |
| 			repo=$(git config --get remote.origin.url |\
 | |
| 				sed 's!https://!!g' || true)
 | |
| 
 | |
| 			info "Auto-detected repo as $repo"
 | |
| 		else
 | |
| 			if [ "$list_only" != "true" ]; then
 | |
| 				echo >&2 "ERROR: need repo" && usage && exit 1
 | |
| 			fi
 | |
| 		fi
 | |
| 	fi
 | |
| 
 | |
| 	repo_path=$GOPATH/src/$repo
 | |
| 
 | |
| 	announce
 | |
| 
 | |
| 	local all_check_funcs=$(typeset -F|awk '{print $3}'|grep "${check_func_regex}"|sort)
 | |
| 
 | |
| 	# Run user-specified check and quit
 | |
| 	if [ -n "$func" ]; then
 | |
| 		single_func_only="true"
 | |
| 		run_or_list_check_function "$func"
 | |
| 		exit 0
 | |
| 	fi
 | |
| 
 | |
| 	for func in $all_check_funcs
 | |
| 	do
 | |
| 		run_or_list_check_function "$func"
 | |
| 	done
 | |
| }
 | |
| 
 | |
| main "$@"
 |