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