From ccdc63e3b853edcf78df97ff53e39adb9e4c9db1 Mon Sep 17 00:00:00 2001 From: "Eugene R." Date: Fri, 6 Sep 2024 12:40:40 +0300 Subject: [PATCH] feat: add support for AWS S3 connector (#143) --- README.md | 1 + aws/doc.go | 2 + aws/go.mod | 21 +++ aws/go.sum | 24 +++ aws/s3.go | 293 ++++++++++++++++++++++++++++++++ examples/aws/.gitignore | 1 + examples/aws/docker-compose.yml | 33 ++++ examples/aws/s3/main.go | 65 +++++++ examples/go.mod | 20 +++ examples/go.sum | 36 ++++ 10 files changed, 496 insertions(+) create mode 100644 aws/doc.go create mode 100644 aws/go.mod create mode 100644 aws/go.sum create mode 100644 aws/s3.go create mode 100644 examples/aws/.gitignore create mode 100644 examples/aws/docker-compose.yml create mode 100644 examples/aws/s3/main.go diff --git a/README.md b/README.md index e39d91d..340d30b 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Supported Connectors: * [Aerospike](https://www.aerospike.com/) * [Apache Kafka](https://kafka.apache.org/) * [Apache Pulsar](https://pulsar.apache.org/) +* AWS ([S3](https://aws.amazon.com/s3/)) * [NATS](https://nats.io/) * [Redis](https://redis.io/) diff --git a/aws/doc.go b/aws/doc.go new file mode 100644 index 0000000..b38ce65 --- /dev/null +++ b/aws/doc.go @@ -0,0 +1,2 @@ +// Package aws implements streaming connectors for Amazon Web Services. +package aws diff --git a/aws/go.mod b/aws/go.mod new file mode 100644 index 0000000..978292c --- /dev/null +++ b/aws/go.mod @@ -0,0 +1,21 @@ +module github.com/reugn/go-streams/aws + +go 1.21.0 + +require ( + github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 + github.com/reugn/go-streams v0.10.0 +) + +require ( + github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect + github.com/aws/smithy-go v1.20.4 // indirect +) diff --git a/aws/go.sum b/aws/go.sum new file mode 100644 index 0000000..43efde0 --- /dev/null +++ b/aws/go.sum @@ -0,0 +1,24 @@ +github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= +github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19/go.mod h1:Vx+GucNSsdhaxs3aZIKfSUjKVGsxN25nX2SRcdhuw08= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/reugn/go-streams v0.10.0 h1:Y0wHNihEbHsFOFV2/xTOKvud4ZpJPaRTET01fwx2/rQ= +github.com/reugn/go-streams v0.10.0/go.mod h1:QI5XXifJkVJl2jQ6Cra8I9DvWdJTgqcFYR7amvXZ9Lg= diff --git a/aws/s3.go b/aws/s3.go new file mode 100644 index 0000000..ffd3002 --- /dev/null +++ b/aws/s3.go @@ -0,0 +1,293 @@ +package aws + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "log/slog" + "sync" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/reugn/go-streams" + "github.com/reugn/go-streams/flow" +) + +const s3DefaultChunkSize = 5 * 1024 * 1024 // 5 MB + +// S3SourceConfig represents the configuration options for the S3 source +// connector. +type S3SourceConfig struct { + // The name of the S3 bucket to read from. + Bucket string + // The path within the bucket to use. If empty, the root of the + // bucket will be used. + Path string + // The number of concurrent workers to use when reading data from S3. + // The default is 1. + Parallelism int + // The size of chunks in bytes to use when reading data from S3. + // The default is 5 MB. + ChunkSize int +} + +// S3Source represents the AWS S3 source connector. +type S3Source struct { + client *s3.Client + config *S3SourceConfig + objectCh chan string + out chan any + logger *slog.Logger +} + +var _ streams.Source = (*S3Source)(nil) + +// NewS3Source returns a new [S3Source]. +// The connector reads all objects within the configured path and transmits +// them as an [S3Object] through the output channel. +func NewS3Source(ctx context.Context, client *s3.Client, + config *S3SourceConfig, logger *slog.Logger) *S3Source { + + if logger == nil { + logger = slog.Default() + } + logger = logger.With(slog.Group("connector", + slog.String("name", "aws.s3"), + slog.String("type", "source"))) + + if config.Parallelism < 1 { + config.Parallelism = 1 + } + if config.ChunkSize < 1 { + config.ChunkSize = s3DefaultChunkSize + } + + s3Source := &S3Source{ + client: client, + config: config, + objectCh: make(chan string, config.Parallelism), + out: make(chan any), + logger: logger, + } + + // list objects in the configured path + go s3Source.listObjects(ctx) + + // read the objects and send data downstream + go s3Source.getObjects(ctx) + + return s3Source +} + +// listObjects reads the list of objects in the configured path and streams +// the keys to the objectCh channel. +func (s *S3Source) listObjects(ctx context.Context) { + var continuationToken *string + for { + listResponse, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &s.config.Bucket, + Prefix: &s.config.Path, + ContinuationToken: continuationToken, + }) + + if err != nil { + s.logger.Error("Failed to list objects", slog.Any("error", err), + slog.Any("continuationToken", continuationToken)) + break + } + + for _, object := range listResponse.Contents { + s.objectCh <- *object.Key + } + + continuationToken = listResponse.NextContinuationToken + if continuationToken == nil { + break + } + } + // close the objects channel + close(s.objectCh) +} + +// getObjects reads the objects data and sends it downstream. +func (s *S3Source) getObjects(ctx context.Context) { + var wg sync.WaitGroup + for i := 0; i < s.config.Parallelism; i++ { + wg.Add(1) + go func() { + defer wg.Done() + loop: + for { + select { + case key, ok := <-s.objectCh: + if !ok { + break loop + } + objectOutput, err := s.client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &s.config.Bucket, + Key: &key, + }) + if err != nil { + s.logger.Error("Failed to get object", slog.Any("error", err), + slog.String("key", key)) + } + + var data []byte + n, err := bufio.NewReaderSize(objectOutput.Body, s.config.ChunkSize). + Read(data) + if err != nil { + s.logger.Error("Failed to read object", slog.Any("error", err), + slog.String("key", key)) + continue + } + + s.logger.Debug("Successfully read object", slog.String("key", key), + slog.Int("size", n)) + + // send the read data downstream as an S3Object + s.out <- &S3Object{ + Key: key, + Data: bytes.NewReader(data), + } + case <-ctx.Done(): + s.logger.Debug("Object reading finished", slog.Any("error", ctx.Err())) + break loop + } + } + }() + } + + // wait for all object readers to exit + wg.Wait() + s.logger.Info("Closing connector") + // close the output channel + close(s.out) +} + +// Via streams data to a specified operator and returns it. +func (s *S3Source) Via(operator streams.Flow) streams.Flow { + flow.DoStream(s, operator) + return operator +} + +// Out returns the output channel of the S3Source connector. +func (s *S3Source) Out() <-chan any { + return s.out +} + +// S3Object contains details of the S3 object. +type S3Object struct { + // Key is the object name including any subdirectories. + // For example, "directory/file.json". + Key string + // Data is an [io.Reader] representing the binary content of the object. + // This can be a file, a buffer, or any other type that implements the + // io.Reader interface. + Data io.Reader +} + +// S3SinkConfig represents the configuration options for the S3 sink +// connector. +type S3SinkConfig struct { + // The name of the S3 bucket to write to. + Bucket string + // The number of concurrent workers to use when writing data to S3. + // The default is 1. + Parallelism int +} + +// S3Sink represents the AWS S3 sink connector. +type S3Sink struct { + client *s3.Client + config *S3SinkConfig + in chan any + logger *slog.Logger +} + +var _ streams.Sink = (*S3Sink)(nil) + +// NewS3Sink returns a new [S3Sink]. +// Incoming elements are expected to be of the [S3PutObject] type. These will +// be uploaded to the configured bucket using their key field as the path. +func NewS3Sink(ctx context.Context, client *s3.Client, + config *S3SinkConfig, logger *slog.Logger) *S3Sink { + + if logger == nil { + logger = slog.Default() + } + logger = logger.With(slog.Group("connector", + slog.String("name", "aws.s3"), + slog.String("type", "sink"))) + + if config.Parallelism < 1 { + config.Parallelism = 1 + } + + s3Sink := &S3Sink{ + client: client, + config: config, + in: make(chan any, config.Parallelism), + logger: logger, + } + + // start writing incoming data + go s3Sink.writeObjects(ctx) + + return s3Sink +} + +// writeObjects writes incoming stream data elements to S3 using the +// configured parallelism. +func (s *S3Sink) writeObjects(ctx context.Context) { + var wg sync.WaitGroup + for i := 0; i < s.config.Parallelism; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for data := range s.in { + var err error + switch object := data.(type) { + case S3Object: + err = s.writeObject(ctx, &object) + case *S3Object: + err = s.writeObject(ctx, object) + default: + s.logger.Error("Unsupported data type", + slog.String("type", fmt.Sprintf("%T", object))) + } + + if err != nil { + s.logger.Error("Error writing object", + slog.Any("error", err)) + } + } + }() + } + + // wait for all writers to exit + wg.Wait() + s.logger.Info("All object writers exited") +} + +// writeObject writes a single object to S3. +func (s *S3Sink) writeObject(ctx context.Context, putObject *S3Object) error { + putObjectOutput, err := s.client.PutObject(ctx, &s3.PutObjectInput{ + Bucket: &s.config.Bucket, + Key: &putObject.Key, + Body: putObject.Data, + }) + if err != nil { + return fmt.Errorf("failed to put object: %w", err) + } + + s.logger.Debug("Successfully put object", slog.String("key", putObject.Key), + slog.Any("etag", putObjectOutput.ETag)) + + return nil +} + +// In returns the input channel of the S3Sink connector. +func (s *S3Sink) In() chan<- any { + return s.in +} diff --git a/examples/aws/.gitignore b/examples/aws/.gitignore new file mode 100644 index 0000000..69ae309 --- /dev/null +++ b/examples/aws/.gitignore @@ -0,0 +1 @@ +/s3/s3 \ No newline at end of file diff --git a/examples/aws/docker-compose.yml b/examples/aws/docker-compose.yml new file mode 100644 index 0000000..9881446 --- /dev/null +++ b/examples/aws/docker-compose.yml @@ -0,0 +1,33 @@ +services: + minio: + image: minio/minio:latest + container_name: minio-server + ports: + - "9000:9000" + - "9001:9001" + expose: + - "9000" + - "9001" + healthcheck: + test: [ "CMD", "mc", "ready", "local" ] + interval: 10s + timeout: 10s + retries: 3 + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + command: server /data/ --console-address :9001 + + minio-client: + image: minio/mc + container_name: minio-client + depends_on: + minio: + condition: service_healthy + entrypoint: > + /bin/sh -c " + /usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin; + /usr/bin/mc mb myminio/stream-test; + /usr/bin/mc anonymous set public myminio/stream-test; + exit 0; + " diff --git a/examples/aws/s3/main.go b/examples/aws/s3/main.go new file mode 100644 index 0000000..2f5ec8f --- /dev/null +++ b/examples/aws/s3/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/s3" + connector "github.com/reugn/go-streams/aws" + "github.com/reugn/go-streams/flow" +) + +func main() { + ctx := context.Background() + client, err := newS3Client(ctx) + if err != nil { + log.Fatal(err) + } + + sourceConfig := &connector.S3SourceConfig{ + Bucket: "stream-test", + Path: "source", + } + source := connector.NewS3Source(ctx, client, sourceConfig, nil) + + mapObjects := flow.NewMap(transform, 1) + + sinkConfig := &connector.S3SinkConfig{ + Bucket: "stream-test", + } + sink := connector.NewS3Sink(ctx, client, sinkConfig, nil) + + source. + Via(mapObjects). + To(sink) + + time.Sleep(time.Second) +} + +func newS3Client(ctx context.Context) (*s3.Client, error) { + cfg, err := config.LoadDefaultConfig(ctx, + config.WithSharedConfigProfile("minio"), + config.WithRegion("us-east-1"), + ) + + if err != nil { + return nil, fmt.Errorf("unable to load aws config: %w", err) + } + + client := s3.NewFromConfig(cfg, func(o *s3.Options) { + o.BaseEndpoint = aws.String("http://localhost:9000") + o.UsePathStyle = true + }) + + return client, nil +} + +var transform = func(object *connector.S3Object) *connector.S3Object { + object.Key = strings.ReplaceAll(object.Key, "source", "sink") + return object +} diff --git a/examples/go.mod b/examples/go.mod index de0d6b3..b2ac1c2 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -6,12 +6,16 @@ require ( github.com/IBM/sarama v1.43.3 github.com/aerospike/aerospike-client-go/v7 v7.6.1 github.com/apache/pulsar-client-go v0.13.1 + github.com/aws/aws-sdk-go-v2 v1.30.5 + github.com/aws/aws-sdk-go-v2/config v1.27.30 + github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 github.com/gorilla/websocket v1.5.1 github.com/nats-io/nats.go v1.37.0 github.com/nats-io/stan.go v0.10.4 github.com/redis/go-redis/v9 v9.6.1 github.com/reugn/go-streams v0.10.0 github.com/reugn/go-streams/aerospike v0.0.0 + github.com/reugn/go-streams/aws v0.0.0 github.com/reugn/go-streams/kafka v0.0.0 github.com/reugn/go-streams/nats v0.0.0 github.com/reugn/go-streams/pulsar v0.0.0 @@ -26,6 +30,21 @@ require ( github.com/DataDog/zstd v1.5.6 // indirect github.com/ardielle/ardielle-go v1.5.2 // indirect github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.29 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect + github.com/aws/smithy-go v1.20.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -106,6 +125,7 @@ require ( replace ( github.com/reugn/go-streams/aerospike => ../aerospike + github.com/reugn/go-streams/aws => ../aws github.com/reugn/go-streams/kafka => ../kafka github.com/reugn/go-streams/nats => ../nats github.com/reugn/go-streams/pulsar => ../pulsar diff --git a/examples/go.sum b/examples/go.sum index eda8343..85bc65b 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -25,6 +25,42 @@ github.com/ardielle/ardielle-go v1.5.2 h1:TilHTpHIQJ27R1Tl/iITBzMwiUGSlVfiVhwDNG github.com/ardielle/ardielle-go v1.5.2/go.mod h1:I4hy1n795cUhaVt/ojz83SNVCYIGsAFAONtv2Dr7HUI= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= +github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= +github.com/aws/aws-sdk-go-v2/config v1.27.30 h1:AQF3/+rOgeJBQP3iI4vojlPib5X6eeOYoa/af7OxAYg= +github.com/aws/aws-sdk-go-v2/config v1.27.30/go.mod h1:yxqvuubha9Vw8stEgNiStO+yZpP68Wm9hLmcm+R/Qk4= +github.com/aws/aws-sdk-go-v2/credentials v1.17.29 h1:CwGsupsXIlAFYuDVHv1nnK0wnxO0wZ/g1L8DSK/xiIw= +github.com/aws/aws-sdk-go-v2/credentials v1.17.29/go.mod h1:BPJ/yXV92ZVq6G8uYvbU0gSl8q94UB63nMT5ctNO38g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19/go.mod h1:Vx+GucNSsdhaxs3aZIKfSUjKVGsxN25nX2SRcdhuw08= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wINh+4UK+k/0Yo/q8= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=