884 lines
22 KiB
Go
884 lines
22 KiB
Go
/*
|
|
Copyright 2020 The Flux authors
|
|
|
|
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 repository
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
. "github.com/onsi/gomega"
|
|
"github.com/opencontainers/go-digest"
|
|
"helm.sh/helm/v3/pkg/chart"
|
|
helmgetter "helm.sh/helm/v3/pkg/getter"
|
|
"helm.sh/helm/v3/pkg/repo"
|
|
|
|
"github.com/fluxcd/source-controller/internal/helm"
|
|
)
|
|
|
|
var now = time.Now()
|
|
|
|
const (
|
|
testFile = "../testdata/local-index.yaml"
|
|
chartmuseumTestFile = "../testdata/chartmuseum-index.yaml"
|
|
chartmuseumJSONTestFile = "../testdata/chartmuseum-index.json"
|
|
unorderedTestFile = "../testdata/local-index-unordered.yaml"
|
|
)
|
|
|
|
// mockGetter is a simple mocking getter.Getter implementation, returning
|
|
// a byte response to any provided URL.
|
|
type mockGetter struct {
|
|
Response []byte
|
|
LastCalledURL string
|
|
}
|
|
|
|
func (g *mockGetter) Get(u string, _ ...helmgetter.Option) (*bytes.Buffer, error) {
|
|
r := g.Response
|
|
g.LastCalledURL = u
|
|
return bytes.NewBuffer(r), nil
|
|
}
|
|
|
|
// Index load tests are derived from https://github.com/helm/helm/blob/v3.3.4/pkg/repo/index_test.go#L108
|
|
// to ensure parity with Helm behaviour.
|
|
func TestIndexFromFile(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
// Create an index file that exceeds the max index size.
|
|
tmpDir := t.TempDir()
|
|
bigIndexFile := filepath.Join(tmpDir, "index.yaml")
|
|
data := make([]byte, helm.MaxIndexSize+10)
|
|
g.Expect(os.WriteFile(bigIndexFile, data, 0o640)).ToNot(HaveOccurred())
|
|
|
|
tests := []struct {
|
|
name string
|
|
filename string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "regular index file",
|
|
filename: testFile,
|
|
},
|
|
{
|
|
name: "chartmuseum index file",
|
|
filename: chartmuseumTestFile,
|
|
},
|
|
{
|
|
name: "chartmuseum json index file",
|
|
filename: chartmuseumJSONTestFile,
|
|
},
|
|
{
|
|
name: "error if index size exceeds max size",
|
|
filename: bigIndexFile,
|
|
wantErr: "exceeds the maximum index file size",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
i, err := IndexFromFile(tt.filename)
|
|
if tt.wantErr != "" {
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
|
return
|
|
}
|
|
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
verifyLocalIndex(t, i)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIndexFromBytes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
b []byte
|
|
wantName string
|
|
wantVersion string
|
|
wantDigest string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "index",
|
|
b: []byte(`
|
|
apiVersion: v1
|
|
entries:
|
|
nginx:
|
|
- urls:
|
|
- https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz
|
|
name: nginx
|
|
description: string
|
|
version: 0.2.0
|
|
home: https://github.com/something/else
|
|
digest: "sha256:1234567890abcdef"
|
|
`),
|
|
wantName: "nginx",
|
|
wantVersion: "0.2.0",
|
|
wantDigest: "sha256:1234567890abcdef",
|
|
},
|
|
{
|
|
name: "index without API version",
|
|
b: []byte(`entries:
|
|
nginx:
|
|
- name: nginx`),
|
|
wantErr: "no API version specified",
|
|
},
|
|
{
|
|
name: "index with duplicate entry",
|
|
b: []byte(`apiVersion: v1
|
|
entries:
|
|
nginx:
|
|
- name: nginx"
|
|
nginx:
|
|
- name: nginx`),
|
|
wantErr: "key \"nginx\" already set in map",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
t.Parallel()
|
|
|
|
i, err := IndexFromBytes(tt.b)
|
|
if tt.wantErr != "" {
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
|
g.Expect(i).To(BeNil())
|
|
return
|
|
}
|
|
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(i).ToNot(BeNil())
|
|
got, err := i.Get(tt.wantName, tt.wantVersion)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(got.Digest).To(Equal(tt.wantDigest))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIndexFromBytes_Unordered(t *testing.T) {
|
|
b, err := os.ReadFile(unorderedTestFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
i, err := IndexFromBytes(b)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
verifyLocalIndex(t, i)
|
|
}
|
|
|
|
func TestNewChartRepository(t *testing.T) {
|
|
repositoryURL := "https://example.com"
|
|
providers := helmgetter.Providers{
|
|
helmgetter.Provider{
|
|
Schemes: []string{"https"},
|
|
New: helmgetter.NewHTTPGetter,
|
|
},
|
|
}
|
|
options := []helmgetter.Option{helmgetter.WithBasicAuth("username", "password")}
|
|
|
|
t.Run("should construct chart repository", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r, err := NewChartRepository(repositoryURL, "", providers, nil, options...)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(r).ToNot(BeNil())
|
|
g.Expect(r.URL).To(Equal(repositoryURL))
|
|
g.Expect(r.Client).ToNot(BeNil())
|
|
g.Expect(r.Options).To(Equal(options))
|
|
})
|
|
|
|
t.Run("should error on URL parsing failure", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
r, err := NewChartRepository("https://ex ample.com", "", nil, nil, nil)
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(err).To(BeAssignableToTypeOf(&url.Error{}))
|
|
g.Expect(r).To(BeNil())
|
|
|
|
})
|
|
|
|
t.Run("should error on unsupported scheme", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r, err := NewChartRepository("http://example.com", "", providers, nil, nil)
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(err.Error()).To(Equal("scheme \"http\" not supported"))
|
|
g.Expect(r).To(BeNil())
|
|
})
|
|
}
|
|
|
|
func TestChartRepository_GetChartVersion(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
r.Index = repo.NewIndexFile()
|
|
charts := []struct {
|
|
name string
|
|
version string
|
|
url string
|
|
digest string
|
|
created time.Time
|
|
}{
|
|
{name: "chart", version: "0.0.1", url: "http://example.com/charts", digest: "sha256:1234567890"},
|
|
{name: "chart", version: "0.1.0", url: "http://example.com/charts", digest: "sha256:1234567890abc"},
|
|
{name: "chart", version: "0.1.1", url: "http://example.com/charts", digest: "sha256:1234567890abc"},
|
|
{name: "chart", version: "0.1.5+b.min.minute", url: "http://example.com/charts", digest: "sha256:1234567890abc", created: now.Add(-time.Minute)},
|
|
{name: "chart", version: "0.1.5+a.min.hour", url: "http://example.com/charts", digest: "sha256:1234567890abc", created: now.Add(-time.Hour)},
|
|
{name: "chart", version: "0.1.5+c.now", url: "http://example.com/charts", digest: "sha256:1234567890abc", created: now},
|
|
{name: "chart", version: "0.2.0", url: "http://example.com/charts", digest: "sha256:1234567890abc"},
|
|
{name: "chart", version: "1.0.0", url: "http://example.com/charts", digest: "sha256:1234567890abc"},
|
|
{name: "chart", version: "1.1.0-rc.1", url: "http://example.com/charts", digest: "sha256:1234567890abc"},
|
|
}
|
|
for _, c := range charts {
|
|
g.Expect(r.Index.MustAdd(
|
|
&chart.Metadata{Name: c.name, Version: c.version},
|
|
fmt.Sprintf("%s-%s.tgz", c.name, c.version), c.url, c.digest),
|
|
).To(Succeed())
|
|
if !c.created.IsZero() {
|
|
r.Index.Entries["chart"][len(r.Index.Entries["chart"])-1].Created = c.created
|
|
}
|
|
}
|
|
r.Index.SortEntries()
|
|
|
|
tests := []struct {
|
|
name string
|
|
chartName string
|
|
chartVersion string
|
|
wantVersion string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "exact match",
|
|
chartName: "chart",
|
|
chartVersion: "0.0.1",
|
|
wantVersion: "0.0.1",
|
|
},
|
|
{
|
|
name: "stable version",
|
|
chartName: "chart",
|
|
chartVersion: "",
|
|
wantVersion: "1.0.0",
|
|
},
|
|
{
|
|
name: "stable version (asterisk)",
|
|
chartName: "chart",
|
|
chartVersion: "*",
|
|
wantVersion: "1.0.0",
|
|
},
|
|
{
|
|
name: "semver range",
|
|
chartName: "chart",
|
|
chartVersion: "<1.0.0",
|
|
wantVersion: "0.2.0",
|
|
},
|
|
{
|
|
name: "unfulfilled range",
|
|
chartName: "chart",
|
|
chartVersion: ">2.0.0",
|
|
wantErr: "no 'chart' chart with version matching '>2.0.0' found",
|
|
},
|
|
{
|
|
name: "invalid chart",
|
|
chartName: "non-existing",
|
|
wantErr: repo.ErrNoChartName.Error(),
|
|
},
|
|
{
|
|
name: "match newest if ambiguous",
|
|
chartName: "chart",
|
|
chartVersion: "0.1.5",
|
|
wantVersion: "0.1.5+c.now",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
cv, err := r.GetChartVersion(tt.chartName, tt.chartVersion)
|
|
if tt.wantErr != "" {
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
|
g.Expect(cv).To(BeNil())
|
|
return
|
|
}
|
|
g.Expect(cv).ToNot(BeNil())
|
|
g.Expect(cv.Metadata.Name).To(Equal(tt.chartName))
|
|
g.Expect(cv.Metadata.Version).To(Equal(tt.wantVersion))
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestChartRepository_DownloadChart(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
url string
|
|
chartVersion *repo.ChartVersion
|
|
wantURL string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "relative URL",
|
|
url: "https://example.com",
|
|
chartVersion: &repo.ChartVersion{
|
|
Metadata: &chart.Metadata{Name: "chart"},
|
|
URLs: []string{"charts/foo-1.0.0.tgz"},
|
|
},
|
|
wantURL: "https://example.com/charts/foo-1.0.0.tgz",
|
|
},
|
|
{
|
|
name: "no chart URL",
|
|
chartVersion: &repo.ChartVersion{Metadata: &chart.Metadata{Name: "chart"}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid chart URL",
|
|
chartVersion: &repo.ChartVersion{
|
|
Metadata: &chart.Metadata{Name: "chart"},
|
|
URLs: []string{"https://ex ample.com/charts/foo-1.0.0.tgz"},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
t.Parallel()
|
|
|
|
mg := mockGetter{}
|
|
r := &ChartRepository{
|
|
URL: tt.url,
|
|
Client: &mg,
|
|
}
|
|
res, err := r.DownloadChart(tt.chartVersion)
|
|
if tt.wantErr {
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(res).To(BeNil())
|
|
return
|
|
}
|
|
g.Expect(mg.LastCalledURL).To(Equal(tt.wantURL))
|
|
g.Expect(res).ToNot(BeNil())
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestChartRepository_CacheIndex(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
mg := mockGetter{Response: []byte("foo")}
|
|
|
|
r := newChartRepository()
|
|
r.URL = "https://example.com"
|
|
r.Client = &mg
|
|
r.digests["key"] = "value"
|
|
|
|
err := r.CacheIndex()
|
|
g.Expect(err).To(Not(HaveOccurred()))
|
|
|
|
g.Expect(r.Path).ToNot(BeEmpty())
|
|
t.Cleanup(func() { _ = os.Remove(r.Path) })
|
|
|
|
g.Expect(r.Path).To(BeARegularFile())
|
|
b, _ := os.ReadFile(r.Path)
|
|
g.Expect(b).To(Equal(mg.Response))
|
|
|
|
g.Expect(r.digests).To(BeEmpty())
|
|
}
|
|
|
|
func TestChartRepository_ToJSON(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
r.Path = chartmuseumTestFile
|
|
|
|
_, err := r.ToJSON()
|
|
g.Expect(err).To(HaveOccurred())
|
|
|
|
g.Expect(r.LoadFromPath()).To(Succeed())
|
|
b, err := r.ToJSON()
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
jsonBytes, err := os.ReadFile(chartmuseumJSONTestFile)
|
|
jsonBytes = bytes.TrimRight(jsonBytes, "\n")
|
|
g.Expect(err).To(Not(HaveOccurred()))
|
|
g.Expect(string(b)).To(Equal(string(jsonBytes)))
|
|
}
|
|
|
|
func TestChartRepository_DownloadIndex(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
b, err := os.ReadFile(chartmuseumTestFile)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
mg := mockGetter{Response: b}
|
|
r := &ChartRepository{
|
|
URL: "https://example.com",
|
|
Client: &mg,
|
|
RWMutex: &sync.RWMutex{},
|
|
}
|
|
|
|
t.Run("download index", func(t *testing.T) {
|
|
buf := bytes.NewBuffer([]byte{})
|
|
g.Expect(r.DownloadIndex(buf, helm.MaxIndexSize)).To(Succeed())
|
|
g.Expect(buf.Bytes()).To(Equal(b))
|
|
g.Expect(mg.LastCalledURL).To(Equal(r.URL + "/index.yaml"))
|
|
g.Expect(err).To(BeNil())
|
|
})
|
|
|
|
t.Run("download index size error", func(t *testing.T) {
|
|
buf := bytes.NewBuffer([]byte{})
|
|
g.Expect(r.DownloadIndex(buf, int64(len(b)-1))).To(HaveOccurred())
|
|
g.Expect(mg.LastCalledURL).To(Equal(r.URL + "/index.yaml"))
|
|
})
|
|
}
|
|
|
|
func TestChartRepository_StrategicallyLoadIndex(t *testing.T) {
|
|
t.Run("loads from path", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
i := filepath.Join(t.TempDir(), "index.yaml")
|
|
g.Expect(os.WriteFile(i, []byte(`apiVersion: v1`), 0o600)).To(Succeed())
|
|
|
|
r := newChartRepository()
|
|
r.Path = i
|
|
|
|
err := r.StrategicallyLoadIndex()
|
|
g.Expect(err).To(Succeed())
|
|
g.Expect(r.Index).ToNot(BeNil())
|
|
})
|
|
|
|
t.Run("loads from client", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
r.Client = &mockGetter{
|
|
Response: []byte(`apiVersion: v1`),
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = os.Remove(r.Path)
|
|
})
|
|
|
|
err := r.StrategicallyLoadIndex()
|
|
g.Expect(err).To(Succeed())
|
|
g.Expect(r.Path).ToNot(BeEmpty())
|
|
g.Expect(r.Index).ToNot(BeNil())
|
|
})
|
|
|
|
t.Run("skips if index is already loaded", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
r.Index = repo.NewIndexFile()
|
|
|
|
g.Expect(r.StrategicallyLoadIndex()).To(Succeed())
|
|
})
|
|
}
|
|
|
|
func TestChartRepository_LoadFromPath(t *testing.T) {
|
|
t.Run("loads index", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
i := filepath.Join(t.TempDir(), "index.yaml")
|
|
g.Expect(os.WriteFile(i, []byte(`apiVersion: v1`), 0o600)).To(Succeed())
|
|
|
|
r := newChartRepository()
|
|
r.Path = i
|
|
|
|
g.Expect(r.LoadFromPath()).To(Succeed())
|
|
g.Expect(r.Index).ToNot(BeNil())
|
|
})
|
|
|
|
t.Run("no cache path", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
err := newChartRepository().LoadFromPath()
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(err.Error()).To(ContainSubstring("no cache path"))
|
|
})
|
|
|
|
t.Run("index load error", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
r.Path = filepath.Join(t.TempDir(), "index.yaml")
|
|
|
|
err := r.LoadFromPath()
|
|
g.Expect(err).To(HaveOccurred())
|
|
g.Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue())
|
|
})
|
|
}
|
|
|
|
func TestChartRepository_Digest(t *testing.T) {
|
|
t.Run("with algorithm", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
p := filepath.Join(t.TempDir(), "index.yaml")
|
|
g.Expect(repo.NewIndexFile().WriteFile(p, 0o600)).To(Succeed())
|
|
|
|
r := newChartRepository()
|
|
r.Path = p
|
|
|
|
for _, algo := range []digest.Algorithm{digest.SHA256, digest.SHA512} {
|
|
t.Run(algo.String(), func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
d := r.Digest(algo)
|
|
g.Expect(d).ToNot(BeEmpty())
|
|
g.Expect(d.Algorithm()).To(Equal(algo))
|
|
g.Expect(r.digests[algo]).To(Equal(d))
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("without path", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
g.Expect(r.Digest(digest.SHA256)).To(BeEmpty())
|
|
})
|
|
|
|
t.Run("from cache", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
algo := digest.SHA256
|
|
expect := digest.Digest("sha256:fake")
|
|
|
|
i := filepath.Join(t.TempDir(), "index.yaml")
|
|
g.Expect(os.WriteFile(i, []byte(`apiVersion: v1`), 0o600)).To(Succeed())
|
|
|
|
r := newChartRepository()
|
|
r.Path = i
|
|
r.digests[algo] = expect
|
|
|
|
g.Expect(r.Digest(algo)).To(Equal(expect))
|
|
})
|
|
}
|
|
|
|
func TestChartRepository_HasIndex(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
g.Expect(r.HasIndex()).To(BeFalse())
|
|
r.Index = repo.NewIndexFile()
|
|
g.Expect(r.HasIndex()).To(BeTrue())
|
|
}
|
|
|
|
func TestChartRepository_HasFile(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
g.Expect(r.HasFile()).To(BeFalse())
|
|
|
|
i := filepath.Join(t.TempDir(), "index.yaml")
|
|
g.Expect(os.WriteFile(i, []byte(`apiVersion: v1`), 0o600)).To(Succeed())
|
|
r.Path = i
|
|
g.Expect(r.HasFile()).To(BeTrue())
|
|
}
|
|
|
|
func TestChartRepository_Clear(t *testing.T) {
|
|
t.Run("without index", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
g.Expect(r.Clear()).To(Succeed())
|
|
})
|
|
|
|
t.Run("with index", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
r.Index = repo.NewIndexFile()
|
|
|
|
g.Expect(r.Clear()).To(Succeed())
|
|
g.Expect(r.Index).To(BeNil())
|
|
})
|
|
|
|
t.Run("with index and cached path", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
f, err := os.CreateTemp(t.TempDir(), "index-*.yaml")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(f.Close()).To(Succeed())
|
|
|
|
r := newChartRepository()
|
|
r.Path = f.Name()
|
|
r.Index = repo.NewIndexFile()
|
|
r.digests["key"] = "value"
|
|
r.cached = true
|
|
|
|
g.Expect(r.Clear()).To(Succeed())
|
|
g.Expect(r.Index).To(BeNil())
|
|
g.Expect(r.Path).To(BeEmpty())
|
|
g.Expect(r.digests).To(BeEmpty())
|
|
g.Expect(r.cached).To(BeFalse())
|
|
})
|
|
|
|
t.Run("with path", func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
f, err := os.CreateTemp(t.TempDir(), "index-*.yaml")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(f.Close()).To(Succeed())
|
|
|
|
r := newChartRepository()
|
|
r.Path = f.Name()
|
|
r.digests["key"] = "value"
|
|
|
|
g.Expect(r.Clear()).To(Succeed())
|
|
g.Expect(r.Path).ToNot(BeEmpty())
|
|
g.Expect(r.Path).To(BeARegularFile())
|
|
g.Expect(r.digests).To(BeEmpty())
|
|
})
|
|
}
|
|
|
|
func TestChartRepository_Invalidate(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
r := newChartRepository()
|
|
r.digests["key"] = "value"
|
|
|
|
r.Invalidate()
|
|
g.Expect(r.digests).To(BeEmpty())
|
|
}
|
|
|
|
func verifyLocalIndex(t *testing.T, i *repo.IndexFile) {
|
|
g := NewWithT(t)
|
|
|
|
g.Expect(i.Entries).ToNot(BeNil())
|
|
g.Expect(i.Entries).To(HaveLen(4), "expected 4 entries in index file")
|
|
|
|
alpine, ok := i.Entries["alpine"]
|
|
g.Expect(ok).To(BeTrue(), "expected 'alpine' entry to exist")
|
|
g.Expect(alpine).To(HaveLen(1), "'alpine' should have 1 entry")
|
|
|
|
nginx, ok := i.Entries["nginx"]
|
|
g.Expect(ok).To(BeTrue(), "expected 'nginx' entry to exist")
|
|
g.Expect(nginx).To(HaveLen(2), "'nginx' should have 2 entries")
|
|
|
|
broken, ok := i.Entries["xChartWithDuplicateDependenciesAndMissingAlias"]
|
|
g.Expect(ok).To(BeTrue(), "expected 'xChartWithDuplicateDependenciesAndMissingAlias' entry to exist")
|
|
g.Expect(broken).To(HaveLen(1), "'xChartWithDuplicateDependenciesAndMissingAlias' should have 1 entries")
|
|
|
|
expects := []*repo.ChartVersion{
|
|
{
|
|
Metadata: &chart.Metadata{
|
|
Name: "alpine",
|
|
Description: "string",
|
|
Version: "1.0.0",
|
|
Keywords: []string{"linux", "alpine", "small", "sumtin"},
|
|
Home: "https://github.com/something",
|
|
},
|
|
URLs: []string{
|
|
"https://kubernetes-charts.storage.googleapis.com/alpine-1.0.0.tgz",
|
|
"http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz",
|
|
},
|
|
Digest: "sha256:1234567890abcdef",
|
|
},
|
|
{
|
|
Metadata: &chart.Metadata{
|
|
Name: "nginx",
|
|
Description: "string",
|
|
Version: "0.2.0",
|
|
Keywords: []string{"popular", "web server", "proxy"},
|
|
Home: "https://github.com/something/else",
|
|
},
|
|
URLs: []string{
|
|
"https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz",
|
|
},
|
|
Digest: "sha256:1234567890abcdef",
|
|
},
|
|
{
|
|
Metadata: &chart.Metadata{
|
|
Name: "nginx",
|
|
Description: "string",
|
|
Version: "0.1.0",
|
|
Keywords: []string{"popular", "web server", "proxy"},
|
|
Home: "https://github.com/something",
|
|
},
|
|
URLs: []string{
|
|
"https://kubernetes-charts.storage.googleapis.com/nginx-0.1.0.tgz",
|
|
},
|
|
Digest: "sha256:1234567890abcdef",
|
|
},
|
|
{
|
|
Metadata: &chart.Metadata{
|
|
Name: "xChartWithDuplicateDependenciesAndMissingAlias",
|
|
Description: "string",
|
|
Version: "1.2.3",
|
|
Keywords: []string{"broken", "still accepted"},
|
|
Home: "https://example.com/something",
|
|
Dependencies: []*chart.Dependency{
|
|
{Name: "kube-rbac-proxy", Version: "0.9.1"},
|
|
},
|
|
},
|
|
URLs: []string{
|
|
"https://kubernetes-charts.storage.googleapis.com/nginx-1.2.3.tgz",
|
|
},
|
|
Digest: "sha256:1234567890abcdef",
|
|
},
|
|
}
|
|
tests := []*repo.ChartVersion{alpine[0], nginx[0], nginx[1], broken[0]}
|
|
|
|
for i, tt := range tests {
|
|
expect := expects[i]
|
|
g.Expect(tt.Name).To(Equal(expect.Name))
|
|
g.Expect(tt.Description).To(Equal(expect.Description))
|
|
g.Expect(tt.Version).To(Equal(expect.Version))
|
|
g.Expect(tt.Digest).To(Equal(expect.Digest))
|
|
g.Expect(tt.Home).To(Equal(expect.Home))
|
|
g.Expect(tt.URLs).To(ContainElements(expect.URLs))
|
|
g.Expect(tt.Keywords).To(ContainElements(expect.Keywords))
|
|
g.Expect(tt.Dependencies).To(ContainElements(expect.Dependencies))
|
|
}
|
|
}
|
|
|
|
// This code is taken from https://github.com/helm/helm/blob/v3.15.2/pkg/repo/index_test.go#L601
|
|
// and refers to: https://github.com/helm/helm/issues/12748
|
|
func TestIgnoreSkippableChartValidationError(t *testing.T) {
|
|
type TestCase struct {
|
|
Input error
|
|
ErrorSkipped bool
|
|
}
|
|
testCases := map[string]TestCase{
|
|
"nil": {
|
|
Input: nil,
|
|
},
|
|
"generic_error": {
|
|
Input: fmt.Errorf("foo"),
|
|
},
|
|
"non_skipped_validation_error": {
|
|
Input: chart.ValidationError("chart.metadata.type must be application or library"),
|
|
},
|
|
"skipped_validation_error": {
|
|
Input: chart.ValidationErrorf("more than one dependency with name or alias %q", "foo"),
|
|
ErrorSkipped: true,
|
|
},
|
|
}
|
|
|
|
for name, tc := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
result := ignoreSkippableChartValidationError(tc.Input)
|
|
|
|
if tc.Input == nil {
|
|
if result != nil {
|
|
t.Error("expected nil result for nil input")
|
|
}
|
|
return
|
|
}
|
|
|
|
if tc.ErrorSkipped {
|
|
if result != nil {
|
|
t.Error("expected nil result for skipped error")
|
|
}
|
|
return
|
|
}
|
|
|
|
if tc.Input != result {
|
|
t.Error("expected the result equal to input")
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
var indexWithFirstVersionInvalid = `
|
|
apiVersion: v1
|
|
entries:
|
|
nginx:
|
|
- urls:
|
|
- https://charts.helm.sh/stable/alpine-1.0.0.tgz
|
|
- http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz
|
|
name: nginx
|
|
version: 0..1.0
|
|
description: string
|
|
home: https://github.com/something
|
|
digest: "sha256:1234567890abcdef"
|
|
- urls:
|
|
- https://charts.helm.sh/stable/nginx-0.2.0.tgz
|
|
name: nginx
|
|
description: string
|
|
version: 0.2.0
|
|
home: https://github.com/something/else
|
|
digest: "sha256:1234567890abcdef"
|
|
`
|
|
var indexWithLastVersionInvalid = `
|
|
apiVersion: v1
|
|
entries:
|
|
nginx:
|
|
- urls:
|
|
- https://charts.helm.sh/stable/nginx-0.2.0.tgz
|
|
name: nginx
|
|
description: string
|
|
version: 0.2.0
|
|
home: https://github.com/something/else
|
|
digest: "sha256:1234567890abcdef"
|
|
- urls:
|
|
- https://charts.helm.sh/stable/alpine-1.0.0.tgz
|
|
- http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz
|
|
name: nginx
|
|
version: 0..1.0
|
|
description: string
|
|
home: https://github.com/something
|
|
digest: "sha256:1234567890abcdef"
|
|
`
|
|
|
|
func TestIndexFromBytes_InvalidEntries(t *testing.T) {
|
|
tests := []struct {
|
|
source string
|
|
data string
|
|
}{
|
|
{
|
|
source: "indexWithFirstVersionInvalid",
|
|
data: indexWithFirstVersionInvalid,
|
|
},
|
|
{
|
|
source: "indexWithLastVersionInvalid",
|
|
data: indexWithLastVersionInvalid,
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.source, func(t *testing.T) {
|
|
idx, err := IndexFromBytes([]byte(tc.data))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
cvs := idx.Entries["nginx"]
|
|
if len(cvs) == 0 {
|
|
t.Error("expected one chart version not to be filtered out")
|
|
}
|
|
for _, v := range cvs {
|
|
if v.Version == "0..1.0" {
|
|
t.Error("malformed version was not filtered out")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|