diff --git a/go.mod b/go.mod index 00124d209..b322f68d5 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/superplanehq/superplane -go 1.25 +go 1.25.0 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 - github.com/aws/aws-sdk-go-v2 v1.41.1 + github.com/aws/aws-sdk-go-v2 v1.41.5 github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 github.com/casbin/casbin/v2 v2.134.0 github.com/casbin/gorm-adapter/v3 v3.37.0 @@ -20,7 +20,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/markbates/goth v1.81.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.4.3 @@ -36,10 +36,10 @@ require ( go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.63.0 go.opentelemetry.io/otel/metric v1.40.0 go.opentelemetry.io/otel/trace v1.40.0 - golang.org/x/oauth2 v0.35.0 - golang.org/x/sync v0.19.0 - google.golang.org/api v0.266.0 - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 + golang.org/x/oauth2 v0.36.0 + golang.org/x/sync v0.20.0 + google.golang.org/api v0.271.0 + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 google.golang.org/grpc v1.79.3 google.golang.org/protobuf v1.36.11 gopkg.in/dnaeon/go-vcr.v2 v2.3.0 @@ -49,25 +49,60 @@ require ( ) require ( - cloud.google.com/go/auth v0.18.1 // indirect + cel.dev/expr v0.25.1 // indirect + cloud.google.com/go v0.123.0 // indirect + cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/iam v1.5.3 // indirect + cloud.google.com/go/monitoring v1.24.3 // indirect + cloud.google.com/go/storage v1.61.3 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect - github.com/aws/smithy-go v1.24.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.13 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.13 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.14 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.18 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect + github.com/aws/smithy-go v1.24.2 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect ) require ( @@ -75,7 +110,7 @@ require ( filippo.io/edwards25519 v1.1.1 // indirect github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/casbin/govaluate v1.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deckarep/golang-set/v2 v2.7.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect @@ -105,10 +140,10 @@ require ( github.com/microsoft/go-mssqldb v1.7.2 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rabbitmq/amqp091-go v1.9.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect - github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.6 // indirect @@ -116,11 +151,11 @@ require ( go.opentelemetry.io/otel v1.40.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 go.opentelemetry.io/otel/sdk/metric v1.40.0 - golang.org/x/crypto v0.47.0 - golang.org/x/net v0.49.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect + golang.org/x/crypto v0.48.0 + golang.org/x/net v0.51.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 7a0adcfd0..287d2f13a 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,11 @@ +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,6 +18,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -27,8 +31,12 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs= cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA= +cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= +cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -42,6 +50,10 @@ cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCB cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -51,6 +63,9 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg= +cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw= filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= @@ -78,6 +93,12 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -91,8 +112,46 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= +github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +github.com/aws/aws-sdk-go-v2/config v1.32.13 h1:5KgbxMaS2coSWRrx9TX/QtWbqzgQkOdEa3sZPhBhCSg= +github.com/aws/aws-sdk-go-v2/config v1.32.13/go.mod h1:8zz7wedqtCbw5e9Mi2doEwDyEgHcEE9YOJp6a8jdSMY= +github.com/aws/aws-sdk-go-v2/credentials v1.19.13 h1:mA59E3fokBvyEGHKFdnpNNrvaR351cqiHgRg+JzOSRI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.13/go.mod h1:yoTXOQKea18nrM69wGF9jBdG4WocSZA1h38A+t/MAsk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 h1:NUS3K4BTDArQqNu2ih7yeDLaS3bmHD0YndtA6UP884g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 h1:QKZH0S178gCmFEgst8hN0mCX1KxLgHBKKY/CLqwP8lg= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.14 h1:GcLE9ba5ehAQma6wlopUesYg/hbcOhFNWTjELkiWkh4= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.14/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.18 h1:mP49nTpfKtpXLt5SLn8Uv8z6W+03jYVoOSAl/c02nog= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.18/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBUdErbMnAFFp12Lm/U= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -132,12 +191,16 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.7.0 h1:gIloKvD7yH2oip4VLhsv3JyLLFnC0Y2mlusgcvJYW5k= github.com/deckarep/golang-set/v2 v2.7.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= @@ -153,8 +216,13 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8= github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -181,6 +249,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -281,6 +351,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -298,12 +369,15 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= +github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= +github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -317,6 +391,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1/go.mod h1:qOchhhIlmRcqk/O github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -457,10 +533,15 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/playwright-community/playwright-go v0.5200.1 h1:Sm2oOuhqt0M5Y4kUi/Qh9w4cyyi3ZIWTBeGKImc2UVo= github.com/playwright-community/playwright-go v0.5200.1/go.mod h1:UnnyQZaqUOO5ywAZu60+N4EiWReUqX1MQBBA3Oofvf8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -502,6 +583,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= @@ -514,6 +597,8 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -551,8 +636,12 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.63.0 h1:rATLgFjv0P9qyXQR/aChJ6JVbMtXOQjt49GgT36cBbk= go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.63.0/go.mod h1:34csimR1lUhdT5HH4Rii9aKPrvBcnFRwxLwcevsU+Kk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= @@ -572,6 +661,8 @@ go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTq go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -587,8 +678,10 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= @@ -599,6 +692,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -671,6 +766,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= @@ -678,6 +774,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -690,6 +787,8 @@ golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -709,6 +808,8 @@ golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -724,6 +825,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -768,6 +871,7 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -775,6 +879,7 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -806,6 +911,8 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -837,9 +944,13 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -888,6 +999,7 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -936,6 +1048,8 @@ google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3h google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.266.0 h1:hco+oNCf9y7DmLeAtHJi/uBAY7n/7XC9mZPxu1ROiyk= google.golang.org/api v0.266.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= +google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY= +google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -978,7 +1092,9 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1010,8 +1126,12 @@ google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH8 google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/pkg/blobs/config.go b/pkg/blobs/config.go new file mode 100644 index 000000000..d476e93b9 --- /dev/null +++ b/pkg/blobs/config.go @@ -0,0 +1,118 @@ +package blobs + +import ( + "context" + "fmt" + "os" + + gcsstorage "cloud.google.com/go/storage" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/s3" + "google.golang.org/api/option" +) + +const ( + EnvBackend = "BLOB_STORAGE_BACKEND" + + EnvFilesystemPath = "BLOB_STORAGE_FILESYSTEM_PATH" + + EnvS3Bucket = "BLOB_STORAGE_S3_BUCKET" + EnvS3Region = "BLOB_STORAGE_S3_REGION" + EnvS3Endpoint = "BLOB_STORAGE_S3_ENDPOINT" + EnvS3AccessKey = "BLOB_STORAGE_S3_ACCESS_KEY" + EnvS3SecretKey = "BLOB_STORAGE_S3_SECRET_KEY" + + EnvGCSBucket = "BLOB_STORAGE_GCS_BUCKET" + EnvGCSCredentialsFile = "BLOB_STORAGE_GCS_CREDENTIALS_FILE" +) + +func NewFromEnv() (Storage, error) { + backend := os.Getenv(EnvBackend) + if backend == "" { + backend = BackendMemory + } + + switch backend { + case BackendMemory: + return NewMemoryStorage(), nil + + case BackendFilesystem: + basePath := os.Getenv(EnvFilesystemPath) + if basePath == "" { + return nil, fmt.Errorf("%s is required when backend is %s", EnvFilesystemPath, BackendFilesystem) + } + return NewFilesystemStorage(basePath), nil + + case BackendS3: + return newS3StorageFromEnv() + + case BackendGCS: + return newGCSStorageFromEnv() + + default: + return nil, fmt.Errorf("unknown blob storage backend: %q", backend) + } +} + +func newS3StorageFromEnv() (Storage, error) { + bucket := os.Getenv(EnvS3Bucket) + if bucket == "" { + return nil, fmt.Errorf("%s is required when backend is %s", EnvS3Bucket, BackendS3) + } + + region := os.Getenv(EnvS3Region) + if region == "" { + region = "us-east-1" + } + + ctx := context.Background() + opts := []func(*awsconfig.LoadOptions) error{ + awsconfig.WithRegion(region), + } + + accessKey := os.Getenv(EnvS3AccessKey) + secretKey := os.Getenv(EnvS3SecretKey) + if accessKey != "" && secretKey != "" { + opts = append(opts, awsconfig.WithCredentialsProvider( + credentials.NewStaticCredentialsProvider(accessKey, secretKey, ""), + )) + } + + cfg, err := awsconfig.LoadDefaultConfig(ctx, opts...) + if err != nil { + return nil, fmt.Errorf("failed to load AWS config: %w", err) + } + + var s3Opts []func(*s3.Options) + if endpoint := os.Getenv(EnvS3Endpoint); endpoint != "" { + s3Opts = append(s3Opts, func(o *s3.Options) { + o.BaseEndpoint = &endpoint + o.UsePathStyle = true + }) + } + + client := s3.NewFromConfig(cfg, s3Opts...) + return NewS3Storage(bucket, client), nil +} + +func newGCSStorageFromEnv() (Storage, error) { + bucket := os.Getenv(EnvGCSBucket) + if bucket == "" { + return nil, fmt.Errorf("%s is required when backend is %s", EnvGCSBucket, BackendGCS) + } + + ctx := context.Background() + var opts []option.ClientOption + + if credFile := os.Getenv(EnvGCSCredentialsFile); credFile != "" { + opts = append(opts, option.WithAuthCredentialsFile(option.ServiceAccount, credFile)) + } + + client, err := gcsstorage.NewClient(ctx, opts...) + if err != nil { + return nil, fmt.Errorf("failed to create GCS client: %w", err) + } + + return NewGCSStorage(bucket, client), nil +} diff --git a/pkg/blobs/errors.go b/pkg/blobs/errors.go new file mode 100644 index 000000000..8b6526aa2 --- /dev/null +++ b/pkg/blobs/errors.go @@ -0,0 +1,10 @@ +package blobs + +import "errors" + +var ( + ErrBlobNotFound = errors.New("blob not found") + ErrPresignedURLNotSupported = errors.New("presigned URLs are not supported by this backend") + ErrInvalidScope = errors.New("invalid blob scope") + ErrInvalidBlobPath = errors.New("invalid blob path") +) diff --git a/pkg/blobs/filesystem.go b/pkg/blobs/filesystem.go new file mode 100644 index 000000000..1fe3ce83d --- /dev/null +++ b/pkg/blobs/filesystem.go @@ -0,0 +1,195 @@ +package blobs + +import ( + "context" + "errors" + "io" + "os" + "path/filepath" + "sort" + "strings" + "time" +) + +type FilesystemStorage struct { + basePath string +} + +func NewFilesystemStorage(basePath string) *FilesystemStorage { + return &FilesystemStorage{basePath: basePath} +} + +func (s *FilesystemStorage) Put(_ context.Context, scope Scope, blobPath string, body io.Reader, _ PutOptions) error { + key, err := objectKey(scope, blobPath) + if err != nil { + return err + } + + fullPath, err := s.resolvePath(key) + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + return err + } + + file, err := os.Create(fullPath) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(file, body) + return err +} + +func (s *FilesystemStorage) Get(_ context.Context, scope Scope, blobPath string) (io.ReadCloser, error) { + key, err := objectKey(scope, blobPath) + if err != nil { + return nil, err + } + + fullPath, err := s.resolvePath(key) + if err != nil { + return nil, err + } + + file, err := os.Open(fullPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, ErrBlobNotFound + } + return nil, err + } + + return file, nil +} + +func (s *FilesystemStorage) Delete(_ context.Context, scope Scope, blobPath string) error { + key, err := objectKey(scope, blobPath) + if err != nil { + return err + } + + fullPath, err := s.resolvePath(key) + if err != nil { + return err + } + + if err := os.Remove(fullPath); err != nil { + if errors.Is(err, os.ErrNotExist) { + return ErrBlobNotFound + } + return err + } + + return nil +} + +func (s *FilesystemStorage) List(_ context.Context, scope Scope, input ListInput) (*ListOutput, error) { + prefix, err := scopePrefix(scope) + if err != nil { + return nil, err + } + + baseDir, err := s.resolvePath(prefix) + if err != nil { + return &ListOutput{}, nil + } + + var keys []string + _ = filepath.Walk(baseDir, func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return nil + } + if info.IsDir() { + return nil + } + + rel, relErr := filepath.Rel(filepath.Clean(s.basePath), path) + if relErr != nil { + return nil + } + + keys = append(keys, filepath.ToSlash(rel)) + return nil + }) + + sort.Strings(keys) + + startIdx := 0 + if input.ContinuationToken != "" { + for i, k := range keys { + if k > input.ContinuationToken { + startIdx = i + break + } + if i == len(keys)-1 { + return &ListOutput{}, nil + } + } + } + + maxResults := input.MaxResults + if maxResults <= 0 { + maxResults = 100 + } + + endIdx := startIdx + maxResults + if endIdx > len(keys) { + endIdx = len(keys) + } + + blobs := make([]BlobInfo, 0, endIdx-startIdx) + for _, k := range keys[startIdx:endIdx] { + resolved, resolveErr := s.resolvePath(k) + if resolveErr != nil { + continue + } + + info, statErr := os.Stat(resolved) + if statErr != nil { + continue + } + + blobs = append(blobs, BlobInfo{ + Path: strings.TrimPrefix(k, prefix), + Size: info.Size(), + UpdatedAt: info.ModTime(), + }) + } + + var nextToken string + if endIdx < len(keys) { + nextToken = keys[endIdx-1] + } + + return &ListOutput{ + Blobs: blobs, + NextToken: nextToken, + }, nil +} + +func (s *FilesystemStorage) PresignPut(_ context.Context, _ Scope, _ string, _ PutOptions, _ time.Duration) (*PresignedURL, error) { + return nil, ErrPresignedURLNotSupported +} + +func (s *FilesystemStorage) PresignGet(_ context.Context, _ Scope, _ string, _ time.Duration) (*PresignedURL, error) { + return nil, ErrPresignedURLNotSupported +} + +func (s *FilesystemStorage) resolvePath(key string) (string, error) { + base := filepath.Clean(s.basePath) + fullPath := filepath.Clean(filepath.Join(base, key)) + + rel, err := filepath.Rel(base, fullPath) + if err != nil { + return "", err + } + if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return "", errors.New("blob key points outside base path") + } + + return fullPath, nil +} diff --git a/pkg/blobs/gcs.go b/pkg/blobs/gcs.go new file mode 100644 index 000000000..9c725fe74 --- /dev/null +++ b/pkg/blobs/gcs.go @@ -0,0 +1,173 @@ +package blobs + +import ( + "context" + "errors" + "io" + "strings" + "time" + + gcsstorage "cloud.google.com/go/storage" + "google.golang.org/api/iterator" +) + +type GCSStorage struct { + bucket string + client *gcsstorage.Client +} + +func NewGCSStorage(bucket string, client *gcsstorage.Client) *GCSStorage { + return &GCSStorage{ + bucket: bucket, + client: client, + } +} + +func (s *GCSStorage) Put(ctx context.Context, scope Scope, path string, body io.Reader, opts PutOptions) error { + key, err := objectKey(scope, path) + if err != nil { + return err + } + obj := s.client.Bucket(s.bucket).Object(key) + writer := obj.NewWriter(ctx) + + if opts.ContentType != "" { + writer.ContentType = opts.ContentType + } + + if _, err := io.Copy(writer, body); err != nil { + writer.Close() + return err + } + + return writer.Close() +} + +func (s *GCSStorage) Get(ctx context.Context, scope Scope, path string) (io.ReadCloser, error) { + key, err := objectKey(scope, path) + if err != nil { + return nil, err + } + obj := s.client.Bucket(s.bucket).Object(key) + + reader, err := obj.NewReader(ctx) + if err != nil { + if errors.Is(err, gcsstorage.ErrObjectNotExist) { + return nil, ErrBlobNotFound + } + return nil, err + } + + return reader, nil +} + +func (s *GCSStorage) Delete(ctx context.Context, scope Scope, path string) error { + key, err := objectKey(scope, path) + if err != nil { + return err + } + obj := s.client.Bucket(s.bucket).Object(key) + + if err := obj.Delete(ctx); err != nil { + if errors.Is(err, gcsstorage.ErrObjectNotExist) { + return ErrBlobNotFound + } + return err + } + + return nil +} + +func (s *GCSStorage) List(ctx context.Context, scope Scope, input ListInput) (*ListOutput, error) { + prefix, err := scopePrefix(scope) + if err != nil { + return nil, err + } + + maxResults := 100 + if input.MaxResults > 0 && input.MaxResults <= 1000 { + maxResults = input.MaxResults + } + + query := &gcsstorage.Query{Prefix: prefix} + it := s.client.Bucket(s.bucket).Objects(ctx, query) + + if input.ContinuationToken != "" { + it.PageInfo().Token = input.ContinuationToken + } + + it.PageInfo().MaxSize = maxResults + + var blobs []BlobInfo + for { + attrs, err := it.Next() + if errors.Is(err, iterator.Done) { + break + } + if err != nil { + return nil, err + } + + blobs = append(blobs, BlobInfo{ + Path: strings.TrimPrefix(attrs.Name, prefix), + Size: attrs.Size, + ContentType: attrs.ContentType, + UpdatedAt: attrs.Updated, + }) + + if len(blobs) >= maxResults { + break + } + } + + return &ListOutput{ + Blobs: blobs, + NextToken: it.PageInfo().Token, + }, nil +} + +func (s *GCSStorage) PresignPut(ctx context.Context, scope Scope, path string, opts PutOptions, expiry time.Duration) (*PresignedURL, error) { + key, err := objectKey(scope, path) + if err != nil { + return nil, err + } + + signOpts := &gcsstorage.SignedURLOptions{ + Method: "PUT", + Expires: time.Now().Add(expiry), + } + + if opts.ContentType != "" { + signOpts.ContentType = opts.ContentType + } + + url, err := s.client.Bucket(s.bucket).SignedURL(key, signOpts) + if err != nil { + return nil, err + } + + return &PresignedURL{ + URL: url, + ExpiresAt: time.Now().Add(expiry), + }, nil +} + +func (s *GCSStorage) PresignGet(_ context.Context, scope Scope, path string, expiry time.Duration) (*PresignedURL, error) { + key, err := objectKey(scope, path) + if err != nil { + return nil, err + } + + url, err := s.client.Bucket(s.bucket).SignedURL(key, &gcsstorage.SignedURLOptions{ + Method: "GET", + Expires: time.Now().Add(expiry), + }) + if err != nil { + return nil, err + } + + return &PresignedURL{ + URL: url, + ExpiresAt: time.Now().Add(expiry), + }, nil +} diff --git a/pkg/blobs/keys.go b/pkg/blobs/keys.go new file mode 100644 index 000000000..1fc9da0c8 --- /dev/null +++ b/pkg/blobs/keys.go @@ -0,0 +1,88 @@ +package blobs + +import ( + "fmt" + "path" + "strings" +) + +func objectKey(scope Scope, blobPath string) (string, error) { + if err := validateScope(scope); err != nil { + return "", err + } + + cleaned, err := cleanPath(blobPath) + if err != nil { + return "", err + } + + switch scope.Type { + case ScopeOrganization: + return fmt.Sprintf("blobs/organization/%s/%s", scope.OrganizationID, cleaned), nil + case ScopeCanvas: + return fmt.Sprintf("blobs/canvas/%s/%s", scope.CanvasID, cleaned), nil + case ScopeNode: + return fmt.Sprintf("blobs/node/%s/%s/%s", scope.CanvasID, scope.NodeID, cleaned), nil + case ScopeExecution: + return fmt.Sprintf("blobs/execution/%s/%s", scope.ExecutionID, cleaned), nil + default: + return "", ErrInvalidScope + } +} + +func scopePrefix(scope Scope) (string, error) { + if err := validateScope(scope); err != nil { + return "", err + } + + switch scope.Type { + case ScopeOrganization: + return fmt.Sprintf("blobs/organization/%s/", scope.OrganizationID), nil + case ScopeCanvas: + return fmt.Sprintf("blobs/canvas/%s/", scope.CanvasID), nil + case ScopeNode: + return fmt.Sprintf("blobs/node/%s/%s/", scope.CanvasID, scope.NodeID), nil + case ScopeExecution: + return fmt.Sprintf("blobs/execution/%s/", scope.ExecutionID), nil + default: + return "", ErrInvalidScope + } +} + +func cleanPath(p string) (string, error) { + p = strings.TrimSpace(p) + p = strings.TrimPrefix(p, "/") + p = path.Clean(p) + if p == "." { + return "", nil + } + if p == ".." || strings.HasPrefix(p, "../") || strings.Contains(p, "/../") || strings.HasSuffix(p, "/..") { + return "", ErrInvalidBlobPath + } + return p, nil +} + +func validateScope(scope Scope) error { + switch scope.Type { + case ScopeOrganization: + if strings.TrimSpace(scope.OrganizationID) == "" { + return fmt.Errorf("%w: organization scope requires organization_id", ErrInvalidScope) + } + case ScopeCanvas: + if strings.TrimSpace(scope.CanvasID) == "" { + return fmt.Errorf("%w: canvas scope requires canvas_id", ErrInvalidScope) + } + case ScopeNode: + if strings.TrimSpace(scope.CanvasID) == "" || strings.TrimSpace(scope.NodeID) == "" { + return fmt.Errorf("%w: node scope requires canvas_id and node_id", ErrInvalidScope) + } + case ScopeExecution: + if strings.TrimSpace(scope.ExecutionID) == "" { + return fmt.Errorf("%w: execution scope requires execution_id", ErrInvalidScope) + } + default: + return fmt.Errorf("%w: unknown scope type %q", ErrInvalidScope, scope.Type) + } + + return nil +} diff --git a/pkg/blobs/memory.go b/pkg/blobs/memory.go new file mode 100644 index 000000000..eac14810f --- /dev/null +++ b/pkg/blobs/memory.go @@ -0,0 +1,156 @@ +package blobs + +import ( + "bytes" + "context" + "io" + "sort" + "strings" + "sync" + "time" +) + +type memoryBlob struct { + data []byte + contentType string + updatedAt time.Time +} + +type MemoryStorage struct { + mu sync.RWMutex + blobs map[string]memoryBlob +} + +func NewMemoryStorage() *MemoryStorage { + return &MemoryStorage{ + blobs: make(map[string]memoryBlob), + } +} + +func (s *MemoryStorage) Put(_ context.Context, scope Scope, path string, body io.Reader, opts PutOptions) error { + key, err := objectKey(scope, path) + if err != nil { + return err + } + + data, err := io.ReadAll(body) + if err != nil { + return err + } + + s.mu.Lock() + s.blobs[key] = memoryBlob{ + data: data, + contentType: opts.ContentType, + updatedAt: time.Now(), + } + s.mu.Unlock() + + return nil +} + +func (s *MemoryStorage) Get(_ context.Context, scope Scope, path string) (io.ReadCloser, error) { + key, err := objectKey(scope, path) + if err != nil { + return nil, err + } + + s.mu.RLock() + blob, ok := s.blobs[key] + s.mu.RUnlock() + + if !ok { + return nil, ErrBlobNotFound + } + + return io.NopCloser(bytes.NewReader(blob.data)), nil +} + +func (s *MemoryStorage) Delete(_ context.Context, scope Scope, path string) error { + key, err := objectKey(scope, path) + if err != nil { + return err + } + + s.mu.Lock() + defer s.mu.Unlock() + + if _, ok := s.blobs[key]; !ok { + return ErrBlobNotFound + } + + delete(s.blobs, key) + return nil +} + +func (s *MemoryStorage) List(_ context.Context, scope Scope, input ListInput) (*ListOutput, error) { + prefix, err := scopePrefix(scope) + if err != nil { + return nil, err + } + + s.mu.RLock() + var keys []string + for k := range s.blobs { + if strings.HasPrefix(k, prefix) { + keys = append(keys, k) + } + } + s.mu.RUnlock() + + sort.Strings(keys) + + startIdx := 0 + if input.ContinuationToken != "" { + for i, k := range keys { + if k > input.ContinuationToken { + startIdx = i + break + } + if i == len(keys)-1 { + return &ListOutput{}, nil + } + } + } + + maxResults := input.MaxResults + if maxResults <= 0 { + maxResults = 100 + } + + endIdx := startIdx + maxResults + if endIdx > len(keys) { + endIdx = len(keys) + } + + s.mu.RLock() + blobs := make([]BlobInfo, 0, endIdx-startIdx) + for _, k := range keys[startIdx:endIdx] { + blob := s.blobs[k] + blobs = append(blobs, BlobInfo{ + Path: strings.TrimPrefix(k, prefix), + Size: int64(len(blob.data)), + ContentType: blob.contentType, + UpdatedAt: blob.updatedAt, + }) + } + s.mu.RUnlock() + + var nextToken string + if endIdx < len(keys) { + nextToken = keys[endIdx-1] + } + + return &ListOutput{ + Blobs: blobs, + NextToken: nextToken, + }, nil +} + +func (s *MemoryStorage) PresignPut(_ context.Context, _ Scope, _ string, _ PutOptions, _ time.Duration) (*PresignedURL, error) { + return nil, ErrPresignedURLNotSupported +} + +func (s *MemoryStorage) PresignGet(_ context.Context, _ Scope, _ string, _ time.Duration) (*PresignedURL, error) { + return nil, ErrPresignedURLNotSupported +} diff --git a/pkg/blobs/s3.go b/pkg/blobs/s3.go new file mode 100644 index 000000000..0dca7c6cb --- /dev/null +++ b/pkg/blobs/s3.go @@ -0,0 +1,205 @@ +package blobs + +import ( + "context" + "errors" + "io" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/aws/smithy-go" +) + +type S3Storage struct { + bucket string + client *s3.Client + presignClient *s3.PresignClient +} + +func NewS3Storage(bucket string, client *s3.Client) *S3Storage { + return &S3Storage{ + bucket: bucket, + client: client, + presignClient: s3.NewPresignClient(client), + } +} + +func (s *S3Storage) Put(ctx context.Context, scope Scope, path string, body io.Reader, opts PutOptions) error { + key, err := objectKey(scope, path) + if err != nil { + return err + } + + input := &s3.PutObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(key), + Body: body, + } + + if opts.ContentType != "" { + input.ContentType = aws.String(opts.ContentType) + } + + _, err = s.client.PutObject(ctx, input) + return err +} + +func (s *S3Storage) Get(ctx context.Context, scope Scope, path string) (io.ReadCloser, error) { + key, err := objectKey(scope, path) + if err != nil { + return nil, err + } + + output, err := s.client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(key), + }) + if err != nil { + if isS3NotFound(err) { + return nil, ErrBlobNotFound + } + return nil, err + } + + return output.Body, nil +} + +func (s *S3Storage) Delete(ctx context.Context, scope Scope, path string) error { + key, err := objectKey(scope, path) + if err != nil { + return err + } + + _, err = s.client.HeadObject(ctx, &s3.HeadObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(key), + }) + if err != nil { + if isS3NotFound(err) { + return ErrBlobNotFound + } + return err + } + + _, err = s.client.DeleteObject(ctx, &s3.DeleteObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(key), + }) + if err != nil { + if isS3NotFound(err) { + return ErrBlobNotFound + } + return err + } + + return nil +} + +func (s *S3Storage) List(ctx context.Context, scope Scope, input ListInput) (*ListOutput, error) { + prefix, err := scopePrefix(scope) + if err != nil { + return nil, err + } + + maxKeys := int32(100) + if input.MaxResults > 0 && input.MaxResults <= 1000 { + maxKeys = int32(input.MaxResults) + } + + listInput := &s3.ListObjectsV2Input{ + Bucket: aws.String(s.bucket), + Prefix: aws.String(prefix), + MaxKeys: aws.Int32(maxKeys), + } + + if input.ContinuationToken != "" { + listInput.ContinuationToken = aws.String(input.ContinuationToken) + } + + output, err := s.client.ListObjectsV2(ctx, listInput) + if err != nil { + return nil, err + } + + blobs := make([]BlobInfo, 0, len(output.Contents)) + for _, obj := range output.Contents { + blobs = append(blobs, BlobInfo{ + Path: strings.TrimPrefix(aws.ToString(obj.Key), prefix), + Size: aws.ToInt64(obj.Size), + UpdatedAt: aws.ToTime(obj.LastModified), + }) + } + + var nextToken string + if output.NextContinuationToken != nil { + nextToken = *output.NextContinuationToken + } + + return &ListOutput{ + Blobs: blobs, + NextToken: nextToken, + }, nil +} + +func (s *S3Storage) PresignPut(ctx context.Context, scope Scope, path string, opts PutOptions, expiry time.Duration) (*PresignedURL, error) { + key, err := objectKey(scope, path) + if err != nil { + return nil, err + } + + input := &s3.PutObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(key), + } + + if opts.ContentType != "" { + input.ContentType = aws.String(opts.ContentType) + } + + result, err := s.presignClient.PresignPutObject(ctx, input, s3.WithPresignExpires(expiry)) + if err != nil { + return nil, err + } + + return &PresignedURL{ + URL: result.URL, + ExpiresAt: time.Now().Add(expiry), + }, nil +} + +func (s *S3Storage) PresignGet(ctx context.Context, scope Scope, path string, expiry time.Duration) (*PresignedURL, error) { + key, err := objectKey(scope, path) + if err != nil { + return nil, err + } + + result, err := s.presignClient.PresignGetObject(ctx, &s3.GetObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(key), + }, s3.WithPresignExpires(expiry)) + if err != nil { + return nil, err + } + + return &PresignedURL{ + URL: result.URL, + ExpiresAt: time.Now().Add(expiry), + }, nil +} + +func isS3NotFound(err error) bool { + var notFound *types.NoSuchKey + if errors.As(err, ¬Found) { + return true + } + + var apiErr smithy.APIError + if errors.As(err, &apiErr) { + return apiErr.ErrorCode() == "NotFound" || apiErr.ErrorCode() == "NoSuchKey" + } + + return false +} diff --git a/pkg/blobs/storage.go b/pkg/blobs/storage.go new file mode 100644 index 000000000..2d13a58a5 --- /dev/null +++ b/pkg/blobs/storage.go @@ -0,0 +1,69 @@ +package blobs + +import ( + "context" + "io" + "time" +) + +const ( + BackendMemory = "memory" + BackendFilesystem = "filesystem" + BackendS3 = "s3" + BackendGCS = "gcs" +) + +type ScopeType string + +const ( + ScopeOrganization ScopeType = "organization" + ScopeCanvas ScopeType = "canvas" + ScopeNode ScopeType = "node" + ScopeExecution ScopeType = "execution" +) + +type Scope struct { + Type ScopeType + OrganizationID string + CanvasID string + NodeID string + ExecutionID string +} + +type PutOptions struct { + ContentType string +} + +type BlobInfo struct { + Path string + Size int64 + ContentType string + UpdatedAt time.Time +} + +type ListInput struct { + MaxResults int + // ContinuationToken is opaque and backend-specific. + // Callers should only pass tokens previously returned by List. + ContinuationToken string +} + +type ListOutput struct { + Blobs []BlobInfo + // NextToken is opaque and backend-specific. + NextToken string +} + +type PresignedURL struct { + URL string + ExpiresAt time.Time +} + +type Storage interface { + Put(ctx context.Context, scope Scope, path string, body io.Reader, opts PutOptions) error + Get(ctx context.Context, scope Scope, path string) (io.ReadCloser, error) + Delete(ctx context.Context, scope Scope, path string) error + List(ctx context.Context, scope Scope, input ListInput) (*ListOutput, error) + PresignPut(ctx context.Context, scope Scope, path string, opts PutOptions, expiry time.Duration) (*PresignedURL, error) + PresignGet(ctx context.Context, scope Scope, path string, expiry time.Duration) (*PresignedURL, error) +} diff --git a/pkg/blobs/storage_test.go b/pkg/blobs/storage_test.go new file mode 100644 index 000000000..df8c2a5a1 --- /dev/null +++ b/pkg/blobs/storage_test.go @@ -0,0 +1,351 @@ +package blobs + +import ( + "context" + "errors" + "io" + "os" + "strings" + "testing" +) + +func TestMemoryStorageRoundTrip(t *testing.T) { + t.Parallel() + runRoundTripTest(t, NewMemoryStorage()) +} + +func TestFilesystemStorageRoundTrip(t *testing.T) { + t.Parallel() + runRoundTripTest(t, NewFilesystemStorage(t.TempDir())) +} + +func TestMemoryStorageList(t *testing.T) { + t.Parallel() + runListTest(t, NewMemoryStorage()) +} + +func TestFilesystemStorageList(t *testing.T) { + t.Parallel() + runListTest(t, NewFilesystemStorage(t.TempDir())) +} + +func TestMemoryStorageListPagination(t *testing.T) { + t.Parallel() + runListPaginationTest(t, NewMemoryStorage()) +} + +func TestFilesystemStorageListPagination(t *testing.T) { + t.Parallel() + runListPaginationTest(t, NewFilesystemStorage(t.TempDir())) +} + +func TestMemoryStorageScopeIsolation(t *testing.T) { + t.Parallel() + runScopeIsolationTest(t, NewMemoryStorage()) +} + +func TestFilesystemStorageScopeIsolation(t *testing.T) { + t.Parallel() + runScopeIsolationTest(t, NewFilesystemStorage(t.TempDir())) +} + +func TestMemoryStoragePresignReturnsNotSupported(t *testing.T) { + t.Parallel() + runPresignNotSupportedTest(t, NewMemoryStorage()) +} + +func TestFilesystemStoragePresignReturnsNotSupported(t *testing.T) { + t.Parallel() + runPresignNotSupportedTest(t, NewFilesystemStorage(t.TempDir())) +} + +func TestFilesystemStorageRejectsPathTraversal(t *testing.T) { + t.Parallel() + store := NewFilesystemStorage(t.TempDir()) + scope := Scope{Type: ScopeOrganization, OrganizationID: "org-1"} + + err := store.Put(context.Background(), scope, "../../../etc/passwd", strings.NewReader("bad"), PutOptions{}) + if err == nil { + t.Fatal("expected error for path traversal, got nil") + } +} + +func TestObjectKeyRejectsUnknownScope(t *testing.T) { + t.Parallel() + + _, err := objectKey(Scope{Type: ScopeType("invalid")}, "file.txt") + if !errors.Is(err, ErrInvalidScope) { + t.Fatalf("expected ErrInvalidScope for unknown scope type, got %v", err) + } +} + +func TestObjectKeyRejectsMissingScopeIDs(t *testing.T) { + t.Parallel() + + cases := []Scope{ + {Type: ScopeOrganization}, + {Type: ScopeCanvas}, + {Type: ScopeNode, CanvasID: "canvas-only"}, + {Type: ScopeNode, NodeID: "node-only"}, + {Type: ScopeExecution}, + } + + for _, scope := range cases { + _, err := objectKey(scope, "file.txt") + if !errors.Is(err, ErrInvalidScope) { + t.Fatalf("expected ErrInvalidScope for scope %+v, got %v", scope, err) + } + } +} + +func TestListRejectsInvalidScope(t *testing.T) { + t.Parallel() + + stores := []Storage{ + NewMemoryStorage(), + NewFilesystemStorage(t.TempDir()), + } + + for _, store := range stores { + _, err := store.List(context.Background(), Scope{Type: ScopeType("invalid")}, ListInput{}) + if !errors.Is(err, ErrInvalidScope) { + t.Fatalf("expected ErrInvalidScope for %T, got %v", store, err) + } + } +} + +func TestNewFromEnvDefaultsToMemory(t *testing.T) { + unsetEnv(t, EnvBackend) + + store, err := NewFromEnv() + if err != nil { + t.Fatalf("NewFromEnv failed: %v", err) + } + if _, ok := store.(*MemoryStorage); !ok { + t.Fatal("expected MemoryStorage as default backend") + } +} + +func TestNewFromEnvFilesystem(t *testing.T) { + setEnv(t, EnvBackend, BackendFilesystem) + setEnv(t, EnvFilesystemPath, t.TempDir()) + + store, err := NewFromEnv() + if err != nil { + t.Fatalf("NewFromEnv failed: %v", err) + } + if _, ok := store.(*FilesystemStorage); !ok { + t.Fatal("expected FilesystemStorage") + } +} + +func TestNewFromEnvFilesystemMissingPath(t *testing.T) { + setEnv(t, EnvBackend, BackendFilesystem) + unsetEnv(t, EnvFilesystemPath) + + _, err := NewFromEnv() + if err == nil { + t.Fatal("expected error when filesystem path is not set") + } +} + +func TestNewFromEnvUnknownBackend(t *testing.T) { + setEnv(t, EnvBackend, "nosuchbackend") + + _, err := NewFromEnv() + if err == nil { + t.Fatal("expected error for unknown backend") + } +} + +// --- shared test helpers --- + +func runRoundTripTest(t *testing.T, store Storage) { + t.Helper() + ctx := context.Background() + scope := Scope{Type: ScopeOrganization, OrganizationID: "org-1"} + + err := store.Put(ctx, scope, "test/file.txt", strings.NewReader("hello"), PutOptions{ContentType: "text/plain"}) + if err != nil { + t.Fatalf("Put failed: %v", err) + } + + reader, err := store.Get(ctx, scope, "test/file.txt") + if err != nil { + t.Fatalf("Get failed: %v", err) + } + defer reader.Close() + + content, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("ReadAll failed: %v", err) + } + if string(content) != "hello" { + t.Fatalf("expected %q, got %q", "hello", string(content)) + } + + err = store.Delete(ctx, scope, "test/file.txt") + if err != nil { + t.Fatalf("Delete failed: %v", err) + } + + _, err = store.Get(ctx, scope, "test/file.txt") + if !errors.Is(err, ErrBlobNotFound) { + t.Fatalf("expected ErrBlobNotFound after delete, got %v", err) + } +} + +func runListTest(t *testing.T, store Storage) { + t.Helper() + ctx := context.Background() + scope := Scope{Type: ScopeCanvas, CanvasID: "canvas-1"} + + for _, name := range []string{"a.txt", "b.txt", "sub/c.txt"} { + err := store.Put(ctx, scope, name, strings.NewReader("data"), PutOptions{}) + if err != nil { + t.Fatalf("Put %s failed: %v", name, err) + } + } + + out, err := store.List(ctx, scope, ListInput{}) + if err != nil { + t.Fatalf("List failed: %v", err) + } + if len(out.Blobs) != 3 { + t.Fatalf("expected 3 blobs, got %d", len(out.Blobs)) + } +} + +func runListPaginationTest(t *testing.T, store Storage) { + t.Helper() + ctx := context.Background() + scope := Scope{Type: ScopeExecution, ExecutionID: "exec-1"} + + for _, name := range []string{"1.txt", "2.txt", "3.txt", "4.txt", "5.txt"} { + err := store.Put(ctx, scope, name, strings.NewReader("data"), PutOptions{}) + if err != nil { + t.Fatalf("Put %s failed: %v", name, err) + } + } + + page1, err := store.List(ctx, scope, ListInput{MaxResults: 2}) + if err != nil { + t.Fatalf("List page 1 failed: %v", err) + } + if len(page1.Blobs) != 2 { + t.Fatalf("expected 2 blobs on page 1, got %d", len(page1.Blobs)) + } + if page1.NextToken == "" { + t.Fatal("expected non-empty NextToken for page 1") + } + + page2, err := store.List(ctx, scope, ListInput{MaxResults: 2, ContinuationToken: page1.NextToken}) + if err != nil { + t.Fatalf("List page 2 failed: %v", err) + } + if len(page2.Blobs) != 2 { + t.Fatalf("expected 2 blobs on page 2, got %d", len(page2.Blobs)) + } + + page3, err := store.List(ctx, scope, ListInput{MaxResults: 2, ContinuationToken: page2.NextToken}) + if err != nil { + t.Fatalf("List page 3 failed: %v", err) + } + if len(page3.Blobs) != 1 { + t.Fatalf("expected 1 blob on page 3, got %d", len(page3.Blobs)) + } + if page3.NextToken != "" { + t.Fatalf("expected empty NextToken on last page, got %q", page3.NextToken) + } +} + +func runScopeIsolationTest(t *testing.T, store Storage) { + t.Helper() + ctx := context.Background() + + scopeA := Scope{Type: ScopeOrganization, OrganizationID: "org-a"} + scopeB := Scope{Type: ScopeOrganization, OrganizationID: "org-b"} + + err := store.Put(ctx, scopeA, "shared.txt", strings.NewReader("from A"), PutOptions{}) + if err != nil { + t.Fatalf("Put to scope A failed: %v", err) + } + + err = store.Put(ctx, scopeB, "shared.txt", strings.NewReader("from B"), PutOptions{}) + if err != nil { + t.Fatalf("Put to scope B failed: %v", err) + } + + readerA, err := store.Get(ctx, scopeA, "shared.txt") + if err != nil { + t.Fatalf("Get from scope A failed: %v", err) + } + defer readerA.Close() + contentA, _ := io.ReadAll(readerA) + + readerB, err := store.Get(ctx, scopeB, "shared.txt") + if err != nil { + t.Fatalf("Get from scope B failed: %v", err) + } + defer readerB.Close() + contentB, _ := io.ReadAll(readerB) + + if string(contentA) != "from A" { + t.Fatalf("scope A content: expected %q, got %q", "from A", string(contentA)) + } + if string(contentB) != "from B" { + t.Fatalf("scope B content: expected %q, got %q", "from B", string(contentB)) + } + + listA, _ := store.List(ctx, scopeA, ListInput{}) + listB, _ := store.List(ctx, scopeB, ListInput{}) + + if len(listA.Blobs) != 1 { + t.Fatalf("scope A should have 1 blob, got %d", len(listA.Blobs)) + } + if len(listB.Blobs) != 1 { + t.Fatalf("scope B should have 1 blob, got %d", len(listB.Blobs)) + } +} + +func runPresignNotSupportedTest(t *testing.T, store Storage) { + t.Helper() + ctx := context.Background() + scope := Scope{Type: ScopeOrganization, OrganizationID: "org-1"} + + _, err := store.PresignPut(ctx, scope, "file.txt", PutOptions{}, 0) + if !errors.Is(err, ErrPresignedURLNotSupported) { + t.Fatalf("PresignPut: expected ErrPresignedURLNotSupported, got %v", err) + } + + _, err = store.PresignGet(ctx, scope, "file.txt", 0) + if !errors.Is(err, ErrPresignedURLNotSupported) { + t.Fatalf("PresignGet: expected ErrPresignedURLNotSupported, got %v", err) + } +} + +func setEnv(t *testing.T, key, value string) { + t.Helper() + prev, had := os.LookupEnv(key) + t.Cleanup(func() { + if had { + _ = os.Setenv(key, prev) + } else { + _ = os.Unsetenv(key) + } + }) + _ = os.Setenv(key, value) +} + +func unsetEnv(t *testing.T, key string) { + t.Helper() + prev, had := os.LookupEnv(key) + t.Cleanup(func() { + if had { + _ = os.Setenv(key, prev) + } else { + _ = os.Unsetenv(key) + } + }) + _ = os.Unsetenv(key) +}