From 53b6922e5bf9ecaf27ec3284c58659d2a6468c86 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Tue, 5 May 2026 07:58:07 -0400 Subject: [PATCH 1/2] feat(provider): implement opt-in Enumerator interface (workflow#536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements interfaces.Enumerator (added in workflow PR #557 / v0.21.2) on DOProvider, enabling `wfctl infra cleanup --tag ` to drive tag-scoped teardown of DO resources. EnumerateByTag probes Tags.Get to distinguish unknown-tag (404 → empty result) from API failure, then queries: - Droplets via native ListByTag - Volumes via List + client-side Tags filter (no native tag-filter param) - Databases via List + client-side Tags filter Other DO resource types either do not support tags or are deferred; per- provider coverage is documented in workflow/docs/WFCTL.md. Tests cover the compile-time interface assertion, the happy path across all three resource types (with off-tag entries to exercise the filter), the 404-tag-not-found case, the empty-tag rejection, and the nil-client defensive guard. Bumps workflow dep to v0.21.2 to pull in interfaces.Enumerator. Co-Authored-By: Claude Opus 4.7 (1M context) --- go.mod | 56 +++---- go.sum | 239 +++++++++++++------------- internal/provider.go | 143 ++++++++++++++++ internal/provider_enumerator_test.go | 240 +++++++++++++++++++++++++++ 4 files changed, 531 insertions(+), 147 deletions(-) create mode 100644 internal/provider_enumerator_test.go diff --git a/go.mod b/go.mod index 9ed48da..496c324 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/GoCodeAlone/workflow-plugin-digitalocean go 1.26.0 require ( - github.com/GoCodeAlone/workflow v0.20.6-0.20260505011403-e2c582bece90 - github.com/aws/aws-sdk-go-v2 v1.41.5 - github.com/aws/aws-sdk-go-v2/credentials v1.19.12 + github.com/GoCodeAlone/workflow v0.21.2 + github.com/aws/aws-sdk-go-v2 v1.41.6 + github.com/aws/aws-sdk-go-v2/credentials v1.19.15 github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2 github.com/digitalocean/godo v1.178.0 golang.org/x/oauth2 v0.36.0 @@ -43,32 +43,31 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/RoaringBitmap/roaring v1.9.4 // indirect github.com/Workiva/go-datastructures v1.1.7 // indirect - github.com/andybalholm/brotli v1.2.0 // indirect + github.com/andybalholm/brotli v1.2.1 // indirect github.com/armon/go-metrics v0.4.1 // 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.12 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // 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.21 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 // indirect github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.33.8 // indirect github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.13 // indirect github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.12 // indirect github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.0 // indirect github.com/aws/aws-sdk-go-v2/service/ecs v1.76.0 // indirect github.com/aws/aws-sdk-go-v2/service/eks v1.81.2 // 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/accept-encoding v1.13.8 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 // indirect github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.4 // indirect github.com/aws/aws-sdk-go-v2/service/route53 v1.62.5 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect - github.com/aws/smithy-go v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 // indirect + github.com/aws/smithy-go v1.25.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -80,11 +79,11 @@ require ( github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/danieljoos/wincred v1.2.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/deckarep/golang-set/v2 v2.8.0 // indirect + github.com/deckarep/golang-set/v2 v2.9.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v28.5.2+incompatible // indirect - github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/eapache/go-resiliency v1.7.0 // indirect @@ -96,7 +95,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/flowchartsman/retry v1.2.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -111,7 +110,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect github.com/googleapis/gax-go/v2 v2.19.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect @@ -146,7 +145,7 @@ require ( github.com/klauspost/compress v1.18.5 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.21 // indirect github.com/miekg/dns v1.1.72 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -156,7 +155,7 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/nats.go v1.50.0 // indirect + github.com/nats-io/nats.go v1.51.0 // indirect github.com/nats-io/nkeys v0.4.15 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect @@ -182,7 +181,7 @@ require ( github.com/tidwall/btree v1.8.1 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/redcon v1.6.2 // indirect - github.com/tochemey/goakt/v4 v4.1.1 // indirect + github.com/tochemey/goakt/v4 v4.2.2 // indirect github.com/tochemey/olric v0.3.9 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect @@ -193,6 +192,7 @@ require ( github.com/zalando/go-keyring v0.2.8 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect go.etcd.io/bbolt v1.4.3 // indirect + go.mongodb.org/mongo-driver v1.17.9 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect @@ -210,17 +210,17 @@ require ( go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/crypto v0.50.0 // indirect - golang.org/x/mod v0.34.0 // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.43.0 // indirect + golang.org/x/tools v0.44.0 // indirect google.golang.org/api v0.272.0 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect modernc.org/libc v1.70.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index 1ef1a78..2a58b3a 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/GoCodeAlone/modular/modules/jsonschema v1.15.0 h1:xb1mI4NZkzvNKQ2F6nk github.com/GoCodeAlone/modular/modules/jsonschema v1.15.0/go.mod h1:hhGouwAVsonmJ4Lain4jINZ9nZCoc9l9eF3BHbmR8eE= github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.8.0 h1:cvdLHbM/vzvygQTcAWSJsy+dAPzzwWyjzKMmTBFcFIo= github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.8.0/go.mod h1:/9ipMG4qM2CHQ14BfXKdVlYRJelef6M8MFI5TbZv67M= -github.com/GoCodeAlone/workflow v0.20.6-0.20260505011403-e2c582bece90 h1:A4efhC8phY+M8KOB9ccga8COdjYMw+OKHhUuWcKgltA= -github.com/GoCodeAlone/workflow v0.20.6-0.20260505011403-e2c582bece90/go.mod h1:IaonttCuyJcjT+XryXUuh3ATvTI7jx0t8e3n+ASCOOA= +github.com/GoCodeAlone/workflow v0.21.2 h1:c3ayFyz3bDK10XbC2YmuGdrWYE/7PRwDmOhAjxMow6k= +github.com/GoCodeAlone/workflow v0.21.2/go.mod h1:Ue+5YDScTZgtA36q6r/kDaIRxGJFkyxXbeyJVNVJ0Cc= github.com/GoCodeAlone/yaegi v0.17.2 h1:WK6Y6e0t1a6U7r+S2dN3CGWW1PizYD3zO0zneToZPxM= github.com/GoCodeAlone/yaegi v0.17.2/go.mod h1:z5Pr6Wse6QJcQvpgxTxzMAevFarH0N37TG88Y9dprx0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc= @@ -84,30 +84,28 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alicebob/miniredis/v2 v2.36.1 h1:Dvc5oAnNOr7BIfPn7tF269U8DvRW1dBG2D5n0WrfYMI= github.com/alicebob/miniredis/v2 v2.36.1/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= +github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/antithesishq/antithesis-sdk-go v0.7.0 h1:uWDG8BqLD1lI2ps38WDz2vXflrTX2+vLX0SvZtztJtE= github.com/antithesishq/antithesis-sdk-go v0.7.0/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -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 v1.41.6 h1:1AX0AthnBQzMx1vbmir3Y4WsnJgiydmnJjiLu+LvXOg= +github.com/aws/aws-sdk-go-v2 v1.41.6/go.mod h1:dy0UzBIfwSeot4grGvY1AqFWN5zgziMmWGzysDnHFcQ= 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.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= -github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= -github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= -github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= -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.21 h1:SwGMTMLIlvDNyhMteQ6r8IJSBPlRdXX5d4idhIGbkXA= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21/go.mod h1:UUxgWxofmOdAMuqEsSppbDtGKLfR04HGsD0HXzvhI1k= +github.com/aws/aws-sdk-go-v2/config v1.32.16 h1:Q0iQ7quUgJP0F/SCRTieScnaMdXr9h/2+wze1u3cNeM= +github.com/aws/aws-sdk-go-v2/config v1.32.16/go.mod h1:duCCnJEFqpt2RC6no1iK6q+8HpwOAkiUua0pY507dQc= +github.com/aws/aws-sdk-go-v2/credentials v1.19.15 h1:fyvgWTszojq8hEnMi8PPBTvZdTtEVmAVyo+NFLHBhH4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.15/go.mod h1:gJiYyMOjNg8OEdRWOf3CrFQxM2a98qmrtjx1zuiQfB8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 h1:IOGsJ1xVWhsi+ZO7/NW8OuZZBtMJLZbk4P5HDjJO0jQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22/go.mod h1:b+hYdbU+jGKfXE8kKM6g1+h+L/Go3vMvzlxBsiuGsxg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 h1:GmLa5Kw1ESqtFpXsx5MmC84QWa/ZrLZvlJGa2y+4kcQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22/go.mod h1:6sW9iWm9DK9YRpRGga/qzrzNLgKpT2cIxb7Vo2eNOp0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 h1:dY4kWZiSaXIzxnKlj17nHnBcXXBfac6UlsAx2qL6XrU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22/go.mod h1:KIpEUx0JuRZLO7U6cbV204cWAEco2iC3l061IxlwLtI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 h1:FPXsW9+gMuIeKmz7j6ENWcWtBGTe1kH8r9thNt5Uxx4= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23/go.mod h1:7J8iGMdRKk6lw2C+cMIphgAnT8uTwBwNOsGkyOCm80U= github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.33.8 h1:I0AMtyv5tqQ/VNDDalbbujALCWl64TP3F61bBw4U8Qs= github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.33.8/go.mod h1:qnrKR+Jzg9NbZqy+YusE7frSZUaYQ7EPJvki4+SwS3U= github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.13 h1:juPaAcploym78WhVwleVHNLPmgURO6gkObC442Hal1s= @@ -122,12 +120,12 @@ github.com/aws/aws-sdk-go-v2/service/eks v1.81.2 h1:6c/Jkyx1gYLiZGl6VPjApViaoPiY github.com/aws/aws-sdk-go-v2/service/eks v1.81.2/go.mod h1:xdUh6tdF9A8hc+PE84kmHbF/zsVPNiKnc6oLgulq1Eo= github.com/aws/aws-sdk-go-v2/service/iam v1.53.7 h1:n9YLiWtX3+6pTLZWvRJmtq5JIB9NA/KFelyCg5fOlTU= github.com/aws/aws-sdk-go-v2/service/iam v1.53.7/go.mod h1:sP46Vo6MeJcM4s0ZXcG2PFmfiSyixhIuC/74W52yKuk= -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/accept-encoding v1.13.8 h1:HtOTYcbVcGABLOVuPYaIihj6IlkqubBwFj10K5fxRek= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8/go.mod h1:VsK9abqQeGlzPgUr+isNWzPlK2vKe9INMLWnY65f5Xs= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 h1:qtJZ70afD3ISKWnoX3xB0J2otEqu3LqicRcDBqsj0hQ= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12/go.mod h1:v2pNpJbRNl4vEUWEh5ytQok0zACAKfdmKS51Hotc3pQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 h1:PUmZeJU6Y1Lbvt9WFuJ0ugUK2xn6hIWUBBbKuOWF30s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22/go.mod h1:nO6egFBoAaoXze24a2C0NjQCvdpk8OueRoYimvEB9jo= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 h1:siU1A6xjUZ2N8zjTHSXFhB9L/2OY8Dqs0xXiLjF30jA= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20/go.mod h1:4TLZCmVJDM3FOu5P5TJP0zOlu9zWgDWU7aUxWbr+rcw= github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.4 h1:3m9iJtMtLq75jKRAfw0kapoHUlbzi0CRVigysBN/FHA= @@ -136,16 +134,16 @@ github.com/aws/aws-sdk-go-v2/service/route53 v1.62.5 h1:Z+/OLsb85Kpq7TVLCspskqeP github.com/aws/aws-sdk-go-v2/service/route53 v1.62.5/go.mod h1:TmxGowuBYwjmHFOsEDxaZdsQE62JJzOmtiWafTi/czg= github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2 h1:MRNiP6nqa20aEl8fQ6PJpEq11b2d40b16sm4WD7QgMU= github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2/go.mod h1:FrNA56srbsr3WShiaelyWYEo70x80mXnVZ17ZZfbeqg= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= -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/aws/aws-sdk-go-v2/service/signin v1.0.10 h1:a1Fq/KXn75wSzoJaPQTgZO0wHGqE9mjFnylnqEPTchA= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.10/go.mod h1:p6+MXNxW7IA6dMgHfTAzljuwSKD0NCm/4lbS4t6+7vI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 h1:x6bKbmDhsgSZwv6q19wY/u3rLk/3FGjJWyqKcIRufpE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.16/go.mod h1:CudnEVKRtLn0+3uMV0yEXZ+YZOKnAtUJ5DmDhilVnIw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 h1:oK/njaL8GtyEihkWMD4k3VgHCT64RQKkZwh0DG5j8ak= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20/go.mod h1:JHs8/y1f3zY7U5WcuzoJ/yAYGYtNIVPKLIbp61euvmg= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 h1:ks8KBcZPh3PYISr5dAiXCM5/Thcuxk8l+PG4+A0exds= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.0/go.mod h1:pFw33T0WLvXU3rw1WBkpMlkgIn54eCB5FYLhjDc9Foo= +github.com/aws/smithy-go v1.25.0 h1:Sz/XJ64rwuiKtB6j98nDIPyYrV1nVNJ4YU74gttcl5U= +github.com/aws/smithy-go v1.25.0/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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -199,8 +197,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= -github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/deckarep/golang-set/v2 v2.9.0 h1:prva4eP9UysWagLyKrtn074ughi0NnkIf0A4M5yOCKI= +github.com/deckarep/golang-set/v2 v2.9.0/go.mod h1:EWknQXbs0mcFpat2QOoXV0Ee57cD+w6ZEN76BR2JVrM= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/digitalocean/godo v1.178.0 h1:+B4xGOaoFwwwpM7TKhoyGHdmFg5eF9zDB1YfOLvNJ2E= @@ -211,8 +209,8 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -246,8 +244,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= @@ -265,34 +263,34 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= -github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= -github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU= -github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA= -github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c= -github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= -github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g= -github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k= -github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk= -github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc= -github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= -github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= -github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo= -github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4= -github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU= -github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g= -github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw= -github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY= -github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU= -github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14= -github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M= -github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII= -github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E= -github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc= -github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ= -github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ= +github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= +github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= +github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= +github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= +github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= +github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= +github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= +github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= +github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= +github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= @@ -357,10 +355,10 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= -github.com/hashicorp/consul/api v1.33.7 h1:apLZVzX7O7BLgHyh4pvczcsBzPmYSVXGKZQbOaA1ae0= -github.com/hashicorp/consul/api v1.33.7/go.mod h1:SjR3cjwCUSLLDfVw5dFg76rnnKjOySxr8W8lC5s01C8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/hashicorp/consul/api v1.34.1 h1:/qKFfBJ5GEmY3sIlK1HulRJciLoT1pCGBkq2gwILXVg= +github.com/hashicorp/consul/api v1.34.1/go.mod h1:K+7fQ7o5QEyoFRyXtX3/iGSxDbg9jYWGJ5rDyrXzHU8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -447,8 +445,8 @@ github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/ github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kapetan-io/tackle v0.13.0 h1:kcQTbgZN+4T89ktqlpW2TBATjiBmfjIyuZUukvRrYZU= -github.com/kapetan-io/tackle v0.13.0/go.mod h1:5ZGq3U/Qgpq0ccxyx2+Zovg2ceM9yl6DOVL2R90of4g= +github.com/kapetan-io/tackle v0.14.0 h1:Qu3zq6+95DLX7n71Up9X8SGFycCuvMyzo2D0m5Hkblk= +github.com/kapetan-io/tackle v0.14.0/go.mod h1:pDr4mjpo2RQO/q/je1dGuGwnBVwZcsRp60wgDV2hA3c= github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= @@ -466,8 +464,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lufia/plan9stats v0.0.0-20260324052639-156f7da3f749 h1:Qj3hTcdWH8uMZDI41HNuTuJN525C7NBrbtH5kSO6fPk= -github.com/lufia/plan9stats v0.0.0-20260324052639-156f7da3f749/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak= +github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -476,8 +474,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= @@ -493,6 +491,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= @@ -523,10 +525,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt/v2 v2.8.1 h1:V0xpGuD/N8Mi+fQNDynXohVvp7ZztevW5io8CUWlPmU= github.com/nats-io/jwt/v2 v2.8.1/go.mod h1:nWnOEEiVMiKHQpnAy4eXlizVEtSfzacZ1Q43LIRavZg= -github.com/nats-io/nats-server/v2 v2.12.6 h1:Egbx9Vl7Ch8wTtpXPGqbehkZ+IncKqShUxvrt1+Enc8= -github.com/nats-io/nats-server/v2 v2.12.6/go.mod h1:4HPlrvtmSO3yd7KcElDNMx9kv5EBJBnJJzQPptXlheo= -github.com/nats-io/nats.go v1.50.0 h1:5zAeQrTvyrKrWLJ0fu02W3br8ym57qf7csDzgLOpcds= -github.com/nats-io/nats.go v1.50.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno= +github.com/nats-io/nats-server/v2 v2.12.7 h1:prQ9cPiWHcnwfT81Wi5lU9LL8TLY+7pxDru6fQYLCQQ= +github.com/nats-io/nats-server/v2 v2.12.7/go.mod h1:dOnmkprKMluTmTF7/QHZioxlau3sKHUM/LBPy9AiBPw= +github.com/nats-io/nats.go v1.51.0 h1:ByW84XTz6W03GSSsygsZcA+xgKK8vPGaa/FCAAEHnAI= +github.com/nats-io/nats.go v1.51.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno= github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4= github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= @@ -602,8 +604,8 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEV github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= -github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= +github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -630,12 +632,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/testcontainers/testcontainers-go v0.41.0 h1:mfpsD0D36YgkxGj2LrIyxuwQ9i2wCKAD+ESsYM1wais= -github.com/testcontainers/testcontainers-go v0.41.0/go.mod h1:pdFrEIfaPl24zmBjerWTTYaY0M6UHsqA1YSvsoU40MI= -github.com/testcontainers/testcontainers-go/modules/consul v0.41.0 h1:ssCWgKf4dst0Ys2J69kohXuXjINJXph0QgFf6mlwCbU= -github.com/testcontainers/testcontainers-go/modules/consul v0.41.0/go.mod h1:PwMdtDOg4IIImUWFLX2ZDMulqR70JCrfkZdIVM/lMN8= -github.com/testcontainers/testcontainers-go/modules/etcd v0.41.0 h1:HDEpWRH7JTCSUeJkcwkbRCUClZ8qyT6Z4RgfjR3JMr4= -github.com/testcontainers/testcontainers-go/modules/etcd v0.41.0/go.mod h1:PpbzL8aLFNc8VFd6yAqleklm60cvN+s8BiQh8VsNDfg= +github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= +github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= +github.com/testcontainers/testcontainers-go/modules/consul v0.42.0 h1:oQqQAPaiv5WvLB6lCapjohWRbMi1pYmPSTSDQrVv3nc= +github.com/testcontainers/testcontainers-go/modules/consul v0.42.0/go.mod h1:5/t9MNZTBLJ08QzPdVe0XXjLg7W31+udMM3+hoRYXa4= +github.com/testcontainers/testcontainers-go/modules/etcd v0.42.0 h1:Hy4Zt7/JfoNW35Vz99lH/yeRMgRy7ebxnwNJPHhpkZg= +github.com/testcontainers/testcontainers-go/modules/etcd v0.42.0/go.mod h1:+2oLnkMw0McOfhjlXEljY7LoXruENqsTaSIeHFy/VWU= github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4= github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= @@ -649,12 +651,10 @@ github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYI github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= -github.com/tochemey/goakt/v4 v4.1.1 h1:MO3HmcsDxTANSEZ9Js+dpwb/YD4qELBzV+0gbO9WrFQ= -github.com/tochemey/goakt/v4 v4.1.1/go.mod h1:fdUODkdd7FRkM4jumOd9jVBoCjB1L4YnAAF6WTYHMo0= +github.com/tochemey/goakt/v4 v4.2.2 h1:1SauIOrd4MdKsPFo8YUfy8V8+zZVYWodNhV7lP3DD5w= +github.com/tochemey/goakt/v4 v4.2.2/go.mod h1:ScoWT3Qb0SF4AjKvwBxiGzLGXrMikpfOunyx1XFefGo= github.com/tochemey/olric v0.3.9 h1:MU3VVQ3TZwdRzyxai0myxNMZj0lMK/RCjhaYh2Xe6aQ= github.com/tochemey/olric v0.3.9/go.mod h1:r5OVAIw1zaZJ5WKvKj1c4XnLwFaYpH8EJpm4dAD8Bp0= -github.com/travisjeffery/go-dynaport v1.0.0 h1:m/qqf5AHgB96CMMSworIPyo1i7NZueRsnwdzdCJ8Ajw= -github.com/travisjeffery/go-dynaport v1.0.0/go.mod h1:0LHuDS4QAx+mAc4ri3WkQdavgVoBIZ7cE9ob17KIAJk= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -688,12 +688,14 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.etcd.io/etcd/api/v3 v3.6.9 h1:UA7iKfEW1AzgihcBSGXci2kDGQiokSq41F9HMCI/RTI= -go.etcd.io/etcd/api/v3 v3.6.9/go.mod h1:csEk/qTfxKL36NqJdU15Tgtl65A8dyEY2BYo7PRsIwk= -go.etcd.io/etcd/client/pkg/v3 v3.6.9 h1:T8nuk8Lz64C+Hzb0coBFLMSlVSQZBpAtFk46swdM1DA= -go.etcd.io/etcd/client/pkg/v3 v3.6.9/go.mod h1:WEy3PpwbbEBVRdh1NVJYsuUe/8eyI21PNJRazeD8z/Y= -go.etcd.io/etcd/client/v3 v3.6.9 h1:3X555hQXmhRr27O37wls53g68CpUiPOiHXrZfz2Al+o= -go.etcd.io/etcd/client/v3 v3.6.9/go.mod h1:KO7H1HLYh1qaljuVZJQwBFk1lRce6pJzt+C81GEnrlM= +go.etcd.io/etcd/api/v3 v3.6.10 h1:jlwjtELjA8yi2VWpOFH+0w0lGr3K6mVDyn0RDB9aaAY= +go.etcd.io/etcd/api/v3 v3.6.10/go.mod h1:pdV4VeFmvhdNjB4LWRkC8ReLyRBAxUOze3GarMhE2sk= +go.etcd.io/etcd/client/pkg/v3 v3.6.10 h1:tBT7podcPhuVbCVkAEzx8bC5I+aqxfLwBN8/As1arrA= +go.etcd.io/etcd/client/pkg/v3 v3.6.10/go.mod h1:WEy3PpwbbEBVRdh1NVJYsuUe/8eyI21PNJRazeD8z/Y= +go.etcd.io/etcd/client/v3 v3.6.10 h1:J598zJ+C/ZPvImypmq5waj84+bovePrlZERHklf34y0= +go.etcd.io/etcd/client/v3 v3.6.10/go.mod h1:iHhUDUcEwaKs1YFq3MgmI9U4zhTVasp/vgdVbFf1RS8= +go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= +go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= 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.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU= @@ -742,13 +744,13 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= -golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -804,7 +806,6 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -827,8 +828,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -840,10 +841,10 @@ google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwR google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= -google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d h1:/aDRtSZJjyLQzm75d+a1wOJaqyKBMvIAfeQmoa3ORiI= -google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:etfGUgejTiadZAUaEP14NP97xi1RGeawqkjDARA/UOs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 h1:zUWMZsvo/IJcD1t6MNCPO/azZTwz0TvwCBqr5aifoVY= +google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529/go.mod h1:a5OGAgyRr4lqco7AG9hQM9Fwh0N2ZV4grR0eXFEsXQg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -874,16 +875,16 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= -k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4= -k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8= -k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg= -k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c= +k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= +k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= +k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= +k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= +k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8= +k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260319004828-5883c5ee87b9 h1:Sztf7ESG9tAXRW/ACJZjrj5jhdOUqS2KFRQT+CTvu78= -k8s.io/kube-openapi v0.0.0-20260319004828-5883c5ee87b9/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f h1:4Qiq0YAoQATdgmHALJWz9rJ4fj20pB3xebpB4CFNhYM= +k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= @@ -918,7 +919,7 @@ sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5E sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/provider.go b/internal/provider.go index 4206706..78eac54 100644 --- a/internal/provider.go +++ b/internal/provider.go @@ -39,6 +39,7 @@ type DOProvider struct { var _ interfaces.IaCProvider = (*DOProvider)(nil) var _ interfaces.ProviderMigrationRepairer = (*DOProvider)(nil) +var _ interfaces.Enumerator = (*DOProvider)(nil) // NewDOProvider creates an uninitialised DOProvider. func NewDOProvider() *DOProvider { @@ -275,6 +276,148 @@ func (p *DOProvider) Apply(ctx context.Context, plan *interfaces.IaCPlan) (*inte return result, nil } +// EnumerateByTag implements the opt-in interfaces.Enumerator interface. +// +// Lists DO resources tagged with the given tag and returns them as +// interfaces.ResourceRef values keyed on the same (Name, Type, ProviderID) +// tuple that the corresponding ResourceDriver(type).Delete consumes. Used by +// `wfctl infra cleanup --tag ` to drive tag-scoped teardown. +// +// Implementation strategy: +// - Tags.Get is queried first as a probe: if the tag itself does not exist +// in the DO account (404), the result is an empty slice (not an error). +// This matches operator expectation that running cleanup against a tag +// that has never been used reports "no resources" rather than failing. +// A 200 here indicates the tag is known to DO, but the per-resource +// queries below are what actually populate the result slice — Tags.Get's +// own response only carries counts + last-tagged metadata, not a full +// list of tagged resources. +// - Droplets uses the native ListByTag endpoint. +// - Volumes and Databases do not expose a tag-filter parameter, so the +// full list is fetched and filtered client-side on the Tags slice. +// - Other DO resource types (load balancers, k8s clusters, app platform, +// etc.) either do not support tags or have not yet been wired here. +// The cleanup subcommand documents per-provider coverage in +// workflow/docs/WFCTL.md `#### infra cleanup`. +// +// The contract for the returned ResourceRef.ProviderID matches what each +// ResourceDriver expects on Delete: a string-formatted droplet ID for +// droplets, the volume ID for volumes, and the database cluster UUID for +// databases. Name is the user-facing resource name. Type is the canonical +// `infra.` matching DOProvider's driver registration. +func (p *DOProvider) EnumerateByTag(ctx context.Context, tag string) ([]interfaces.ResourceRef, error) { + if p.client == nil { + return nil, fmt.Errorf("digitalocean: EnumerateByTag called on provider that is not initialized — call Initialize first") + } + if tag == "" { + return nil, fmt.Errorf("digitalocean: EnumerateByTag requires a non-empty tag argument") + } + + // Probe Tags.Get to distinguish "tag does not exist" (return empty) from + // "API call failed" (return error). godo wraps non-2xx responses in + // godo.ErrorResponse with the HTTP Response embedded. + if _, _, err := p.client.Tags.Get(ctx, tag); err != nil { + var doErr *godo.ErrorResponse + if errors.As(err, &doErr) && doErr.Response != nil && doErr.Response.StatusCode == 404 { + return nil, nil + } + return nil, fmt.Errorf("digitalocean: get tag %q: %w", tag, err) + } + + var refs []interfaces.ResourceRef + + // Droplets — native ListByTag, paginated. + dropletPage := &godo.ListOptions{Page: 1, PerPage: 200} + for { + droplets, resp, err := p.client.Droplets.ListByTag(ctx, tag, dropletPage) + if err != nil { + return nil, fmt.Errorf("digitalocean: list droplets by tag %q: %w", tag, err) + } + for _, d := range droplets { + refs = append(refs, interfaces.ResourceRef{ + Name: d.Name, + Type: "infra.droplet", + ProviderID: fmt.Sprintf("%d", d.ID), + }) + } + if resp == nil || resp.Links == nil || resp.Links.IsLastPage() { + break + } + nextPage, err := resp.Links.CurrentPage() + if err != nil { + return nil, fmt.Errorf("digitalocean: paginate droplets: %w", err) + } + dropletPage.Page = nextPage + 1 + } + + // Volumes — list all, filter client-side on Tags membership. + volumePage := &godo.ListVolumeParams{ListOptions: &godo.ListOptions{Page: 1, PerPage: 200}} + for { + volumes, resp, err := p.client.Storage.ListVolumes(ctx, volumePage) + if err != nil { + return nil, fmt.Errorf("digitalocean: list volumes for tag %q: %w", tag, err) + } + for _, v := range volumes { + if !stringSliceContains(v.Tags, tag) { + continue + } + refs = append(refs, interfaces.ResourceRef{ + Name: v.Name, + Type: "infra.volume", + ProviderID: v.ID, + }) + } + if resp == nil || resp.Links == nil || resp.Links.IsLastPage() { + break + } + nextPage, err := resp.Links.CurrentPage() + if err != nil { + return nil, fmt.Errorf("digitalocean: paginate volumes: %w", err) + } + volumePage.ListOptions.Page = nextPage + 1 + } + + // Databases — list all, filter client-side on Tags membership. + dbPage := &godo.ListOptions{Page: 1, PerPage: 200} + for { + databases, resp, err := p.client.Databases.List(ctx, dbPage) + if err != nil { + return nil, fmt.Errorf("digitalocean: list databases for tag %q: %w", tag, err) + } + for _, db := range databases { + if !stringSliceContains(db.Tags, tag) { + continue + } + refs = append(refs, interfaces.ResourceRef{ + Name: db.Name, + Type: "infra.database", + ProviderID: db.ID, + }) + } + if resp == nil || resp.Links == nil || resp.Links.IsLastPage() { + break + } + nextPage, err := resp.Links.CurrentPage() + if err != nil { + return nil, fmt.Errorf("digitalocean: paginate databases: %w", err) + } + dbPage.Page = nextPage + 1 + } + + return refs, nil +} + +// stringSliceContains reports whether s is present in slice. Used by EnumerateByTag +// to filter resources whose tag list includes the requested tag. +func stringSliceContains(slice []string, s string) bool { + for _, v := range slice { + if v == s { + return true + } + } + return false +} + // Destroy deletes the given resources. func (p *DOProvider) Destroy(ctx context.Context, resources []interfaces.ResourceRef) (*interfaces.DestroyResult, error) { result := &interfaces.DestroyResult{} diff --git a/internal/provider_enumerator_test.go b/internal/provider_enumerator_test.go new file mode 100644 index 0000000..acde8cd --- /dev/null +++ b/internal/provider_enumerator_test.go @@ -0,0 +1,240 @@ +package internal + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "sort" + "strings" + "testing" + + "github.com/GoCodeAlone/workflow/interfaces" + "github.com/digitalocean/godo" +) + +// TestDOProvider_ImplementsEnumerator is a compile-time assertion that the DO +// plugin satisfies the opt-in interfaces.Enumerator interface added in workflow +// PR #557 (workflow#536). The cleanup subcommand type-asserts against this +// interface and skips providers that do not implement it. +func TestDOProvider_ImplementsEnumerator(t *testing.T) { + var _ interfaces.Enumerator = (*DOProvider)(nil) +} + +// TestDOProvider_EnumerateByTag_NilClient pins the defensive guard: if the +// caller invokes EnumerateByTag on an uninitialised provider (no godo client), +// the method returns a clear error rather than panicking on nil-pointer +// deref. +func TestDOProvider_EnumerateByTag_NilClient(t *testing.T) { + p := NewDOProvider() // Initialize NOT called → p.client is nil + _, err := p.EnumerateByTag(context.Background(), "any-tag") + if err == nil { + t.Fatal("expected error from EnumerateByTag on uninitialised provider; got nil") + } + if !strings.Contains(err.Error(), "not initialized") { + t.Errorf("error %q should mention not initialized", err.Error()) + } +} + +// enumeratorFakeAPI is an httptest-backed mock that implements the subset of +// the DO REST API consumed by EnumerateByTag: GET /v2/tags/{name}, +// GET /v2/droplets?tag_name={tag}, GET /v2/volumes, GET /v2/databases. +// +// The mock returns canned JSON for each endpoint based on the maps populated +// by the test. Pagination links are intentionally omitted — the production +// EnumerateByTag handles pagination, but for unit-test scope a single page +// per endpoint is sufficient to exercise the result-shape contract. +type enumeratorFakeAPI struct { + dropletsByTag map[string][]godo.Droplet + volumes []godo.Volume + databases []godo.Database + // tagExists maps tag-name → whether GET /v2/tags/{name} returns 200. + // When the tag does not exist, the API returns 404; EnumerateByTag must + // treat 404 as "no resources" (empty result) not an error. + tagExists map[string]bool +} + +// handler returns an http.HandlerFunc that routes the path/method to the +// canned response. Anything not handled returns 501 so the test surfaces the +// gap rather than silently coercing to an empty list. +func (f *enumeratorFakeAPI) handler(t *testing.T) http.HandlerFunc { + t.Helper() + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + switch { + case strings.HasPrefix(r.URL.Path, "/v2/tags/") && r.Method == http.MethodGet: + tagName := strings.TrimPrefix(r.URL.Path, "/v2/tags/") + if !f.tagExists[tagName] { + w.WriteHeader(http.StatusNotFound) + _, _ = fmt.Fprintf(w, `{"id":"not_found","message":"tag %q not found"}`, tagName) + return + } + // Tags.Get response shape — counts only. + _, _ = fmt.Fprintf(w, `{"tag":{"name":%q,"resources":{"count":0,"droplets":{"count":%d},"volumes":{"count":%d},"databases":{"count":%d},"images":{"count":0},"volume_snapshots":{"count":0}}}}`, + tagName, len(f.dropletsByTag[tagName]), len(f.volumes), len(f.databases)) + return + + case r.URL.Path == "/v2/droplets" && r.Method == http.MethodGet: + tag := r.URL.Query().Get("tag_name") + droplets := f.dropletsByTag[tag] + writeDroplets(t, w, droplets) + return + + case r.URL.Path == "/v2/volumes" && r.Method == http.MethodGet: + writeVolumes(t, w, f.volumes) + return + + case r.URL.Path == "/v2/databases" && r.Method == http.MethodGet: + writeDatabases(t, w, f.databases) + return + } + + w.WriteHeader(http.StatusNotImplemented) + _, _ = fmt.Fprintf(w, `{"id":"not_implemented","message":"%s %s not handled by enumeratorFakeAPI"}`, r.Method, r.URL.Path) + } +} + +func writeDroplets(t *testing.T, w http.ResponseWriter, droplets []godo.Droplet) { + t.Helper() + parts := make([]string, 0, len(droplets)) + for _, d := range droplets { + tags := jsonStringArray(d.Tags) + parts = append(parts, fmt.Sprintf(`{"id":%d,"name":%q,"tags":%s}`, d.ID, d.Name, tags)) + } + _, _ = fmt.Fprintf(w, `{"droplets":[%s],"links":{},"meta":{"total":%d}}`, strings.Join(parts, ","), len(droplets)) +} + +func writeVolumes(t *testing.T, w http.ResponseWriter, volumes []godo.Volume) { + t.Helper() + parts := make([]string, 0, len(volumes)) + for _, v := range volumes { + tags := jsonStringArray(v.Tags) + parts = append(parts, fmt.Sprintf(`{"id":%q,"name":%q,"tags":%s}`, v.ID, v.Name, tags)) + } + _, _ = fmt.Fprintf(w, `{"volumes":[%s],"links":{},"meta":{"total":%d}}`, strings.Join(parts, ","), len(volumes)) +} + +func writeDatabases(t *testing.T, w http.ResponseWriter, databases []godo.Database) { + t.Helper() + parts := make([]string, 0, len(databases)) + for _, db := range databases { + tags := jsonStringArray(db.Tags) + parts = append(parts, fmt.Sprintf(`{"id":%q,"name":%q,"tags":%s}`, db.ID, db.Name, tags)) + } + _, _ = fmt.Fprintf(w, `{"databases":[%s],"links":{},"meta":{"total":%d}}`, strings.Join(parts, ","), len(databases)) +} + +func jsonStringArray(s []string) string { + if len(s) == 0 { + return "[]" + } + parts := make([]string, len(s)) + for i, v := range s { + parts[i] = fmt.Sprintf("%q", v) + } + return "[" + strings.Join(parts, ",") + "]" +} + +// newProviderForEnumeratorTest returns a DOProvider whose godo client is wired +// to a httptest server running the given mock. It does NOT call Initialize +// because Initialize requires a token and would also reset BaseURL. Instead +// the test directly constructs the godo client + sets BaseURL. +func newProviderForEnumeratorTest(t *testing.T, mock *enumeratorFakeAPI) (*DOProvider, *httptest.Server) { + t.Helper() + srv := httptest.NewServer(mock.handler(t)) + t.Cleanup(srv.Close) + + client := godo.NewClient(srv.Client()) + base, err := url.Parse(srv.URL + "/") + if err != nil { + t.Fatalf("parse httptest URL: %v", err) + } + client.BaseURL = base + return &DOProvider{client: client, region: "nyc3"}, srv +} + +func TestDOProvider_EnumerateByTag_ReturnsTaggedResources(t *testing.T) { + mock := &enumeratorFakeAPI{ + tagExists: map[string]bool{"bmw-prod": true}, + dropletsByTag: map[string][]godo.Droplet{ + "bmw-prod": { + {ID: 1001, Name: "bmw-app-1", Tags: []string{"bmw-prod"}}, + {ID: 1002, Name: "bmw-app-2", Tags: []string{"bmw-prod", "extra"}}, + }, + }, + volumes: []godo.Volume{ + {ID: "vol-aaa", Name: "bmw-data", Tags: []string{"bmw-prod"}}, + {ID: "vol-bbb", Name: "other-data", Tags: []string{"unrelated"}}, // must be filtered out + }, + databases: []godo.Database{ + {ID: "db-ccc", Name: "bmw-pg", Tags: []string{"bmw-prod"}}, + {ID: "db-ddd", Name: "other-pg", Tags: []string{}}, // must be filtered out + }, + } + + p, _ := newProviderForEnumeratorTest(t, mock) + + refs, err := p.EnumerateByTag(context.Background(), "bmw-prod") + if err != nil { + t.Fatalf("EnumerateByTag: %v", err) + } + + // Sort by Name for stable comparison; provider does not guarantee order. + sort.Slice(refs, func(i, j int) bool { return refs[i].Name < refs[j].Name }) + + want := []interfaces.ResourceRef{ + {Name: "bmw-app-1", Type: "infra.droplet", ProviderID: "1001"}, + {Name: "bmw-app-2", Type: "infra.droplet", ProviderID: "1002"}, + {Name: "bmw-data", Type: "infra.volume", ProviderID: "vol-aaa"}, + {Name: "bmw-pg", Type: "infra.database", ProviderID: "db-ccc"}, + } + sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name }) + + if len(refs) != len(want) { + t.Fatalf("EnumerateByTag returned %d refs, want %d: %+v", len(refs), len(want), refs) + } + for i := range want { + if refs[i] != want[i] { + t.Errorf("ref[%d] = %+v, want %+v", i, refs[i], want[i]) + } + } +} + +// TestDOProvider_EnumerateByTag_TagNotFound verifies that when the tag does +// not exist (DO API returns 404 from GET /v2/tags/{name}), EnumerateByTag +// returns an empty slice rather than an error. Operators running cleanup +// against a tag that has never been used should see "no resources" not "tag +// lookup failed". +func TestDOProvider_EnumerateByTag_TagNotFound(t *testing.T) { + mock := &enumeratorFakeAPI{ + tagExists: map[string]bool{}, // bmw-prod NOT in the tag set + } + + p, _ := newProviderForEnumeratorTest(t, mock) + + refs, err := p.EnumerateByTag(context.Background(), "bmw-prod") + if err != nil { + t.Fatalf("EnumerateByTag for missing tag: %v", err) + } + if len(refs) != 0 { + t.Errorf("EnumerateByTag for missing tag returned %d refs, want 0: %+v", len(refs), refs) + } +} + +// TestDOProvider_EnumerateByTag_EmptyTag rejects empty input early — passing +// "" to godo would query /v2/tags/ which has different semantics (list all +// tags) and pollute results. The contract requires a non-empty tag. +func TestDOProvider_EnumerateByTag_EmptyTag(t *testing.T) { + mock := &enumeratorFakeAPI{} + p, _ := newProviderForEnumeratorTest(t, mock) + + _, err := p.EnumerateByTag(context.Background(), "") + if err == nil { + t.Fatal("expected error for empty tag; got nil") + } + if !strings.Contains(err.Error(), "tag") { + t.Errorf("error %q should mention the tag argument", err.Error()) + } +} From 2f222fdbbbc506a8405f6f74e2f73dddf71c3cd5 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Tue, 5 May 2026 08:26:02 -0400 Subject: [PATCH 2/2] fix(provider): wire EnumerateByTag through gRPC dispatch + split caches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses three findings from the PR 6b Copilot review: 1. doModuleInstance.InvokeMethod now routes "IaCProvider.EnumerateByTag" through to the underlying provider when it implements interfaces.Enumerator. Without this case, the host's remoteIaCProvider would type-assert ok=false and `wfctl infra cleanup` would silently skip every DO provider — even though DOProvider itself implements Enumerator. Returns codes.Unimplemented when the provider does NOT implement Enumerator so the host can interpret it as the same skip semantic the in-process type-assertion would yield. 2. EnumerateByTag now splits the godo.Databases.List response by EngineSlug. DO models SQL clusters and managed Redis under the same /v2/databases endpoint; the DO plugin's CacheDriver vs DatabaseDriver split mirrors that. Emitting every entry as `infra.database` would silently misclassify caches and route their Delete to the wrong driver in the cleanup dispatcher. 3. New tests: TestDOModuleInstance_InvokeMethod_EnumerateByTag pins the dispatch contract end-to-end (stubbed provider implements Enumerator → InvokeMethod returns serialized refs); the non-Enumerator case pins the codes.Unimplemented branch. The existing happy-path provider test now includes a redis-engine cluster and asserts it surfaces as infra.cache. Also tightens droplet ID conversion to strconv.Itoa. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/module_instance.go | 38 ++++++++++ internal/provider.go | 22 +++++- internal/provider_enumerator_test.go | 101 ++++++++++++++++++++++++++- 3 files changed, 155 insertions(+), 6 deletions(-) diff --git a/internal/module_instance.go b/internal/module_instance.go index 0ccb7d4..f07398c 100644 --- a/internal/module_instance.go +++ b/internal/module_instance.go @@ -83,6 +83,9 @@ func (m *doModuleInstance) InvokeMethodContext(ctx context.Context, method strin case "IaCProvider.RepairDirtyMigration": return m.invokeProviderRepairDirtyMigration(ctx, args) + case "IaCProvider.EnumerateByTag": + return m.invokeProviderEnumerateByTag(ctx, args) + case "ResourceDriver.Update": return m.invokeDriverUpdate(args) @@ -242,6 +245,41 @@ func (m *doModuleInstance) invokeProviderBootstrapStateBackend(ctx context.Conte return structToMap(result) } +// invokeProviderEnumerateByTag routes the "IaCProvider.EnumerateByTag" method +// to the underlying provider when it implements the opt-in +// interfaces.Enumerator. Returns codes.Unimplemented when the provider does +// not implement Enumerator so wfctl's remoteIaCProvider can fall through to +// the same skip-with-structured-log path it would take for a non-Enumerator +// in-process provider (matching infra_cleanup.go's `enum, ok := ...` gate). +// +// args contract: {"tag": } — the tag to enumerate. Empty / missing +// tag falls through to provider.EnumerateByTag's own validation (returns an +// error) so the same input contract is enforced regardless of dispatch path. +// +// response shape: {"refs": [, ...]}. Each ref is encoded via +// structToMap so the host's remoteIaCProvider can decode with anyToStruct +// (the symmetric helper used elsewhere in this plugin's dispatch surface). +func (m *doModuleInstance) invokeProviderEnumerateByTag(ctx context.Context, args map[string]any) (map[string]any, error) { + enum, ok := m.provider.(interfaces.Enumerator) + if !ok { + return nil, status.Error(codes.Unimplemented, "provider does not implement Enumerator") + } + tag := stringArg(args, "tag") + refs, err := enum.EnumerateByTag(ctx, tag) + if err != nil { + return nil, err + } + refList := make([]any, len(refs)) + for i, r := range refs { + rm, mErr := structToMap(r) + if mErr != nil { + return nil, fmt.Errorf("IaCProvider.EnumerateByTag: serialize ref: %w", mErr) + } + refList[i] = rm + } + return map[string]any{"refs": refList}, nil +} + func (m *doModuleInstance) invokeProviderRepairDirtyMigration(ctx context.Context, args map[string]any) (map[string]any, error) { repairer, ok := m.provider.(interfaces.ProviderMigrationRepairer) if !ok { diff --git a/internal/provider.go b/internal/provider.go index 78eac54..b097a44 100644 --- a/internal/provider.go +++ b/internal/provider.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "sort" + "strconv" "time" "github.com/GoCodeAlone/workflow-plugin-digitalocean/internal/drivers" @@ -337,7 +338,7 @@ func (p *DOProvider) EnumerateByTag(ctx context.Context, tag string) ([]interfac refs = append(refs, interfaces.ResourceRef{ Name: d.Name, Type: "infra.droplet", - ProviderID: fmt.Sprintf("%d", d.ID), + ProviderID: strconv.Itoa(d.ID), }) } if resp == nil || resp.Links == nil || resp.Links.IsLastPage() { @@ -377,7 +378,18 @@ func (p *DOProvider) EnumerateByTag(ctx context.Context, tag string) ([]interfac volumePage.ListOptions.Page = nextPage + 1 } - // Databases — list all, filter client-side on Tags membership. + // Databases + Caches — list all (single endpoint covers both), filter + // client-side on Tags membership, and split on EngineSlug. + // + // godo.Databases.List returns BOTH SQL-style database clusters (postgres, + // mysql, mongodb, etc.) AND managed Redis caches in a single response + // (DO models them under the same /v2/databases endpoint). The DO plugin + // splits these into separate driver types — DatabaseDriver vs + // CacheDriver — based on EngineSlug ("redis" → cache; everything else + // → database). EnumerateByTag must mirror that split so the cleanup + // dispatcher routes Delete to the correct driver and the dry-run / + // audit log surfaces the canonical resource type. Emitting every entry + // as `infra.database` would silently misclassify caches. dbPage := &godo.ListOptions{Page: 1, PerPage: 200} for { databases, resp, err := p.client.Databases.List(ctx, dbPage) @@ -388,9 +400,13 @@ func (p *DOProvider) EnumerateByTag(ctx context.Context, tag string) ([]interfac if !stringSliceContains(db.Tags, tag) { continue } + resourceType := "infra.database" + if db.EngineSlug == "redis" { + resourceType = "infra.cache" + } refs = append(refs, interfaces.ResourceRef{ Name: db.Name, - Type: "infra.database", + Type: resourceType, ProviderID: db.ID, }) } diff --git a/internal/provider_enumerator_test.go b/internal/provider_enumerator_test.go index acde8cd..a22e58f 100644 --- a/internal/provider_enumerator_test.go +++ b/internal/provider_enumerator_test.go @@ -121,7 +121,7 @@ func writeDatabases(t *testing.T, w http.ResponseWriter, databases []godo.Databa parts := make([]string, 0, len(databases)) for _, db := range databases { tags := jsonStringArray(db.Tags) - parts = append(parts, fmt.Sprintf(`{"id":%q,"name":%q,"tags":%s}`, db.ID, db.Name, tags)) + parts = append(parts, fmt.Sprintf(`{"id":%q,"name":%q,"engine":%q,"tags":%s}`, db.ID, db.Name, db.EngineSlug, tags)) } _, _ = fmt.Fprintf(w, `{"databases":[%s],"links":{},"meta":{"total":%d}}`, strings.Join(parts, ","), len(databases)) } @@ -169,8 +169,14 @@ func TestDOProvider_EnumerateByTag_ReturnsTaggedResources(t *testing.T) { {ID: "vol-bbb", Name: "other-data", Tags: []string{"unrelated"}}, // must be filtered out }, databases: []godo.Database{ - {ID: "db-ccc", Name: "bmw-pg", Tags: []string{"bmw-prod"}}, - {ID: "db-ddd", Name: "other-pg", Tags: []string{}}, // must be filtered out + // SQL-style cluster — must surface as infra.database. + {ID: "db-ccc", Name: "bmw-pg", EngineSlug: "pg", Tags: []string{"bmw-prod"}}, + // Managed Redis on the SAME /v2/databases endpoint — must surface + // as infra.cache, not infra.database. Pins the engine-split fix + // from PR review finding #2. + {ID: "redis-eee", Name: "bmw-cache", EngineSlug: "redis", Tags: []string{"bmw-prod"}}, + // Off-tag — must be filtered out. + {ID: "db-ddd", Name: "other-pg", EngineSlug: "pg", Tags: []string{}}, }, } @@ -187,6 +193,7 @@ func TestDOProvider_EnumerateByTag_ReturnsTaggedResources(t *testing.T) { want := []interfaces.ResourceRef{ {Name: "bmw-app-1", Type: "infra.droplet", ProviderID: "1001"}, {Name: "bmw-app-2", Type: "infra.droplet", ProviderID: "1002"}, + {Name: "bmw-cache", Type: "infra.cache", ProviderID: "redis-eee"}, {Name: "bmw-data", Type: "infra.volume", ProviderID: "vol-aaa"}, {Name: "bmw-pg", Type: "infra.database", ProviderID: "db-ccc"}, } @@ -238,3 +245,91 @@ func TestDOProvider_EnumerateByTag_EmptyTag(t *testing.T) { t.Errorf("error %q should mention the tag argument", err.Error()) } } + +// fakeEnumeratorProvider is an IaCProvider that ALSO implements Enumerator, +// used by the dispatch-level test below to verify the doModuleInstance +// InvokeMethod proxy reaches the underlying provider. +// +// Embeds fakeIaCProvider (defined in module_instance_test.go) for the bulk +// of the IaCProvider surface; only EnumerateByTag and call-tracking fields +// are added here. +type fakeEnumeratorProvider struct { + fakeIaCProvider + enumerateCalled bool + enumerateTag string + enumerateRefs []interfaces.ResourceRef + enumerateErr error +} + +func (f *fakeEnumeratorProvider) EnumerateByTag(_ context.Context, tag string) ([]interfaces.ResourceRef, error) { + f.enumerateCalled = true + f.enumerateTag = tag + return f.enumerateRefs, f.enumerateErr +} + +// TestDOModuleInstance_InvokeMethod_EnumerateByTag pins finding #1 of the +// PR 6b Copilot review: the doModuleInstance.InvokeMethod dispatch table +// must route "IaCProvider.EnumerateByTag" through to the underlying +// provider when it implements interfaces.Enumerator. Without this case, +// the host's remoteIaCProvider would type-assert ok=false and the cleanup +// dispatcher would silently skip every DO provider — even though +// DOProvider implements Enumerator directly. +func TestDOModuleInstance_InvokeMethod_EnumerateByTag(t *testing.T) { + prov := &fakeEnumeratorProvider{ + enumerateRefs: []interfaces.ResourceRef{ + {Name: "bmw-app", Type: "infra.droplet", ProviderID: "12345"}, + {Name: "bmw-cache", Type: "infra.cache", ProviderID: "redis-uuid"}, + }, + } + mi := &doModuleInstance{provider: prov} + + out, err := mi.InvokeMethod("IaCProvider.EnumerateByTag", map[string]any{ + "tag": "bmw-prod", + }) + if err != nil { + t.Fatalf("InvokeMethod: %v", err) + } + if !prov.enumerateCalled { + t.Fatal("provider.EnumerateByTag was not called via dispatch") + } + if prov.enumerateTag != "bmw-prod" { + t.Errorf("enumerateTag = %q, want %q", prov.enumerateTag, "bmw-prod") + } + rawRefs, ok := out["refs"].([]any) + if !ok { + t.Fatalf(`out["refs"] type = %T, want []any`, out["refs"]) + } + if len(rawRefs) != 2 { + t.Fatalf("refs length = %d, want 2: %+v", len(rawRefs), rawRefs) + } + // Sample the first ref to confirm structToMap shape — keys match the + // JSON tags on interfaces.ResourceRef. + first, ok := rawRefs[0].(map[string]any) + if !ok { + t.Fatalf("ref[0] type = %T, want map[string]any", rawRefs[0]) + } + if first["name"] != "bmw-app" || first["type"] != "infra.droplet" || first["provider_id"] != "12345" { + t.Errorf("ref[0] = %+v, want name=bmw-app type=infra.droplet provider_id=12345", first) + } +} + +// TestDOModuleInstance_InvokeMethod_EnumerateByTag_NonEnumeratorProvider +// pins the codes.Unimplemented branch. When the underlying provider does +// NOT implement interfaces.Enumerator, the dispatch must surface an +// Unimplemented gRPC error so the host's remoteIaCProvider can interpret +// it as "skip this provider" (same semantic as the in-process type-assert +// returning ok=false). Without this branch, callers couldn't distinguish +// "provider does not support this op" from "provider crashed". +func TestDOModuleInstance_InvokeMethod_EnumerateByTag_NonEnumeratorProvider(t *testing.T) { + mi := &doModuleInstance{provider: &fakeIaCProvider{}} + + _, err := mi.InvokeMethod("IaCProvider.EnumerateByTag", map[string]any{ + "tag": "bmw-prod", + }) + if err == nil { + t.Fatal("expected Unimplemented error from non-Enumerator provider; got nil") + } + if !strings.Contains(err.Error(), "Enumerator") { + t.Errorf("error %q should mention Enumerator", err.Error()) + } +}