Support proper semver ranges for Helm charts
This commit changes the semver range parser to `blang/semver`, which is also used to parse semver tags for GitRepository sources.
This commit is contained in:
parent
25f0552ef9
commit
d38b8fe193
|
@ -4,7 +4,7 @@ metadata:
|
||||||
name: helmchart-sample
|
name: helmchart-sample
|
||||||
spec:
|
spec:
|
||||||
name: podinfo
|
name: podinfo
|
||||||
version: '^2.0.0'
|
version: '>=2.0.0 <3.0.0'
|
||||||
helmRepositoryRef:
|
helmRepositoryRef:
|
||||||
name: helmrepository-sample
|
name: helmrepository-sample
|
||||||
interval: 1m
|
interval: 1m
|
||||||
|
|
|
@ -26,7 +26,6 @@ import (
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
"helm.sh/helm/v3/pkg/getter"
|
"helm.sh/helm/v3/pkg/getter"
|
||||||
"helm.sh/helm/v3/pkg/repo"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
@ -36,9 +35,9 @@ import (
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/recorder"
|
"github.com/fluxcd/pkg/recorder"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||||
"github.com/fluxcd/source-controller/internal/helm"
|
"github.com/fluxcd/source-controller/internal/helm"
|
||||||
)
|
)
|
||||||
|
@ -174,30 +173,8 @@ func (r *HelmChartReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HelmChartReconciler) reconcile(ctx context.Context, repository sourcev1.HelmRepository, chart sourcev1.HelmChart) (sourcev1.HelmChart, error) {
|
func (r *HelmChartReconciler) reconcile(ctx context.Context, repository sourcev1.HelmRepository, chart sourcev1.HelmChart) (sourcev1.HelmChart, error) {
|
||||||
indexBytes, err := ioutil.ReadFile(repository.Status.Artifact.Path)
|
cv, err := helm.GetDownloadableChartVersionFromIndex(repository.Status.Artifact.Path, chart.Spec.Name, chart.Spec.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to read Helm repository index file: %w", err)
|
|
||||||
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
|
|
||||||
}
|
|
||||||
index := &repo.IndexFile{}
|
|
||||||
if err := yaml.Unmarshal(indexBytes, index); err != nil {
|
|
||||||
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// find referenced chart in index
|
|
||||||
cv, err := index.Get(chart.Spec.Name, chart.Spec.Version)
|
|
||||||
if err != nil {
|
|
||||||
switch err {
|
|
||||||
case repo.ErrNoChartName:
|
|
||||||
err = fmt.Errorf("chart '%s' could not be found in Helm repository '%s'", chart.Spec.Name, repository.Name)
|
|
||||||
case repo.ErrNoChartVersion:
|
|
||||||
err = fmt.Errorf("no chart with version '%s' found for '%s'", chart.Spec.Version, chart.Spec.Name)
|
|
||||||
}
|
|
||||||
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cv.URLs) == 0 {
|
|
||||||
err = fmt.Errorf("chart '%s' has no downloadable URLs", cv.Name)
|
|
||||||
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
|
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +184,6 @@ func (r *HelmChartReconciler) reconcile(ctx context.Context, repository sourcev1
|
||||||
u, err := url.Parse(ref)
|
u, err := url.Parse(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("invalid chart URL format '%s': %w", ref, err)
|
err = fmt.Errorf("invalid chart URL format '%s': %w", ref, err)
|
||||||
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := r.Getters.ByScheme(u.Scheme)
|
c, err := r.Getters.ByScheme(u.Scheme)
|
||||||
|
|
|
@ -101,7 +101,7 @@ var _ = Describe("HelmChartReconciler", func() {
|
||||||
},
|
},
|
||||||
Spec: sourcev1.HelmChartSpec{
|
Spec: sourcev1.HelmChartSpec{
|
||||||
Name: "helmchart",
|
Name: "helmchart",
|
||||||
Version: "*",
|
Version: "",
|
||||||
HelmRepositoryRef: corev1.LocalObjectReference{Name: repositoryKey.Name},
|
HelmRepositoryRef: corev1.LocalObjectReference{Name: repositoryKey.Name},
|
||||||
Interval: metav1.Duration{Duration: pullInterval},
|
Interval: metav1.Duration{Duration: pullInterval},
|
||||||
},
|
},
|
||||||
|
@ -203,7 +203,6 @@ var _ = Describe("HelmChartReconciler", func() {
|
||||||
},
|
},
|
||||||
Spec: sourcev1.HelmChartSpec{
|
Spec: sourcev1.HelmChartSpec{
|
||||||
Name: "helmchart",
|
Name: "helmchart",
|
||||||
Version: "*",
|
|
||||||
HelmRepositoryRef: corev1.LocalObjectReference{Name: repositoryKey.Name},
|
HelmRepositoryRef: corev1.LocalObjectReference{Name: repositoryKey.Name},
|
||||||
Interval: metav1.Duration{Duration: 1 * time.Hour},
|
Interval: metav1.Duration{Duration: 1 * time.Hour},
|
||||||
},
|
},
|
||||||
|
@ -218,7 +217,7 @@ var _ = Describe("HelmChartReconciler", func() {
|
||||||
return ""
|
return ""
|
||||||
}, timeout, interval).Should(Equal("1.0.0"))
|
}, timeout, interval).Should(Equal("1.0.0"))
|
||||||
|
|
||||||
chart.Spec.Version = "~0.1.0"
|
chart.Spec.Version = "<0.2.0"
|
||||||
Expect(k8sClient.Update(context.Background(), chart)).Should(Succeed())
|
Expect(k8sClient.Update(context.Background(), chart)).Should(Succeed())
|
||||||
Eventually(func() string {
|
Eventually(func() string {
|
||||||
_ = k8sClient.Get(context.Background(), key, chart)
|
_ = k8sClient.Get(context.Background(), key, chart)
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Flux CD contributors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package helm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"helm.sh/helm/v3/pkg/repo"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDownloadableChartVersionFromIndex(path, chart, version string) (*repo.ChartVersion, error) {
|
||||||
|
b, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read Helm repository index file: %w", err)
|
||||||
|
}
|
||||||
|
index := &repo.IndexFile{}
|
||||||
|
if err := yaml.Unmarshal(b, index); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal Helm repository index file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cv *repo.ChartVersion
|
||||||
|
if version == "" || version == "*" {
|
||||||
|
cv, err = index.Get(chart, version)
|
||||||
|
if err != nil {
|
||||||
|
if err == repo.ErrNoChartName {
|
||||||
|
err = fmt.Errorf("chart '%s' could not be found in Helm repository index", chart)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entries, ok := index.Entries[chart]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("chart '%s' could not be found in Helm repository index", chart)
|
||||||
|
}
|
||||||
|
|
||||||
|
rng, err := semver.ParseRange(version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("semver range parse error: %w", err)
|
||||||
|
}
|
||||||
|
versionEntryLookup := make(map[string]*repo.ChartVersion)
|
||||||
|
var versionsInRange []semver.Version
|
||||||
|
for _, e := range entries {
|
||||||
|
v, _ := semver.ParseTolerant(e.Version)
|
||||||
|
if rng(v) {
|
||||||
|
versionsInRange = append(versionsInRange, v)
|
||||||
|
versionEntryLookup[v.String()] = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(versionsInRange) == 0 {
|
||||||
|
return nil, fmt.Errorf("no match found for semver: %s", version)
|
||||||
|
}
|
||||||
|
semver.Sort(versionsInRange)
|
||||||
|
|
||||||
|
latest := versionsInRange[len(versionsInRange)-1]
|
||||||
|
cv = versionEntryLookup[latest.String()]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cv.URLs) == 0 {
|
||||||
|
return nil, fmt.Errorf("no downloadable URLs for chart '%s' with version '%s'", cv.Name, cv.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cv, nil
|
||||||
|
}
|
Loading…
Reference in New Issue