internal/helm: add remote builder tests

- For remote builds, if the build option has a version metadata, the
  chart should be repackaged with the provided version.
- Update internal/helm/testdata/charts/helmchart-0.1.0.tgz to include
  value files for testing merge chart values.

Signed-off-by: Sunny <darkowlzz@protonmail.com>
This commit is contained in:
Sunny 2021-11-16 20:23:52 +05:30 committed by Hidde Beydals
parent 7c910e37a2
commit 753abed30c
3 changed files with 192 additions and 2 deletions

View File

@ -100,8 +100,9 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
return nil, fmt.Errorf("failed to download chart for remote reference: %w", err)
}
// Use literal chart copy from remote if no custom value files options are set
if len(opts.GetValueFiles()) == 0 {
// Use literal chart copy from remote if no custom value files options are
// set or build option version metadata isn't set.
if len(opts.GetValueFiles()) == 0 && opts.VersionMetadata == "" {
if err = validatePackageAndWriteToPath(res, p); err != nil {
return nil, err
}
@ -127,6 +128,8 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
result.ValueFiles = opts.GetValueFiles()
}
chart.Metadata.Version = result.Version
// Package the chart with the custom values
if err = packageToPath(chart, p); err != nil {
return nil, err

View File

@ -17,13 +17,200 @@ limitations under the License.
package chart
import (
"bytes"
"context"
"math/rand"
"os"
"strings"
"sync"
"testing"
"time"
. "github.com/onsi/gomega"
helmchart "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
helmgetter "helm.sh/helm/v3/pkg/getter"
"github.com/fluxcd/source-controller/internal/helm/repository"
)
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz1234567890")
func randStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
func init() {
rand.Seed(time.Now().UnixNano())
}
// mockIndexChartGetter returns specific response for index and chart queries.
type mockIndexChartGetter struct {
IndexResponse []byte
ChartResponse []byte
requestedURL string
}
func (g *mockIndexChartGetter) Get(u string, _ ...helmgetter.Option) (*bytes.Buffer, error) {
g.requestedURL = u
r := g.ChartResponse
if strings.HasSuffix(u, "index.yaml") {
r = g.IndexResponse
}
return bytes.NewBuffer(r), nil
}
func (g *mockIndexChartGetter) LastGet() string {
return g.requestedURL
}
func TestRemoteBuilder_Build(t *testing.T) {
g := NewWithT(t)
chartGrafana, err := os.ReadFile("./../testdata/charts/helmchart-0.1.0.tgz")
g.Expect(err).ToNot(HaveOccurred())
g.Expect(chartGrafana).ToNot(BeEmpty())
index := []byte(`
apiVersion: v1
entries:
grafana:
- urls:
- https://example.com/grafana.tgz
description: string
version: 6.17.4
`)
mockGetter := &mockIndexChartGetter{
IndexResponse: index,
ChartResponse: chartGrafana,
}
mockRepo := func() *repository.ChartRepository {
return &repository.ChartRepository{
URL: "https://grafana.github.io/helm-charts/",
Client: mockGetter,
RWMutex: &sync.RWMutex{},
}
}
tests := []struct {
name string
reference Reference
buildOpts BuildOptions
repository *repository.ChartRepository
wantValues chartutil.Values
wantVersion string
wantPackaged bool
wantErr string
}{
{
name: "invalid reference",
reference: LocalReference{},
wantErr: "expected remote chart reference",
},
{
name: "invalid reference - no name",
reference: RemoteReference{},
wantErr: "no name set for remote chart reference",
},
{
name: "chart not in repo",
reference: RemoteReference{Name: "foo"},
repository: mockRepo(),
wantErr: "failed to get chart version for remote reference",
},
{
name: "chart version not in repo",
reference: RemoteReference{Name: "grafana", Version: "1.1.1"},
repository: mockRepo(),
wantErr: "failed to get chart version for remote reference",
},
{
name: "invalid version metadata",
reference: RemoteReference{Name: "grafana"},
repository: mockRepo(),
buildOpts: BuildOptions{VersionMetadata: "^"},
wantErr: "Invalid Metadata string",
},
{
name: "with version metadata",
reference: RemoteReference{Name: "grafana"},
repository: mockRepo(),
buildOpts: BuildOptions{VersionMetadata: "foo"},
wantVersion: "6.17.4+foo",
wantPackaged: true,
},
// TODO: Test setting BuildOptions CachedChart and Force.
{
name: "default values",
reference: RemoteReference{Name: "grafana"},
repository: mockRepo(),
wantVersion: "0.1.0",
wantValues: chartutil.Values{
"replicaCount": float64(1),
},
},
{
name: "merge values",
reference: RemoteReference{Name: "grafana"},
buildOpts: BuildOptions{
ValueFiles: []string{"a.yaml", "b.yaml", "c.yaml"},
},
repository: mockRepo(),
wantVersion: "6.17.4",
wantValues: chartutil.Values{
"a": "b",
"b": "d",
},
wantPackaged: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
targetPath := "/tmp/remote-chart-builder-" + randStringRunes(5) + ".tgz"
defer os.RemoveAll(targetPath)
if tt.repository != nil {
_, err := tt.repository.CacheIndex()
g.Expect(err).ToNot(HaveOccurred())
// Cleanup the cache index path.
defer os.Remove(tt.repository.CachePath)
}
b := NewRemoteBuilder(tt.repository)
cb, err := b.Build(context.TODO(), tt.reference, targetPath, tt.buildOpts)
if tt.wantErr != "" {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
g.Expect(cb).To(BeZero())
return
}
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cb.Packaged).To(Equal(tt.wantPackaged), "unexpected Build.Packaged value")
g.Expect(cb.Path).ToNot(BeEmpty(), "empty Build.Path")
// Load the resulting chart and verify the values.
resultChart, err := loader.Load(cb.Path)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(resultChart.Metadata.Version).To(Equal(tt.wantVersion))
for k, v := range tt.wantValues {
g.Expect(v).To(Equal(resultChart.Values[k]))
}
})
}
}
func Test_mergeChartValues(t *testing.T) {
tests := []struct {
name string

Binary file not shown.