diff --git a/.automation/test/powershell/README.md b/.automation/test/powershell/README.md new file mode 100644 index 00000000..5a778c98 --- /dev/null +++ b/.automation/test/powershell/README.md @@ -0,0 +1,13 @@ +# PowerShell Test Cases +This folder holds the test cases for **PowerShell**. + +## Additional Docs +No Additional information is needed for this test case. + +## Good Test Cases +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/powershell/powershell_bad_1.ps1 b/.automation/test/powershell/powershell_bad_1.ps1 new file mode 100644 index 00000000..f1709a0a --- /dev/null +++ b/.automation/test/powershell/powershell_bad_1.ps1 @@ -0,0 +1,14 @@ +#Plaintext Parameters +function BadFunction { + param( + [String]$Username = 'me', + [String]$Password = 'password' + ) + $Username + $Password + $VariableThatIsNotUsedLater = '5' + try { + 'Empty Catch Block' + } catch {} +} + diff --git a/.automation/test/powershell/powershell_bad_1.psd1 b/.automation/test/powershell/powershell_bad_1.psd1 new file mode 100644 index 00000000..5940164c --- /dev/null +++ b/.automation/test/powershell/powershell_bad_1.psd1 @@ -0,0 +1,3 @@ +@{ + 'Hello'='World +} diff --git a/.automation/test/powershell/powershell_bad_1.psm1 b/.automation/test/powershell/powershell_bad_1.psm1 new file mode 100644 index 00000000..60d812b1 --- /dev/null +++ b/.automation/test/powershell/powershell_bad_1.psm1 @@ -0,0 +1 @@ +Write-Output "hello world! diff --git a/.automation/test/powershell/powershell_good_1.ps1 b/.automation/test/powershell/powershell_good_1.ps1 new file mode 100644 index 00000000..d762886f --- /dev/null +++ b/.automation/test/powershell/powershell_good_1.ps1 @@ -0,0 +1 @@ +Write-Output "hello world!" \ No newline at end of file diff --git a/.automation/test/powershell/powershell_good_1.psd1 b/.automation/test/powershell/powershell_good_1.psd1 new file mode 100644 index 00000000..68432ff9 --- /dev/null +++ b/.automation/test/powershell/powershell_good_1.psd1 @@ -0,0 +1,3 @@ +@{ + 'Hello'='World' +} \ No newline at end of file diff --git a/.automation/test/powershell/powershell_good_1.psm1 b/.automation/test/powershell/powershell_good_1.psm1 new file mode 100644 index 00000000..d762886f --- /dev/null +++ b/.automation/test/powershell/powershell_good_1.psm1 @@ -0,0 +1 @@ +Write-Output "hello world!" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index fc6a0212..01f00e31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,25 @@ RUN apk add --no-cache \ libxml2-utils perl \ ruby ruby-dev ruby-bundler ruby-rdoc make \ py3-setuptools ansible-lint \ - go + go \ + ca-certificates less ncurses-terminfo-base \ + krb5-libs libgcc libintl libssl1.1 libstdc++ \ + tzdata userspace-rcu zlib icu-libs lttng-ust + +######################################### +# Install Powershell + PSScriptAnalyzer # +######################################### +# Reference: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux?view=powershell-7 +# Slightly modified to always retrieve latest stable Powershell version +RUN mkdir -p /opt/microsoft/powershell/7 \ + && curl -s https://api.github.com/repos/powershell/powershell/releases/latest \ + | grep browser_download_url \ + | grep linux-alpine-x64 \ + | cut -d '"' -f 4 \ + | xargs -n 1 wget -O - \ + | tar -xzC /opt/microsoft/powershell/7 \ + && ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh \ + && pwsh -c 'install-module psscriptanalyzer -force' ##################### # Run Pip3 Installs # @@ -82,7 +100,10 @@ RUN gem install rubocop:0.74.0 rubocop-rails rubocop-github:0.13.0 # Need to fix the version as it installs 'rubocop:0.85.1' as a dep, and forces the default # We then need to promote the correct version, uninstall, and fix deps -RUN sh -c 'gem install --default rubocop:0.74.0; yes | gem uninstall rubocop:0.85.1 -a -x -I; gem install rubocop:0.74.0' +RUN sh -c 'INCORRECT_VERSION=$(gem list rhc -e rubocop | grep rubocop | awk "{print $2}" | cut -d"(" -f2 | cut -d"," -f1); \ + gem install --default rubocop:0.74.0; \ + yes | gem uninstall rubocop:$INCORRECT_VERSION -a -x -I; \ + gem install rubocop:0.74.0' ###################### # Install shellcheck # @@ -135,6 +156,7 @@ ENV GITHUB_SHA=${GITHUB_SHA} \ VALIDATE_TERRAFORM=${VALIDATE_TERRAFORM} \ VALIDATE_CSS=${VALIDATE_CSS} \ VALIDATE_ENV=${VALIDATE_ENV} \ + VALIDATE_POWERSHELL=${VALIDATE_POWERSHELL} \ ANSIBLE_DIRECTORY=${ANSIBLE_DIRECTORY} \ RUN_LOCAL=${RUN_LOCAL} \ TEST_CASE_RUN=${TEST_CASE_RUN} \ diff --git a/README.md b/README.md index 43f040a1..19d49dec 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Developers on **GitHub** can call the **GitHub Action** to lint their code base | **TypeScript** | [eslint](https://eslint.org/) [standard js](https://standardjs.com/) | | **XML** | [LibXML](http://xmlsoft.org/) | | **YAML** | [YamlLint](https://github.com/adrienverge/yamllint) | +| **PowerShell** | [PSScriptAnalyzer](https://github.com/PowerShell/Psscriptanalyzer) | | **ENV** | [dotenv-linter](https://github.com/dotenv-linter/dotenv-linter) | ## How to use @@ -134,6 +135,7 @@ and won't run anything unexpected. | **VALIDATE_TYPESCRIPT_STANDARD** | `true` | Flag to enable or disable the linting process of the language. (Utilizing: standard) | | **VALIDATE_DOCKER** | `true` | Flag to enable or disable the linting process of the language. | | **VALIDATE_GO** | `true` | Flag to enable or disable the linting process of the language. | +| **VALIDATE_POWERSHELL** | `true` | Flag to enable or disable the linting process of the language. | | **VALIDATE_TERRAFORM** | `true` | Flag to enable or disable the linting process of the language. | | **VALIDATE_CSS** | `true` | Flag to enable or disable the linting process of the language. | | **VALIDATE_ENV** | `true` | Flag to enable or disable the linting process of the language. | diff --git a/TEMPLATES/.powershell-psscriptanalyzer.psd1 b/TEMPLATES/.powershell-psscriptanalyzer.psd1 new file mode 100644 index 00000000..ab462662 --- /dev/null +++ b/TEMPLATES/.powershell-psscriptanalyzer.psd1 @@ -0,0 +1,18 @@ +#Documentation: https://github.com/PowerShell/PSScriptAnalyzer/blob/master/docs/markdown/Invoke-ScriptAnalyzer.md#-settings +@{ + #CustomRulePath='path\to\CustomRuleModule.psm1' + #RecurseCustomRulePath='path\of\customrules' + #Severity = @( + # 'Error' + # 'Warning' + #) + #IncludeDefaultRules=$true + #ExcludeRules = @( + # 'PSAvoidUsingWriteHost', + # 'MyCustomRuleName' + #) + #IncludeRules = @( + # 'PSAvoidUsingWriteHost', + # 'MyCustomRuleName' + #) +} \ No newline at end of file diff --git a/lib/linter.sh b/lib/linter.sh index 45fd2ecf..4c1a5b08 100755 --- a/lib/linter.sh +++ b/lib/linter.sh @@ -48,25 +48,28 @@ GO_LINTER_RULES="$DEFAULT_RULES_LOCATION/$GO_FILE_NAME" # Path to th # Terraform Vars TERRAFORM_FILE_NAME='.tflint.hcl' # Name of the file TERRAFORM_LINTER_RULES="$DEFAULT_RULES_LOCATION/$TERRAFORM_FILE_NAME" # Path to the Terraform lint rules +# Powershell Vars +POWERSHELL_FILE_NAME='.powershell-psscriptanalyzer.psd1' # Name of the file +POWERSHELL_LINTER_RULES="$DEFAULT_RULES_LOCATION/$POWERSHELL_FILE_NAME" # Path to the Powershell lint rules # CSS Vars CSS_FILE_NAME='.stylelintrc.json' # Name of the file CSS_LINTER_RULES="$DEFAULT_RULES_LOCATION/$CSS_FILE_NAME" # Path to the CSS lint rules - ####################################### # Linter array for information prints # ####################################### LINTER_ARRAY=("jsonlint" "yamllint" "xmllint" "markdownlint" "shellcheck" "pylint" "perl" "rubocop" "coffeelint" "eslint" "standard" "ansible-lint" "/dockerfilelint/bin/dockerfilelint" "golangci-lint" "tflint" - "stylelint" "dotenv-linter") + "stylelint" "dotenv-linter" "powershell") ############################# # Language array for prints # ############################# LANGUAGE_ARRAY=('YML' 'JSON' 'XML' 'MARKDOWN' 'BASH' 'PERL' 'RUBY' 'PYTHON' 'COFFEESCRIPT' 'ANSIBLE' 'JAVASCRIPT_STANDARD' 'JAVASCRIPT_ES' - 'TYPESCRIPT_STANDARD' 'TYPESCRIPT_ES' 'DOCKER' 'GO' 'TERRAFORM' 'CSS' "ENV") + 'TYPESCRIPT_STANDARD' 'TYPESCRIPT_ES' 'DOCKER' 'GO' 'TERRAFORM' 'CSS' + 'ENV' 'POWERSHELL') ################### # GitHub ENV Vars # @@ -94,6 +97,7 @@ VALIDATE_TYPESCRIPT_STANDARD="${VALIDATE_TYPESCRIPT_STANDARD}" # Boolean to val VALIDATE_DOCKER="${VALIDATE_DOCKER}" # Boolean to validate language VALIDATE_GO="${VALIDATE_GO}" # Boolean to validate language VALIDATE_TERRAFORM="${VALIDATE_TERRAFORM}" # Boolean to validate language +VALIDATE_POWERSHELL="${VALIDATE_POWERSHELL}" # Boolean to validate language VALIDATE_CSS="${VALIDATE_CSS}" # Boolean to validate language VALIDATE_ENV="${VALIDATE_ENV}" # Boolean to validate language TEST_CASE_RUN="${TEST_CASE_RUN}" # Boolean to validate only test cases @@ -138,6 +142,7 @@ FILE_ARRAY_TYPESCRIPT_STANDARD=() # Array of files to check FILE_ARRAY_DOCKER=() # Array of files to check FILE_ARRAY_GO=() # Array of files to check FILE_ARRAY_TERRAFORM=() # Array of files to check +FILE_ARRAY_POWERSHELL=() # Array of files to check FILE_ARRAY_CSS=() # Array of files to check FILE_ARRAY_ENV=() # Array of files to check @@ -161,6 +166,7 @@ ERRORS_FOUND_TYPESCRIPT_ES=0 # Count of errors found ERRORS_FOUND_DOCKER=0 # Count of errors found ERRORS_FOUND_GO=0 # Count of errors found ERRORS_FOUND_TERRAFORM=0 # Count of errors found +ERRORS_FOUND_POWERSHELL=0 # Count of errors found ERRORS_FOUND_CSS=0 # Count of errors found ERRORS_FOUND_ENV=0 # Count of errors found @@ -739,6 +745,7 @@ GetValidationInfo() VALIDATE_DOCKER=$(echo "$VALIDATE_DOCKER" | awk '{print tolower($0)}') VALIDATE_GO=$(echo "$VALIDATE_GO" | awk '{print tolower($0)}') VALIDATE_TERRAFORM=$(echo "$VALIDATE_TERRAFORM" | awk '{print tolower($0)}') + VALIDATE_POWERSHELL=$(echo "$VALIDATE_POWERSHELL" | awk '{print tolower($0)}') VALIDATE_CSS=$(echo "$VALIDATE_CSS" | awk '{print tolower($0)}') VALIDATE_ENV=$(echo "$VALIDATE_ENV" | awk '{print tolower($0)}') @@ -763,6 +770,7 @@ GetValidationInfo() -n "$VALIDATE_DOCKER" || \ -n "$VALIDATE_GO" || \ -n "$VALIDATE_TERRAFORM" || \ + -n "$VALIDATE_POWERSHELL" || \ -n "$VALIDATE_CSS" || \ -n "$VALIDATE_ENV" ]]; then ANY_SET="true" @@ -1006,6 +1014,20 @@ GetValidationInfo() VALIDATE_TERRAFORM="true" fi + ######################################### + # Validate if we should check POWERSHELL # + ######################################### + if [[ "$ANY_SET" == "true" ]]; then + # Some linter flags were set - only run those set to true + if [[ -z "$VALIDATE_POWERSHELL" ]]; then + # POWERSHELL flag was not set - default to false + VALIDATE_POWERSHELL="false" + fi + else + # No linter flags were set - default all to true + VALIDATE_POWERSHELL="true" + fi + ################################### # Validate if we should check CSS # ################################### @@ -1122,6 +1144,11 @@ GetValidationInfo() else PRINT_ARRAY+=("- Excluding [TERRAFORM] files in code base...") fi + if [[ "$VALIDATE_POWERSHELL" == "true" ]]; then + PRINT_ARRAY+=("- Validating [POWERSHELL] files in code base...") + else + PRINT_ARRAY+=("- Excluding [POWERSHELL] files in code base...") + fi if [[ "$VALIDATE_CSS" == "true" ]]; then PRINT_ARRAY+=("- Validating [CSS] files in code base...") else @@ -1461,6 +1488,14 @@ BuildFileList() # Set the READ_ONLY_CHANGE_FLAG since this could be exec # ########################################################## READ_ONLY_CHANGE_FLAG=1 + ########################### + # Get the Powershell files # + ########################### + elif [ "$FILE_TYPE" == "ps1" ]; then + ################################ + # Append the file to the array # + ################################ + FILE_ARRAY_POWERSHELL+=("$FILE") elif [ "$FILE_TYPE" == "css" ]; then ################################ # Append the file to the array # @@ -1704,10 +1739,26 @@ LintCodebase() echo "---------------------------" echo "File:[$FILE]" - ################################ - # Lint the file with the rules # - ################################ - LINT_CMD=$(cd "$GITHUB_WORKSPACE" || exit; $LINTER_COMMAND "$FILE" 2>&1) + #################### + # Set the base Var # + #################### + LINT_CMD='' + + ####################################### + # Corner case for Powershell subshell # + ####################################### + if [[ "$FILE_TYPE" == "POWERSHELL" ]]; then + ################################ + # Lint the file with the rules # + ################################ + # Need to append "'" to make the pwsh call syntax correct, also exit with exit code from inner subshell + LINT_CMD=$(cd "$GITHUB_WORKSPACE/$TEST_CASE_FOLDER" || exit; $LINTER_COMMAND "$FILE"; exit $? 2>&1) + else + ################################ + # Lint the file with the rules # + ################################ + LINT_CMD=$(cd "$GITHUB_WORKSPACE" || exit; $LINTER_COMMAND "$FILE" 2>&1) + fi ####################### # Load the error code # @@ -1874,6 +1925,12 @@ TestCodebase() # Lint the file with the rules # ################################ LINT_CMD=$(cd "$GITHUB_WORKSPACE/$TEST_CASE_FOLDER/ansible" || exit; $LINTER_COMMAND "$FILE" 2>&1) + elif [[ "$FILE_TYPE" == "POWERSHELL" ]]; then + ################################ + # Lint the file with the rules # + ################################ + # Need to append "'" to make the pwsh call syntax correct, also exit with exit code from inner subshell + LINT_CMD=$(cd "$GITHUB_WORKSPACE/$TEST_CASE_FOLDER" || exit; $LINTER_COMMAND "$FILE"; exit $? 2>&1) else ################################ # Lint the file with the rules # @@ -1990,6 +2047,7 @@ Footer() [ "$ERRORS_FOUND_DOCKER" -ne 0 ] || \ [ "$ERRORS_FOUND_GO" -ne 0 ] || \ [ "$ERRORS_FOUND_TERRAFORM" -ne 0 ] || \ + [ "$ERRORS_FOUND_POWERSHELL" -ne 0 ] || \ [ "$ERRORS_FOUND_RUBY" -ne 0 ] || \ [ "$ERRORS_FOUND_CSS" -ne 0 ] || \ [ "$ERRORS_FOUND_ENV" -ne 0 ]; then @@ -2049,6 +2107,7 @@ RunTestCases() TestCodebase "DOCKER" "/dockerfilelint/bin/dockerfilelint" "/dockerfilelint/bin/dockerfilelint" ".*\(Dockerfile\)\$" TestCodebase "ANSIBLE" "ansible-lint" "ansible-lint -v -c $ANSIBLE_LINTER_RULES" "ansible-lint" TestCodebase "TERRAFORM" "tflint" "tflint -c $TERRAFORM_LINTER_RULES" ".*\.\(tf\)\$" + TestCodebase "POWERSHELL" "pwsh" "pwsh -c Invoke-ScriptAnalyzer -EnableExit -Settings $POWERSHELL_LINTER_RULES -Path" ".*\.\(ps1\|psm1\|psd1\|ps1xml\|pssc\|psrc\|cdxml\)\$" TestCodebase "CSS" "stylelint" "stylelint --config $CSS_LINTER_RULES" ".*\.\(css\)\$" TestCodebase "ENV" "dotenv-linter" "dotenv-linter" ".*\.\(env\)\$" @@ -2105,6 +2164,8 @@ GetLinterRules "$GO_FILE_NAME" "$GO_LINTER_RULES" GetLinterRules "$DOCKER_FILE_NAME" "$DOCKER_LINTER_RULES" # Get Terraform rules GetLinterRules "$TERRAFORM_FILE_NAME" "$TERRAFORM_LINTER_RULES" +# Get PowerShell rules +GetLinterRules "$POWERSHELL_FILE_NAME" "$POWERSHELL_LINTER_RULES" # Get CSS rules GetLinterRules "$CSS_FILE_NAME" "$CSS_LINTER_RULES" @@ -2358,6 +2419,17 @@ if [ "$VALIDATE_DOCKER" == "true" ]; then LintCodebase "DOCKER" "/dockerfilelint/bin/dockerfilelint" "/dockerfilelint/bin/dockerfilelint" ".*\(Dockerfile\)\$" "${FILE_ARRAY_DOCKER[@]}" fi +###################### +# POWERSHELL LINTING # +###################### +if [ "$VALIDATE_POWERSHELL" == "true" ]; then + ############################# + # Lint the powershell files # + ############################# + # LintCodebase "FILE_TYPE" "LINTER_NAME" "LINTER_CMD" "FILE_TYPES_REGEX" "FILE_ARRAY" + LintCodebase "POWERSHELL" "pwsh" "pwsh -c Invoke-ScriptAnalyzer -EnableExit -Settings $POWERSHELL_LINTER_RULES -Path" ".*\.\(ps1\|psm1\|psd1\|ps1xml\|pssc\|psrc\|cdxml\)\$" "${FILE_ARRAY_POWERSHELL[@]}" +fi + ########## # Footer # ##########