Merge pull request #1015 from somtochiama/helm-repo-url

Fix: Normalize helm repository url with query params properly
This commit is contained in:
Hidde Beydals 2023-02-15 15:45:47 +01:00 committed by GitHub
commit 7fe19031d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 38 deletions

View File

@ -509,7 +509,10 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
ctxTimeout, cancel := context.WithTimeout(ctx, repo.Spec.Timeout.Duration) ctxTimeout, cancel := context.WithTimeout(ctx, repo.Spec.Timeout.Duration)
defer cancel() defer cancel()
normalizedURL := repository.NormalizeURL(repo.Spec.URL) normalizedURL, err := repository.NormalizeURL(repo.Spec.URL)
if err != nil {
return chartRepoConfigErrorReturn(err, obj)
}
// Construct the Getter options from the HelmRepository data // Construct the Getter options from the HelmRepository data
clientOpts := []helmgetter.Option{ clientOpts := []helmgetter.Option{
helmgetter.WithURL(normalizedURL), helmgetter.WithURL(normalizedURL),
@ -1021,7 +1024,10 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
keychain authn.Keychain keychain authn.Keychain
) )
normalizedURL := repository.NormalizeURL(url) normalizedURL, err := repository.NormalizeURL(url)
if err != nil {
return nil, err
}
obj, err := r.resolveDependencyRepository(ctx, url, namespace) obj, err := r.resolveDependencyRepository(ctx, url, namespace)
if err != nil { if err != nil {
// Return Kubernetes client errors, but ignore others // Return Kubernetes client errors, but ignore others
@ -1201,8 +1207,8 @@ func (r *HelmChartReconciler) indexHelmRepositoryByURL(o client.Object) []string
if !ok { if !ok {
panic(fmt.Sprintf("Expected a HelmRepository, got %T", o)) panic(fmt.Sprintf("Expected a HelmRepository, got %T", o))
} }
u := repository.NormalizeURL(repo.Spec.URL) u, err := repository.NormalizeURL(repo.Spec.URL)
if u != "" { if u != "" && err == nil {
return []string{u} return []string{u}
} }
return nil return nil

View File

@ -266,7 +266,10 @@ func (dm *DependencyManager) resolveRepository(url string) (repo repository.Down
dm.mu.Lock() dm.mu.Lock()
defer dm.mu.Unlock() defer dm.mu.Unlock()
nUrl := repository.NormalizeURL(url) nUrl, err := repository.NormalizeURL(url)
if err != nil {
return
}
err = repository.ValidateDepURL(nUrl) err = repository.ValidateDepURL(nUrl)
if err != nil { if err != nil {
return return

View File

@ -27,7 +27,6 @@ import (
"os" "os"
"path" "path"
"sort" "sort"
"strings"
"sync" "sync"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
@ -271,31 +270,16 @@ func (r *ChartRepository) DownloadChart(chart *repo.ChartVersion) (*bytes.Buffer
// always the correct one to pick, check for updates once in awhile. // always the correct one to pick, check for updates once in awhile.
// Ref: https://github.com/helm/helm/blob/v3.3.0/pkg/downloader/chart_downloader.go#L241 // Ref: https://github.com/helm/helm/blob/v3.3.0/pkg/downloader/chart_downloader.go#L241
ref := chart.URLs[0] ref := chart.URLs[0]
u, err := url.Parse(ref) resolvedUrl, err := repo.ResolveReferenceURL(r.URL, ref)
if err != nil { if err != nil {
err = fmt.Errorf("invalid chart URL format '%s': %w", ref, err)
return nil, err return nil, err
} }
// Prepend the chart repository base URL if the URL is relative
if !u.IsAbs() {
repoURL, err := url.Parse(r.URL)
if err != nil {
err = fmt.Errorf("invalid chart repository URL format '%s': %w", r.URL, err)
return nil, err
}
q := repoURL.Query()
// Trailing slash is required for ResolveReference to work
repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/"
u = repoURL.ResolveReference(u)
u.RawQuery = q.Encode()
}
t := transport.NewOrIdle(r.tlsConfig) t := transport.NewOrIdle(r.tlsConfig)
clientOpts := append(r.Options, getter.WithTransport(t)) clientOpts := append(r.Options, getter.WithTransport(t))
defer transport.Release(t) defer transport.Release(t)
return r.Client.Get(u.String(), clientOpts...) return r.Client.Get(resolvedUrl, clientOpts...)
} }
// CacheIndex attempts to write the index from the remote into a new temporary file // CacheIndex attempts to write the index from the remote into a new temporary file

View File

@ -18,6 +18,7 @@ package repository
import ( import (
"fmt" "fmt"
"net/url"
"strings" "strings"
helmreg "helm.sh/helm/v3/pkg/registry" helmreg "helm.sh/helm/v3/pkg/registry"
@ -35,17 +36,22 @@ var (
) )
// NormalizeURL normalizes a ChartRepository URL by its scheme. // NormalizeURL normalizes a ChartRepository URL by its scheme.
func NormalizeURL(repositoryURL string) string { func NormalizeURL(repositoryURL string) (string, error) {
if repositoryURL == "" { if repositoryURL == "" {
return "" return "", nil
}
u, err := url.Parse(repositoryURL)
if err != nil {
return "", err
} }
if strings.Contains(repositoryURL, helmreg.OCIScheme) { if u.Scheme == helmreg.OCIScheme {
return strings.TrimRight(repositoryURL, "/") u.Path = strings.TrimRight(u.Path, "/")
return u.String(), nil
} }
return strings.TrimRight(repositoryURL, "/") + "/" u.Path = strings.TrimRight(u.Path, "/") + "/"
return u.String(), nil
} }
// ValidateDepURL returns an error if the given depended repository URL declaration is not supported // ValidateDepURL returns an error if the given depended repository URL declaration is not supported

View File

@ -27,6 +27,7 @@ func TestNormalizeURL(t *testing.T) {
name string name string
url string url string
want string want string
wantErr bool
}{ }{
{ {
name: "with slash", name: "with slash",
@ -43,11 +44,6 @@ func TestNormalizeURL(t *testing.T) {
url: "http://example.com//", url: "http://example.com//",
want: "http://example.com/", want: "http://example.com/",
}, },
{
name: "empty",
url: "",
want: "",
},
{ {
name: "oci with slash", name: "oci with slash",
url: "oci://example.com/", url: "oci://example.com/",
@ -58,12 +54,38 @@ func TestNormalizeURL(t *testing.T) {
url: "oci://example.com//", url: "oci://example.com//",
want: "oci://example.com", want: "oci://example.com",
}, },
{
name: "url with query",
url: "http://example.com?st=pr",
want: "http://example.com/?st=pr",
},
{
name: "url with slash and query",
url: "http://example.com/?st=pr",
want: "http://example.com/?st=pr",
},
{
name: "empty url",
url: "",
want: "",
},
{
name: "bad url",
url: "://badurl.",
wantErr: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t) g := NewWithT(t)
got := NormalizeURL(tt.url) got, err := NormalizeURL(tt.url)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
return
}
g.Expect(err).To(Not(HaveOccurred()))
g.Expect(got).To(Equal(tt.want)) g.Expect(got).To(Equal(tt.want))
}) })
} }