Skip to content

Commit

Permalink
feat(oci): Add unit tests for oci clients (#95)
Browse files Browse the repository at this point in the history
* feat(oci): Add unit tests for oci clients
  • Loading branch information
tompizmor authored Feb 2, 2021
1 parent e505c10 commit ba3d2a7
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 5 deletions.
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ module github.com/bitnami-labs/charts-syncer

go 1.15

// Pin to specific version to not hit the next issue:
// Error: vendor/github.com/docker/distribution/registry/registry.go:157:10: undefined: letsencrypt.Manager
replace github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d

require (
github.com/bitnami-labs/pbjson v1.1.0
github.com/containerd/containerd v1.3.2
github.com/deislabs/oras v0.8.1
github.com/docker/distribution v2.7.1+incompatible
github.com/golang/protobuf v1.4.2
github.com/google/go-cmp v0.5.0
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f
Expand All @@ -21,6 +26,5 @@ require (
gopkg.in/yaml.v2 v2.3.0
helm.sh/helm/v3 v3.2.1
k8s.io/klog v1.0.0
rsc.io/letsencrypt v0.0.3 // indirect
sigs.k8s.io/yaml v1.2.0
)
5 changes: 1 addition & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,8 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492 h1:FwssHbCDJD025h+BchanCwE1Q8fyMgqDr2mOQAWOLGw=
github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20191216044856-a8371794149d h1:jC8tT/S0OGx2cswpeUTn4gOIea8P08lD3VFQT0cOZ50=
github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce h1:KXS1Jg+ddGcWA8e1N7cupxaHHZhit5rB9tfDU+mfjyY=
github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
Expand Down Expand Up @@ -926,8 +925,6 @@ k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM=
rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
Expand Down
12 changes: 12 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -291,3 +292,14 @@ func NormalizeChartURL(repoURL, indexURL string) (string, error) {
}
return chartURL, nil
}

// GetListenAddress returns a free local direction
func GetListenAddress() (string, error) {
lst, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return "", errors.Trace(err)
}
defer lst.Close()

return lst.Addr().String(), nil
}
208 changes: 208 additions & 0 deletions pkg/client/oci/oci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package oci_test

import (
"context"
"fmt"
"io/ioutil"
"net/url"
"os"
"path"
"reflect"
"sort"
"testing"
"time"

"github.com/bitnami-labs/charts-syncer/api"
"github.com/bitnami-labs/charts-syncer/internal/cache"
"github.com/bitnami-labs/charts-syncer/internal/utils"
"github.com/bitnami-labs/charts-syncer/pkg/client/oci"
"github.com/bitnami-labs/charts-syncer/pkg/client/types"
"github.com/docker/distribution/configuration"
"github.com/docker/distribution/registry"
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
"helm.sh/helm/v3/pkg/chart"
)

var (
ociRepo = &api.Repo{
Kind: api.Kind_OCI,
Auth: &api.Auth{
Username: "user",
Password: "password",
},
}
)

func prepareTest(t *testing.T) *oci.Repo {
t.Helper()

// Define cache dir
cacheDir, err := ioutil.TempDir("", "client")
if err != nil {
t.Fatal(err)
}
cache, err := cache.New(cacheDir, ociRepo.GetUrl())
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { os.RemoveAll(cacheDir) })

// Create oci client
client, err := oci.New(ociRepo, cache)
if err != nil {
t.Fatal(err)
}
return client
}

// Creates an HTTP server that knows how to reply to all OCI related request except PUSH one.
func prepareHttpServer(t *testing.T) *oci.Repo {
t.Helper()

// Create HTTP server
tester := oci.NewTester(t, ociRepo)
ociRepo.Url = tester.GetURL() + "/someproject/charts"

return prepareTest(t)
}

// Starts an OCI compliant server (docker-registry) so our push command based on oras cli works out-of-the-box.
// This way we don't have to mimic all the low-level HTTP requests made by oras.
func prepareOciServer(t *testing.T) *oci.Repo {
t.Helper()

// Create OCI server as docker registry
config := &configuration.Configuration{}

addr, err := utils.GetListenAddress()
if err != nil {
t.Fatal(err)
}
dockerRegistryHost := "http://" + addr
config.HTTP.Addr = fmt.Sprintf(addr)
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
dockerRegistry, err := registry.NewRegistry(context.Background(), config)
if err != nil {
t.Fatal(err)
}
go dockerRegistry.ListenAndServe()
ociRepo.Url = dockerRegistryHost + "/someproject/charts"
return prepareTest(t)
}

func TestFetch(t *testing.T) {
c := prepareHttpServer(t)
chartPath, err := c.Fetch("kafka", "12.2.1")
if err != nil {
t.Fatal(err)
}
t.Logf("ChartPath is %q", chartPath)
if _, err := os.Stat(chartPath); err != nil {
t.Errorf("chart package does not exist")
}
contentType, err := utils.GetFileContentType(chartPath)
if err != nil {
t.Fatalf("error checking contentType of %s file", chartPath)
}
if contentType != "application/x-gzip" {
t.Errorf("incorrect content type, got: %s, want: %s.", contentType, "application/x-gzip")
}
}

func TestHas(t *testing.T) {
c := prepareHttpServer(t)
has, err := c.Has("kafka", "12.2.1")
if err != nil {
t.Fatal(err)
}
if !has {
t.Errorf("chart not found in index")
}
}

func TestList(t *testing.T) {
c := prepareHttpServer(t)
expectedError := "list method is not supported yet"
_, err := c.List()
if err.Error() != expectedError {
t.Errorf("unexpected error message. got: %q, want: %q", err.Error(), expectedError)
}
}

func TestListChartVersions(t *testing.T) {
c := prepareHttpServer(t)
want := []string{"12.2.1"}
got, err := c.ListChartVersions("kafka")
if err != nil {
t.Fatal(err)
}
sort.Strings(want)
sort.Strings(got)
if !reflect.DeepEqual(want, got) {
t.Errorf("unexpected list of charts. got: %v, want: %v", got, want)
}
}

func TestGetChartDetails(t *testing.T) {
c := prepareHttpServer(t)
want := types.ChartDetails{
PublishedAt: time.Now(),
Digest: "sha256:11e974d88391a39e4dd6d7d6c4350b237b1cca1bf32f2074bba41109eaa5f438",
}
got, err := c.GetChartDetails("kafka", "12.2.1")
if err != nil {
t.Fatal(err)
}
if want.Digest != got.Digest {
t.Errorf("unexpected digest in chart. got: %v, want: %v", got, want)
}
}

func TestReload(t *testing.T) {
c := prepareHttpServer(t)
expectedError := "reload method is not supported yet"
err := c.Reload()
if err.Error() != expectedError {
t.Errorf("unexpected error message. got: %q, want: %q", err.Error(), expectedError)
}
}

func TestGetDownloadURL(t *testing.T) {
c := prepareHttpServer(t)
u, err := url.Parse(ociRepo.Url)
if err != nil {
t.Fatal(err)
}
u.Path = path.Join("v2", u.Path, "kafka/blobs/sha256:11e974d88391a39e4dd6d7d6c4350b237b1cca1bf32f2074bba41109eaa5f438")
want := u.String()
got, err := c.GetDownloadURL("kafka", "12.2.1")
if err != nil {
t.Fatal(err)
}
if got != want {
t.Errorf("wrong download URL. got: %v, want: %v", got, want)
}
}

func TestUpload(t *testing.T) {
c := prepareOciServer(t)
chartMetadata := &chart.Metadata{
Name: "apache",
Version: "7.3.15",
}
if err := c.Upload("../../../testdata/apache-7.3.15.tgz", chartMetadata); err != nil {
t.Fatal(err)
}
chartPath, err := c.Fetch("apache", "7.3.15")
if _, err := os.Stat(chartPath); err != nil {
t.Errorf("chart package does not exist")
}
contentType, err := utils.GetFileContentType(chartPath)
if err != nil {
t.Fatalf("error checking contentType of %s file", chartPath)
}
if contentType != "application/x-gzip" {
t.Errorf("incorrect content type, got: %s, want: %s.", contentType, "application/x-gzip")
}
}
Loading

0 comments on commit ba3d2a7

Please sign in to comment.