Fix S3 broken glob matching
parent
2f07640e0a
commit
b5fe9e5e73
|
@ -11,9 +11,6 @@ import (
|
|||
)
|
||||
|
||||
func GlobS3(path string) (prefix string, pattern string) {
|
||||
if !HasMeta(path) {
|
||||
return path, ""
|
||||
}
|
||||
prefix, pattern = splitDirPattern(path)
|
||||
return
|
||||
}
|
||||
|
@ -23,23 +20,25 @@ func HasMeta(path string) bool {
|
|||
return strings.ContainsAny(path, magicChars)
|
||||
}
|
||||
|
||||
func splitDirPattern(p string) (base string, pattern string) {
|
||||
base = p
|
||||
// Should split a path :
|
||||
// - prefix : path part that should not contains glob patterns, that is used in S3 query to filter result
|
||||
// - pattern : should contains the glob pattern to be used by doublestar matching library
|
||||
func splitDirPattern(p string) (prefix string, pattern string) {
|
||||
sep := "/"
|
||||
|
||||
for {
|
||||
if !HasMeta(base) {
|
||||
break
|
||||
splitPath := strings.Split(p, sep)
|
||||
prefixEnded := false
|
||||
for _, s := range splitPath {
|
||||
if HasMeta(s) || prefixEnded {
|
||||
prefixEnded = true
|
||||
pattern = strings.Join([]string{pattern, s}, sep)
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(base, sep) {
|
||||
return "", base
|
||||
}
|
||||
base = base[:strings.LastIndex(base, sep)]
|
||||
|
||||
prefix = strings.Join([]string{prefix, s}, sep)
|
||||
|
||||
}
|
||||
if len(base) == len(p) {
|
||||
return p, ""
|
||||
}
|
||||
return base, p[len(base)+1:]
|
||||
return strings.Trim(prefix, sep), strings.Trim(pattern, sep)
|
||||
}
|
||||
|
||||
func Glob(pattern string) ([]string, error) {
|
||||
|
|
|
@ -41,12 +41,12 @@ func (s *S3Enumerator) Enumerate() ([]string, error) {
|
|||
}
|
||||
|
||||
bucket := bucketPath[0]
|
||||
// prefix should contains everything that does not have a glob pattern
|
||||
// Pattern should be the glob matcher string
|
||||
prefix, pattern := GlobS3(strings.Join(bucketPath[1:], "/"))
|
||||
|
||||
fullPattern := prefix
|
||||
if pattern != "" {
|
||||
fullPattern = strings.Join([]string{prefix, pattern}, "/")
|
||||
}
|
||||
fullPattern := strings.Join([]string{prefix, pattern}, "/")
|
||||
fullPattern = strings.Trim(fullPattern, "/")
|
||||
|
||||
files := make([]string, 0)
|
||||
input := &s3.ListObjectsV2Input{
|
||||
|
|
|
@ -125,6 +125,108 @@ func TestS3Enumerator_Enumerate(t *testing.T) {
|
|||
},
|
||||
want: []string{"bucket-name/a/nested/prefix/state2"},
|
||||
},
|
||||
{
|
||||
name: "test results with simple doublestar glob",
|
||||
config: config.SupplierConfig{
|
||||
Path: "bucket-name/**/*.tfstate",
|
||||
},
|
||||
mocks: func(client *awstest.MockFakeS3) {
|
||||
input := &s3.ListObjectsV2Input{
|
||||
Bucket: awssdk.String("bucket-name"),
|
||||
Prefix: awssdk.String(""),
|
||||
}
|
||||
client.On(
|
||||
"ListObjectsV2Pages",
|
||||
input,
|
||||
mock.MatchedBy(func(callback func(res *s3.ListObjectsV2Output, lastPage bool) bool) bool {
|
||||
callback(&s3.ListObjectsV2Output{
|
||||
Contents: []*s3.Object{
|
||||
{
|
||||
Key: awssdk.String("a/nested/prefix/1/state1.tfstate"),
|
||||
Size: awssdk.Int64(5),
|
||||
},
|
||||
{
|
||||
Key: awssdk.String("a/nested/folder1/2/state2.tfstate"),
|
||||
Size: awssdk.Int64(5),
|
||||
},
|
||||
{
|
||||
Key: awssdk.String("a/nested/prefix/state3.tfstate"),
|
||||
Size: awssdk.Int64(5),
|
||||
},
|
||||
},
|
||||
}, false)
|
||||
callback(&s3.ListObjectsV2Output{
|
||||
Contents: []*s3.Object{
|
||||
{
|
||||
Key: awssdk.String("a/nested/prefix/4/4/state4.tfstate"),
|
||||
Size: awssdk.Int64(5),
|
||||
},
|
||||
{
|
||||
Key: awssdk.String("a/nested/state5.tfstate"),
|
||||
Size: awssdk.Int64(5),
|
||||
},
|
||||
{
|
||||
Key: awssdk.String("a/nested/prefix/state6.tfstate.backup"),
|
||||
Size: awssdk.Int64(5),
|
||||
},
|
||||
},
|
||||
}, true)
|
||||
return true
|
||||
}),
|
||||
).Return(nil)
|
||||
},
|
||||
want: []string{
|
||||
"bucket-name/a/nested/prefix/1/state1.tfstate",
|
||||
"bucket-name/a/nested/folder1/2/state2.tfstate",
|
||||
"bucket-name/a/nested/prefix/state3.tfstate",
|
||||
"bucket-name/a/nested/prefix/4/4/state4.tfstate",
|
||||
"bucket-name/a/nested/state5.tfstate",
|
||||
},
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "test results with glob and prefix after glob",
|
||||
config: config.SupplierConfig{
|
||||
Path: "bucket-name/a/**/b/*.tfstate",
|
||||
},
|
||||
mocks: func(client *awstest.MockFakeS3) {
|
||||
input := &s3.ListObjectsV2Input{
|
||||
Bucket: awssdk.String("bucket-name"),
|
||||
Prefix: awssdk.String("a"),
|
||||
}
|
||||
client.On(
|
||||
"ListObjectsV2Pages",
|
||||
input,
|
||||
mock.MatchedBy(func(callback func(res *s3.ListObjectsV2Output, lastPage bool) bool) bool {
|
||||
callback(&s3.ListObjectsV2Output{
|
||||
Contents: []*s3.Object{
|
||||
{
|
||||
Key: awssdk.String("a/prefix/b/state1.tfstate"),
|
||||
Size: awssdk.Int64(5),
|
||||
},
|
||||
{
|
||||
Key: awssdk.String("a/b/state2.tfstate"),
|
||||
Size: awssdk.Int64(5),
|
||||
},
|
||||
{
|
||||
Key: awssdk.String("a/prefix/state3.tfstate"),
|
||||
Size: awssdk.Int64(5),
|
||||
}, {
|
||||
Key: awssdk.String("a/prefix/state4.tfstate.backup"),
|
||||
Size: awssdk.Int64(5),
|
||||
},
|
||||
},
|
||||
}, true)
|
||||
return true
|
||||
}),
|
||||
).Return(nil)
|
||||
},
|
||||
want: []string{
|
||||
"bucket-name/a/prefix/b/state1.tfstate",
|
||||
"bucket-name/a/b/state2.tfstate",
|
||||
},
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "test results with glob",
|
||||
config: config.SupplierConfig{
|
||||
|
|
Loading…
Reference in New Issue