Skip to content

Commit d99ff62

Browse files
authored
Merge pull request #5923 from iamKunal/main
feat(debuginfo-upload): add signed requests support for s3
2 parents e137921 + ce05acf commit d99ff62

3 files changed

Lines changed: 153 additions & 11 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ require (
2828
github.com/improbable-eng/grpc-web v0.15.0
2929
github.com/klauspost/compress v1.18.0
3030
github.com/m1gwings/treedrawer v0.3.3-beta
31+
github.com/minio/minio-go/v7 v7.0.72
3132
github.com/nanmu42/limitio v1.0.0
3233
github.com/oklog/run v1.2.0
3334
github.com/olekukonko/tablewriter v0.0.5
@@ -197,7 +198,6 @@ require (
197198
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
198199
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
199200
github.com/minio/md5-simd v1.1.2 // indirect
200-
github.com/minio/minio-go/v7 v7.0.72 // indirect
201201
github.com/mitchellh/go-homedir v1.1.0 // indirect
202202
github.com/mitchellh/mapstructure v1.5.0 // indirect
203203
github.com/moby/docker-image-spec v1.3.1 // indirect

pkg/signedrequests/client.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type ErrUnsupportedProvider struct {
3232
}
3333

3434
func (e ErrUnsupportedProvider) Error() string {
35-
return "provider not supported (only GCS is currently supported): " + string(e.Provider)
35+
return "provider not supported (only GCS and S3 are currently supported): " + string(e.Provider)
3636
}
3737

3838
type Client interface {
@@ -51,21 +51,27 @@ type Client interface {
5151
}
5252

5353
func NewClient(ctx context.Context, bucketConf *client.BucketConfig) (Client, error) {
54-
if bucketConf.Type != client.GCS {
55-
return nil, ErrUnsupportedProvider{Provider: bucketConf.Type}
56-
}
57-
5854
config, err := yaml.Marshal(bucketConf.Config)
5955
if err != nil {
6056
return nil, fmt.Errorf("failed to marshal bucket config: %w", err)
6157
}
6258

63-
c, err := NewGCSClient(ctx, config)
64-
if err != nil {
65-
return nil, fmt.Errorf("failed to create GCS client: %w", err)
59+
switch bucketConf.Type {
60+
case client.GCS:
61+
c, err := NewGCSClient(ctx, config)
62+
if err != nil {
63+
return nil, fmt.Errorf("failed to create GCS client: %w", err)
64+
}
65+
return NewPrefixedClient(c, bucketConf.Prefix), nil
66+
case client.S3:
67+
c, err := NewS3Client(ctx, config)
68+
if err != nil {
69+
return nil, fmt.Errorf("failed to create S3 client: %w", err)
70+
}
71+
return NewPrefixedClient(c, bucketConf.Prefix), nil
72+
default:
73+
return nil, ErrUnsupportedProvider{Provider: bucketConf.Type}
6674
}
67-
68-
return NewPrefixedClient(c, bucketConf.Prefix), nil
6975
}
7076

7177
func NewPrefixedClient(client Client, prefix string) Client {

pkg/signedrequests/s3.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2022-2025 The Parca Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package signedrequests
15+
16+
import (
17+
"context"
18+
"errors"
19+
"fmt"
20+
"io"
21+
"net/url"
22+
"time"
23+
24+
"github.com/minio/minio-go/v7"
25+
"github.com/minio/minio-go/v7/pkg/credentials"
26+
"github.com/thanos-io/objstore/providers/s3"
27+
"gopkg.in/yaml.v3"
28+
)
29+
30+
type S3Client struct {
31+
minioClient *minio.Client
32+
bucketName string
33+
closer io.Closer
34+
}
35+
36+
func NewS3Client(ctx context.Context, conf []byte) (*S3Client, error) {
37+
var s3c s3.Config
38+
if err := yaml.Unmarshal(conf, &s3c); err != nil {
39+
return nil, err
40+
}
41+
42+
return NewS3BucketWithConfig(ctx, s3c)
43+
}
44+
45+
func NewS3BucketWithConfig(ctx context.Context, s3c s3.Config) (*S3Client, error) {
46+
if s3c.Bucket == "" {
47+
return nil, errors.New("missing S3 bucket name for stored blocks")
48+
}
49+
50+
// Setup credentials
51+
var creds *credentials.Credentials
52+
if s3c.AccessKey != "" && s3c.SecretKey != "" {
53+
creds = credentials.NewStaticV4(s3c.AccessKey, s3c.SecretKey, s3c.SessionToken)
54+
} else {
55+
// Use default credential chain (IAM roles, env vars, etc.)
56+
creds = credentials.NewIAM("")
57+
}
58+
59+
// Setup endpoint
60+
endpoint := s3c.Endpoint
61+
if endpoint == "" {
62+
// Default to AWS S3 endpoint for the region
63+
if s3c.Region != "" {
64+
endpoint = fmt.Sprintf("s3.%s.amazonaws.com", s3c.Region)
65+
} else {
66+
endpoint = "s3.amazonaws.com"
67+
}
68+
}
69+
70+
// Create minio client
71+
minioClient, err := minio.New(endpoint, &minio.Options{
72+
Creds: creds,
73+
Secure: !s3c.Insecure,
74+
Region: s3c.Region,
75+
BucketLookup: s3c.BucketLookupType.MinioType(),
76+
})
77+
if err != nil {
78+
return nil, fmt.Errorf("create minio client: %w", err)
79+
}
80+
81+
return &S3Client{
82+
minioClient: minioClient,
83+
bucketName: s3c.Bucket,
84+
closer: nil, // minio client doesn't need explicit closing
85+
}, nil
86+
}
87+
88+
func (c *S3Client) Close() error {
89+
// AWS session doesn't have a Close method, but we implement it for consistency
90+
// with the Client interface
91+
return nil
92+
}
93+
94+
func (c *S3Client) SignedPUT(
95+
ctx context.Context,
96+
objectKey string,
97+
size int64,
98+
expiry time.Time,
99+
) (string, error) {
100+
duration := time.Until(expiry)
101+
if duration <= 0 {
102+
return "", errors.New("expiry time must be in the future")
103+
}
104+
105+
// Create presigned URL for PUT operation
106+
reqParams := make(url.Values)
107+
reqParams.Set("Content-Length", fmt.Sprintf("%d", size))
108+
109+
presignedURL, err := c.minioClient.PresignedPutObject(ctx, c.bucketName, objectKey, duration)
110+
if err != nil {
111+
return "", fmt.Errorf("failed to generate presigned PUT URL: %w", err)
112+
}
113+
114+
return presignedURL.String(), nil
115+
}
116+
117+
func (c *S3Client) SignedGET(
118+
ctx context.Context,
119+
objectKey string,
120+
expiry time.Time,
121+
) (string, error) {
122+
duration := time.Until(expiry)
123+
if duration <= 0 {
124+
return "", errors.New("expiry time must be in the future")
125+
}
126+
127+
// Create presigned URL for GET operation
128+
reqParams := make(url.Values)
129+
130+
presignedURL, err := c.minioClient.PresignedGetObject(ctx, c.bucketName, objectKey, duration, reqParams)
131+
if err != nil {
132+
return "", fmt.Errorf("failed to generate presigned GET URL: %w", err)
133+
}
134+
135+
return presignedURL.String(), nil
136+
}

0 commit comments

Comments
 (0)