Skip to content

Commit ff442dd

Browse files
committed
Feat #1745: Support semver version ranges in helm_release version attribute
1 parent a4f1c1d commit ff442dd

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

helm/data_helm_template.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ func (d *HelmTemplate) Schema(ctx context.Context, req datasource.SchemaRequest,
398398
"version": schema.StringAttribute{
399399
Optional: true,
400400
Computed: true,
401-
Description: "Specify the exact chart version to install. If this is not specified, the latest version is installed.",
401+
Description: "Specify the exact chart version to install. If this is not specified, the latest version is installed. Supports semver range syntax (e.g., ^1.2.3, >= 1.0.0 < 2.0.0) for standard chart repositories and OCI registries.",
402402
},
403403
"wait": schema.BoolAttribute{
404404
Optional: true,
@@ -887,6 +887,15 @@ func chartPathOptionsModel(model *HelmTemplateModel, meta *Meta, cpo *action.Cha
887887

888888
version := getVersionModel(model)
889889

890+
if registry.IsOCI(repository) && version != "" {
891+
resolvedVersion, resolveDiags := resolveOCIVersionConstraint(meta, repository, model.Chart.ValueString(), version)
892+
diags.Append(resolveDiags...)
893+
if resolveDiags.HasError() {
894+
return nil, "", diags
895+
}
896+
version = resolvedVersion
897+
}
898+
890899
cpo.CaFile = model.RepositoryCaFile.ValueString()
891900
cpo.CertFile = model.RepositoryCertFile.ValueString()
892901
cpo.KeyFile = model.RepositoryKeyFile.ValueString()

helm/resource_helm_release.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
3333
"github.com/hashicorp/terraform-plugin-framework/types"
3434
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
35+
"github.com/Masterminds/semver/v3"
3536
"github.com/hashicorp/terraform-plugin-log/tflog"
3637
"github.com/pkg/errors"
3738
"helm.sh/helm/v3/pkg/action"
@@ -539,7 +540,7 @@ func (r *HelmRelease) Schema(ctx context.Context, req resource.SchemaRequest, re
539540
"version": schema.StringAttribute{
540541
Optional: true,
541542
Computed: true,
542-
Description: "Specify the exact chart version to install. If this is not specified, the latest version is installed",
543+
Description: "Specify the exact chart version to install. If this is not specified, the latest version is installed. Supports semver range syntax (e.g., ^1.2.3, >= 1.0.0 < 2.0.0) for standard chart repositories and OCI registries.",
543544
},
544545
"wait": schema.BoolAttribute{
545546
Optional: true,
@@ -1324,6 +1325,15 @@ func chartPathOptions(model *HelmReleaseModel, meta *Meta, cpo *action.ChartPath
13241325

13251326
version := getVersion(model)
13261327

1328+
if registry.IsOCI(repository) && version != "" {
1329+
resolvedVersion, resolveDiags := resolveOCIVersionConstraint(meta, repository, model.Chart.ValueString(), version)
1330+
diags.Append(resolveDiags...)
1331+
if resolveDiags.HasError() {
1332+
return nil, "", diags
1333+
}
1334+
version = resolvedVersion
1335+
}
1336+
13271337
cpo.CaFile = model.RepositoryCaFile.ValueString()
13281338
cpo.CertFile = model.RepositoryCertFile.ValueString()
13291339
cpo.KeyFile = model.RepositoryKeyFile.ValueString()
@@ -1379,6 +1389,58 @@ func getVersion(model *HelmReleaseModel) string {
13791389
return strings.TrimSpace(version)
13801390
}
13811391

1392+
func resolveOCIVersionConstraint(meta *Meta, repository, chartName, version string) (string, diag.Diagnostics) {
1393+
var diags diag.Diagnostics
1394+
1395+
if version == "" {
1396+
return version, diags
1397+
}
1398+
1399+
_, err := semver.StrictNewVersion(version)
1400+
if err == nil {
1401+
return version, diags
1402+
}
1403+
1404+
constraint, err := semver.NewConstraint(version)
1405+
if err != nil {
1406+
return version, diags
1407+
}
1408+
1409+
u, err := url.Parse(repository)
1410+
if err != nil {
1411+
diags.AddError("Invalid OCI Repository URL", fmt.Sprintf("Failed to parse OCI repository URL %s: %s", repository, err))
1412+
return "", diags
1413+
}
1414+
u.Path = pathpkg.Join(u.Path, chartName)
1415+
ref := strings.TrimPrefix(u.String(), "oci://")
1416+
1417+
tags, err := meta.RegistryClient.Tags(ref)
1418+
if err != nil {
1419+
diags.AddError("Error listing OCI registry tags", fmt.Sprintf("Unable to list tags for OCI reference %s: %s", ref, err))
1420+
return "", diags
1421+
}
1422+
1423+
var bestMatch *semver.Version
1424+
for _, tag := range tags {
1425+
v, err := semver.StrictNewVersion(tag)
1426+
if err != nil {
1427+
continue
1428+
}
1429+
if constraint.Check(v) {
1430+
if bestMatch == nil || v.GreaterThan(bestMatch) {
1431+
bestMatch = v
1432+
}
1433+
}
1434+
}
1435+
1436+
if bestMatch == nil {
1437+
diags.AddError("No matching chart version found", fmt.Sprintf("No chart version in repository %s satisfies constraint %q", repository, version))
1438+
return "", diags
1439+
}
1440+
1441+
return bestMatch.String(), diags
1442+
}
1443+
13821444
func isChartInstallable(ch *chart.Chart) error {
13831445
switch ch.Metadata.Type {
13841446
case "", "application":

0 commit comments

Comments
 (0)