Skip to content

Commit 440ba63

Browse files
authored
Merge pull request #18138 from moshevayner/add-linode-provider-draft
feat(vfs): add Linode (Akamai) object storage schema support
2 parents a825f4d + 21de341 commit 440ba63

4 files changed

Lines changed: 109 additions & 0 deletions

File tree

util/pkg/vfs/context.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ func (c *VFSContext) BuildVfsPath(p string) (Path, error) {
185185
return c.buildDOPath(p)
186186
}
187187

188+
if strings.HasPrefix(p, "linode://") {
189+
return c.buildLinodePath(p)
190+
}
191+
188192
if strings.HasPrefix(p, "hos://") {
189193
return c.buildHetznerPath(p)
190194
}
@@ -360,6 +364,24 @@ func (c *VFSContext) buildDOPath(p string) (*S3Path, error) {
360364
return s3path, nil
361365
}
362366

367+
func (c *VFSContext) buildLinodePath(p string) (*S3Path, error) {
368+
u, err := url.Parse(p)
369+
if err != nil {
370+
return nil, fmt.Errorf("invalid Linode object storage path: %q", p)
371+
}
372+
if u.Scheme != "linode" {
373+
return nil, fmt.Errorf("invalid Linode object storage path: %q", p)
374+
}
375+
376+
bucket := strings.TrimSuffix(u.Host, "/")
377+
if bucket == "" {
378+
return nil, fmt.Errorf("invalid Linode object storage path: %q", p)
379+
}
380+
381+
s3path := newS3Path(c.s3Context, u.Scheme, bucket, u.Path, false)
382+
return s3path, nil
383+
}
384+
363385
func (c *VFSContext) buildHetznerPath(p string) (*S3Path, error) {
364386
u, err := url.Parse(p)
365387
if err != nil {

util/pkg/vfs/s3context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ func (s *S3Context) getClient(ctx context.Context, region string, scheme string)
116116
o.BaseEndpoint = aws.String(endpoint)
117117
o.UsePathStyle = true
118118
o.DisableLogOutputChecksumValidationSkipped = true
119+
// Linode (Akamai) requires checksum-when-required behavior
120+
if scheme == "linode" {
121+
o.RequestChecksumCalculation = aws.RequestChecksumCalculationWhenRequired
122+
o.ResponseChecksumValidation = aws.ResponseChecksumValidationWhenRequired
123+
}
119124
} else {
120125
o.EndpointResolverV2 = &ResolverV2{}
121126
}

util/pkg/vfs/s3fs.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,10 @@ func (p *S3Path) IsBucketPublic(ctx context.Context) (bool, error) {
644644
}
645645

646646
func (p *S3Path) IsPublic() (bool, error) {
647+
if p.scheme == "linode" {
648+
// Linode (Akamai) does not implement GetObjectAcl. In that case we conservatively treat the object as non-public and continue.
649+
return false, nil
650+
}
647651
ctx := context.TODO()
648652
client, err := p.client(ctx)
649653
if err != nil {

util/pkg/vfs/s3fs_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ func Test_S3Path_Parse(t *testing.T) {
5151
if err != nil {
5252
t.Fatalf("unexpected error parsing s3 path: %v", err)
5353
}
54+
if s3path.scheme != "s3" {
55+
t.Fatalf("unexpected scheme for s3 path, got %q expected \"s3\": %v", s3path.scheme, s3path)
56+
}
5457
if s3path.bucket != g.ExpectedBucket {
5558
t.Fatalf("unexpected s3 path: %v", s3path)
5659
}
@@ -65,6 +68,81 @@ func Test_S3Path_Parse(t *testing.T) {
6568
}
6669
}
6770

71+
func Test_LinodePath_Parse(t *testing.T) {
72+
grid := []struct {
73+
Input string
74+
ExpectError bool
75+
ExpectedBucket string
76+
ExpectedPath string
77+
}{
78+
{
79+
Input: "linode://bucket",
80+
ExpectedBucket: "bucket",
81+
ExpectedPath: "",
82+
},
83+
{
84+
Input: "linode://bucket/path",
85+
ExpectedBucket: "bucket",
86+
ExpectedPath: "path",
87+
},
88+
{
89+
Input: "linode://bucket2/path/subpath",
90+
ExpectedBucket: "bucket2",
91+
ExpectedPath: "path/subpath",
92+
},
93+
{
94+
Input: "linode:///bucket/path/subpath",
95+
ExpectError: true,
96+
},
97+
}
98+
for _, g := range grid {
99+
s3path, err := Context.buildLinodePath(g.Input)
100+
if !g.ExpectError {
101+
if err != nil {
102+
t.Fatalf("unexpected error parsing linode path: %v", err)
103+
}
104+
if s3path.scheme != "linode" {
105+
t.Fatalf("expected scheme=\"linode\" for linode path, got %q: %v", s3path.scheme, s3path)
106+
}
107+
if s3path.bucket != g.ExpectedBucket {
108+
t.Fatalf("unexpected linode path: %v", s3path)
109+
}
110+
if s3path.key != g.ExpectedPath {
111+
t.Fatalf("unexpected linode path: %v", s3path)
112+
}
113+
} else {
114+
if err == nil {
115+
t.Fatalf("unexpected error parsing %q", g.Input)
116+
}
117+
}
118+
}
119+
}
120+
121+
func Test_NonLinodeObjectStoragePaths_HaveCorrectScheme(t *testing.T) {
122+
grid := []struct {
123+
name string
124+
input string
125+
scheme string
126+
build func(string) (*S3Path, error)
127+
}{
128+
{name: "do", input: "do://bucket/path", scheme: "do", build: Context.buildDOPath},
129+
{name: "hos", input: "hos://bucket/path", scheme: "hos", build: Context.buildHetznerPath},
130+
{name: "scw", input: "scw://bucket/path", scheme: "scw", build: Context.buildSCWPath},
131+
}
132+
133+
for _, tc := range grid {
134+
t.Run(tc.name, func(t *testing.T) {
135+
s3path, err := tc.build(tc.input)
136+
if err != nil {
137+
t.Fatalf("unexpected error parsing %s path: %v", tc.name, err)
138+
}
139+
if s3path.scheme != tc.scheme {
140+
t.Fatalf("unexpected scheme for %s path, got %q expected %q", tc.name, s3path.scheme, tc.scheme)
141+
}
142+
})
143+
}
144+
}
145+
68146
func TestGetHTTPsUrl(t *testing.T) {
69147
grid := []struct {
70148
Path string

0 commit comments

Comments
 (0)