-
Notifications
You must be signed in to change notification settings - Fork 206
HIP: Bring .helmignore to parity with .gitignore for Charts v3 #426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,346 @@ | ||
| --- | ||
| hip: 9999 | ||
| title: "Bring .helmignore to parity with .gitignore file targeting syntax" | ||
| authors: ["Scott Rigby <scott@r6by.com>"] | ||
| created: "2025-12-14" | ||
| type: "feature" | ||
| status: "draft" | ||
| requires: ["HIP-0020"] | ||
| replaces: | ||
| superseded-by: | ||
| --- | ||
|
|
||
| ## Abstract | ||
|
|
||
| This proposal brings `.helmignore` file targeting semantics to full parity with `.gitignore` syntax and matching rules for Helm Charts v3. The current `.helmignore` implementation diverges from `.gitignore` in critical ways—most notably in rule evaluation order (first-match vs. last-match), negation pattern behavior, and lack of `**` recursive glob support. These differences cause confusion and bugs for users who reasonably expect `.helmignore` to behave like `.gitignore`. By scoping this change to Charts v3 (per [HIP-0020][hip-0020]), existing charts continue to work unchanged while v3 charts opt into consistent, predictable ignore behavior. | ||
|
|
||
| ## Motivation | ||
|
|
||
| Users familiar with `.gitignore` expect the same behavior from `.helmignore`. The current divergence causes: | ||
|
|
||
| 1. **Broken negation patterns**: Users cannot use whitelist-style patterns (e.g., `/*` then `!Chart.yaml`) because the current implementation has inverted negation semantics ([#8688], [#3622], [#1776]). | ||
|
|
||
| 2. **Missing recursive globs**: The `**` pattern is explicitly unsupported, forcing verbose workarounds for common cases like `**/test/` ([#12592]). | ||
|
|
||
| 3. **Unexpected first-match behavior**: Git uses "last matching rule wins"; Helm stops at the first match. This breaks patterns that progressively refine what to exclude. | ||
|
|
||
| 4. **Scope confusion**: Users expect `.helmignore` to only affect `helm package`, but it affects all commands including `template`, `install`, and `upgrade` ([#6075], [helm-www#1171]). | ||
|
|
||
| 5. **Documentation gaps**: Current docs don't clearly explain limitations or differences from `.gitignore` ([#4638], [helm-www#1312]). | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is one more problem iirc -- today Helm only respects
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .helmignore definitely ignores files during helm runtime operations too - on both a packed ann unpacked chart - including helm template, install, etc. Here is an unpacked example: d=$(mktemp -d)
mkdir -p $d/templates $d/files
cat > $d/Chart.yaml <<'EOF'
apiVersion: v2
name: test
version: 0.1.0
EOF
cat > $d/templates/cm.yaml <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Chart.Name }}-cm
data:
{{- (.Files.Glob "files/*").AsConfig | nindent 2 }}
EOF
cat > $d/templates/pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: {{ .Chart.Name }}-pod
spec:
containers:
- name: nginx
image: nginx:alpine
EOF
cat > $d/files/test.conf <<'EOF'
test including .conf file
EOF
cat > $d/files/test.txt <<'EOF'
test including .txt file
EOF
tree $d
echo '\n=== template WITHOUT .helmignore ==='
helm template $d
cat > $d/.helmignore <<'EOF'
*.txt
pod.yaml
EOF
echo '\n=== template WITH .helmignore ==='
helm template $d
echo '\n=== install WITH .helmignore ==='
helm install --dry-run test $dshould return /var/folders/7j/bxcg16h177zfgfkzdlv8rb240000gn/T/tmp.5QdzSnmxHz
├── Chart.yaml
├── files
│ ├── test.conf
│ └── test.txt
└── templates
├── cm.yaml
└── pod.yaml
3 directories, 5 files
=== template WITHOUT .helmignore ===
---
# Source: test/templates/cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
data:
test.conf: |
test including .conf file
test.txt: |
test including .txt file
---
# Source: test/templates/pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: nginx
image: nginx:alpine
=== template WITH .helmignore ===
---
# Source: test/templates/cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
data:
test.conf: |
test including .conf file
=== install WITH .helmignore ===
level=WARN msg="--dry-run is deprecated and should be replaced with '--dry-run=client'"
NAME: test
LAST DEPLOYED: Wed Dec 17 22:14:13 2025
NAMESPACE: default
STATUS: pending-install
REVISION: 1
DESCRIPTION: Dry run complete
TEST SUITE: None
HOOKS:
MANIFEST:
---
# Source: test/templates/cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
data:
test.conf: |
test including .conf file
|
||
| ### Evidence of User Pain | ||
|
|
||
| A comprehensive search of helm/helm issues reveals **27 directly related issues** and **5 pull requests**. Key themes: | ||
|
|
||
| - **Pattern matching logic**: [#8688], [#3622], [#1776], [#12592] | ||
| - **Scope/behavior confusion**: [#6075], [#3050], [#10764] | ||
| - **Feature requests for global ignore**: [#1674], [#5675] | ||
| - **Documentation issues**: [#4638], [helm-www#1171], [helm-www#1312], [helm-www#1460] | ||
|
|
||
| The recurring user expectation is clear: `.helmignore` should work like `.gitignore`. | ||
|
|
||
| ## Rationale | ||
|
|
||
| ### Why .gitignore Parity? | ||
|
|
||
| 1. **Reduced cognitive load**: Developers already know `.gitignore` semantics. Aligning `.helmignore` enables existing knowledge and patterns to be reused with Helm. | ||
|
|
||
| 2. **Well-documented spec**: Git's ignore format is [thoroughly documented][git-gitignore] and battle-tested across millions of repositories. | ||
|
|
||
| 3. **Ecosystem consistency**: Tools like Docker (`.dockerignore`), npm (`.npmignore`), and FluxCD (`.sourceignore`) all follow `.gitignore` semantics. | ||
|
|
||
| ### Why Charts v3? | ||
|
|
||
| Per [HIP-0020][hip-0020], Charts v3 provides a clean opt-in boundary for breaking changes: | ||
|
|
||
| 1. **Breaking change isolation**: Charts explicitly declare `apiVersion: v3`, accepting new semantics. Charts v2 behavior is preserved unchanged. | ||
|
|
||
| 2. **No forced migration**: Existing charts continue to work. Users migrate on their own schedule. | ||
|
|
||
| 3. **Independent evolution**: Chart changes are decoupled from Helm version, allowing adequate time for testing and adoption. | ||
|
|
||
| This is the appropriate scope because changing `.helmignore` semantics would break charts that depend on current (albeit buggy) behavior. | ||
|
|
||
| ## Specification | ||
|
|
||
| ### Behavioral Parity with .gitignore | ||
|
|
||
| Charts v3 `.helmignore` files SHALL support the full `.gitignore` pattern format as documented at [git-scm.com/docs/gitignore][git-gitignore]: | ||
|
|
||
| #### Pattern Types | ||
|
|
||
| | Pattern | Behavior | | ||
| | ----------- | ------------------------------------------------ | | ||
| | Blank lines | Ignored (separators for readability) | | ||
| | `# comment` | Lines starting with `#` are comments | | ||
| | `\#` | Escaped `#` matches literal `#` filename | | ||
| | `!pattern` | Negation—re-includes previously excluded matches | | ||
| | `/pattern` | Leading `/` anchors pattern to chart root | | ||
| | `pattern/` | Trailing `/` matches directories only | | ||
| | `*` | Matches anything except `/` | | ||
| | `?` | Matches any single character except `/` | | ||
| | `[a-z]` | Character class matching | | ||
| | `**` | Recursive glob (see below) | | ||
| | `\ ` | Escaped trailing space (preserved) | | ||
| | `\!` | Escaped `!` matches literal `!` filename | | ||
|
|
||
| #### Recursive Glob Semantics (`**`) | ||
|
|
||
| - `**/foo` — matches `foo` in all directories | ||
| - `foo/**` — matches everything inside `foo/` | ||
| - `a/**/b` — matches `a/b`, `a/x/b`, `a/x/y/b`, etc. | ||
|
|
||
| #### Rule Evaluation Order | ||
|
|
||
| **Last matching rule wins**. All rules are evaluated; the final matching rule determines whether a path is included or excluded. This enables patterns like: | ||
|
|
||
| ``` | ||
| # Exclude everything | ||
| /* | ||
|
|
||
| # But include these | ||
| !Chart.yaml | ||
| !values.yaml | ||
| !templates/ | ||
| ``` | ||
|
|
||
| #### Negation Behavior | ||
|
|
||
| - `!pattern` re-includes files that match, reversing a prior exclusion | ||
| - **Limitation**: Cannot re-include a file if its parent directory was excluded (Git skips listing excluded directories for performance) | ||
|
|
||
| #### Scope Clarification | ||
|
|
||
| Helm has no concept of staging or committing files. This proposal addresses **file targeting syntax and semantics only**—specifically, which files are included/excluded during chart operations. Git's behavior around staged/committed files does not apply. | ||
|
|
||
| ### Technical Requirements | ||
|
|
||
| 1. **Parser/Matcher**: Implement a `.gitignore`-compatible lexer, parser, and matcher supporting all pattern types above. | ||
|
|
||
| 2. **Last-match semantics**: Evaluate all rules; final matching rule determines outcome. | ||
|
|
||
| 3. **Path normalization**: Normalize paths to forward slashes for cross-platform consistency. | ||
|
|
||
| 4. **Directory short-circuit**: Once a directory is excluded, skip traversing its contents (matches Git's performance optimization). | ||
|
|
||
| 5. **Self-exclusion**: `.helmignore` MAY be automatically excluded (unlike current behavior per [#9436]). | ||
|
|
||
| ### Integration Points | ||
|
|
||
| Apply `.gitignore`-parity matching in all chart file operations: | ||
|
|
||
| - `helm package` | ||
| - `helm lint` | ||
| - `helm template` | ||
| - `helm install` / `helm upgrade` (for local charts) | ||
| - `.Files.Get` / `.Files.Glob` (file access in templates) | ||
|
|
||
| ### Gating | ||
|
|
||
| - **Charts v3 (`apiVersion: v3`)**: New `.gitignore`-parity semantics | ||
| - **Charts v2 (`apiVersion: v2`)**: Existing behavior unchanged | ||
|
|
||
| ## Backwards Compatibility | ||
|
|
||
| ### Charts v2 | ||
|
|
||
| No behavior change. Existing `.helmignore` files continue to work exactly as before, preserving compatibility for all current charts. | ||
|
|
||
| ### Charts v3 | ||
|
|
||
| Charts opting into v3 accept new `.helmignore` semantics. Potential breaking changes for charts migrating from v2: | ||
|
|
||
| | Current Behavior | New Behavior | Migration Impact | | ||
| | ------------------------ | -------------------------- | ------------------------------------------------------------ | | ||
| | First-match evaluation | Last-match evaluation | Patterns relying on early termination may behave differently | | ||
| | Negation inverts match | Negation re-includes | Whitelist patterns will finally work correctly | | ||
| | `**` causes error | `**` works | No breakage (feature addition) | | ||
| | Trailing spaces stripped | Preserved with `\ ` | Unlikely to affect real charts | | ||
| | No escape sequences | `\#`, `\!`, `\ ` supported | No breakage (feature addition) | | ||
|
|
||
| ### Migration Guidance | ||
|
|
||
| 1. **Test with v3**: Before changing `apiVersion`, test charts with new semantics | ||
| 2. **Review negation patterns**: Patterns with `!` may now work as originally intended | ||
| 3. **Simplify patterns**: `**` globs can replace verbose directory-specific patterns | ||
|
|
||
| ## Security Implications | ||
|
|
||
| None. This change only affects which local files are considered during chart operations. It does not: | ||
|
|
||
| - Introduce new attack surfaces | ||
| - Add remote dependencies | ||
| - Change network behavior | ||
| - Affect chart signing or verification | ||
|
|
||
| ## How to Teach This | ||
|
|
||
| ### Documentation Updates | ||
|
|
||
| 1. **Primary statement**: "Charts v3 `.helmignore` uses identical pattern rules to `.gitignore`." | ||
|
|
||
| 2. **Link to Git documentation**: Reference [git-scm.com/docs/gitignore][git-gitignore] as the canonical spec. | ||
|
|
||
| 3. **Migration guide**: Document behavior differences between v2 and v3. | ||
|
|
||
| 4. **Examples**: Provide side-by-side comparisons showing: | ||
| - Whitelist patterns (`/*`, `!Chart.yaml`) | ||
| - Recursive globs (`**/test/`) | ||
| - Last-match ordering | ||
|
|
||
| ### Release Notes | ||
|
|
||
| - Highlight UX improvement for users expecting `.gitignore` behavior | ||
| - Link to this HIP and HIP-0020 for context | ||
| - Emphasize opt-in nature via Charts v3 | ||
|
|
||
| ### Example Patterns | ||
|
|
||
| ```gitignore | ||
| # Comments start with # | ||
|
|
||
| # Ignore all dotfiles | ||
| .* | ||
|
|
||
| # But keep .helmignore itself | ||
| !.helmignore | ||
|
|
||
| # Ignore test directories anywhere | ||
| **/test/ | ||
| **/tests/ | ||
|
|
||
| # Ignore IDE files | ||
| .idea/ | ||
| .vscode/ | ||
| *.swp | ||
| *.swo | ||
| *~ | ||
|
|
||
| # Whitelist approach: exclude everything, then include specifics | ||
| /* | ||
| !Chart.yaml | ||
| !values.yaml | ||
| !values.schema.json | ||
| !templates/ | ||
| !charts/ | ||
| !crds/ | ||
| !files/ | ||
|
|
||
| # Directory-only patterns (trailing slash) | ||
| vendor/ | ||
| node_modules/ | ||
|
|
||
| # Anchored to root (leading slash) | ||
| /local-only.yaml | ||
| ``` | ||
|
|
||
| ## Reference Implementation | ||
|
|
||
| Implementation will be gated behind Charts v3 as specified in [HIP-0020][hip-0020]: | ||
|
|
||
| 1. New matcher package under `internal/chart/v3/ignore/` implementing `.gitignore` semantics | ||
| 2. Integration with v3 chart loader | ||
| 3. Comprehensive test suite covering: | ||
| - All pattern types from Git documentation | ||
| - Edge cases from reported issues ([#8688], [#3622], [#1776]) | ||
| - Cross-platform path handling | ||
| 4. Migration tests validating v2 charts remain unchanged | ||
| 5. Update `helm create` scaffold for v3 charts (once [PR #31592][pr-31592] merges) to generate a `.helmignore` demonstrating gitignore-parity patterns | ||
|
|
||
| Implementation details (library choice, architecture) are intentionally left to implementers. A separate implementation research document will evaluate options including existing Go libraries and FluxCD's `.sourceignore` implementation. | ||
|
|
||
| ## Rejected Ideas | ||
|
|
||
| ### Shell out to `git check-ignore` | ||
|
|
||
| Calling the `git` binary would provide perfect behavioral parity. Rejected because: | ||
|
|
||
| - Helm binaries must not have external runtime dependencies | ||
| - Adds complexity for containerized/minimal environments | ||
| - Performance overhead for repeated invocations | ||
|
|
||
| ### Partial parity (e.g., add `**` but keep first-match) | ||
|
|
||
| Rejected because partial fixes would leave confusing inconsistencies. Users expect `.gitignore` behavior; half-measures perpetuate the problem. | ||
|
|
||
| ### Change behavior for all chart versions | ||
|
|
||
| Rejected because it would break existing charts that depend on current (albeit buggy) semantics. Charts v3 provides clean isolation for breaking changes. | ||
|
|
||
| ## Open Issues | ||
|
|
||
| 1. **Self-exclusion default**: Should `.helmignore` be automatically excluded in v3? (Currently requires explicit entry per [#9436]) | ||
|
|
||
| 2. **Global ignore file**: Users have requested `~/.helmignore` support ([#1674], [#5675]). This is out of scope for this HIP but could be addressed separately. | ||
|
|
||
| 3. **Scope refinement**: Should `.helmignore` have different behavior for `helm package` vs. other commands? ([#6075], [#3050]). Out of scope for this HIP. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. helm/helm#12592 too |
||
|
|
||
| ## References | ||
|
|
||
| ### Helm HIPs | ||
|
|
||
| - [HIP-0020: Charts v3 Enablement][hip-0020] | ||
|
|
||
| ### Git Documentation | ||
|
|
||
| - [git-scm.com/docs/gitignore][git-gitignore] | ||
|
|
||
| ### Issues Directly Addressed by This HIP | ||
|
|
||
| These issues are resolved by implementing `.gitignore` pattern matching parity: | ||
|
|
||
| - [#8688][#8688] — Negation semantics are inverted; `!pattern` ignores non-matches instead of re-including matches. | ||
| - [#3622][#3622] — Whitelist patterns (`/*` then `!Chart.yaml`) fail with "chart metadata missing" due to broken negation logic. | ||
| - [#1776][#1776] — Pattern `.*` incorrectly matched the current directory, breaking charts. Shows user expectation of gitignore behavior. | ||
| - [#12592][#12592] — Patterns like `charts/*/README.md` don't work; `**` glob support and improved matching would help. | ||
| - [#12265][#12265] (PR) — Attempted partial fix for negation, stalled 2 years as a breaking change. This HIP provides the proper scope via Charts v3. | ||
|
|
||
| ### Issues Out of Scope (Context Only) | ||
|
|
||
| These issues concern _when_ `.helmignore` applies, not pattern syntax. Included for context but not addressed by this HIP: | ||
|
|
||
| - [#6075][#6075] — Users expected ignore to affect only `helm package`, but it affects all commands. This is intentional; HIP clarifies but doesn't change this. | ||
| - [#3050][#3050] — Files in `.helmignore` are inaccessible to `.Files.Get`. Architectural issue about ignore scope, not pattern matching. | ||
| - [#10764][#10764] — README files consume release storage; users want "exclude from release but include in package." Requires new scope distinction. | ||
| - [#9436][#9436] — `.helmignore` doesn't exclude itself from packages. Listed as open question for Charts v3. | ||
|
|
||
| ### Feature Requests (Out of Scope) | ||
|
|
||
| - [#1674][#1674], [#5675][#5675] — Global `~/.helmignore` support. Already partially implemented; could adopt gitignore parity separately. | ||
| - [#989][#989] — Default patterns for editor swap files. Addressed previously; shows user expectation of sensible defaults. | ||
|
|
||
| ### Documentation Issues | ||
|
|
||
| These show user confusion that better docs (and gitignore parity) would reduce: | ||
|
|
||
| - [#4638][#4638] — Requested more `.helmignore` documentation; shows need for clearer syntax docs. | ||
| - [helm-www#1171][helm-www#1171] — Docs say ignore affects "packaging" only, but it affects all operations. Needs correction regardless of HIP. | ||
| - [helm-www#1312][helm-www#1312] — Docs don't specify `.helmignore` must be in chart root, not working directory. | ||
| - [helm-www#1460][helm-www#1460] — Example pattern `/temp*` broke charts by matching `templates/`. Shows need for better examples. | ||
|
|
||
| ### Other Related PRs | ||
|
|
||
| - [#13293][#13293] — Fix for broken symlinks in `.helmignore`. Tangentially related to ignore handling. | ||
|
|
||
| <!-- Link definitions --> | ||
|
|
||
| [hip-0020]: https://github.com/helm/community/blob/main/hips/hip-0020.md | ||
| [pr-31592]: https://github.com/helm/helm/pull/31592 | ||
| [git-gitignore]: https://git-scm.com/docs/gitignore | ||
| [#8688]: https://github.com/helm/helm/issues/8688 | ||
| [#3622]: https://github.com/helm/helm/issues/3622 | ||
| [#1776]: https://github.com/helm/helm/issues/1776 | ||
| [#12592]: https://github.com/helm/helm/issues/12592 | ||
| [#6075]: https://github.com/helm/helm/issues/6075 | ||
| [#3050]: https://github.com/helm/helm/issues/3050 | ||
| [#9436]: https://github.com/helm/helm/issues/9436 | ||
| [#10764]: https://github.com/helm/helm/issues/10764 | ||
| [#1674]: https://github.com/helm/helm/issues/1674 | ||
| [#5675]: https://github.com/helm/helm/issues/5675 | ||
| [#989]: https://github.com/helm/helm/issues/989 | ||
| [#4638]: https://github.com/helm/helm/issues/4638 | ||
| [#12265]: https://github.com/helm/helm/pull/12265 | ||
| [helm-www#1171]: https://github.com/helm/helm-www/issues/1171 | ||
| [helm-www#1312]: https://github.com/helm/helm-www/issues/1312 | ||
| [helm-www#1460]: https://github.com/helm/helm-www/issues/1460 | ||
| [#13293]: https://github.com/helm/helm/pull/13293 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think users would expect
.helmignoreto affect all operations, no?Easy example is
helm install ./fooworks locally, thenhelm package ./foo; helm install foo.tar.gzwill fail, because a required file is not present in the chart (it was excluded byhelm package)