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
2023-12-16 03:30:33 -05:00
debug " TEST_CASE_FOLDER: ${ TEST_CASE_FOLDER } "
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 "----------------------------------------------"
2023-12-16 03:30:33 -05:00
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 [*] } "
2020-11-30 12:19:17 -05:00
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-06-29 10:55:59 -04:00
2024-01-11 15:30:00 -05:00
# We want a lowercase value
local -l INDIVIDUAL_TEST_FOLDER
2024-01-10 06:35:05 -05:00
# Folder for specific tests. By convention, it's the lowercased FILE_TYPE
2024-01-11 15:30:00 -05:00
INDIVIDUAL_TEST_FOLDER = " ${ FILE_TYPE } "
2024-01-10 06:35:05 -05:00
debug " INDIVIDUAL_TEST_FOLDER for ${ FILE } : ${ INDIVIDUAL_TEST_FOLDER } "
2021-08-30 10:47:50 -04:00
2024-01-10 06:35:05 -05:00
local TEST_CASE_DIRECTORY
TEST_CASE_DIRECTORY = " ${ TEST_CASE_FOLDER } / ${ INDIVIDUAL_TEST_FOLDER } "
debug " TEST_CASE_DIRECTORY for ${ FILE } : ${ TEST_CASE_DIRECTORY } "
2020-10-14 04:59:34 -04:00
2024-01-10 06:35:05 -05:00
if [ [ " ${ TEST_CASE_RUN } " = = "true" ] ] ; then
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 } "
2023-09-29 16:25:37 -04:00
fi
2024-01-10 06:35:05 -05:00
if [ [ ${ FILE } != *" ${ TEST_CASE_DIRECTORY } " * ] ] ; then
debug " Skipping ${ FILE } because it's not in the test case directory for ${ FILE_TYPE } ... "
continue
2020-10-14 04:59:34 -04:00
fi
fi
2024-01-10 06:35:05 -05:00
local FILE_NAME
FILE_NAME = $( basename " ${ FILE } " 2>& 1)
debug " FILE_NAME for ${ FILE } : ${ FILE_NAME } "
2020-10-14 04:59:34 -04:00
2024-01-10 06:35:05 -05:00
local DIR_NAME
DIR_NAME = $( dirname " ${ FILE } " 2>& 1)
debug " DIR_NAME for ${ FILE } : ${ DIR_NAME } "
2020-06-29 10:55:59 -04:00
2020-07-06 13:46:51 -04:00
( ( "INDEX++" ) )
2024-01-10 06:35:05 -05:00
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
LINT_CMD = $(
2023-12-24 11:56:15 -05:00
cd " ${ FILE } " || exit
2022-09-27 03:59:15 -04:00
# 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
)
2023-12-16 03:30:33 -05: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
)
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
)
2023-12-15 04:29:34 -05:00
#############################################################################################
# Corner case for TERRAFORM_TFLINT as it can't use the full path and needs to fetch modules #
#############################################################################################
2022-01-06 12:04:10 -05:00
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 #
########################################
2024-01-10 06:35:05 -05:00
local FILE_STATUS
# Assume that the file should pass linting checks
FILE_STATUS = "good"
if [ [ " ${ TEST_CASE_RUN } " = = "true" ] ] ; then
if [ [ ${ FILE } = = *"bad" * ] ] ; then
FILE_STATUS = "bad"
debug " We are running in test mode. Updating the expected FILE_STATUS for ${ FILE } to: ${ FILE_STATUS } "
fi
fi
########################################
# File status = good, this should pass #
########################################
2020-10-14 04:59:34 -04:00
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
if [ ${ ERROR_CODE } -ne 0 ] ; then
2024-01-10 17:54:13 -05:00
error " Found errors when running ${ LINTER_NAME } on ${ FILE } . Error code: ${ ERROR_CODE } . File type: ${ FILE_TYPE } . Command output: ${ NC } \n------\n ${ LINT_CMD } \n------ "
# Increment the error count
( ( " ERRORS_FOUND_ ${ FILE_TYPE } ++ " ) )
2020-08-28 11:08:21 -04:00
else
2020-10-14 04:59:34 -04:00
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
2024-01-10 06:35:05 -05:00
if [ [ " ${ TEST_CASE_RUN } " = = "false" ] ] ; then
fatal " All files are supposed to pass linting checks when not running in test mode. TEST_CASE_RUN: ${ TEST_CASE_RUN } "
fi
2023-01-11 11:24:48 -05:00
( ( "BAD_TEST_CASES_COUNT++" ) )
2020-10-14 04:59:34 -04:00
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
2024-01-10 06:35:05 -05:00
info " - File: ${ F [W] } [ ${ FILE_NAME } ] ${ F [B] } failed test case (Error code: ${ ERROR_CODE } ) with ${ F [W] } [ ${ LINTER_NAME } ] ${ F [B] } as expected "
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
}