diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..5fddbf9 --- /dev/null +++ b/.envrc @@ -0,0 +1,13 @@ +#!/bin/sh +if command -v lsb_release &> /dev/null +then + mkdir -p .direnv + use_flake() { + watch_file flake.nix + watch_file flake.lock + eval "$(nix print-dev-env --profile "$(direnv_layout_dir)/flake-profile")" + } +fi +export DIRENV_WARN_TIMEOUT=100h +watch_file *.nix +use flake diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 712529c..8e20008 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,7 +18,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Helm Release - uses: IMIO/gha/helm-release-notify@v3.9.3 + uses: IMIO/gha/helm-release-notify@v4 with: TARGET_DIR: plausible-analytics MATTERMOST_WEBHOOK_URL: ${{ secrets.COMMON_MATTERMOST_WEBHOOK_URL }} diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 83f0440..0ea25a8 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -8,7 +8,7 @@ runs-on: ubuntu-latest steps: - name: Lint and Test Charts - uses: IMIO/gha/helm-test-notify@v3.9.3 + uses: IMIO/gha/helm-test-notify@v4 with: HELM_RELEASE: plausible-analytics HELM_NAMESPACE: plausible-analytics diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a1930cf..e2cabe5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,7 +8,7 @@ jobs: runs-on: gha-runners steps: - name: Lint and Test Charts - uses: IMIO/gha/helm-test-notify@v3.9.3 + uses: IMIO/gha/helm-test-notify@v4 with: HELM_RELEASE: plausible-analytics HELM_NAMESPACE: plausible-analytics diff --git a/.gitignore b/.gitignore index 7b8ebaf..1610142 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,9 @@ chart/ # Ignore IDE specific files .vscode/ -.idea/ \ No newline at end of file +.idea/ + +.direnv/* +*.tgz +.pre-commit-config.yaml +Chart.lock \ No newline at end of file diff --git a/.helmignore b/.helmignore index 4d790cd..773d2db 100644 --- a/.helmignore +++ b/.helmignore @@ -21,4 +21,9 @@ *~ # CI -.github/ \ No newline at end of file +.github/ + +helmlint.sh +.envrc +.direnv/ +flake.* \ No newline at end of file diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..dc32c98 --- /dev/null +++ b/flake.nix @@ -0,0 +1,39 @@ +{ + description = "Helm lint config"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + utils.url = "github:numtide/flake-utils"; + pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; + }; + outputs = { self, nixpkgs, utils, pre-commit-hooks }: + utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + }; + in + { + checks = { + pre-commit-check = pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = { + nixpkgs-fmt.enable = true; + helmlint = { + name = "helmlint"; + enable = true; + entry = "./helmlint.sh"; + types = [ "yaml" ]; + }; + }; + }; + }; + devShell = pkgs.mkShell { + inherit (self.checks.${system}.pre-commit-check) shellHook; + packages = with pkgs; [ + kubernetes-helm + ]; + }; + }); +} + diff --git a/helmlint.sh b/helmlint.sh new file mode 100755 index 0000000..78f978a --- /dev/null +++ b/helmlint.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +set -e + +# Run helm lint on the chart path. +# A typical helm chart directory structure looks as follows: +# +# └── root +# ├── README.md +# ├── Chart.yaml +# ├── charts +# │ └── postgres-9.5.6.tar.gz +# └── templates +# ├── deployment.yaml +# ├── service.yaml +# └── _helpers.tpl +# +# The `Chart.yaml` file is metadata that helm uses to index the chart, and is added to the release info. It includes things +# like `name`, `version`, and `maintainers`. +# The `charts` directory are subcharts / dependencies that are deployed with the chart. +# The `templates` directory is what contains the go templates to render the Kubernetes resource yaml. Also includes +# helper template definitions (suffix `.tpl`). +# +# Any time files in `templates` or `charts` changes, we should run `helm lint`. `helm lint` can only be run on the root +# path of a chart, so this pre-commit hook will take the changed files and resolve it to the helm chart path. The helm +# chart path is determined by a heuristic: it is the directory containing the `Chart.yaml` file. +# +# Note that pre-commit will only feed this the files that changed in the commit, so we can't do the filtering at the +# hook setting level (e.g `files: Chart.yaml` will not work if no changes are made in the Chart.yaml file). + +# OSX GUI apps do not pick up environment variables the same way as Terminal apps and there are no easy solutions, +# especially as Apple changes the GUI app behavior every release (see https://stackoverflow.com/q/135688/483528). As a +# workaround to allow GitHub Desktop to work, add this (hopefully harmless) setting here. +export PATH=$PATH:/usr/local/bin + +# Take the current working directory to know when to stop walking up the tree +readonly cwd_abspath="$(realpath "$PWD")" + +# https://stackoverflow.com/a/8574392 +# Usage: contains_element "val" "${array[@]}" +# Returns: 0 if there is a match, 1 otherwise +contains_element() { + local -r match="$1" + shift + + for e in "$@"; do + if [[ "$e" == "$match" ]]; then + return 0 + fi + done + return 1 +} + +# Only log debug statements if PRECOMMIT_DEBUG environment variable is set. +# Log to stderr. +debug() { + if [[ ! -z $PRECOMMIT_DEBUG ]]; then + >&2 echo "$@" + fi +} + +# Recursively walk up the tree until the current working directory and check if the changed file is part of a helm +# chart. Helm charts have a Chart.yaml file. +chart_path() { + local -r changed_file="$1" + + # We check both the current dir as well as the parent dir, in case the current dir is a file + local -r changed_file_abspath="$(realpath "$changed_file")" + local -r changed_file_dir="$(dirname "$changed_file_abspath")" + + debug "Checking directory $changed_file_abspath and $changed_file_dir for Chart.yaml" + + # Base case: we have walked to the top of dir tree + if [[ "$changed_file_abspath" == "$cwd_abspath" ]]; then + debug "No chart path found" + echo "" + return 0 + fi + + # The changed file is itself the helm chart indicator, Chart.yaml + if [[ "$(basename "$changed_file_abspath")" == "Chart.yaml" ]]; then + debug "Chart path found: $changed_file_dir" + echo "$changed_file_dir" + return 0 + fi + + # The changed_file is the directory containing the helm chart package file + if [[ -f "$changed_file_abspath/Chart.yaml" ]]; then + debug "Chart path found: $changed_file_abspath" + echo "$changed_file_abspath" + return 0 + fi + + # The directory of changed_file is the directory containing the helm chart package file + if [[ -f "$changed_file_dir/Chart.yaml" ]]; then + debug "Chart path found: $changed_file_dir" + echo "$changed_file_dir" + return 0 + fi + + # None of the above, so recurse and do again in the parent dir + chart_path "$changed_file_dir" +} + +# An array to keep track of which charts we already linted +seen_chart_paths=() + +for file in "$@"; do + debug "Checking $file" + file_chart_path=$(chart_path "$file") + debug "Resolved $file to chart path $file_chart_path" + + # The chart values.yaml file may not have all the values defined to enforce default values, which will cause the + # linter to fail. To support this, this pre-commit hook looks for a special values file called `linter_values.yaml` + # which should define the additional values that will be fed to the linter. + if [[ -f "$file_chart_path/linter_values.yaml" ]]; then + linter_values_arg="$file_chart_path/linter_values.yaml" + else + linter_values_arg="" + fi + + if [[ ! -z "$file_chart_path" ]]; then + if contains_element "$file_chart_path" "${seen_chart_paths[@]}"; then + debug "Already linted $file_chart_path" + elif [[ -z "$linter_values_arg" ]]; then + helm lint "$file_chart_path" + seen_chart_paths+=( "$file_chart_path" ) + else + # Combine both linter_values.yaml and values.yaml + helm lint -f "$file_chart_path/values.yaml" -f "$linter_values_arg" "$file_chart_path" + seen_chart_paths+=( "$file_chart_path" ) + fi + fi +done