Skip to content

Commit abccd7f

Browse files
committed
introduce ObjectKeyPrefix
1 parent 7c26a3e commit abccd7f

15 files changed

+288
-66
lines changed

internal/cli/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ go_library(
1414
deps = [
1515
"//config",
1616
"//metadata",
17+
"//objects",
1718
"//program",
1819
"//stores",
1920
"@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//:azblob",

internal/cli/helpers.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,19 @@ func removePathPrefix(objects []string, prefix string) ([]string, error) {
4242
}
4343

4444
func initStoreFromConfig(ctx context.Context, cfg config.Config, fsys afero.Fs, opts stores.Options) (stores.Store, error) {
45-
var s stores.Store
4645
switch cfg.StoreType {
4746
case stores.StoreTypeS3:
4847
s3, err := stores.NewS3StoreClient(ctx, fsys, opts)
4948
if err != nil {
5049
return nil, fmt.Errorf("improper stores.S3Client init: %v", err)
5150
}
52-
s = stores.Store(s3)
51+
return stores.Store(s3), nil
5352
case stores.StoreTypeGCS:
5453
gcs, err := stores.NewGCSStoreClient(ctx, fsys, opts)
5554
if err != nil {
5655
return nil, fmt.Errorf("improper stores.GCSClient init: %v", err)
5756
}
58-
s = stores.Store(gcs)
57+
return stores.Store(gcs), nil
5958
case stores.StoreTypeAzureBlob:
6059
az, err := stores.NewAzureBlobStore(
6160
ctx,
@@ -66,7 +65,7 @@ func initStoreFromConfig(ctx context.Context, cfg config.Config, fsys afero.Fs,
6665
if err != nil {
6766
return nil, fmt.Errorf("improper stores.AzureBlobStore init: %v", err)
6867
}
69-
s = stores.Store(az)
68+
return stores.Store(az), nil
7069
case stores.StoreTypeGoPlugin:
7170
// FIXME: allow specifying command arguments for plugin
7271
ps, err := stores.NewPluggableStore(ctx, opts)
@@ -77,12 +76,10 @@ func initStoreFromConfig(ctx context.Context, cfg config.Config, fsys afero.Fs,
7776
default:
7877
return nil, fmt.Errorf("type %s is not supported", cfg.StoreType)
7978
}
80-
81-
return s, nil
8279
}
8380

8481
func setLoggerOpts() {
85-
if vv {
82+
if VV {
8683
logger.SetLevel(2)
8784
}
8885
logger.SetFlags(log.LUTC)

internal/cli/init.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ func initCmd() *cobra.Command {
3434
initCmd.PersistentFlags().String("region", "us-east-1", "Default region for the storage backend")
3535
initCmd.PersistentFlags().String("store_type", "", "Storage backend to use")
3636
initCmd.PersistentFlags().String("plugin_address", "", "Address for go-plugin that provides implementation for Store")
37+
initCmd.PersistentFlags().String("object_key_prefix",
38+
"",
39+
"Prefixed added to backend upload and retrieve requests. This does not affect the location of written metadata for objects.")
3740

3841
return initCmd
3942
}
@@ -63,6 +66,9 @@ func initPreExecFn(cmd *cobra.Command, args []string) error {
6366
if err := viper.BindPFlag("plugin_address", cmd.PersistentFlags().Lookup("plugin_address")); err != nil {
6467
return errors.New("Failed to bind plugin_address to viper")
6568
}
69+
if err := viper.BindPFlag("object_key_prefix", cmd.PersistentFlags().Lookup("object_key_prefix")); err != nil {
70+
return errors.New("Failed to bind object_key_prefix to viper")
71+
}
6672

6773
return nil
6874
}

internal/cli/root.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
var (
1717
// These vars are available to every sub command
1818
debug bool
19-
vv bool
19+
VV bool
2020

2121
// TODO (@radsec) Update this to be dynamic with GH action on new release and tagging....
2222
version string = "development"
@@ -68,13 +68,13 @@ func rootCmd() *cobra.Command {
6868

6969
// At the rootCmd level, set these global flags that will be available to downstream cmds
7070
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Run in debug mode")
71-
rootCmd.PersistentFlags().BoolVar(&vv, "vv", false, "Run in verbose logging mode")
71+
rootCmd.PersistentFlags().BoolVar(&VV, "vv", false, "Run in verbose logging mode")
7272

7373
// Defaults set here will be used if they do not exist in the config file
7474
viper.SetDefault("store_type", stores.StoreTypeUndefined)
7575
viper.SetDefault("metadata_file_extension", metadata.MetadataFileExtension)
7676

77-
if vv {
77+
if VV {
7878
logger.SetLevel(2)
7979
}
8080

internal/cli/upload.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/discentem/cavorite/config"
1515
"github.com/discentem/cavorite/metadata"
16+
cavoriteObjLib "github.com/discentem/cavorite/objects"
1617
"github.com/discentem/cavorite/program"
1718
"github.com/discentem/cavorite/stores"
1819
)
@@ -70,7 +71,18 @@ func upload(ctx context.Context, fsys afero.Fs, s stores.Store, objects ...strin
7071

7172
logger.Infof("Uploading to: %s", opts.BackendAddress)
7273
logger.Infof("Uploading file: %s", objects)
73-
if err := s.Upload(ctx, objects...); err != nil {
74+
75+
var derivedKeys []string
76+
prefixOp := cavoriteObjLib.AddPrefixToKey{Prefix: opts.ObjectKeyPrefix}
77+
derivedKeys = objects
78+
if opts.ObjectKeyPrefix != "" {
79+
derivedKeys = cavoriteObjLib.ModifyMultipleKeys(
80+
prefixOp,
81+
objects...,
82+
)
83+
}
84+
85+
if err := s.Upload(ctx, derivedKeys...); err != nil {
7486
logger.Error(err)
7587
return fmt.Errorf("%w for %v", ErrUpload, objects)
7688
}
@@ -87,11 +99,14 @@ func upload(ctx context.Context, fsys afero.Fs, s stores.Store, objects ...strin
8799
errResult = multierr.Append(err)
88100
continue
89101
}
102+
mon := prefixOp.Modify(obj)
103+
fmt.Println("mon:", mon)
90104
err = metadata.WriteToFsys(metadata.FsysWriteRequest{
91-
Object: obj,
92-
Fsys: fsys,
93-
Fi: f,
94-
Extension: opts.MetadataFileExtension,
105+
Object: mon,
106+
Fsys: fsys,
107+
Fi: f,
108+
MetadataPath: obj,
109+
Extension: opts.MetadataFileExtension,
95110
})
96111
if err != nil {
97112
errResult = multierr.Append(fmt.Errorf("%w for %s", ErrWriteMetadataToFsys, obj))

internal/cli/upload_test.go

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cli
33
import (
44
"context"
55
"fmt"
6+
"os"
67
"testing"
78
"time"
89

@@ -45,6 +46,7 @@ type simpleStore struct {
4546
sourceFsys afero.Fs
4647
// bucketFsys acts as remote artifact storage, where objects will be uploaded
4748
bucketFsys afero.Fs
49+
options stores.Options
4850
}
4951

5052
func (s simpleStore) Upload(ctx context.Context, objects ...string) error {
@@ -54,10 +56,7 @@ func (s simpleStore) Retrieve(ctx context.Context, objects ...string) error {
5456
return nil
5557
}
5658
func (s simpleStore) GetOptions() (stores.Options, error) {
57-
return stores.Options{
58-
MetadataFileExtension: "cfile",
59-
BackendAddress: "simpleStore/Test",
60-
}, nil
59+
return s.options, nil
6160
}
6261

6362
func (s simpleStore) Close() error {
@@ -84,14 +83,22 @@ func TestUpload(t *testing.T) {
8483
sStore := simpleStore{
8584
sourceFsys: *sourceFsys,
8685
bucketFsys: *bucket,
86+
options: stores.Options{
87+
MetadataFileExtension: "cfile",
88+
BackendAddress: "simpleStore/Test",
89+
},
8790
}
8891
err = upload(context.Background(), *sourceFsys, sStore, objs...)
8992
assert.NoError(t, err)
9093

94+
err = testutils.WalkFs(*sourceFsys, os.Stdout)
95+
require.NoError(t, err)
96+
9197
sopts, err := sStore.GetOptions()
9298
assert.NoError(t, err)
9399
for _, f := range objs {
94-
b, _ := afero.ReadFile(sStore.sourceFsys, fmt.Sprintf("%s.%s", f, sopts.MetadataFileExtension))
100+
b, err := afero.ReadFile(sStore.sourceFsys, fmt.Sprintf("%s.%s", f, sopts.MetadataFileExtension))
101+
require.NoError(t, err)
95102
assert.Equal(t, fmt.Sprintf(`{
96103
"name": "%s",
97104
"checksum": "35bafb1ce99aef3ab068afbaabae8f21fd9b9f02d3a9442e364fa92c0b3eeef0",
@@ -102,6 +109,57 @@ func TestUpload(t *testing.T) {
102109
assert.NoError(t, err)
103110
}
104111

112+
// TestUploadWithPrefix tests whether metadata gets generated correctly
113+
func TestUploadWithPrefix(t *testing.T) {
114+
mTime, _ := time.Parse("2006-01-02T15:04:05.000Z", "2014-11-12T11:45:26.371Z")
115+
sourceFsys, err := testutils.MemMapFsWith(map[string]testutils.MapFile{
116+
"someFile": {
117+
ModTime: &mTime,
118+
Content: []byte(`stuff`),
119+
},
120+
"someOtherFile": {
121+
ModTime: &mTime,
122+
Content: []byte(`stuff`),
123+
},
124+
})
125+
assert.NoError(t, err)
126+
bucket, err := testutils.MemMapFsWith(map[string]testutils.MapFile{})
127+
assert.NoError(t, err)
128+
sStore := simpleStore{
129+
sourceFsys: *sourceFsys,
130+
bucketFsys: *bucket,
131+
options: stores.Options{
132+
MetadataFileExtension: "cfile",
133+
BackendAddress: "simpleStore/Test",
134+
ObjectKeyPrefix: "aCoolPrefix",
135+
},
136+
}
137+
err = upload(context.Background(), *sourceFsys, sStore, "someFile", "someOtherFile")
138+
require.NoError(t, err)
139+
140+
err = testutils.WalkFs(*sourceFsys, os.Stdout)
141+
require.NoError(t, err)
142+
143+
sopts, err := sStore.GetOptions()
144+
assert.NoError(t, err)
145+
for _, f := range []string{"someFile", "someOtherFile"} {
146+
b, err := afero.ReadFile(*sourceFsys, fmt.Sprintf("%s.%s", f, sopts.MetadataFileExtension))
147+
require.NoError(t, err)
148+
var objkey string
149+
if sopts.ObjectKeyPrefix != "" {
150+
objkey = fmt.Sprintf("%s/%s", sopts.ObjectKeyPrefix, f)
151+
} else {
152+
objkey = fmt.Sprintf(f)
153+
}
154+
expected := fmt.Sprintf(`{
155+
"name": "%s",
156+
"checksum": "35bafb1ce99aef3ab068afbaabae8f21fd9b9f02d3a9442e364fa92c0b3eeef0",
157+
"date_modified": "2014-11-12T11:45:26.371Z"
158+
}`, objkey)
159+
assert.Equal(t, expected, string(b))
160+
}
161+
}
162+
105163
// TestUploadPartialFail tests whether metadata generation will succeed for n+1 even if n fails
106164
func TestUploadPartialFail(t *testing.T) {
107165
mTime, _ := time.Parse("2006-01-02T15:04:05.000Z", "2014-11-12T11:45:26.371Z")

metadata/metadata.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,18 @@ func ParseCfileWithExtension(fsys afero.Fs, obj, ext string) (*ObjectMetaData, e
7272
}
7373

7474
type FsysWriteRequest struct {
75-
Object string
76-
Fsys afero.Fs
77-
Fi afero.File
78-
Extension string
75+
Object string
76+
Fsys afero.Fs
77+
Fi afero.File
78+
MetadataPath string
79+
Extension string
7980
}
8081

8182
// WriteToFsys generates Cavorite metadata for req.Object and writes it to req.Fsys
8283
func WriteToFsys(req FsysWriteRequest) (err error) {
84+
if req.MetadataPath == "" {
85+
return fmt.Errorf("req.MetadataPath cannot be %q", "")
86+
}
8387
logger.V(2).Infof("object: %s", req.Object)
8488
// generate metadata
8589
m, err := GenerateFromFile(req.Fi)
@@ -93,7 +97,7 @@ func WriteToFsys(req FsysWriteRequest) (err error) {
9397
return err
9498
}
9599
// Write metadata to disk
96-
metadataPath := fmt.Sprintf("%s.%s", req.Object, req.Extension)
100+
metadataPath := fmt.Sprintf("%s.%s", req.MetadataPath, req.Extension)
97101
logger.V(2).Infof("writing metadata to %s", metadataPath)
98102
if err := afero.WriteFile(req.Fsys, metadataPath, blob, 0644); err != nil {
99103
return err

objects/BUILD.bazel

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "objects",
5+
srcs = ["objects.go"],
6+
importpath = "github.com/discentem/cavorite/objects",
7+
visibility = ["//visibility:public"],
8+
)
9+
10+
go_test(
11+
name = "objects_test",
12+
srcs = ["objects_test.go"],
13+
embed = [":objects"],
14+
deps = ["@com_github_stretchr_testify//assert"],
15+
)

objects/objects.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package objects
2+
3+
import "fmt"
4+
5+
type KeyModifier interface {
6+
Modify(string) string
7+
}
8+
9+
type AddPrefixToKey struct {
10+
Prefix string
11+
}
12+
13+
func (p AddPrefixToKey) Modify(original string) string {
14+
if p.Prefix == "" {
15+
return original
16+
}
17+
return fmt.Sprintf("%s/%s", p.Prefix, original)
18+
}
19+
20+
func ModifyMultipleKeys(modder KeyModifier, originalKeys ...string) []string {
21+
newKeys := []string{}
22+
for _, key := range originalKeys {
23+
newKeys = append(newKeys, modder.Modify(key))
24+
}
25+
return newKeys
26+
}

objects/objects_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package objects
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestAddPrefixModify(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
originalKey string
13+
prefix string
14+
expected string
15+
}{
16+
{
17+
name: "single level key",
18+
originalKey: "thing",
19+
prefix: "stuff",
20+
expected: "stuff/thing",
21+
},
22+
{
23+
name: "multi level key",
24+
originalKey: "apple/banana/strawberry",
25+
prefix: "cake",
26+
expected: "cake/apple/banana/strawberry",
27+
},
28+
{
29+
name: "empty prefix",
30+
originalKey: "whatever/thing/stuff",
31+
prefix: "",
32+
expected: "whatever/thing/stuff",
33+
},
34+
}
35+
36+
for _, test := range tests {
37+
modder := AddPrefixToKey{Prefix: test.prefix}
38+
actual := modder.Modify(test.originalKey)
39+
assert.Equal(t, test.expected, actual)
40+
}
41+
42+
}

stores/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ go_test(
4343
"azure_test.go",
4444
"gcs_test.go",
4545
"s3_test.go",
46+
"stores_test.go",
4647
],
4748
embed = [":stores"],
4849
deps = [

stores/options.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ type Options struct {
77
PluginAddress string `json:"plugin_address,omitempty" mapstructure:"plugin_address"`
88
MetadataFileExtension string `json:"metadata_file_extension" mapstructure:"metadata_file_extension"`
99
Region string `json:"region" mapstructure:"region"`
10+
/*
11+
If ObjectKeyPrefix is set to "team-bucket", and the initialized backend supports it,
12+
- `cavorite upload whatever/thing` will be written to `team-bucket/whatever/thing`
13+
- `cavorite retrieve whatever/thing` will request `team-bucket/whatever/thing`
14+
*/
15+
ObjectKeyPrefix string `json:"object_key_prefix" mapstructure:"object_key_prefix"`
1016
}
1117

1218
var ErrMetadataFileExtensionEmpty = fmt.Errorf("options.MetadatafileExtension cannot be %q", "")

0 commit comments

Comments
 (0)