superlint/lib/functions/worker.sh

450 lines
18 KiB
Bash
Raw Normal View History

2020-06-29 10:55:59 -04:00
#!/usr/bin/env bash
################################################################################
################################################################################
########### Super-Linter linting Functions @admiralawkbar ######################
################################################################################
################################################################################
########################## FUNCTION CALLS BELOW ################################
################################################################################
################################################################################
#### Function LintCodebase #####################################################
2020-07-01 17:40:40 -04:00
function LintCodebase() {
# Call comes through as:
2020-11-06 14:12:17 -05:00
# LintCodebase "${LANGUAGE}" "${LINTER_NAME}" "${LINTER_COMMAND}" "${FILTER_REGEX_INCLUDE}" "${FILTER_REGEX_EXCLUDE}" "${TEST_CASE_RUN}" "${!LANGUAGE_FILE_ARRAY}"
2020-06-29 10:55:59 -04:00
####################
# Pull in the vars #
####################
FILE_TYPE="${1}" && shift # Pull the variable and remove from array path (Example: JSON)
LINTER_NAME="${1}" && shift # Pull the variable and remove from array path (Example: jsonlint)
LINTER_COMMAND="${1}" && shift # Pull the variable and remove from array path (Example: jsonlint -c ConfigFile /path/to/file)
FILTER_REGEX_INCLUDE="${1}" && shift # Pull the variable and remove from array path (Example: */src/*,*/test/*)
FILTER_REGEX_EXCLUDE="${1}" && shift # Pull the variable and remove from array path (Example: */examples/*,*/test/*.test)
2020-11-06 14:12:17 -05:00
TEST_CASE_RUN="${1}" && shift # Flag for if running in test cases
FILE_ARRAY=("$@") # Array of files to validate (Example: ${FILE_ARRAY_JSON})
2020-06-29 10:55:59 -04:00
##########################
# Initialize empty Array #
##########################
LIST_FILES=()
###################################################
# Array to track directories where tflint was run #
###################################################
declare -A TFLINT_SEEN_DIRS
2020-06-29 10:55:59 -04:00
################
# Set the flag #
################
SKIP_FLAG=0
INDEX=0
2020-06-29 10:55:59 -04:00
# We use these flags to check how many "bad" and "good" test cases we ran
BAD_TEST_CASES_COUNT=0
GOOD_TEST_CASES_COUNT=0
2020-06-29 10:55:59 -04:00
############################################################
# Check to see if we need to go through array or all files #
############################################################
if [ ${#FILE_ARRAY[@]} -eq 0 ]; then
2020-06-29 10:55:59 -04:00
SKIP_FLAG=1
2020-07-30 16:39:05 -04:00
debug " - No files found in changeset to lint for language:[${FILE_TYPE}]"
else
2020-06-29 10:55:59 -04:00
# We have files added to array of files to check
LIST_FILES=("${FILE_ARRAY[@]}") # Copy the array into list
fi
debug "SKIP_FLAG: ${SKIP_FLAG}, list of files to lint: ${LIST_FILES[*]}"
2020-06-29 10:55:59 -04:00
###############################
# Check if any data was found #
###############################
2020-07-21 13:09:07 -04:00
if [ ${SKIP_FLAG} -eq 0 ]; then
WORKSPACE_PATH="${GITHUB_WORKSPACE}"
2020-10-19 15:05:38 -04:00
if [ "${TEST_CASE_RUN}" == "true" ]; then
debug "TEST_CASE_FOLDER: ${TEST_CASE_FOLDER}"
2020-11-06 14:28:37 -05:00
WORKSPACE_PATH="${GITHUB_WORKSPACE}/${TEST_CASE_FOLDER}"
fi
debug "Workspace path: ${WORKSPACE_PATH}"
################
# print header #
################
info ""
info "----------------------------------------------"
info "----------------------------------------------"
debug "Running LintCodebase. FILE_TYPE: ${FILE_TYPE}. Linter name: ${LINTER_NAME}, linter command: ${LINTER_COMMAND}, TEST_CASE_RUN: ${TEST_CASE_RUN}, FILTER_REGEX_INCLUDE: ${FILTER_REGEX_INCLUDE}, FILTER_REGEX_EXCLUDE: ${FILTER_REGEX_EXCLUDE}, files to lint: ${FILE_ARRAY[*]}"
if [ "${TEST_CASE_RUN}" = "true" ]; then
info "Testing Codebase [${FILE_TYPE}] files..."
else
info "Linting [${FILE_TYPE}] files..."
fi
info "----------------------------------------------"
info "----------------------------------------------"
2020-06-29 10:55:59 -04:00
##################
# Lint the files #
##################
2020-07-01 17:40:40 -04:00
for FILE in "${LIST_FILES[@]}"; do
2020-10-24 10:13:16 -04:00
debug "Linting FILE: ${FILE}"
2020-07-23 13:52:43 -04:00
###################################
# Get the file name and directory #
###################################
2020-07-21 13:09:07 -04:00
FILE_NAME=$(basename "${FILE}" 2>&1)
2020-07-23 13:52:43 -04:00
DIR_NAME=$(dirname "${FILE}" 2>&1)
2020-06-29 10:55:59 -04:00
############################
# Get the file pass status #
############################
# Example: markdown_good_1.md -> good
FILE_STATUS=$(echo "${FILE_NAME}" | cut -f2 -d'_')
# Example: clan_format_good_1.md -> good
SECONDARY_STATUS=$(echo "${FILE_NAME}" | cut -f3 -d'_')
####################################
# Catch edge cases of double names #
####################################
if [ "${SECONDARY_STATUS}" == 'good' ] || [ "${SECONDARY_STATUS}" == 'bad' ]; then
FILE_STATUS="${SECONDARY_STATUS}"
fi
2020-10-24 10:13:16 -04:00
###################
# Check if docker #
###################
if [[ ${FILE_TYPE} == *"DOCKER"* ]]; then
2020-10-24 10:13:16 -04:00
debug "FILE_TYPE for FILE ${FILE} is related to Docker: ${FILE_TYPE}"
if [[ ${FILE} == *"good"* ]]; then
2020-10-24 10:13:16 -04:00
debug "Setting FILE_STATUS for FILE ${FILE} to 'good'"
#############
# Good file #
#############
FILE_STATUS='good'
2020-10-24 10:13:16 -04:00
elif [[ ${FILE} == *"bad"* ]]; then
debug "Setting FILE_STATUS for FILE ${FILE} to 'bad'"
############
# Bad file #
############
FILE_STATUS='bad'
fi
fi
#####################
# Check if Renovate #
#####################
if [[ ${FILE_TYPE} == *"RENOVATE"* ]]; then
debug "FILE_TYPE for FILE ${FILE} is related to Renovate: ${FILE_TYPE}"
if [[ ${FILE} == *"good"* ]]; then
debug "Setting FILE_STATUS for FILE ${FILE} to 'good'"
#############
# Good file #
#############
FILE_STATUS='good'
elif [[ ${FILE} == *"bad"* ]]; then
debug "Setting FILE_STATUS for FILE ${FILE} to 'bad'"
############
# Bad file #
############
FILE_STATUS='bad'
fi
fi
#######################################
# Check if Cargo.toml for Rust Clippy #
#######################################
if [[ ${FILE_TYPE} == *"RUST"* ]] && [[ ${LINTER_NAME} == "clippy" ]]; then
debug "FILE_TYPE for FILE ${FILE} is related to Rust Clippy: ${FILE_TYPE}"
if [[ ${FILE} == *"good"* ]]; then
debug "Setting FILE_STATUS for FILE ${FILE} to 'good'"
#############
# Good file #
#############
FILE_STATUS='good'
elif [[ ${FILE} == *"bad"* ]]; then
debug "Setting FILE_STATUS for FILE ${FILE} to 'bad'"
############
# Bad file #
############
FILE_STATUS='bad'
fi
fi
2020-10-24 10:13:16 -04:00
#########################################################
# If not found, assume it should be linted successfully #
#########################################################
if [ -z "${FILE_STATUS}" ] || { [ "${FILE_STATUS}" != "good" ] && [ "${FILE_STATUS}" != "bad" ]; }; then
debug "FILE_STATUS (${FILE_STATUS}) is empty, or not set to 'good' or 'bad'. Assuming it should be linted correctly. Setting FILE_STATUS to 'good'..."
FILE_STATUS="good"
fi
INDIVIDUAL_TEST_FOLDER="${FILE_TYPE,,}" # Folder for specific tests. By convention, it's the lowercased FILE_TYPE
TEST_CASE_DIRECTORY="${TEST_CASE_FOLDER}/${INDIVIDUAL_TEST_FOLDER}"
debug "File: ${FILE}, FILE_NAME: ${FILE_NAME}, DIR_NAME:${DIR_NAME}, FILE_STATUS: ${FILE_STATUS}, INDIVIDUAL_TEST_FOLDER: ${INDIVIDUAL_TEST_FOLDER}, TEST_CASE_DIRECTORY: ${TEST_CASE_DIRECTORY}"
if [[ ${FILE_TYPE} != "ANSIBLE" ]]; then
# These linters expect files inside a directory, not a directory. So we add a trailing slash
TEST_CASE_DIRECTORY="${TEST_CASE_DIRECTORY}/"
debug "Updated TEST_CASE_DIRECTORY for ${FILE_TYPE} to: ${TEST_CASE_DIRECTORY}"
fi
if [[ ${FILE} != *"${TEST_CASE_DIRECTORY}"* ]] && [ "${TEST_CASE_RUN}" == "true" ]; then
debug "Skipping ${FILE} because it's not in the test case directory for ${FILE_TYPE}..."
2020-10-08 09:18:08 -04:00
continue
2020-06-29 10:55:59 -04:00
fi
##################################
# Increase the linted file index #
##################################
(("INDEX++"))
2020-06-29 10:55:59 -04:00
##############
# File print #
##############
2020-07-30 16:39:05 -04:00
info "---------------------------"
info "File:[${FILE}]"
2020-06-29 10:55:59 -04:00
2020-07-21 09:23:32 -04:00
#################################
# Add the language to the array #
#################################
2020-07-21 13:09:07 -04:00
LINTED_LANGUAGES_ARRAY+=("${FILE_TYPE}")
2020-06-29 10:55:59 -04:00
####################
# Set the base Var #
####################
LINT_CMD=''
#####################
# Check for ansible #
#####################
if [[ ${FILE_TYPE} == "ANSIBLE" ]]; then
LINT_CMD=$(
cd "${FILE}" || exit
# Don't pass the file to lint to enable ansible-lint autodetection mode.
# See https://ansible-lint.readthedocs.io/usage for details
${LINTER_COMMAND} 2>&1
)
2020-07-02 17:31:16 -04:00
####################################
# Corner case for pwsh subshell #
# - PowerShell (PSScriptAnalyzer) #
# - ARM (arm-ttk) #
####################################
elif [[ ${FILE_TYPE} == "POWERSHELL" ]] || [[ ${FILE_TYPE} == "ARM" ]]; then
2020-06-29 10:55:59 -04:00
################################
# Lint the file with the rules #
################################
# Need to run PowerShell commands using pwsh -c, also exit with exit code from inner subshell
2020-07-01 17:40:40 -04:00
LINT_CMD=$(
cd "${WORKSPACE_PATH}" || exit
2020-11-10 12:23:15 -05:00
pwsh -NoProfile -NoLogo -Command "${LINTER_COMMAND} \"${FILE}\"; if (\${Error}.Count) { exit 1 }"
2020-07-01 17:40:40 -04:00
exit $? 2>&1
)
2020-07-23 13:52:43 -04:00
###############################################################################
2020-08-15 15:29:22 -04:00
# Corner case for R as we have to pass it to R #
###############################################################################
elif [[ ${FILE_TYPE} == "R" ]]; then
#######################################
# Lint the file with the updated path #
#######################################
if [ ! -f "${DIR_NAME}/.lintr" ]; then
r_dir="${WORKSPACE_PATH}"
else
r_dir="${DIR_NAME}"
fi
2020-08-15 15:29:22 -04:00
LINT_CMD=$(
cd "$r_dir" || exit
R --slave -e "lints <- lintr::lint('$FILE');print(lints);errors <- purrr::keep(lints, ~ .\$type == 'error');quit(save = 'no', status = if (length(errors) > 0) 1 else 0)" 2>&1
2020-08-15 15:29:22 -04:00
)
2020-08-28 14:44:11 -04:00
#########################################################
# Corner case for C# as it writes to tty and not stdout #
#########################################################
elif [[ ${FILE_TYPE} == "CSHARP" ]]; then
LINT_CMD=$(
2020-08-31 11:11:12 -04:00
cd "${DIR_NAME}" || exit
${LINTER_COMMAND} "${FILE_NAME}" | tee /dev/tty2 2>&1
exit "${PIPESTATUS[0]}"
2020-08-28 14:44:11 -04:00
)
###########################################################################################
# Corner case for GO_MODULES because it expects that the working directory is a Go module #
###########################################################################################
elif [[ ${FILE_TYPE} == "GO_MODULES" ]]; then
debug "Linting a Go module. Changing the working directory to ${FILE} before running the linter."
LINT_CMD=$(
cd "${FILE}" || exit 1
${LINTER_COMMAND} 2>&1
)
#######################################################
# Corner case for KTLINT as it cant use the full path #
#######################################################
elif [[ ${FILE_TYPE} == "KOTLIN" ]]; then
LINT_CMD=$(
cd "${DIR_NAME}" || exit
${LINTER_COMMAND} "${FILE_NAME}" 2>&1
)
2023-09-12 12:58:09 -04:00
######################
# Check for Renovate #
######################
elif [[ ${FILE_TYPE} == "RENOVATE" ]]; then
LINT_CMD=$(
cd "${WORKSPACE_PATH}" || exit
RENOVATE_CONFIG_FILE="${FILE}" ${LINTER_COMMAND} 2>&1
)
#############################################################################################
# Corner case for TERRAFORM_TFLINT as it can't use the full path and needs to fetch modules #
#############################################################################################
elif [[ ${FILE_TYPE} == "TERRAFORM_TFLINT" ]]; then
# Check the cache to see if we've already prepped this directory for tflint
if [[ ! -v "TFLINT_SEEN_DIRS[${DIR_NAME}]" ]]; then
debug " Setting up TERRAFORM_TFLINT cache for ${DIR_NAME}"
TF_DOT_DIR="${DIR_NAME}/.terraform"
if [ -d "${TF_DOT_DIR}" ]; then
# Just in case there's something in the .terraform folder, keep a copy of it
TF_BACKUP_DIR="/tmp/.terraform-tflint-backup${DIR_NAME}"
debug " Backing up ${TF_DOT_DIR} to ${TF_BACKUP_DIR}"
mkdir -p "${TF_BACKUP_DIR}"
cp -r "${TF_DOT_DIR}" "${TF_BACKUP_DIR}"
# Store the destination directory so we can restore from our copy later
TFLINT_SEEN_DIRS[${DIR_NAME}]="${TF_BACKUP_DIR}"
else
# Just let the cache know we've seen this before
TFLINT_SEEN_DIRS[${DIR_NAME}]='false'
fi
(
cd "${DIR_NAME}" || exit
terraform get >/dev/null
)
fi
LINT_CMD=$(
cd "${DIR_NAME}" || exit
${LINTER_COMMAND} --filter="${FILE_NAME}" 2>&1
)
2020-06-29 10:55:59 -04:00
else
################################
# Lint the file with the rules #
################################
2020-07-01 17:40:40 -04:00
LINT_CMD=$(
cd "${WORKSPACE_PATH}" || exit
2020-07-21 13:09:07 -04:00
${LINTER_COMMAND} "${FILE}" 2>&1
2020-07-01 17:40:40 -04:00
)
2020-06-29 10:55:59 -04:00
fi
#######################
# Load the error code #
#######################
ERROR_CODE=$?
########################################
# Check for if it was supposed to pass #
########################################
if [[ ${FILE_STATUS} == "good" ]]; then
# Increase the good test cases count
(("GOOD_TEST_CASES_COUNT++"))
##############################
# Check the shell for errors #
##############################
if [ ${ERROR_CODE} -ne 0 ]; then
debug "Found errors. Error code: ${ERROR_CODE}, File type: ${FILE_TYPE}, Error on missing exec bit: ${ERROR_ON_MISSING_EXEC_BIT}"
if [[ ${FILE_TYPE} == "BASH_EXEC" ]] && [[ "${ERROR_ON_MISSING_EXEC_BIT}" == "false" ]]; then
########
# WARN #
########
warn "Warnings found in [${LINTER_NAME}] linter!"
warn "${LINT_CMD}"
else
#########
# Error #
#########
error "Found errors in [${LINTER_NAME}] linter!"
error "Error code: ${ERROR_CODE}. Command output:${NC}\n------\n${LINT_CMD}\n------"
# Increment the error count
(("ERRORS_FOUND_${FILE_TYPE}++"))
fi
2020-08-28 11:08:21 -04:00
else
###########
# Success #
###########
info " - File:${F[W]}[${FILE_NAME}]${F[B]} was linted with ${F[W]}[${LINTER_NAME}]${F[B]} successfully"
if [ -n "${LINT_CMD}" ]; then
info " - Command output:${NC}\n------\n${LINT_CMD}\n------"
fi
fi
else
#######################################
# File status = bad, this should fail #
#######################################
# Increase the bad test cases count
(("BAD_TEST_CASES_COUNT++"))
##############################
# Check the shell for errors #
##############################
if [ ${ERROR_CODE} -eq 0 ]; then
2020-08-28 11:08:21 -04:00
#########
# Error #
#########
error "Found errors in [${LINTER_NAME}] linter!"
error "This file should have failed test case!"
error "Error code: ${ERROR_CODE}. Command output:${NC}\n------\n${LINT_CMD}\n------"
2020-08-28 11:08:21 -04:00
# Increment the error count
(("ERRORS_FOUND_${FILE_TYPE}++"))
else
###########
# Success #
###########
info " - File:${F[W]}[${FILE_NAME}]${F[B]} failed test case (Error code: ${ERROR_CODE}) with ${F[W]}[${LINTER_NAME}]${F[B]} successfully"
2020-08-28 11:08:21 -04:00
fi
2020-06-29 10:55:59 -04:00
fi
debug "Error code: ${ERROR_CODE}. Command output:${NC}\n------\n${LINT_CMD}\n------"
2020-06-29 10:55:59 -04:00
done
fi
2020-06-29 10:55:59 -04:00
# Clean up after TFLINT
for TF_DIR in "${!TFLINT_SEEN_DIRS[@]}"; do
(
cd "${TF_DIR}" || exit
rm -rf .terraform
# Check to see if there was a .terraform folder there before we got started, restore it if so
POTENTIAL_BACKUP_DIR="${TFLINT_SEEN_DIRS[${TF_DIR}]}"
if [[ "${POTENTIAL_BACKUP_DIR}" != 'false' ]]; then
# Put the copy back in place
debug " Restoring ${TF_DIR}/.terraform from ${POTENTIAL_BACKUP_DIR}"
mv "${POTENTIAL_BACKUP_DIR}/.terraform" .terraform
fi
)
done
if [ "${TEST_CASE_RUN}" = "true" ]; then
debug "The ${LINTER_NAME} (linter: ${LINTER_NAME}) test suite has ${INDEX} test, of which ${BAD_TEST_CASES_COUNT} 'bad' (supposed to fail), ${GOOD_TEST_CASES_COUNT} 'good' (supposed to pass)."
# Check if we ran at least one test
if [ "${INDEX}" -eq 0 ]; then
error "Failed to find any tests ran for the Linter:[${LINTER_NAME}]!"
fatal "Validate logic and that tests exist for linter: ${LINTER_NAME}"
fi
# Check if we ran 'bad' tests
if [ "${BAD_TEST_CASES_COUNT}" -eq 0 ]; then
if [ "${FILE_TYPE}" = "ANSIBLE" ]; then
debug "There are no 'bad' tests for ${FILE_TYPE}, but it's a corner case that we allow because ${LINTER_NAME} is supposed to lint entire directories and the test suite doesn't support this corner case for 'bad' tests yet."
else
error "Failed to find any tests that are expected to fail for the Linter:[${LINTER_NAME}]!"
fatal "Validate logic and that tests that are expected to fail exist for linter: ${LINTER_NAME}"
fi
fi
# Check if we ran 'good' tests
if [ "${GOOD_TEST_CASES_COUNT}" -eq 0 ]; then
error "Failed to find any tests that are expected to pass for the Linter:[${LINTER_NAME}]!"
fatal "Validate logic and that tests that are expected to pass exist for linter: ${LINTER_NAME}"
fi
2020-07-14 09:48:28 -04:00
fi
2020-06-29 10:55:59 -04:00
}