internal/helm: local builder & dep manager test

Add more chart local builder and dependency manager tests.

Signed-off-by: Sunny <darkowlzz@protonmail.com>
This commit is contained in:
Sunny 2021-11-16 16:26:05 +05:30 committed by Hidde Beydals
parent 32e19ebcd0
commit 7c910e37a2
4 changed files with 294 additions and 9 deletions

1
go.mod
View File

@ -38,6 +38,7 @@ require (
github.com/minio/minio-go/v7 v7.0.10
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.14.0
github.com/otiai10/copy v1.7.0
github.com/spf13/pflag v1.0.5
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 // indirect
github.com/yvasiyarov/gorelic v0.0.7 // indirect

7
go.sum
View File

@ -738,6 +738,13 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=

View File

@ -17,14 +17,225 @@ limitations under the License.
package chart
import (
"context"
"os"
"path/filepath"
"sync"
"testing"
. "github.com/onsi/gomega"
"github.com/otiai10/copy"
helmchart "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/repo"
"github.com/fluxcd/source-controller/internal/helm/getter"
"github.com/fluxcd/source-controller/internal/helm/repository"
)
func TestLocalBuilder_Build(t *testing.T) {
g := NewWithT(t)
// Prepare chart repositories to be used for charts with remote dependency.
chartB, err := os.ReadFile("./../testdata/charts/helmchart-0.1.0.tgz")
g.Expect(err).ToNot(HaveOccurred())
g.Expect(chartB).ToNot(BeEmpty())
mockRepo := func() *repository.ChartRepository {
return &repository.ChartRepository{
Client: &getter.MockGetter{
Response: chartB,
},
Index: &repo.IndexFile{
Entries: map[string]repo.ChartVersions{
"grafana": {
&repo.ChartVersion{
Metadata: &helmchart.Metadata{
Name: "grafana",
Version: "6.17.4",
},
URLs: []string{"https://example.com/grafana.tgz"},
},
},
},
},
RWMutex: &sync.RWMutex{},
}
}
tests := []struct {
name string
reference Reference
buildOpts BuildOptions
valueFiles []helmchart.File
repositories map[string]*repository.ChartRepository
dependentChartPaths []string
wantValues chartutil.Values
wantVersion string
wantPackaged bool
wantErr string
}{
{
name: "invalid reference",
reference: RemoteReference{},
wantErr: "expected local chart reference",
},
{
name: "invalid local reference - no path",
reference: LocalReference{},
wantErr: "no path set for local chart reference",
},
{
name: "invalid local reference - no file",
reference: LocalReference{Path: "/tmp/non-existent-path.xyz"},
wantErr: "no such file or directory",
},
{
name: "invalid version metadata",
reference: LocalReference{Path: "./../testdata/charts/helmchart"},
buildOpts: BuildOptions{VersionMetadata: "^"},
wantErr: "Invalid Metadata string",
},
{
name: "with version metadata",
reference: LocalReference{Path: "./../testdata/charts/helmchart"},
buildOpts: BuildOptions{VersionMetadata: "foo"},
wantVersion: "0.1.0+foo",
wantPackaged: true,
},
// TODO: Test setting BuildOptions CachedChart and Force.
{
name: "already packaged chart",
reference: LocalReference{Path: "./../testdata/charts/helmchart-0.1.0.tgz"},
wantVersion: "0.1.0",
wantPackaged: false,
},
{
name: "default values",
reference: LocalReference{Path: "./../testdata/charts/helmchart"},
wantValues: chartutil.Values{
"replicaCount": float64(1),
},
wantVersion: "0.1.0",
wantPackaged: true,
},
{
name: "with value files",
reference: LocalReference{Path: "./../testdata/charts/helmchart"},
buildOpts: BuildOptions{
ValueFiles: []string{"custom-values1.yaml", "custom-values2.yaml"},
},
valueFiles: []helmchart.File{
{
Name: "custom-values1.yaml",
Data: []byte(`replicaCount: 11
nameOverride: "foo-name-override"`),
},
{
Name: "custom-values2.yaml",
Data: []byte(`replicaCount: 20
fullnameOverride: "full-foo-name-override"`),
},
},
wantValues: chartutil.Values{
"replicaCount": float64(20),
"nameOverride": "foo-name-override",
"fullnameOverride": "full-foo-name-override",
},
wantVersion: "0.1.0",
wantPackaged: true,
},
{
name: "chart with dependencies",
reference: LocalReference{Path: "./../testdata/charts/helmchartwithdeps"},
repositories: map[string]*repository.ChartRepository{
"https://grafana.github.io/helm-charts/": mockRepo(),
},
dependentChartPaths: []string{"./../testdata/charts/helmchart"},
wantVersion: "0.1.0",
wantPackaged: true,
},
{
name: "v1 chart",
reference: LocalReference{Path: "./../testdata/charts/helmchart-v1"},
wantValues: chartutil.Values{
"replicaCount": float64(1),
},
wantVersion: "0.2.0",
wantPackaged: true,
},
{
name: "v1 chart with dependencies",
reference: LocalReference{Path: "./../testdata/charts/helmchartwithdeps-v1"},
repositories: map[string]*repository.ChartRepository{
"https://grafana.github.io/helm-charts/": mockRepo(),
},
dependentChartPaths: []string{"./../testdata/charts/helmchart-v1"},
wantVersion: "0.3.0",
wantPackaged: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
workDir, err := os.MkdirTemp("", "local-builder-")
g.Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(workDir)
// Only if the reference is a LocalReference, set the WorkDir.
localRef, ok := tt.reference.(LocalReference)
if ok {
localRef.WorkDir = workDir
tt.reference = localRef
}
// Write value file in the base dir.
for _, f := range tt.valueFiles {
vPath := filepath.Join(workDir, f.Name)
g.Expect(os.WriteFile(vPath, f.Data, 0644)).ToNot(HaveOccurred())
}
// Write chart dependencies in the base dir.
for _, dcp := range tt.dependentChartPaths {
// Construct the chart path relative to the testdata chart.
helmchartDir := filepath.Join(workDir, "testdata", "charts", filepath.Base(dcp))
g.Expect(copy.Copy(dcp, helmchartDir)).ToNot(HaveOccurred())
}
// Target path with name similar to the workDir.
targetPath := workDir + ".tgz"
defer os.RemoveAll(targetPath)
dm := NewDependencyManager(
WithRepositories(tt.repositories),
)
b := NewLocalBuilder(dm)
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_mergeFileValues(t *testing.T) {
tests := []struct {
name string

View File

@ -35,6 +35,36 @@ import (
)
func TestDependencyManager_Build(t *testing.T) {
g := NewWithT(t)
// Mock chart used as grafana chart in the test below. The cached repository
// takes care of the actual grafana related details in the chart index.
chartGrafana, err := os.ReadFile("./../testdata/charts/helmchart-0.1.0.tgz")
g.Expect(err).ToNot(HaveOccurred())
g.Expect(chartGrafana).ToNot(BeEmpty())
mockRepo := func() *repository.ChartRepository {
return &repository.ChartRepository{
Client: &getter.MockGetter{
Response: chartGrafana,
},
Index: &repo.IndexFile{
Entries: map[string]repo.ChartVersions{
"grafana": {
&repo.ChartVersion{
Metadata: &helmchart.Metadata{
Name: "grafana",
Version: "6.17.4",
},
URLs: []string{"https://example.com/grafana.tgz"},
},
},
},
},
RWMutex: &sync.RWMutex{},
}
}
tests := []struct {
name string
baseDir string
@ -45,12 +75,6 @@ func TestDependencyManager_Build(t *testing.T) {
wantChartFunc func(g *WithT, c *helmchart.Chart)
wantErr string
}{
//{
// // TODO(hidde): add various happy paths
//},
//{
// // TODO(hidde): test Chart.lock
//},
{
name: "build failure returns error",
baseDir: "./../testdata/charts",
@ -61,7 +85,44 @@ func TestDependencyManager_Build(t *testing.T) {
name: "no dependencies returns zero",
baseDir: "./../testdata/charts",
path: "helmchart",
want: 0,
wantChartFunc: func(g *WithT, c *helmchart.Chart) {
g.Expect(c.Dependencies()).To(HaveLen(0))
},
want: 0,
},
{
name: "no dependency returns zero - v1",
baseDir: "./../testdata/charts",
path: "helmchart-v1",
wantChartFunc: func(g *WithT, c *helmchart.Chart) {
g.Expect(c.Dependencies()).To(HaveLen(0))
},
want: 0,
},
{
name: "build with dependencies using lock file",
baseDir: "./../testdata/charts",
path: "helmchartwithdeps",
repositories: map[string]*repository.ChartRepository{
"https://grafana.github.io/helm-charts/": mockRepo(),
},
getChartRepositoryCallback: func(url string) (*repository.ChartRepository, error) {
return &repository.ChartRepository{URL: "https://grafana.github.io/helm-charts/"}, nil
},
wantChartFunc: func(g *WithT, c *helmchart.Chart) {
g.Expect(c.Dependencies()).To(HaveLen(2))
g.Expect(c.Lock.Dependencies).To(HaveLen(3))
},
want: 2,
},
{
name: "build with dependencies - v1",
baseDir: "./../testdata/charts",
path: "helmchartwithdeps-v1",
wantChartFunc: func(g *WithT, c *helmchart.Chart) {
g.Expect(c.Dependencies()).To(HaveLen(1))
},
want: 1,
},
}
for _, tt := range tests {
@ -71,10 +132,11 @@ func TestDependencyManager_Build(t *testing.T) {
chart, err := loader.Load(filepath.Join(tt.baseDir, tt.path))
g.Expect(err).ToNot(HaveOccurred())
got, err := NewDependencyManager(
dm := NewDependencyManager(
WithRepositories(tt.repositories),
WithRepositoryCallback(tt.getChartRepositoryCallback),
).Build(context.TODO(), LocalReference{WorkDir: tt.baseDir, Path: tt.path}, chart)
)
got, err := dm.Build(context.TODO(), LocalReference{WorkDir: tt.baseDir, Path: tt.path}, chart)
if tt.wantErr != "" {
g.Expect(err).To(HaveOccurred())
@ -198,6 +260,10 @@ func TestDependencyManager_addLocalDependency(t *testing.T) {
return
}
g.Expect(err).ToNot(HaveOccurred())
if tt.wantFunc != nil {
tt.wantFunc(g, chart)
}
})
}
}