diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index e738c33..4866d48 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,4 +1,6 @@ -# This configuration file allows our pre-commit hooks to be used with pre-commit: http://pre-commit.com/ +--- +# This configuration file allows our pre-commit hooks to be used with +# pre-commit: http://pre-commit.com/ - id: terraform-fmt name: Terraform fmt @@ -25,3 +27,29 @@ language: script pass_filenames: false +- id: helm-lint + name: Helm lint + description: Runs 'helm lint' on Helm charts + entry: hooks/helm-lint.sh + language: script + files: (Chart\.yaml|values.*\.yaml|templates/.*\.yaml)$ + exclude: '(^|.*/)(\.terraform|examples)/' + require_serial: true + +- id: yamllint + name: YAML Lint + description: Validates YAML syntax and style + entry: hooks/yamllint.sh + language: script + files: \.(yaml|yml)$ + exclude: '(^|.*/)(\.terraform|examples)/' + require_serial: false + +- id: helm-template-check + name: Helm template check + description: Validates that Helm templates can render without errors + entry: hooks/helm-template-check.sh + language: script + files: (Chart\.yaml|values.*\.yaml|templates/.*\.yaml)$ + exclude: '(^|.*/)(\.terraform|examples)/' + require_serial: true diff --git a/README.md b/README.md index f9e2239..dd66598 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,278 @@ -# Pre-commit hooks +# Pre-commit Hooks Collection -This repo defines Git pre-commit hooks intended for use with [pre-commit](https://pre-commit.com/). The currently -supported hooks are: +
-* **terraform-fmt**: Checks that all Terraform files (`*.tf`) are properly formatted (`terraform fmt --check -diff`). -* **terraform-validate**: Runs `terraform init -backend=false` and then `terraform validate`. - > Notes: directories requiring a private registry and lacking credentials are marked as **skipped** (do not fail the commit). Both hooks ignore `.terraform/` and `examples/`. +**Quality gates for your Infrastructure as Code** -## General Usage +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) +[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://pre-commit.com/) -In each of your repos, add a file called `.pre-commit-config.yaml` with the following contents: +
+ +--- + +## Overview + +This repository provides a curated collection of **Git pre-commit hooks** designed to enforce best practices and catch issues early in your Infrastructure as Code (IaC) workflows. By integrating these hooks into your development process, you can ensure code quality, consistency, and compliance before changes ever reach your repository. + +### Why Use Pre-commit Hooks? + +- **Catch issues early** - Identify problems before they reach CI/CD +- **Fast feedback loop** - Get instant validation on your local machine +- **Enforce standards** - Maintain consistent code quality across teams +- **Prevent broken commits** - Block commits that don't meet your criteria +- **Team collaboration** - Share the same quality gates with everyone + +--- + +## Available Hooks + +### Terraform Hooks + +| Hook | Description | What it does | +|------|-------------|--------------| +| `terraform-fmt` | **Format checker** | Verifies all `.tf` files are properly formatted using `terraform fmt --check -diff` | +| `terraform-validate` | **Syntax validator** | Runs `terraform init -backend=false` followed by `terraform validate` to catch configuration errors | + +> **Note:** Directories requiring private registries without credentials are automatically skipped. Both hooks ignore `.terraform/` and `examples/` directories. + +### Helm Hooks + +| Hook | Description | What it does | +|------|-------------|--------------| +| `helm-lint` | **Chart linter** | Validates Helm charts using `helm lint` to check for common issues and best practices | +| `helm-template-check` | **Template validator** | Renders templates with `helm template` to ensure they generate valid Kubernetes manifests | + +> **Note:** Helm hooks automatically discover charts by locating `Chart.yaml` files in your repository. + +--- + +## Quick Start + +### Prerequisites + +First, ensure you have [pre-commit](https://pre-commit.com/) installed on your system: + +```bash +# macOS +brew install pre-commit + +# Linux (recommended) +pipx install pre-commit + +# Linux (alternative) +pip install --user pre-commit + +# Windows (WSL or Git Bash) +pip install pre-commit +``` + +### Installation + +### Step 1: Add the configuration file + +Create a `.pre-commit-config.yaml` file in the root of your repository: ```yaml repos: - - repo: git@github.com:craftech-io/pre-commit.git # or https://github.com/craftech-io/pre-commit.git - rev: + - repo: git@github.com:craftech-io/pre-commit.git + # or use: https://github.com/craftech-io/pre-commit.git + rev: # Use the latest release tag hooks: + # Terraform hooks - id: terraform-fmt - id: terraform-validate - verbose: true + verbose: true # Show detailed output + + # Helm hooks + - id: helm-lint + - id: helm-template-check ``` -Next, have every developer: +> **Tip:** Replace `` with the latest release tag (e.g., `v1.0.0`). Check the [releases page](https://github.com/craftech-io/pre-commit/releases) for available versions. + +**Step 2: Install the hooks** -1. Install [pre-commit](https://pre-commit.com/#install). - - macOS: `brew install pre-commit` - - Linux: `pipx install pre-commit` (or `pip install --user pre-commit`) -2. Run `pre-commit install` in the repo. +Run this command in your repository: + +```bash +pre-commit install +``` -That's it! Now every time you commit a code change (`.tf` file), the hooks in the `hooks:` config will execute. -If any hook fails, the commit is aborted; if all pass, the commit succeeds. +**Step 3: You're all set!** -## Running Against All Files At Once +Now, every time you run `git commit`, the configured hooks will automatically execute. If any hook fails, the commit will be blocked, allowing you to fix issues before they're committed. -### Example: Formatting and validating all files +--- -If you'd like to run the hooks across the whole repo (useful the first time), you can run: +## Usage + +### Automatic Validation (Recommended) + +Once installed, hooks run automatically on every commit: ```bash -# Check formatting for all Terraform files -pre-commit run terraform-fmt --all-files +git add . +git commit -m "feat: add new infrastructure" +# Hooks will run automatically here! +``` -# Validate all Terraform directories -pre-commit run terraform-validate --all-files +### Manual Execution + +Run hooks manually without committing: -# Or run every configured hook across the repo +```bash +# Run all configured hooks on staged files +pre-commit run + +# Run a specific hook on staged files +pre-commit run terraform-fmt + +# Run hooks on all files in the repository pre-commit run --all-files + +# Run a specific hook on all files +pre-commit run terraform-validate --all-files +``` + +### Common Commands + +```bash +# Run all hooks with verbose output +pre-commit run --all-files -v + +# Run only Terraform hooks +pre-commit run terraform-fmt --all-files +pre-commit run terraform-validate --all-files + +# Run only Helm hooks +pre-commit run helm-lint --all-files +pre-commit run helm-template-check --all-files + +# Update hooks to the latest version +pre-commit autoupdate + +# Temporarily bypass hooks (not recommended!) +git commit --no-verify +``` + +--- + +## Advanced Configuration + +### Customizing Hook Behavior + +You can customize hooks in your `.pre-commit-config.yaml`: + +```yaml +repos: + - repo: git@github.com:craftech-io/pre-commit.git + rev: + hooks: + - id: terraform-fmt + # Run on specific file patterns only + files: ^modules/ + + - id: terraform-validate + # Exclude specific directories + exclude: ^(examples|tests)/ + + - id: helm-lint + # Always show verbose output + verbose: true + + - id: helm-template-check + # Run even if files haven't changed + always_run: false +``` + +### Combining with Other Hooks + +Mix these hooks with other pre-commit hooks for comprehensive validation: + +```yaml +repos: + # This repository's hooks + - repo: git@github.com:craftech-io/pre-commit.git + rev: + hooks: + - id: terraform-fmt + - id: helm-lint + + # Additional hooks from other sources + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files ``` -> Tip: for detailed output on demand, use `-v`, e.g. `pre-commit run -v terraform-validate --all-files`. +--- + +## Contributing + +We welcome contributions! Here's how you can help improve this project: + +### Adding New Hooks + +1. **Create the hook script** in the `hooks/` directory +2. **Make it executable**: `chmod +x hooks/your-hook.sh` +3. **Add entry** to `.pre-commit-hooks.yaml` +4. **Test thoroughly** with various scenarios +5. **Submit a pull request** with a clear description + +### Testing Your Changes + +Before submitting a PR, test your hooks: + +```bash +# Test on a specific file +bash hooks/your-hook.sh path/to/test/file + +# Test with pre-commit +pre-commit try-repo /path/to/your/local/repo your-hook-id --verbose --all-files +``` + +### Development Guidelines + +- Follow existing code style and structure +- Include error handling and clear error messages +- Add colorized output for better readability +- Write descriptive commit messages +- Update documentation (README, comments) +- Test on multiple scenarios (success, failure, edge cases) + +### Reporting Issues + +Found a bug or have a suggestion? Please [open an issue](https://github.com/craftech-io/pre-commit/issues) with: + +- **Clear description** of the problem or enhancement +- **Steps to reproduce** (for bugs) +- **Expected vs actual behavior** +- **Environment details** (OS, tool versions) + +--- + +## Additional Resources + +- [Pre-commit Documentation](https://pre-commit.com/) +- [Terraform Documentation](https://www.terraform.io/docs) +- [Helm Documentation](https://helm.sh/docs/) +- [Pre-commit Hook Examples](https://github.com/pre-commit/pre-commit-hooks) + +--- ## License -This code is released under the Apache 2.0 License. Please see [LICENSE](LICENSE) for more details. \ No newline at end of file +This project is released under the **Apache 2.0 License**. + + +
+ +**[Back to Top](#pre-commit-hooks-collection)** + +Made by Craftech + +
\ No newline at end of file diff --git a/hooks/helm-lint.sh b/hooks/helm-lint.sh new file mode 100755 index 0000000..3dc9818 --- /dev/null +++ b/hooks/helm-lint.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -uo pipefail +IFS=$'\n\t' + +RED='\033[31m'; GRN='\033[32m'; YLW='\033[33m'; BLD='\033[1m'; RST='\033[0m' +if [[ "${PRE_COMMIT_COLOR:-}" == "never" ]]; then RED=''; GRN=''; YLW=''; BLD=''; RST=''; fi + +# Verificar que helm esté instalado +if ! command -v helm &>/dev/null; then + echo -e "${RED}✗ Error: helm no está instalado${RST}" + echo " Instalá helm desde: https://helm.sh/docs/intro/install/" + exit 1 +fi + +# Recolectar directorios únicos que contienen Chart.yaml +if [[ "$#" -gt 0 ]]; then + # Buscar Chart.yaml en los directorios de los archivos modificados + mapfile -t CHART_DIRS < <( + for f in "$@"; do + dir="$(dirname "$f")" + # Buscar hacia arriba hasta encontrar Chart.yaml + while [[ "$dir" != "." && "$dir" != "/" ]]; do + if [[ -f "$dir/Chart.yaml" ]]; then + echo "$dir" + break + fi + dir="$(dirname "$dir")" + done + done | sort -u + ) +else + # Buscar todos los Chart.yaml en el repo + mapfile -t CHART_DIRS < <(find . -name "Chart.yaml" -exec dirname {} \; | sort -u) +fi + +# Si no hay charts, salir exitosamente +if [[ "${#CHART_DIRS[@]}" -eq 0 ]]; then + echo -e "${YLW}! No se encontraron Helm charts (Chart.yaml)${RST}" + exit 0 +fi + +exit_code=0 + +for chart_dir in "${CHART_DIRS[@]}"; do + echo -e "${BLD}>> Validando Helm chart: ${chart_dir}${RST}" + + pushd "$chart_dir" >/dev/null + + set +e + out="$(helm lint . 2>&1)" + status=$? + set -e + + if [[ $status -eq 0 ]]; then + echo -e " ${GRN}✓ Chart válido${RST}" + # Mostrar warnings si existen pero el lint pasó + if grep -q "WARNING" <<<"$out"; then + echo -e " ${YLW}⚠ Warnings encontrados:${RST}" + echo "$out" | grep "WARNING" | sed 's/^/ /' + fi + else + echo -e " ${RED}✗ Lint falló${RST}" + echo "$out" | sed 's/^/ /' + exit_code=1 + fi + + popd >/dev/null + echo +done + +if [[ $exit_code -eq 0 ]]; then + echo -e "${GRN}✓ Todos los charts pasaron helm lint${RST}" +else + echo -e "${RED}✗ Algunos charts tienen errores de lint${RST}" +fi + +exit "$exit_code" diff --git a/hooks/helm-template-check.sh b/hooks/helm-template-check.sh new file mode 100755 index 0000000..689202d --- /dev/null +++ b/hooks/helm-template-check.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +set -uo pipefail +IFS=$'\n\t' + +RED='\033[31m'; GRN='\033[32m'; YLW='\033[33m'; BLD='\033[1m'; RST='\033[0m' +if [[ "${PRE_COMMIT_COLOR:-}" == "never" ]]; then RED=''; GRN=''; YLW=''; BLD=''; RST=''; fi + +# Verificar que helm esté instalado +if ! command -v helm &>/dev/null; then + echo -e "${RED}✗ Error: helm no está instalado${RST}" + echo " Instalá helm desde: https://helm.sh/docs/intro/install/" + exit 1 +fi + +# Recolectar directorios únicos que contienen Chart.yaml +if [[ "$#" -gt 0 ]]; then + # Buscar Chart.yaml en los directorios de los archivos modificados + mapfile -t CHART_DIRS < <( + for f in "$@"; do + dir="$(dirname "$f")" + # Buscar hacia arriba hasta encontrar Chart.yaml + while [[ "$dir" != "." && "$dir" != "/" ]]; do + if [[ -f "$dir/Chart.yaml" ]]; then + echo "$dir" + break + fi + dir="$(dirname "$dir")" + done + done | sort -u + ) +else + # Buscar todos los Chart.yaml en el repo + mapfile -t CHART_DIRS < <(find . -name "Chart.yaml" -exec dirname {} \; | sort -u) +fi + +# Si no hay charts, salir exitosamente +if [[ "${#CHART_DIRS[@]}" -eq 0 ]]; then + echo -e "${YLW}! No se encontraron Helm charts (Chart.yaml)${RST}" + exit 0 +fi + +exit_code=0 + +for chart_dir in "${CHART_DIRS[@]}"; do + echo -e "${BLD}>> Validando templates de Helm chart: ${chart_dir}${RST}" + + # Verificar que exista el directorio templates + if [[ ! -d "$chart_dir/templates" ]]; then + echo -e " ${YLW}! Skipped (no hay directorio templates/)${RST}" + echo + continue + fi + + pushd "$chart_dir" >/dev/null + + # Verificar si existe values.yaml + values_file="values.yaml" + if [[ ! -f "$values_file" ]]; then + echo -e " ${YLW}! Skipped (no existe values.yaml)${RST}" + popd >/dev/null + echo + continue + fi + + # Array para almacenar archivos de valores a probar + declare -a values_files=("$values_file") + + # Buscar archivos de ejemplo en examples/ + if [[ -d "examples" ]]; then + while IFS= read -r -d '' example_file; do + values_files+=("$example_file") + done < <(find examples -type f \( -name "*.yaml" -o -name "*.yml" \) -print0 2>/dev/null | sort -z) + fi + + echo -e " ${BLD}Casos de prueba encontrados: ${#values_files[@]}${RST}" + + # Iterar sobre todos los archivos de valores + for values_test_file in "${values_files[@]}"; do + # Obtener nombre descriptivo del archivo + if [[ "$values_test_file" == "values.yaml" ]]; then + test_name="valores por defecto" + else + test_name="$(basename "$values_test_file")" + fi + + echo -e " ${BLD}→ Probando: ${test_name}${RST}" + + set +e + # Ejecutar helm template solo para verificar que renderice sin errores + # No validamos el output, solo que pueda generar algo + out="$(helm template test-release . --values "$values_test_file" 2>&1 >/dev/null)" + status=$? + set -e + + if [[ $status -eq 0 ]]; then + echo -e " ${GRN}✓ Templates renderizan correctamente${RST}" + else + echo -e " ${RED}✗ Error al renderizar templates${RST}" + echo "$out" | sed 's/^/ /' + exit_code=1 + fi + done + + popd >/dev/null + echo +done + +if [[ $exit_code -eq 0 ]]; then + echo -e "${GRN}✓ Todos los templates se renderizaron correctamente con todos los casos de prueba${RST}" +else + echo -e "${RED}✗ Algunos templates tienen errores en uno o más casos de prueba${RST}" +fi + +exit "$exit_code" \ No newline at end of file diff --git a/hooks/yamllint.sh b/hooks/yamllint.sh new file mode 100755 index 0000000..b3b5320 --- /dev/null +++ b/hooks/yamllint.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -uo pipefail +IFS=$'\n\t' + +RED='\033[31m'; GRN='\033[32m'; YLW='\033[33m'; BLD='\033[1m'; RST='\033[0m' +if [[ "${PRE_COMMIT_COLOR:-}" == "never" ]]; then RED=''; GRN=''; YLW=''; BLD=''; RST=''; fi + +# Verificar que yamllint esté instalado +if ! command -v yamllint &>/dev/null; then + echo -e "${RED}✗ Error: yamllint no está instalado${RST}" + echo " Instalá yamllint:" + echo " - macOS: brew install yamllint" + echo " - Linux: pip install yamllint (o pipx install yamllint)" + exit 1 +fi + +exit_code=0 + +# Si no hay archivos, salir +if [[ "$#" -eq 0 ]]; then + echo -e "${YLW}! No hay archivos YAML para validar${RST}" + exit 0 +fi + +for f in "$@"; do + echo -e "${BLD}→ ${f}${RST}" + + set +e + out="$(yamllint -f parsable "$f" 2>&1)" + status=$? + set -e + + if [[ $status -eq 0 ]]; then + echo -e " ${GRN}✓ YAML válido${RST}" + else + echo -e " ${RED}✗ Errores de sintaxis YAML${RST}" + # Formatear output para mejor legibilidad + echo "$out" | sed 's/^/ /' + exit_code=1 + fi + + echo +done + +exit "$exit_code"