mirror of https://github.com/helm/helm
Signed-off-by: Björn Wenzel <bjoern-wenzel@hotmail.de>pull/12173/head
parent
03911aeab7
commit
8452e815f0
@ -0,0 +1,273 @@
|
|||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
|
"helm.sh/helm/v3/pkg/release"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Driver = (*S3Driver)(nil)
|
||||||
|
|
||||||
|
// S3DriverName is the string name of the driver.
|
||||||
|
const S3DriverName = "S3Driver"
|
||||||
|
|
||||||
|
type S3Client interface {
|
||||||
|
GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error)
|
||||||
|
PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
|
||||||
|
HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error)
|
||||||
|
DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error)
|
||||||
|
ListObjectsV2(context.Context, *s3.ListObjectsV2Input, ...func(*s3.Options)) (*s3.ListObjectsV2Output, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// S3Driver is an implementation of the driver interface
|
||||||
|
type S3Driver struct {
|
||||||
|
bucket string
|
||||||
|
namespace string
|
||||||
|
client S3Client
|
||||||
|
Log func(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewS3 initializes a new S3Driver an implementation of the driver interface
|
||||||
|
func NewS3(bucket string, logger func(string, ...interface{}), namespace string) (*S3Driver, error) {
|
||||||
|
endpointResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
|
||||||
|
lUrl, lUrlExists := os.LookupEnv("HELM_DRIVER_S3_BUCKET_LOCATION_URL")
|
||||||
|
if service == s3.ServiceID && lUrlExists {
|
||||||
|
return aws.Endpoint{
|
||||||
|
PartitionID: "aws",
|
||||||
|
URL: lUrl,
|
||||||
|
SigningRegion: "us-east-2",
|
||||||
|
HostnameImmutable: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// performs a fallback to the default endpoint resolver
|
||||||
|
return aws.Endpoint{}, &aws.EndpointNotFoundError{}
|
||||||
|
})
|
||||||
|
|
||||||
|
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithEndpointResolverWithOptions(endpointResolver))
|
||||||
|
if err != nil {
|
||||||
|
logger("failed to load aws sdk configuration with error %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := s3.NewFromConfig(cfg, func(options *s3.Options) {
|
||||||
|
pStyle, pStyleExists := os.LookupEnv("HELM_DRIVER_S3_USE_PATH_STYLE")
|
||||||
|
if pStyleExists {
|
||||||
|
val, err := strconv.ParseBool(pStyle)
|
||||||
|
if err == nil {
|
||||||
|
options.UsePathStyle = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return &S3Driver{
|
||||||
|
client: client,
|
||||||
|
bucket: bucket,
|
||||||
|
namespace: namespace,
|
||||||
|
Log: logger,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new release and stores it on S3. If the object already exists, ErrReleaseExists is returned.
|
||||||
|
func (s *S3Driver) Create(key string, rls *release.Release) error {
|
||||||
|
s3Key := fmt.Sprintf("%s/%s", s.namespace, key)
|
||||||
|
exists, err := s.pathAlreadyExists(s3Key)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("Failed to check if release already exists with error %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return ErrReleaseExists
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Update(key, rls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathAlreadyExists returns a boolean if the release at a specific path already exists
|
||||||
|
func (s *S3Driver) pathAlreadyExists(path string) (bool, error) {
|
||||||
|
input := &s3.HeadObjectInput{
|
||||||
|
Bucket: &s.bucket,
|
||||||
|
Key: &path,
|
||||||
|
}
|
||||||
|
_, err := s.client.HeadObject(context.Background(), input)
|
||||||
|
if err != nil {
|
||||||
|
var responseError *awshttp.ResponseError
|
||||||
|
if errors.As(err, &responseError) && responseError.ResponseError.HTTPStatusCode() == http.StatusNotFound {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates a release by using the Create function
|
||||||
|
func (s *S3Driver) Update(key string, rls *release.Release) error {
|
||||||
|
s3Key := fmt.Sprintf("%s/%s", s.namespace, key)
|
||||||
|
|
||||||
|
r, err := encodeRelease(rls)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("Failed to decode release %q with error %v", key, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes := []byte(r)
|
||||||
|
input := &s3.PutObjectInput{
|
||||||
|
Bucket: &s.bucket,
|
||||||
|
Key: &s3Key,
|
||||||
|
Body: bytes.NewReader(bodyBytes),
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"name": rls.Name,
|
||||||
|
"owner": "helm",
|
||||||
|
"status": rls.Info.Status.String(),
|
||||||
|
"version": strconv.Itoa(rls.Version),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, pErr := s.client.PutObject(context.Background(), input)
|
||||||
|
|
||||||
|
return pErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete loads a release and deletes it from S3
|
||||||
|
func (s *S3Driver) Delete(key string) (*release.Release, error) {
|
||||||
|
rel, err := s.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("failed to get %s with error %v", key, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s3Key := fmt.Sprintf("%s/%s", s.namespace, key)
|
||||||
|
input := &s3.DeleteObjectInput{
|
||||||
|
Bucket: &s.bucket,
|
||||||
|
Key: &s3Key,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, delErr := s.client.DeleteObject(context.Background(), input)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("failed to delete object %s with error %v", s3Key, err)
|
||||||
|
return nil, delErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return rel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get loads a release based on the namespace & release name
|
||||||
|
func (s *S3Driver) Get(key string) (*release.Release, error) {
|
||||||
|
s3Key := fmt.Sprintf("%s/%s", s.namespace, key)
|
||||||
|
return s.getByPath(s3Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getByPath internal method to load a release by path consisting of the pattern namespace/release.name
|
||||||
|
func (s *S3Driver) getByPath(path string) (*release.Release, error) {
|
||||||
|
input := &s3.GetObjectInput{
|
||||||
|
Bucket: &s.bucket,
|
||||||
|
Key: &path,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.GetObject(context.Background(), input)
|
||||||
|
if err != nil {
|
||||||
|
var nsk *types.NoSuchKey
|
||||||
|
if errors.As(err, &nsk) {
|
||||||
|
return nil, ErrReleaseNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Log("failed to get release with s3 key %s with error %v", path, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
bodyBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body := string(bodyBytes)
|
||||||
|
rel, err := decodeRelease(body)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("failed to decode release from s3 key %s with error %v", path, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rel.Labels = resp.Metadata
|
||||||
|
return rel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists all releases within the S3 Bucket limited by the s.namespace parameter to limit it to a specific namespace.
|
||||||
|
func (s *S3Driver) List(filter func(*release.Release) bool) ([]*release.Release, error) {
|
||||||
|
input := &s3.ListObjectsV2Input{
|
||||||
|
Bucket: &s.bucket,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.namespace) != 0 {
|
||||||
|
prefix := fmt.Sprintf("%s/", s.namespace)
|
||||||
|
input.Prefix = &prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
var releases []*release.Release
|
||||||
|
paginator := s3.NewListObjectsV2Paginator(s.client, input)
|
||||||
|
for paginator.HasMorePages() {
|
||||||
|
page, err := paginator.NextPage(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
s.Log("failed to paginate through s3 bucket %s with error %v", s.bucket, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, obj := range page.Contents {
|
||||||
|
key := obj.Key
|
||||||
|
r, err := s.getByPath(*key)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("list: failed to decode load release with key: %s: %v", key, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter(r) {
|
||||||
|
releases = append(releases, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query is an internal facade to the List function that limits the result to all objects that have the matching labels
|
||||||
|
func (s *S3Driver) Query(labels map[string]string) ([]*release.Release, error) {
|
||||||
|
releases, err := s.List(func(r *release.Release) bool {
|
||||||
|
for key, val := range labels {
|
||||||
|
if r.Labels[key] != val {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.Log("failed to query releases with error %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(releases) == 0 {
|
||||||
|
return nil, ErrReleaseNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the driver.
|
||||||
|
func (s *S3Driver) Name() string {
|
||||||
|
return S3DriverName
|
||||||
|
}
|
@ -0,0 +1,411 @@
|
|||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
"helm.sh/helm/v3/pkg/release"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockS3Client struct {
|
||||||
|
getObjectCounter int
|
||||||
|
getObjectOverwrite func(params *s3.GetObjectInput) (*s3.GetObjectOutput, error)
|
||||||
|
putObjectCounter int
|
||||||
|
putObjectOverwrite func(params *s3.PutObjectInput) (*s3.PutObjectOutput, error)
|
||||||
|
headObjectCounter int
|
||||||
|
headObjectOverwite func(params *s3.HeadObjectInput) (*s3.HeadObjectOutput, error)
|
||||||
|
deleteObjectCounter int
|
||||||
|
deleteObjectOverwrite func(params *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error)
|
||||||
|
listObjectsV2Counter int
|
||||||
|
listObjectsV2Overwrite func(*s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockS3Client) GetObject(_ context.Context, params *s3.GetObjectInput, _ ...func(*s3.Options)) (*s3.GetObjectOutput, error) {
|
||||||
|
m.getObjectCounter++
|
||||||
|
return m.getObjectOverwrite(params)
|
||||||
|
}
|
||||||
|
func (m *mockS3Client) PutObject(_ context.Context, params *s3.PutObjectInput, _ ...func(*s3.Options)) (*s3.PutObjectOutput, error) {
|
||||||
|
m.putObjectCounter++
|
||||||
|
return m.putObjectOverwrite(params)
|
||||||
|
}
|
||||||
|
func (m *mockS3Client) HeadObject(_ context.Context, params *s3.HeadObjectInput, _ ...func(*s3.Options)) (*s3.HeadObjectOutput, error) {
|
||||||
|
m.headObjectCounter++
|
||||||
|
return m.headObjectOverwite(params)
|
||||||
|
}
|
||||||
|
func (m *mockS3Client) DeleteObject(_ context.Context, params *s3.DeleteObjectInput, _ ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) {
|
||||||
|
m.deleteObjectCounter++
|
||||||
|
return m.deleteObjectOverwrite(params)
|
||||||
|
}
|
||||||
|
func (m *mockS3Client) ListObjectsV2(_ context.Context, params *s3.ListObjectsV2Input, _ ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) {
|
||||||
|
m.listObjectsV2Counter++
|
||||||
|
return m.listObjectsV2Overwrite(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate(t *testing.T) {
|
||||||
|
ns := "test-namespace"
|
||||||
|
bucket := "test-bucket"
|
||||||
|
releaseName := "test-release"
|
||||||
|
key := "test-release-1"
|
||||||
|
expectedKey := fmt.Sprintf("%s/%s", ns, key)
|
||||||
|
version := 1
|
||||||
|
|
||||||
|
// Create a mock S3 client
|
||||||
|
mockClient := &mockS3Client{
|
||||||
|
putObjectOverwrite: func(params *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
|
||||||
|
if *params.Bucket != bucket {
|
||||||
|
t.Errorf("expected %s as bucket name got %s", bucket, *params.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *params.Key != expectedKey {
|
||||||
|
t.Errorf("expected %s as key but got %s", expectedKey, *params.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Metadata["name"] != releaseName {
|
||||||
|
t.Errorf("Expected metadata name key with value %s but got %s", releaseName, params.Metadata["Name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Metadata["owner"] != "helm" {
|
||||||
|
t.Errorf("Expected metadata owner key with value helm but got %s", params.Metadata["owner"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Metadata["version"] != strconv.Itoa(version) {
|
||||||
|
t.Errorf("Expected metadata version key with value %d but got %s", version, params.Metadata["version"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Metadata["status"] != release.StatusPendingUpgrade.String() {
|
||||||
|
t.Errorf("Expected metadata status key with value %s but got %s", release.StatusPendingUpgrade.String(), params.Metadata["status"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
headObjectOverwite: func(params *s3.HeadObjectInput) (*s3.HeadObjectOutput, error) {
|
||||||
|
if *params.Bucket != bucket {
|
||||||
|
t.Errorf("expected %s as bucket name got %s", bucket, *params.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *params.Key != expectedKey {
|
||||||
|
t.Errorf("expected %s as key but got %s", expectedKey, *params.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
respErr := smithyhttp.ResponseError{
|
||||||
|
Response: &smithyhttp.Response{
|
||||||
|
Response: &http.Response{
|
||||||
|
StatusCode: 404,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, &awshttp.ResponseError{
|
||||||
|
ResponseError: &respErr,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an instance of S3Driver with the mock client
|
||||||
|
driver := &S3Driver{
|
||||||
|
bucket: bucket,
|
||||||
|
namespace: ns,
|
||||||
|
client: mockClient,
|
||||||
|
Log: func(string, ...interface{}) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the input for the test
|
||||||
|
rls := &release.Release{
|
||||||
|
Name: releaseName,
|
||||||
|
Version: version,
|
||||||
|
Info: &release.Info{Status: release.StatusPendingUpgrade},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the Create function
|
||||||
|
err := driver.Create(key, rls)
|
||||||
|
|
||||||
|
// Check if the Create function returned an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Create returned an unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mockClient.putObjectCounter != 1 {
|
||||||
|
t.Errorf("Expected PutObject to be called once but was called %d", mockClient.putObjectCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mockClient.headObjectCounter != 1 {
|
||||||
|
t.Errorf("Expected HeadObject to be called once but was called %d", mockClient.headObjectCounter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdate(t *testing.T) {
|
||||||
|
namespace := "test-namespace"
|
||||||
|
releaseName := "release"
|
||||||
|
version := 1
|
||||||
|
key := "test-key"
|
||||||
|
bucket := "test-bucket"
|
||||||
|
bucketKey := fmt.Sprintf("%s/%s", namespace, key)
|
||||||
|
|
||||||
|
mockClient := &mockS3Client{
|
||||||
|
putObjectOverwrite: func(params *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
|
||||||
|
if *params.Key != bucketKey {
|
||||||
|
t.Errorf("Expected key %s but got %s", bucketKey, *params.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *params.Bucket != bucket {
|
||||||
|
t.Errorf("Expected bucket %s but got %s", bucket, *params.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Metadata["name"] != releaseName {
|
||||||
|
t.Errorf("Expected metadata name key with value %s but got %s", releaseName, params.Metadata["Name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Metadata["owner"] != "helm" {
|
||||||
|
t.Errorf("Expected metadata owner key with value helm but got %s", params.Metadata["owner"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Metadata["version"] != strconv.Itoa(version) {
|
||||||
|
t.Errorf("Expected metadata version key with value %d but got %s", version, params.Metadata["version"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Metadata["status"] != release.StatusDeployed.String() {
|
||||||
|
t.Errorf("Expected metadata status key with value %s but got %s", release.StatusDeployed.String(), params.Metadata["status"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return &s3.PutObjectOutput{}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an instance of S3Driver with the mock client
|
||||||
|
driver := &S3Driver{
|
||||||
|
bucket: bucket,
|
||||||
|
namespace: namespace,
|
||||||
|
client: mockClient,
|
||||||
|
Log: func(string, ...interface{}) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the input for the test
|
||||||
|
rls := &release.Release{
|
||||||
|
Name: releaseName,
|
||||||
|
Version: version,
|
||||||
|
Info: &release.Info{Status: release.StatusDeployed},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the Update function
|
||||||
|
err := driver.Update(key, rls)
|
||||||
|
|
||||||
|
// Check if the Update function returned an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Update returned an unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mockClient.putObjectCounter != 1 {
|
||||||
|
t.Errorf("Expected PutObject to be called once but was called %d", mockClient.putObjectCounter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseToGetObjectOutput(rls *release.Release) *s3.GetObjectOutput {
|
||||||
|
b, _ := encodeRelease(rls)
|
||||||
|
bReader := bytes.NewReader([]byte(b))
|
||||||
|
readCloser := io.NopCloser(bReader)
|
||||||
|
|
||||||
|
return &s3.GetObjectOutput{
|
||||||
|
Body: readCloser,
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"name": rls.Name,
|
||||||
|
"owner": "helm",
|
||||||
|
"version": strconv.Itoa(rls.Version),
|
||||||
|
"status": release.StatusDeployed.String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
key := "test-key"
|
||||||
|
releaseName := "test-release"
|
||||||
|
version := 1
|
||||||
|
namespace := "test-namespace"
|
||||||
|
bucket := "test-bucket"
|
||||||
|
|
||||||
|
mockClient := &mockS3Client{
|
||||||
|
getObjectOverwrite: func(params *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
|
||||||
|
if *params.Bucket != bucket {
|
||||||
|
t.Errorf("Expected bucket %s but got %s", bucket, *params.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedKey := fmt.Sprintf("%s/%s", namespace, key)
|
||||||
|
if *params.Key != expectedKey {
|
||||||
|
t.Errorf("Expected key %s but got %s", expectedKey, *params.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
rls := &release.Release{
|
||||||
|
Name: releaseName,
|
||||||
|
Version: version,
|
||||||
|
Info: &release.Info{Status: release.StatusDeployed},
|
||||||
|
}
|
||||||
|
|
||||||
|
return releaseToGetObjectOutput(rls), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an instance of S3Driver with the mock client
|
||||||
|
driver := &S3Driver{
|
||||||
|
bucket: bucket,
|
||||||
|
namespace: namespace,
|
||||||
|
client: mockClient,
|
||||||
|
Log: func(string, ...interface{}) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := driver.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Get returned an unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mockClient.getObjectCounter != 1 {
|
||||||
|
t.Errorf("Expected GetObject to be called once but was called %d", mockClient.getObjectCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Name != releaseName {
|
||||||
|
t.Errorf("Expected release name %s but got %s", releaseName, resp.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Version != version {
|
||||||
|
t.Errorf("Expected version %d but got %d", version, resp.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Info.Status != release.StatusDeployed {
|
||||||
|
t.Errorf("Expected status %v but got %v", release.StatusDeployed, resp.Info.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Labels["name"] != releaseName {
|
||||||
|
t.Errorf("Expected metadata name key with value %s but got %s", releaseName, resp.Labels["Name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Labels["owner"] != "helm" {
|
||||||
|
t.Errorf("Expected metadata owner key with value helm but got %s", resp.Labels["owner"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Labels["version"] != strconv.Itoa(version) {
|
||||||
|
t.Errorf("Expected metadata version key with value %d but got %s", version, resp.Labels["version"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Labels["status"] != release.StatusDeployed.String() {
|
||||||
|
t.Errorf("Expected metadata status key with value %s but got %s", release.StatusDeployed.String(), resp.Labels["status"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockReleases() map[string]release.Release {
|
||||||
|
return map[string]release.Release{
|
||||||
|
"test-namespace/first-release-1": *releaseStub("first-release", 1, "test-namespace", release.StatusDeployed),
|
||||||
|
"test-namespace/second-release-2": *releaseStub("second-release", 2, "test-namespace", release.StatusPendingUpgrade),
|
||||||
|
"other-test-namespace/third-release-3": *releaseStub("third-release", 3, "other-test-namespace", release.StatusFailed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestList(t *testing.T) {
|
||||||
|
bucket := "test-bucket"
|
||||||
|
rls := mockReleases()
|
||||||
|
|
||||||
|
mockClient := &mockS3Client{
|
||||||
|
listObjectsV2Overwrite: func(input *s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, error) {
|
||||||
|
if input.Prefix != nil {
|
||||||
|
t.Errorf("Expected empty prefix in ListObjectsV2 but got %s", *input.Prefix)
|
||||||
|
}
|
||||||
|
if *input.Bucket != bucket {
|
||||||
|
t.Errorf("Expected bucket %s but got %s", bucket, *input.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
lstResult := []types.Object{}
|
||||||
|
for s, _ := range rls {
|
||||||
|
lstResult = append(lstResult, types.Object{
|
||||||
|
Key: aws.String(s),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &s3.ListObjectsV2Output{
|
||||||
|
IsTruncated: false,
|
||||||
|
Contents: lstResult,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
getObjectOverwrite: func(params *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
|
||||||
|
if value, exists := rls[*params.Key]; exists {
|
||||||
|
return releaseToGetObjectOutput(&value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("GetObject was called with %s doesn't exist in releases", *params.Key)
|
||||||
|
return nil, errors.New("GetObject was called with wrong key")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := &S3Driver{
|
||||||
|
bucket: bucket,
|
||||||
|
namespace: "",
|
||||||
|
client: mockClient,
|
||||||
|
Log: func(string, ...interface{}) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := driver.List(func(r *release.Release) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("List returned an unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp) != 3 {
|
||||||
|
t.Errorf("Expected 3 releases to be returned but got only %d", len(resp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuery(t *testing.T) {
|
||||||
|
bucket := "test-bucket"
|
||||||
|
rls := mockReleases()
|
||||||
|
|
||||||
|
mockClient := &mockS3Client{
|
||||||
|
listObjectsV2Overwrite: func(input *s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, error) {
|
||||||
|
lstResult := []types.Object{}
|
||||||
|
for s := range rls {
|
||||||
|
lstResult = append(lstResult, types.Object{
|
||||||
|
Key: aws.String(s),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &s3.ListObjectsV2Output{
|
||||||
|
IsTruncated: false,
|
||||||
|
Contents: lstResult,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
getObjectOverwrite: func(params *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
|
||||||
|
if value, exists := rls[*params.Key]; exists {
|
||||||
|
return releaseToGetObjectOutput(&value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("GetObject was called with %s doesn't exist in releases", *params.Key)
|
||||||
|
return nil, errors.New("GetObject was called with wrong key")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := &S3Driver{
|
||||||
|
bucket: bucket,
|
||||||
|
namespace: "",
|
||||||
|
client: mockClient,
|
||||||
|
Log: func(string, ...interface{}) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := driver.Query(map[string]string{"name": "first-release"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Query returned an unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp) != 1 {
|
||||||
|
t.Fatalf("Expected 1 releases to be returned but got only %d", len(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp[0].Name != "first-release" {
|
||||||
|
t.Errorf("Expected the only release to be available to be named first-release but got %s", resp[0].Name)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue