-
Notifications
You must be signed in to change notification settings - Fork 892
Validate Helm CLI version (>= 4.2.0) before Kubernetes deploy #17491
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
Merged
mitchdenny
merged 7 commits into
main
from
mitchdenny/issue-16977-validate-helm-is-installed-and-assert-co-a823a9
May 27, 2026
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
3a088b9
Validate Helm CLI version (>= 4.2.0) before Kubernetes deploy
mitchdenny ced45a6
Drop --client flag from helm version invocation
mitchdenny d03916c
Address PR review feedback
mitchdenny 1401438
Bump E2E Helm install version to v4.2.0 to match new floor
mitchdenny df5b207
Bump deployment-tests.yml Helm pin to v4.2.0
mitchdenny b5c7535
Gate AddHelmChart uninstall on prereqs and unify Helm CLI check
mitchdenny 647c241
Defer Helm version check in destroy-helm-{env} until state is found
mitchdenny File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
src/Aspire.Hosting.Kubernetes/Deployment/HelmVersionValidator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Globalization; | ||
| using System.Text; | ||
| using System.Text.RegularExpressions; | ||
|
|
||
| namespace Aspire.Hosting.Kubernetes; | ||
|
|
||
| /// <summary> | ||
| /// Validates that the installed Helm CLI is new enough for the flags and behaviors | ||
| /// Aspire's Kubernetes deployment pipeline depends on. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Aspire authors `helm upgrade --install` invocations against Helm 4.x. In particular | ||
| /// the `--server-side=true --force-conflicts` combination emitted by | ||
| /// <c>WithForceConflicts()</c> matches the Helm 4 form of <c>--server-side</c> (string | ||
| /// valued: <c>"true" | "false" | "auto"</c>, https://github.com/helm/helm/pull/13649). | ||
| /// Validating up-front turns errors like <c>unknown flag: --force-conflicts</c> or | ||
| /// <c>Flag --force has been deprecated, use --force-replace instead</c> into a single | ||
| /// clear actionable message. | ||
| /// </remarks> | ||
| internal static partial class HelmVersionValidator | ||
| { | ||
| /// <summary> | ||
| /// Minimum supported Helm version. See class remarks for rationale. | ||
| /// </summary> | ||
| public static readonly Version MinimumHelmVersion = new(4, 2, 0); | ||
|
|
||
| private const string InstallDocsUrl = "https://helm.sh/docs/intro/install/"; | ||
|
|
||
| // `helm version --short` returns a single line in the shape | ||
| // v4.2.0+gfa15ec0 | ||
| // v4.0.0 | ||
| // v3.18.0+gb88f836 | ||
| // Match the optional `v` followed by MAJOR.MINOR.PATCH; ignore any `+gitsha` | ||
| // build metadata. Intentionally unanchored: some shells / wrappers (oh-my-zsh | ||
| // plugins, asdf shims, alias output, etc.) can prefix banner or shim lines | ||
| // before the actual version token, so we accept the first valid token anywhere | ||
| // in the captured output rather than requiring it at column 0. | ||
| [GeneratedRegex(@"v?(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)")] | ||
| private static partial Regex HelmVersionRegex(); | ||
|
|
||
| /// <summary> | ||
| /// Runs <c>helm version --short</c>, parses the SemVer, and throws | ||
| /// <see cref="InvalidOperationException"/> if the installed version is older than | ||
| /// <see cref="MinimumHelmVersion"/> or if the output cannot be parsed. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// We deliberately do not pass <c>--client</c>. That flag existed in Helm 2 (where | ||
| /// Tiller meant there was a separate server version), was kept as a no-op in | ||
| /// Helm 3, and was removed entirely in Helm 4 — which is our minimum. Passing | ||
| /// <c>--client</c> against Helm 4 fails with <c>Error: unknown flag: --client</c>, | ||
| /// which is exactly the cryptic failure mode this validator exists to prevent. | ||
| /// </remarks> | ||
| public static async Task EnsureMinimumVersionAsync( | ||
| IHelmRunner helmRunner, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(helmRunner); | ||
|
|
||
| var stdout = new StringBuilder(); | ||
| var stderr = new StringBuilder(); | ||
|
|
||
| int exitCode; | ||
| try | ||
| { | ||
| exitCode = await helmRunner.RunAsync( | ||
| "version --short", | ||
| onOutputData: line => stdout.AppendLine(line), | ||
| onErrorData: line => stderr.AppendLine(line), | ||
| cancellationToken: cancellationToken).ConfigureAwait(false); | ||
|
mitchdenny marked this conversation as resolved.
|
||
| } | ||
| catch (Exception ex) when (ex is not OperationCanceledException) | ||
| { | ||
| // ProcessUtil throws when the process itself can't be spawned (helm not on | ||
| // PATH, permission denied, etc.). The exception message from the runner | ||
| // is typically a low-level "No such file or directory" or | ||
| // "permission denied" that doesn't tell users what to do, so wrap it. | ||
| throw new InvalidOperationException( | ||
| $"Helm CLI not found or could not be invoked. Aspire requires Helm {MinimumHelmVersion} or later. Install it from {InstallDocsUrl} and ensure it is available on your PATH.", | ||
| ex); | ||
| } | ||
|
|
||
| if (exitCode != 0) | ||
| { | ||
| var errorText = stderr.ToString().Trim(); | ||
| var detail = string.IsNullOrEmpty(errorText) ? $"exit code {exitCode}" : errorText; | ||
| throw new InvalidOperationException( | ||
| $"'helm version --short' failed ({detail}). Aspire requires Helm {MinimumHelmVersion} or later. See {InstallDocsUrl}."); | ||
| } | ||
|
|
||
| var rawOutput = stdout.ToString().Trim(); | ||
| if (!TryParseHelmVersion(rawOutput, out var detected)) | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"Could not parse Helm version from 'helm version --short' output: '{rawOutput}'. Aspire requires Helm {MinimumHelmVersion} or later. See {InstallDocsUrl}."); | ||
| } | ||
|
|
||
| if (detected < MinimumHelmVersion) | ||
| { | ||
| throw new InvalidOperationException( | ||
| string.Format( | ||
| CultureInfo.InvariantCulture, | ||
| "Helm {0} was detected, but Aspire requires Helm {1} or later to deploy Kubernetes resources. Upgrade Helm from {2}.", | ||
| detected, | ||
| MinimumHelmVersion, | ||
| InstallDocsUrl)); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Extracts the first <c>MAJOR.MINOR.PATCH</c> token from the given Helm version | ||
| /// output. Returns <see langword="false"/> if no version token is present. | ||
| /// </summary> | ||
| internal static bool TryParseHelmVersion(string output, out Version version) | ||
| { | ||
| version = new Version(0, 0, 0); | ||
| if (string.IsNullOrWhiteSpace(output)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| var match = HelmVersionRegex().Match(output); | ||
| if (!match.Success) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| var major = int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture); | ||
| var minor = int.Parse(match.Groups["minor"].ValueSpan, CultureInfo.InvariantCulture); | ||
| var patch = int.Parse(match.Groups["patch"].ValueSpan, CultureInfo.InvariantCulture); | ||
| version = new Version(major, minor, patch); | ||
| return true; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
tests/Aspire.Cli.EndToEnd.Tests/Helpers/KubernetesE2EVersions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace Aspire.Cli.EndToEnd.Tests.Helpers; | ||
|
|
||
| /// <summary> | ||
| /// Single source of truth for the tool versions installed into the E2E test | ||
| /// container during Kubernetes scenarios. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Each value falls back to an environment variable so CI can pin or bump a | ||
| /// version without a code change. The defaults are chosen to satisfy Aspire's | ||
| /// own minimums: | ||
| /// <list type="bullet"> | ||
| /// <item> | ||
| /// <description> | ||
| /// <c>HelmVersion</c> must be at least | ||
| /// <c>Aspire.Hosting.Kubernetes.HelmVersionValidator.MinimumHelmVersion</c> | ||
| /// (currently <c>4.2.0</c>). The Kubernetes deployment pipeline now | ||
| /// fails fast at <c>check-helm-prereqs-{env}</c> for older Helm CLIs, | ||
| /// so an older default here would break every <c>DeployK8s*</c> test. | ||
| /// </description> | ||
| /// </item> | ||
| /// </list> | ||
| /// </remarks> | ||
| internal static class KubernetesE2EVersions | ||
| { | ||
| public static string KindVersion => Environment.GetEnvironmentVariable("KIND_VERSION") ?? "v0.31.0"; | ||
|
|
||
| public static string HelmVersion => Environment.GetEnvironmentVariable("HELM_VERSION") ?? "v4.2.0"; | ||
|
|
||
| public static string KubectlVersion => Environment.GetEnvironmentVariable("KUBECTL_VERSION") ?? "v1.34.3"; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.