mirror of
https://github.com/ivuorinen/actions.git
synced 2026-01-26 03:23:59 +00:00
* chore: update actions, cleanup pr-lint * chore: cleanup pre-commit config, formatting * chore: revert sigstore/cosign-installer downgrade * chore: formatting
440 lines
14 KiB
YAML
440 lines
14 KiB
YAML
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
|
|
# permissions:
|
|
# - contents: read # Required for checking out repository
|
|
# - security-events: write # Required for uploading SARIF results
|
|
---
|
|
name: Go Lint Check
|
|
description: 'Run golangci-lint with advanced configuration, caching, and reporting'
|
|
author: Ismo Vuorinen
|
|
|
|
branding:
|
|
icon: code
|
|
color: blue
|
|
|
|
inputs:
|
|
working-directory:
|
|
description: 'Directory containing Go files'
|
|
required: false
|
|
default: '.'
|
|
golangci-lint-version:
|
|
description: 'Version of golangci-lint to use'
|
|
required: false
|
|
default: 'latest'
|
|
go-version:
|
|
description: 'Go version to use'
|
|
required: false
|
|
default: 'stable'
|
|
config-file:
|
|
description: 'Path to golangci-lint config file'
|
|
required: false
|
|
default: '.golangci.yml'
|
|
timeout:
|
|
description: 'Timeout for analysis (e.g., 5m, 1h)'
|
|
required: false
|
|
default: '5m'
|
|
cache:
|
|
description: 'Enable golangci-lint caching'
|
|
required: false
|
|
default: 'true'
|
|
fail-on-error:
|
|
description: 'Fail workflow if issues are found'
|
|
required: false
|
|
default: 'true'
|
|
report-format:
|
|
description: 'Output format (json, sarif, github-actions)'
|
|
required: false
|
|
default: 'sarif'
|
|
max-retries:
|
|
description: 'Maximum number of retry attempts'
|
|
required: false
|
|
default: '3'
|
|
only-new-issues:
|
|
description: 'Report only new issues since main branch'
|
|
required: false
|
|
default: 'true'
|
|
disable-all:
|
|
description: 'Disable all linters (useful with --enable-*)'
|
|
required: false
|
|
default: 'false'
|
|
enable-linters:
|
|
description: 'Comma-separated list of linters to enable'
|
|
required: false
|
|
disable-linters:
|
|
description: 'Comma-separated list of linters to disable'
|
|
required: false
|
|
token:
|
|
description: 'GitHub token for authentication'
|
|
required: false
|
|
default: ''
|
|
|
|
outputs:
|
|
error-count:
|
|
description: 'Number of errors found'
|
|
value: ${{ steps.lint.outputs.error_count }}
|
|
sarif-file:
|
|
description: 'Path to SARIF report file'
|
|
value: ${{ steps.lint.outputs.sarif_file }}
|
|
cache-hit:
|
|
description: 'Indicates if there was a cache hit'
|
|
value: ${{ steps.cache.outputs.cache-hit }}
|
|
analyzed-files:
|
|
description: 'Number of files analyzed'
|
|
value: ${{ steps.lint.outputs.analyzed_files }}
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
- name: Validate Inputs
|
|
id: validate
|
|
shell: sh
|
|
env:
|
|
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
|
GOLANGCI_LINT_VERSION: ${{ inputs.golangci-lint-version }}
|
|
GO_VERSION: ${{ inputs.go-version }}
|
|
CONFIG_FILE: ${{ inputs.config-file }}
|
|
TIMEOUT: ${{ inputs.timeout }}
|
|
CACHE: ${{ inputs.cache }}
|
|
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
|
|
ONLY_NEW_ISSUES: ${{ inputs.only-new-issues }}
|
|
DISABLE_ALL: ${{ inputs.disable-all }}
|
|
REPORT_FORMAT: ${{ inputs.report-format }}
|
|
MAX_RETRIES: ${{ inputs.max-retries }}
|
|
ENABLE_LINTERS: ${{ inputs.enable-linters }}
|
|
DISABLE_LINTERS: ${{ inputs.disable-linters }}
|
|
run: |
|
|
set -eu
|
|
|
|
# Validate working directory exists
|
|
if [ ! -d "$WORKING_DIRECTORY" ]; then
|
|
echo "::error::Working directory not found at '$WORKING_DIRECTORY'"
|
|
exit 1
|
|
fi
|
|
|
|
# Validate working directory path security (prevent traversal)
|
|
case "$WORKING_DIRECTORY" in
|
|
*..*)
|
|
echo "::error::Invalid working directory path: '$WORKING_DIRECTORY'. Path traversal not allowed"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Validate golangci-lint version format
|
|
if [ -n "$GOLANGCI_LINT_VERSION" ] && [ "$GOLANGCI_LINT_VERSION" != "latest" ]; then
|
|
if ! echo "$GOLANGCI_LINT_VERSION" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$'; then
|
|
echo "::error::Invalid golangci-lint-version format: '$GOLANGCI_LINT_VERSION'. Expected format: vX.Y.Z or 'latest' (e.g., v1.55.2, latest)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Validate Go version format
|
|
if [ -n "$GO_VERSION" ] && [ "$GO_VERSION" != "stable" ]; then
|
|
if ! echo "$GO_VERSION" | grep -Eq '^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$'; then
|
|
echo "::error::Invalid go-version format: '$GO_VERSION'. Expected format: X.Y or X.Y.Z or 'stable' (e.g., 1.21, 1.21.5, stable)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Validate config file path if not default
|
|
if [ "$CONFIG_FILE" != ".golangci.yml" ]; then
|
|
case "$CONFIG_FILE" in
|
|
*..*)
|
|
echo "::error::Invalid config file path: '$CONFIG_FILE'. Path traversal not allowed"
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Validate timeout format (duration with unit)
|
|
if ! echo "$TIMEOUT" | grep -Eq '^[0-9]+(ns|us|µs|ms|s|m|h)$'; then
|
|
echo "::error::Invalid timeout format: '$TIMEOUT'. Expected format with unit: 5m, 1h, 300s (e.g., 5m, 30s, 2h)"
|
|
exit 1
|
|
fi
|
|
|
|
# Validate boolean inputs
|
|
validate_boolean() {
|
|
_value="$1"
|
|
_name="$2"
|
|
_value_lower=$(echo "$_value" | tr '[:upper:]' '[:lower:]')
|
|
|
|
case "$_value_lower" in
|
|
true|false)
|
|
;;
|
|
*)
|
|
echo "::error::Invalid boolean value for $_name: '$_value'. Expected: true or false"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
validate_boolean "$CACHE" "cache"
|
|
validate_boolean "$FAIL_ON_ERROR" "fail-on-error"
|
|
validate_boolean "$ONLY_NEW_ISSUES" "only-new-issues"
|
|
validate_boolean "$DISABLE_ALL" "disable-all"
|
|
|
|
# Validate report format enumerated values
|
|
case "$REPORT_FORMAT" in
|
|
checkstyle|colored-line-number|github-actions|html|json|junit-xml|line-number|sarif|tab|teamcity|xml)
|
|
;;
|
|
*)
|
|
echo "::error::Invalid report-format: '$REPORT_FORMAT'. Allowed values:" \
|
|
"checkstyle, colored-line-number, github-actions, html, json, junit-xml, line-number, sarif, tab, teamcity, xml"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Validate max retries (positive integer with reasonable upper limit)
|
|
if ! echo "$MAX_RETRIES" | grep -Eq '^[0-9]+$' || [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
|
|
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
|
|
exit 1
|
|
fi
|
|
|
|
# Validate linter lists if provided
|
|
validate_linter_list() {
|
|
_linter_list="$1"
|
|
_name="$2"
|
|
|
|
if [ -n "$_linter_list" ]; then
|
|
if ! echo "$_linter_list" | grep -Eq '^[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$'; then
|
|
echo "::error::Invalid $_name format: '$_linter_list'. Expected comma-separated linter names (e.g., gosec,govet,staticcheck)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
validate_linter_list "$ENABLE_LINTERS" "enable-linters"
|
|
validate_linter_list "$DISABLE_LINTERS" "disable-linters"
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
|
with:
|
|
go-version: ${{ inputs.go-version }}
|
|
cache: true
|
|
|
|
- name: Checkout Repository
|
|
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
|
with:
|
|
token: ${{ inputs.token || github.token }}
|
|
|
|
- name: Cache golangci-lint
|
|
id: cache
|
|
if: inputs.cache == 'true'
|
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
|
with:
|
|
path: |
|
|
~/.cache/golangci-lint
|
|
~/.cache/go-build
|
|
key: ${{ runner.os }}-golangci-${{ inputs.golangci-lint-version }}-${{ hashFiles('go.sum', inputs.config-file) }}
|
|
restore-keys: |
|
|
${{ runner.os }}-golangci-${{ inputs.golangci-lint-version }}-
|
|
|
|
- name: Install golangci-lint
|
|
shell: sh
|
|
env:
|
|
MAX_RETRIES: ${{ inputs.max-retries }}
|
|
GOLANGCI_LINT_VERSION: ${{ inputs.golangci-lint-version }}
|
|
run: |
|
|
set -eu
|
|
|
|
# Function to install golangci-lint with retries
|
|
install_golangci_lint() {
|
|
_attempt=1
|
|
_max_attempts="$MAX_RETRIES"
|
|
_version="$GOLANGCI_LINT_VERSION"
|
|
|
|
while [ $_attempt -le $_max_attempts ]; do
|
|
echo "Installation attempt $_attempt of $_max_attempts"
|
|
|
|
# Add 'v' prefix if version is not 'latest' and doesn't already have it
|
|
install_version="$_version"
|
|
if [ "$_version" != "latest" ]; then
|
|
case "$_version" in
|
|
v*) ;;
|
|
*) install_version="v$_version" ;;
|
|
esac
|
|
fi
|
|
|
|
if curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
|
|
sh -s -- -b "$(go env GOPATH)/bin" "$install_version"; then
|
|
return 0
|
|
fi
|
|
|
|
_attempt=$((_attempt + 1))
|
|
if [ $_attempt -le $_max_attempts ]; then
|
|
echo "Installation failed, waiting 10 seconds before retry..."
|
|
sleep 10
|
|
fi
|
|
done
|
|
|
|
echo "::error::Failed to install golangci-lint after $_max_attempts attempts"
|
|
return 1
|
|
}
|
|
|
|
install_golangci_lint
|
|
|
|
- name: Prepare Configuration
|
|
id: config
|
|
shell: sh
|
|
env:
|
|
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
|
CONFIG_FILE: ${{ inputs.config-file }}
|
|
TIMEOUT: ${{ inputs.timeout }}
|
|
run: |
|
|
set -eu
|
|
|
|
cd "$WORKING_DIRECTORY"
|
|
|
|
# Create default config if none exists
|
|
if [ ! -f "$CONFIG_FILE" ]; then
|
|
echo "Creating default golangci-lint configuration..."
|
|
cat > "$CONFIG_FILE" <<EOF
|
|
linters:
|
|
enable-all: true
|
|
disable:
|
|
- exhaustivestruct
|
|
- interfacer
|
|
- scopelint
|
|
- maligned
|
|
|
|
linters-settings:
|
|
govet:
|
|
check-shadowing: true
|
|
golint:
|
|
min-confidence: 0.8
|
|
gocyclo:
|
|
min-complexity: 15
|
|
dupl:
|
|
threshold: 100
|
|
goconst:
|
|
min-len: 3
|
|
min-occurrences: 3
|
|
|
|
issues:
|
|
exclude-use-default: false
|
|
max-issues-per-linter: 0
|
|
max-same-issues: 0
|
|
new: true
|
|
|
|
run:
|
|
deadline: $TIMEOUT
|
|
tests: true
|
|
skip-dirs:
|
|
- vendor
|
|
- third_party
|
|
EOF
|
|
fi
|
|
|
|
- name: Run golangci-lint
|
|
id: lint
|
|
shell: sh
|
|
env:
|
|
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
|
DISABLE_ALL: ${{ inputs.disable-all }}
|
|
ENABLE_LINTERS: ${{ inputs.enable-linters }}
|
|
DISABLE_LINTERS: ${{ inputs.disable-linters }}
|
|
CONFIG_FILE: ${{ inputs.config-file }}
|
|
TIMEOUT: ${{ inputs.timeout }}
|
|
CACHE: ${{ inputs.cache }}
|
|
ONLY_NEW_ISSUES: ${{ inputs.only-new-issues }}
|
|
REPORT_FORMAT: ${{ inputs.report-format }}
|
|
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
|
|
run: |
|
|
set -eu
|
|
|
|
cd "$WORKING_DIRECTORY"
|
|
|
|
# Create reports directory
|
|
mkdir -p reports
|
|
|
|
# Prepare linter configuration
|
|
linter_args=""
|
|
if [ "$DISABLE_ALL" = "true" ]; then
|
|
linter_args="--disable-all"
|
|
fi
|
|
|
|
if [ -n "$ENABLE_LINTERS" ]; then
|
|
linter_args="$linter_args --enable=$ENABLE_LINTERS"
|
|
fi
|
|
|
|
if [ -n "$DISABLE_LINTERS" ]; then
|
|
linter_args="$linter_args --disable=$DISABLE_LINTERS"
|
|
fi
|
|
|
|
# Prepare cache flag
|
|
cache_flag="--no-cache"
|
|
if [ "$CACHE" = "true" ]; then
|
|
cache_flag="--cache"
|
|
fi
|
|
|
|
# Prepare new issues flag
|
|
new_flag=""
|
|
if [ "$ONLY_NEW_ISSUES" = "true" ]; then
|
|
new_flag="--new"
|
|
fi
|
|
|
|
# Run golangci-lint
|
|
echo "Running golangci-lint..."
|
|
|
|
result_file="reports/golangci-lint.$REPORT_FORMAT"
|
|
|
|
GOLANGCI_LINT_CACHE="$HOME/.cache/golangci-lint" \
|
|
golangci-lint run \
|
|
--config "$CONFIG_FILE" \
|
|
--timeout "$TIMEOUT" \
|
|
$cache_flag \
|
|
$new_flag \
|
|
--out-format "$REPORT_FORMAT" \
|
|
$linter_args \
|
|
./... > "$result_file" || {
|
|
exit_code=$?
|
|
|
|
# Count errors
|
|
if [ "$REPORT_FORMAT" = "json" ]; then
|
|
if command -v jq >/dev/null 2>&1; then
|
|
error_count=$(jq '.Issues | length' "$result_file" 2>/dev/null || echo 0)
|
|
else
|
|
echo "::warning::jq not found - falling back to grep for error counting"
|
|
error_count=$(grep -c '"level": "error"' "$result_file" 2>/dev/null || echo 0)
|
|
fi
|
|
else
|
|
error_count=$(grep -c "level\": \"error\"" "$result_file" || echo 0)
|
|
fi
|
|
|
|
echo "error_count=${error_count}" >> $GITHUB_OUTPUT
|
|
|
|
if [ "$FAIL_ON_ERROR" = "true" ]; then
|
|
echo "::error::golangci-lint found ${error_count} issues"
|
|
exit $exit_code
|
|
fi
|
|
}
|
|
|
|
# Count analyzed files
|
|
analyzed_files=$(find . -type f -name "*.go" -not -path "./vendor/*" -not -path "./.git/*" | wc -l)
|
|
echo "analyzed_files=${analyzed_files}" >> $GITHUB_OUTPUT
|
|
echo "sarif_file=$result_file" >> $GITHUB_OUTPUT
|
|
|
|
- name: Upload Lint Results
|
|
if: always() && inputs.report-format == 'sarif'
|
|
uses: github/codeql-action/upload-sarif@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
|
|
with:
|
|
sarif_file: ${{ inputs.working-directory }}/reports/golangci-lint.sarif
|
|
category: golangci-lint
|
|
|
|
- name: Cleanup
|
|
if: always()
|
|
shell: sh
|
|
env:
|
|
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
|
CACHE: ${{ inputs.cache }}
|
|
run: |-
|
|
set -eu
|
|
|
|
cd "$WORKING_DIRECTORY"
|
|
|
|
# Remove temporary files
|
|
rm -rf reports/
|
|
|
|
# Clean cache if not being preserved
|
|
if [ "$CACHE" != "true" ]; then
|
|
rm -rf ~/.cache/golangci-lint
|
|
fi
|