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( ) {
2021-10-05 09:46:38 -04:00
# 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 #
####################
2020-09-05 06:25:44 -04:00
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
2020-09-05 06:25:44 -04:00
FILE_ARRAY = ( " $@ " ) # Array of files to validate (Example: ${FILE_ARRAY_JSON})
2020-06-29 10:55:59 -04:00
##########################
# Initialize empty Array #
##########################
LIST_FILES = ( )
2022-02-14 10:23:17 -05:00
###################################################
# 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
2021-01-27 14:47:34 -05:00
INDEX = 0
2020-06-29 10:55:59 -04:00
2023-01-11 11:24:48 -05: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 #
############################################################
2020-10-03 06:55:34 -04:00
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 } ] "
2020-10-03 06:55:34 -04:00
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
2020-10-14 04:59:34 -04:00
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
2020-10-14 04:59:34 -04:00
WORKSPACE_PATH = " ${ GITHUB_WORKSPACE } "
2020-10-19 15:05:38 -04:00
if [ " ${ TEST_CASE_RUN } " = = "true" ] ; then
2020-11-06 14:28:37 -05:00
WORKSPACE_PATH = " ${ GITHUB_WORKSPACE } / ${ TEST_CASE_FOLDER } "
2020-10-14 04:59:34 -04:00
fi
debug " Workspace path: ${ WORKSPACE_PATH } "
2020-11-30 12:19:17 -05:00
################
# 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
2020-10-14 04:59:34 -04:00
############################
# Get the file pass status #
############################
# Example: markdown_good_1.md -> good
FILE_STATUS = $( echo " ${ FILE_NAME } " | cut -f2 -d'_' )
2021-08-30 10:47:50 -04:00
# 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-14 04:59:34 -04:00
2020-10-24 10:13:16 -04:00
###################
# Check if docker #
###################
2020-10-14 04:59:34 -04:00
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 } "
2020-10-14 04:59:34 -04:00
if [ [ ${ FILE } = = *"good" * ] ] ; then
2020-10-24 10:13:16 -04:00
debug " Setting FILE_STATUS for FILE ${ FILE } to 'good' "
2020-10-14 04:59:34 -04:00
#############
# 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' "
2020-10-14 04:59:34 -04:00
############
# Bad file #
############
FILE_STATUS = 'bad'
fi
fi
2021-03-08 15:13:04 -05:00
#######################################
# 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
2020-10-14 04:59:34 -04:00
INDIVIDUAL_TEST_FOLDER = " ${ FILE_TYPE ,, } " # Folder for specific tests. By convention, it's the lowercased FILE_TYPE
2022-03-14 15:47:04 -04:00
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 } "
2020-10-14 04:59:34 -04:00
2022-03-14 15:47:04 -04:00
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 " ${ FILE_TYPE } expects to lint individual files. Updated TEST_CASE_DIRECTORY to: ${ TEST_CASE_DIRECTORY } "
fi
2020-10-14 04:59:34 -04:00
2022-03-14 15:47:04 -04:00
if [ [ ${ FILE } != *" ${ TEST_CASE_DIRECTORY } " * ] ] && [ " ${ TEST_CASE_RUN } " = = "true" ] ; then
2020-10-14 04:59:34 -04:00
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
2020-07-06 13:46:51 -04:00
##################################
# 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 = ''
2020-10-14 04:59:34 -04:00
#####################
# Check for ansible #
#####################
if [ [ ${ FILE_TYPE } = = "ANSIBLE" ] ] ; then
2022-09-27 03:59:15 -04:00
debug " ANSIBLE_DIRECTORY: ${ ANSIBLE_DIRECTORY } , LINTER_COMMAND: ${ LINTER_COMMAND } , FILE: ${ FILE } "
2020-10-14 04:59:34 -04:00
LINT_CMD = $(
2022-09-27 03:59:15 -04:00
cd " ${ ANSIBLE_DIRECTORY } " || 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-10-14 04:59:34 -04:00
)
2020-07-02 17:31:16 -04:00
####################################
# Corner case for pwsh subshell #
# - PowerShell (PSScriptAnalyzer) #
# - ARM (arm-ttk) #
####################################
2020-10-14 04:59:34 -04:00
elif [ [ ${ FILE_TYPE } = = "POWERSHELL" ] ] || [ [ ${ FILE_TYPE } = = "ARM" ] ] ; then
2020-06-29 10:55:59 -04:00
################################
# Lint the file with the rules #
################################
2020-07-01 04:13:30 -04:00
# 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 = $(
2020-10-14 04:59:34 -04:00
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 #
#######################################
2020-08-17 12:27:31 -04:00
if [ ! -f " ${ DIR_NAME } /.lintr " ] ; then
2020-10-14 04:59:34 -04:00
r_dir = " ${ WORKSPACE_PATH } "
2020-08-17 12:27:31 -04:00
else
r_dir = " ${ DIR_NAME } "
fi
2020-08-15 15:29:22 -04:00
LINT_CMD = $(
2020-08-17 12:27:31 -04:00
cd " $r_dir " || exit
2021-02-26 09:49:08 -05:00
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
2020-09-05 06:25:44 -04:00
${ LINTER_COMMAND } " ${ FILE_NAME } " | tee /dev/tty2 2>& 1
exit " ${ PIPESTATUS [0] } "
2020-08-28 14:44:11 -04:00
)
2021-03-17 14:28:51 -04:00
#######################################################
# 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
)
2022-01-06 12:04:10 -05:00
############################################################################################
# Corner case for TERRAFORM_TFLINT as it cant use the full path and needs to fetch modules #
############################################################################################
elif [ [ ${ FILE_TYPE } = = "TERRAFORM_TFLINT" ] ] ; then
2022-02-14 10:23:17 -05:00
# 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
2022-02-15 13:06:17 -05:00
(
cd " ${ DIR_NAME } " || exit
2022-02-15 14:28:12 -05:00
terraform get >/dev/null
2022-02-15 13:06:17 -05:00
)
2022-02-14 10:23:17 -05:00
fi
2022-01-06 12:04:10 -05:00
LINT_CMD = $(
cd " ${ DIR_NAME } " || exit
2023-07-09 12:56:47 -04:00
${ LINTER_COMMAND } --filter= " ${ FILE_NAME } " 2>& 1
2022-01-06 12:04:10 -05:00
)
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 = $(
2020-10-14 04:59:34 -04:00
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 = $?
2020-10-14 04:59:34 -04:00
########################################
# Check for if it was supposed to pass #
########################################
if [ [ ${ FILE_STATUS } = = "good" ] ] ; then
2023-01-11 11:24:48 -05:00
# Increase the good test cases count
( ( "GOOD_TEST_CASES_COUNT++" ) )
2020-10-14 04:59:34 -04:00
##############################
# 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! "
2021-01-20 13:44:30 -05:00
error " Error code: ${ ERROR_CODE } . Command output: ${ NC } \n------\n ${ LINT_CMD } \n------ "
2020-10-14 04:59:34 -04:00
# Increment the error count
( ( " ERRORS_FOUND_ ${ FILE_TYPE } ++ " ) )
fi
2020-08-28 11:08:21 -04:00
else
2020-10-14 04:59:34 -04:00
###########
# Success #
###########
info " - File: ${ F [W] } [ ${ FILE_NAME } ] ${ F [B] } was linted with ${ F [W] } [ ${ LINTER_NAME } ] ${ F [B] } successfully "
2022-03-01 14:50:47 -05:00
if [ -n " ${ LINT_CMD } " ] ; then
info " - Command output: ${ NC } \n------\n ${ LINT_CMD } \n------ "
fi
2020-10-14 04:59:34 -04:00
fi
else
#######################################
# File status = bad, this should fail #
#######################################
2023-01-11 11:24:48 -05:00
# Increase the bad test cases count
( ( "BAD_TEST_CASES_COUNT++" ) )
2020-10-14 04:59:34 -04:00
##############################
# 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! "
2020-10-14 04:59:34 -04:00
error "This file should have failed test case!"
2021-01-20 13:44:30 -05:00
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 } ++ " ) )
2020-10-14 04:59:34 -04:00
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
2021-01-20 13:44:30 -05:00
debug " Error code: ${ ERROR_CODE } . Command output: ${ NC } \n------\n ${ LINT_CMD } \n------ "
2020-06-29 10:55:59 -04:00
done
2021-01-27 14:47:34 -05:00
fi
2020-06-29 10:55:59 -04:00
2022-02-14 10:23:17 -05: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
2023-01-11 11:24:48 -05:00
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
}