Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ require (
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/openshift/api v0.0.0-20211217221424-8779abfbd571 // indirect
Expand Down Expand Up @@ -326,7 +325,6 @@ require (
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/component-base v0.25.3 // indirect
Expand Down
263 changes: 59 additions & 204 deletions pkg/applications/helmclient/client_integration_test.go

Large diffs are not rendered by default.

221 changes: 26 additions & 195 deletions pkg/applications/helmclient/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,19 @@ package helmclient
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"testing"
"time"

"github.com/distribution/distribution/v3/configuration"
ociregistry "github.com/distribution/distribution/v3/registry"
"github.com/phayes/freeport"
"golang.org/x/crypto/bcrypt"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/pusher"
helmregistry "helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo/repotest"
"helm.sh/helm/v3/pkg/uploader"

"k8c.io/kubermatic/v2/pkg/applications/test"
kubermaticlog "k8c.io/kubermatic/v2/pkg/log"

cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
Expand Down Expand Up @@ -75,28 +63,16 @@ func TestDownloadChart(t *testing.T) {
chartArchiveDir := t.TempDir()

chartGlobPath := path.Join(chartArchiveDir, "*.tgz")
chartArchiveV1Path, chartArchiveV1Size := packageChart(t, "testdata/examplechart", chartArchiveDir)
chartArchiveV2Path, chartArchiveV2Size := packageChart(t, "testdata/examplechart-v2", chartArchiveDir)
chartArchiveV1Path, chartArchiveV1Size := test.PackageChart(t, "testdata/examplechart", chartArchiveDir)
chartArchiveV2Path, chartArchiveV2Size := test.PackageChart(t, "testdata/examplechart-v2", chartArchiveDir)
chartArchiveV1Name := path.Base(chartArchiveV1Path)
chartArchiveV2Name := path.Base(chartArchiveV2Path)

srv, err := repotest.NewTempServerWithCleanup(t, chartGlobPath)
if err != nil {
t.Fatal(err)
}
defer srv.Stop()
if err := srv.CreateIndex(); err != nil {
t.Fatal(err)
}

srvWithAuth := repotest.NewTempServerWithCleanupAndBasicAuth(t, chartGlobPath)
defer srvWithAuth.Stop()
if err := srvWithAuth.CreateIndex(); err != nil {
t.Fatal(err)
}
httpRegistryUrl := test.StartHttpRegistryWithCleanup(t, chartGlobPath)
httpRegistryWithAuthUrl := test.StartHttpRegistryWithAuthAndCleanup(t, chartGlobPath)

ociRegistryUrl := startOciRegistry(t, chartGlobPath)
ociregistryWithAuthUrl, registryConfigFile := startOciRegistryWithAuth(t, chartGlobPath)
ociRegistryUrl := test.StartOciRegistry(t, chartGlobPath)
ociregistryWithAuthUrl, registryConfigFile := test.StartOciRegistryWithAuth(t, chartGlobPath)

testCases := []struct {
name string
Expand All @@ -110,7 +86,7 @@ func TestDownloadChart(t *testing.T) {
}{
{
name: "Download from HTTP repository should be successful",
repoUrl: srv.URL(),
repoUrl: httpRegistryUrl,
chartName: "examplechart",
chartVersion: "0.1.0",
auth: AuthSettings{},
Expand All @@ -120,7 +96,7 @@ func TestDownloadChart(t *testing.T) {
},
{
name: "Download from HTTP repository with empty version should get the latest version",
repoUrl: srv.URL(),
repoUrl: httpRegistryUrl,
chartName: "examplechart",
chartVersion: "",
auth: AuthSettings{},
Expand All @@ -130,7 +106,7 @@ func TestDownloadChart(t *testing.T) {
},
{
name: "Download from HTTP repository with auth should be successful",
repoUrl: srvWithAuth.URL(),
repoUrl: httpRegistryWithAuthUrl,
chartName: "examplechart",
chartVersion: "0.1.0",
auth: AuthSettings{Username: "username", Password: "password"},
Expand All @@ -140,7 +116,7 @@ func TestDownloadChart(t *testing.T) {
},
{
name: "Download from HTTP repository should fail when chart does not exist",
repoUrl: srv.URL(),
repoUrl: httpRegistryUrl,
chartName: "chartthatdoesnotexist",
chartVersion: "0.1.0",
auth: AuthSettings{},
Expand All @@ -149,7 +125,7 @@ func TestDownloadChart(t *testing.T) {
},
{
name: "Download from HTTP repository should fail when version is not a semversion",
repoUrl: srv.URL(),
repoUrl: httpRegistryUrl,
chartName: "examplechart",
chartVersion: "notSemver",
auth: AuthSettings{},
Expand Down Expand Up @@ -257,26 +233,14 @@ func TestBuildDependencies(t *testing.T) {
chartArchiveDir := t.TempDir()
chartGlobPath := path.Join(chartArchiveDir, "*.tgz")

packageChart(t, "testdata/examplechart", chartArchiveDir)
packageChart(t, "testdata/examplechart2", chartArchiveDir)
test.PackageChart(t, "testdata/examplechart", chartArchiveDir)
test.PackageChart(t, "testdata/examplechart2", chartArchiveDir)

srv, err := repotest.NewTempServerWithCleanup(t, chartGlobPath)
defer srv.Stop()
if err != nil {
t.Fatal(err)
}
if err := srv.CreateIndex(); err != nil {
t.Fatal(err)
}
httpRegistryUrl := test.StartHttpRegistryWithCleanup(t, chartGlobPath)
httpRegistryWithAuthUrl := test.StartHttpRegistryWithAuthAndCleanup(t, chartGlobPath)

srvWithAuth := repotest.NewTempServerWithCleanupAndBasicAuth(t, chartGlobPath)
if err := srvWithAuth.CreateIndex(); err != nil {
t.Fatal(err)
}
defer srvWithAuth.Stop()

ociRegistryUrl := startOciRegistry(t, chartGlobPath)
ociRegistryWithAuthUrl, registryConfigFile := startOciRegistryWithAuth(t, chartGlobPath)
ociRegistryUrl := test.StartOciRegistry(t, chartGlobPath)
ociRegistryWithAuthUrl, registryConfigFile := test.StartOciRegistryWithAuth(t, chartGlobPath)

const fileDepChartName = "filedepchart"
const fileDepChartVersion = "2.3.4"
Expand All @@ -296,14 +260,14 @@ func TestBuildDependencies(t *testing.T) {
},
{
name: "http dependencies with Chat.lock file",
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: srv.URL()}},
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: httpRegistryUrl}},
hasLockFile: true,
auth: AuthSettings{},
wantErr: false,
},
{
name: "http dependencies without Chat.lock file",
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: srv.URL()}},
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: httpRegistryUrl}},
hasLockFile: false,
auth: AuthSettings{},
wantErr: false,
Expand Down Expand Up @@ -338,28 +302,28 @@ func TestBuildDependencies(t *testing.T) {
},
{
name: "http and oci dependencies with Chat.lock file",
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: srv.URL()}, {Name: "examplechart2", Version: "0.1.0", Repository: ociRegistryUrl}},
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: httpRegistryUrl}, {Name: "examplechart2", Version: "0.1.0", Repository: ociRegistryUrl}},
hasLockFile: true,
auth: AuthSettings{},
wantErr: false,
},
{
name: "http and oci dependencies without Chat.lock file",
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: srv.URL()}, {Name: "examplechart2", Version: "0.1.0", Repository: ociRegistryUrl}},
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: httpRegistryUrl}, {Name: "examplechart2", Version: "0.1.0", Repository: ociRegistryUrl}},
hasLockFile: false,
auth: AuthSettings{},
wantErr: false,
},
{
name: "http dependencies with Chat.lock file and auth",
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: srvWithAuth.URL()}},
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: httpRegistryWithAuthUrl}},
hasLockFile: true,
auth: AuthSettings{Username: "username", Password: "password"},
wantErr: false,
},
{
name: "http dependencies without Chat.lock file and auth",
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: srvWithAuth.URL()}},
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "0.1.0", Repository: httpRegistryWithAuthUrl}},
hasLockFile: false,
auth: AuthSettings{Username: "username", Password: "password"},
wantErr: false,
Expand All @@ -380,7 +344,7 @@ func TestBuildDependencies(t *testing.T) {
},
{
name: "http dependency with empty version should fail",
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "", Repository: srv.URL()}},
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "", Repository: httpRegistryUrl}},
hasLockFile: false,
auth: AuthSettings{},
wantErr: true,
Expand All @@ -394,7 +358,7 @@ func TestBuildDependencies(t *testing.T) {
},
{
name: "http dependency with non semver should fail",
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "", Repository: srv.URL()}},
dependencies: []*chart.Dependency{{Name: "examplechart", Version: "", Repository: httpRegistryUrl}},
hasLockFile: false,
auth: AuthSettings{},
wantErr: true,
Expand Down Expand Up @@ -535,136 +499,3 @@ func HashReq(req, lock []*chart.Dependency) (string, error) {
s, err := provenance.Digest(bytes.NewBuffer(data))
return "sha256:" + s, err
}

// startOciRegistry start an oci registry and uploads charts archives matching glob and returns the registry URL.
func startOciRegistry(t *testing.T, glob string) string {
registryURL, _ := newOciRegistry(t, glob, false)
return registryURL
}

// startOciRegistryWithAuth start an oci registry with authentication, uploads charts archives matching glob,
// returns the registry URL and registryConfigFile.
// registryConfigFile contains the credentials of the registry.
func startOciRegistryWithAuth(t *testing.T, glob string) (string, string) {
return newOciRegistry(t, glob, true)
}

// startOciRegistry start an oci registry, uploads charts archives matching glob, returns the registry URL and
// registryConfigFile if authentication is enabled.
func newOciRegistry(t *testing.T, glob string, enableAuth bool) (string, string) {
t.Helper()

// Registry config
config := &configuration.Configuration{}
credentialDir := t.TempDir()

var username, password, registryConfigFile string

if enableAuth {
username = "someuser"
password = "somepassword"

encrypedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
t.Fatalf("failed to generate encrypt password: %s", err)
}
authHtpasswd := filepath.Join(credentialDir, "auth.htpasswd")
err = os.WriteFile(authHtpasswd, []byte(fmt.Sprintf("%s:%s\n", username, string(encrypedPassword))), 0600)
if err != nil {
t.Fatalf("failed to write auth.htpasswd file: %s", err)
}

config.Auth = configuration.Auth{
"htpasswd": configuration.Parameters{
"realm": "localhost",
"path": authHtpasswd,
},
}
}

port, err := freeport.GetFreePort()
if err != nil {
t.Fatalf("error finding free port for test registry")
}

config.HTTP.Addr = fmt.Sprintf(":%d", port)
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}

ociRegistryUrl := fmt.Sprintf("oci://localhost:%d/helm-charts", port)

r, err := ociregistry.NewRegistry(context.Background(), config)
if err != nil {
t.Fatal(err)
}

go func() {
if err := r.ListenAndServe(); err != nil {
t.Errorf("can not start http registry: %s", err)
return
}
}()

if glob != "" {
options := []helmregistry.ClientOption{helmregistry.ClientOptWriter(os.Stdout)}
if enableAuth {
registryConfigFile = filepath.Join(credentialDir, "reg-cred")
// to generate auth field : echo '<user>:<password>' | base64
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
if err := os.WriteFile(registryConfigFile, []byte(fmt.Sprintf(`{"auths":{"localhost:%d":{"username":"%s","password":"%s","auth":"%s"}}}`, port, username, password, auth)), 0600); err != nil {
t.Fatal(err)
}
options = append(options, helmregistry.ClientOptCredentialsFile(registryConfigFile))
}
regClient, err := helmregistry.NewClient(options...)
if err != nil {
t.Fatal(err)
}

chartUploader := uploader.ChartUploader{
Out: os.Stdout,
Pushers: pusher.All(&cli.EnvSettings{}),
Options: []pusher.Option{pusher.WithRegistryClient(regClient)},
}

files, err := filepath.Glob(glob)
if err != nil {
t.Fatalf("failed to upload chart, invalid blob: %s", err)
}
for i := range files {
err = chartUploader.UploadTo(files[i], ociRegistryUrl)
if err != nil {
t.Fatalf("can not push chart '%s' to oci registry: %s", files[i], err)
}
}
}

return ociRegistryUrl, registryConfigFile
}

// packageChart packages the chart in chartDir into a chart archive file (i.e. a tgz) in destDir directory and returns
// the full path and the size of the archive.
func packageChart(t *testing.T, chartDir string, destDir string) (string, int64) {
ch, err := loader.LoadDir(chartDir)
if err != nil {
t.Fatalf("failed to load chart '%s': %s", chartDir, err)
}

if reqs := ch.Metadata.Dependencies; reqs != nil {
if err := action.CheckDependencies(ch, reqs); err != nil {
t.Fatalf("invalid dependencies for chart '%s': %s", chartDir, err)
}
}

archivePath, err := chartutil.Save(ch, destDir)
if err != nil {
t.Fatalf("failed to package chart '%s': %s", chartDir, err)
}

expectedChartInfo, err := os.Stat(archivePath)
if err != nil {
t.Fatalf("can get size chart archive %s", err)
}

return archivePath, expectedChartInfo.Size()
}
Loading