Skip to content

Latest commit

 

History

History
452 lines (320 loc) · 8.59 KB

File metadata and controls

452 lines (320 loc) · 8.59 KB

Code Quality & Pre-Commit Hooks

Complete guide to maintaining code quality in the Unraid Management Agent project.

Overview

This project uses automated code quality checks via pre-commit hooks to ensure consistency, security, and best practices before code is committed.

Quick Setup

Automated Setup (Recommended)

# Clone the repository
git clone https://github.com/ruaan-deysel/unraid-management-agent.git
cd unraid-management-agent

# Run the setup script
./scripts/setup-pre-commit.sh

This script:

  1. Installs Python and pip (if needed)
  2. Installs pre-commit via pip
  3. Configures pre-commit hooks
  4. Verifies the installation

Manual Setup

# Install pre-commit
pip install pre-commit

# Install the git hooks
make pre-commit-install

# Or directly:
pre-commit install

Pre-Commit Hooks

The project uses the following pre-commit hooks (configured in .pre-commit-config.yaml):

1. Go Formatting (gofmt)

Automatically formats Go code to match Go standards.

What it checks:

  • Proper indentation (tabs)
  • Consistent spacing
  • Import organization

How to run manually:

gofmt -w .

2. Go Imports (goimports)

Manages Go import statements automatically.

What it checks:

  • Unused imports
  • Import grouping (stdlib, external, internal)
  • Alphabetical ordering

How to run manually:

goimports -w .

3. Golangci-lint

Comprehensive linter running 50+ checks.

What it checks:

  • Code complexity
  • Dead code
  • Inefficient assignments
  • Error handling
  • Security issues
  • Style violations
  • And much more...

Configuration: .golangci.yml

How to run manually:

make lint
# Or directly:
golangci-lint run

4. Gosec

Security-focused static analysis tool.

What it checks:

  • SQL injection vulnerabilities
  • Command injection risks
  • Path traversal attacks
  • Hardcoded credentials
  • Weak cryptography
  • File permission issues

How to run manually:

make security-check
# Or directly:
gosec ./...

5. Go Vet

Official Go static analysis tool.

What it checks:

  • Suspicious constructs
  • Printf format strings
  • Unreachable code
  • Nil pointer dereferences
  • Invalid interface implementations

How to run manually:

go vet ./...

6. Trailing Whitespace

Removes trailing whitespace from all text files.

7. End of File Fixer

Ensures files end with a newline.

8. YAML/JSON Syntax

Validates YAML and JSON file syntax.

Using Pre-Commit

Automatic Checks

Once installed, pre-commit runs automatically when you commit:

git add .
git commit -m "Your commit message"
# Pre-commit hooks run here

If any check fails, the commit is aborted with error messages.

Manual Checks

Run checks without committing:

# Run all hooks
make pre-commit-run

# Run on all files
pre-commit run --all-files

# Run specific hook
pre-commit run golangci-lint --all-files

Skipping Hooks (Not Recommended)

# Skip all hooks for a commit (use sparingly)
git commit -m "Message" --no-verify

Warning: Only skip hooks if absolutely necessary (e.g., work-in-progress commits for backup).

Makefile Commands

Code Quality Commands

make lint              # Run golangci-lint
make security-check    # Run gosec security scan
make pre-commit-run    # Run all pre-commit checks
make pre-commit-install # Install pre-commit hooks

Testing Commands

make test              # Run all tests with race detection
make test-coverage     # Generate coverage report (coverage.html)

Build Commands

make deps              # Install/update dependencies
make local             # Build for current architecture
make release           # Build for Linux/amd64 (Unraid)
make clean             # Clean build artifacts

Code Style Guidelines

Go Code Style

Follow official Go conventions:

// Good: Clear naming, proper error handling
func (c *SystemCollector) Collect() error {
    info, err := c.collectSystemInfo()
    if err != nil {
        return fmt.Errorf("failed to collect system info: %w", err)
    }

    c.ctx.Hub.Pub(info, "system_update")
    return nil
}

// Bad: Unclear naming, poor error handling
func (c *SystemCollector) C() error {
    i, e := c.csi()
    if e != nil {
        return e
    }
    c.ctx.Hub.Pub(i, "system_update")
    return nil
}

Error Handling

Always wrap errors with context:

// Good: Provides context
if err := someFunction(); err != nil {
    return fmt.Errorf("failed to execute someFunction: %w", err)
}

// Bad: Loses context
if err := someFunction(); err != nil {
    return err
}

Security Best Practices

From .github/copilot-instructions.md:

  1. Always validate user input using lib/validation.go:

    if err := lib.ValidateContainerID(containerID); err != nil {
        return err
    }
  2. Never use exec.Command directly — use lib.ExecCommand() or lib.ExecCommandOutput()

  3. Path traversal protection — validate all file paths:

    if err := lib.ValidateShareName(shareName); err != nil {
        return err
    }

Golangci-lint Configuration

The project uses a strict linting configuration in .golangci.yml:

Enabled Linters

  • errcheck: Check for unchecked errors
  • gosimple: Suggest code simplifications
  • govet: Official Go static analysis
  • ineffassign: Detect ineffectual assignments
  • staticcheck: Advanced static analysis
  • typecheck: Type checking
  • unused: Detect unused code
  • gosec: Security vulnerabilities
  • misspell: Common spelling mistakes
  • gofmt: Code formatting
  • goimports: Import management
  • revive: Extended linting rules
  • goconst: Find repeated strings that could be constants
  • dupl: Code duplication detection

Configuration Example

linters-settings:
  errcheck:
    check-blank: true
    check-type-assertions: true

  govet:
    enable-all: true

  gosec:
    severity: medium
    confidence: medium

Continuous Integration

Pre-commit checks also run in GitHub Actions CI:

name: Pre-commit
on: [push, pull_request]

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
      - uses: pre-commit/action@v3.0.0

Troubleshooting

Pre-commit Installation Failed

# Update pip
pip install --upgrade pip

# Reinstall pre-commit
pip install --force-reinstall pre-commit

# Verify installation
pre-commit --version

Hook Failed with Error

# View detailed error
pre-commit run --all-files --verbose

# Update hooks
pre-commit autoupdate

# Clean and reinstall
pre-commit uninstall
pre-commit install

Golangci-lint Too Slow

# Run only fast linters
golangci-lint run --fast

# Run on changed files only
golangci-lint run --new-from-rev=main

Security Check False Positives

Edit .golangci.yml to exclude specific issues:

linters-settings:
  gosec:
    excludes:
      - G304 # File path provided as taint input (if validated elsewhere)

Important: Only exclude after verifying it's a false positive.

Testing Standards

Test Coverage

Maintain test coverage above 70%:

# Generate coverage report
make test-coverage

# View coverage.html in browser
open coverage.html

Test Patterns

Use table-driven tests:

func TestValidateContainerID(t *testing.T) {
    tests := []struct {
        name    string
        id      string
        wantErr bool
    }{
        {"valid short ID", "bbb57ffa3c50", false},
        {"empty ID", "", true},
        {"SQL injection", "'; DROP TABLE--", true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateContainerID(tt.id)
            if (err != nil) != tt.wantErr {
                t.Errorf("got error=%v, wantErr=%v", err, tt.wantErr)
            }
        })
    }
}

Best Practices Summary

  1. Run pre-commit before pushing: make pre-commit-run
  2. Write tests for new code: Aim for 70%+ coverage
  3. Handle errors properly: Always wrap with context
  4. Validate all user input: Use validation functions
  5. Document complex code: Clear comments explain "why"
  6. Keep functions small: Max 50 lines recommended
  7. Follow Go conventions: Use gofmt, goimports

Next Steps


Last Updated: January 2026
Pre-commit Version: 3.5.0+
Golangci-lint Version: 1.55.0+