From 9b7162fc637176a0820fd7378eb8a1971479d5fa Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Mon, 4 May 2026 04:27:25 -0400 Subject: [PATCH 1/6] =?UTF-8?q?fix(provider):=20implement=20IaCProvider.{B?= =?UTF-8?q?ootstrapStateBackend,SupportedCanonicalKeys}=20stubs=20?= =?UTF-8?q?=E2=80=94=20closes=20pre-existing=20gap=20from=20workflow#499?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the two missing IaCProvider methods that were introduced upstream after this plugin's last workflow dep bump: - `BootstrapStateBackend(ctx, cfg) (*BootstrapResult, error)` — returns (nil, nil) per the interface's documented contract for providers that do not manage a state backend. AWS state lives in S3 via a separate workflow path. - `SupportedCanonicalKeys() []string` — returns the full `interfaces.CanonicalKeys()` set per the doc's "built-in and stub providers return the full canonical key set" guidance; per-driver field validation already happens in Diff. Bumps `github.com/GoCodeAlone/workflow` from v0.3.56 to v0.19.2 because the `interfaces.BootstrapResult` return type only exists in v0.18.6+. Without the dep bump, the BootstrapStateBackend method signature would not compile against the plugin's own CI. Surfaced by workflow PR #534's new cross-plugin-build CI gate, which builds this plugin against workflow main via a `replace` directive — exactly the gap class this gate was designed to catch. Co-Authored-By: Claude Opus 4.7 (1M context) --- go.mod | 158 ++++++++-------- go.sum | 432 ++++++++++++++++++++++--------------------- provider/provider.go | 15 ++ 3 files changed, 314 insertions(+), 291 deletions(-) diff --git a/go.mod b/go.mod index bb50dfb..9de8270 100644 --- a/go.mod +++ b/go.mod @@ -3,49 +3,48 @@ module github.com/GoCodeAlone/workflow-plugin-aws go 1.26.0 require ( - github.com/GoCodeAlone/workflow v0.3.56 - github.com/aws/aws-sdk-go-v2 v1.41.4 + github.com/GoCodeAlone/workflow v0.19.2 + github.com/aws/aws-sdk-go-v2 v1.41.5 github.com/aws/aws-sdk-go-v2/config v1.32.12 github.com/aws/aws-sdk-go-v2/credentials v1.19.12 github.com/aws/aws-sdk-go-v2/service/acm v1.32.1 github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.33.8 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.55.2 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.289.1 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.0 github.com/aws/aws-sdk-go-v2/service/ecr v1.44.2 - github.com/aws/aws-sdk-go-v2/service/ecs v1.72.0 - github.com/aws/aws-sdk-go-v2/service/eks v1.80.0 + github.com/aws/aws-sdk-go-v2/service/ecs v1.76.0 + github.com/aws/aws-sdk-go-v2/service/eks v1.81.2 github.com/aws/aws-sdk-go-v2/service/elasticache v1.47.1 github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.54.6 - github.com/aws/aws-sdk-go-v2/service/iam v1.53.3 + github.com/aws/aws-sdk-go-v2/service/iam v1.53.7 github.com/aws/aws-sdk-go-v2/service/rds v1.115.0 - github.com/aws/aws-sdk-go-v2/service/route53 v1.62.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 + github.com/aws/aws-sdk-go-v2/service/route53 v1.62.5 + github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2 ) require ( 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 v0.19.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // 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.60.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect + cloud.google.com/go/storage v1.61.3 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 // indirect github.com/BurntSushi/toml v1.6.0 // indirect - github.com/DataDog/datadog-go/v5 v5.4.0 // indirect - github.com/GoCodeAlone/go-plugin v0.0.0-20260220090904-b4c35f0e4271 // indirect - github.com/GoCodeAlone/modular v1.12.3 // indirect - github.com/GoCodeAlone/modular/modules/auth v1.14.0 // indirect - github.com/GoCodeAlone/modular/modules/cache v1.14.0 // indirect - github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.7.0 // indirect - github.com/GoCodeAlone/modular/modules/jsonschema v1.14.0 // indirect - github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.7.0 // indirect - github.com/GoCodeAlone/modular/modules/scheduler v1.14.0 // indirect - github.com/GoCodeAlone/yaegi v0.17.1 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/DataDog/datadog-go/v5 v5.8.3 // indirect + github.com/GoCodeAlone/go-plugin v1.7.0 // indirect + github.com/GoCodeAlone/modular v1.13.0 // indirect + github.com/GoCodeAlone/modular/modules/auth v1.15.0 // indirect + github.com/GoCodeAlone/modular/modules/cache v1.15.0 // indirect + github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0 // indirect + github.com/GoCodeAlone/modular/modules/jsonschema v1.15.0 // indirect + github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.8.0 // indirect + github.com/GoCodeAlone/yaegi v0.17.2 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.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/IBM/sarama v1.47.0 // indirect @@ -54,19 +53,19 @@ require ( github.com/Workiva/go-datastructures v1.1.7 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // 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.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.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.20 // indirect - github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.11 // indirect - github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 // 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/internal/accept-encoding v1.13.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 // 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/s3shared v1.19.19 // indirect - github.com/aws/aws-sdk-go-v2/service/kinesis v1.38.0 // 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/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 @@ -78,13 +77,14 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudevents/sdk-go/v2 v2.16.2 // indirect - github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect github.com/containerd/errdefs v1.0.0 // indirect 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/digitalocean/godo v1.175.0 // indirect + github.com/digitalocean/godo v1.178.0 // 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 @@ -92,25 +92,27 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/eapache/go-resiliency v1.7.0 // indirect github.com/eapache/queue v1.1.0 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect - github.com/fatih/color v1.18.0 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect + github.com/expr-lang/expr v1.17.8 // indirect + github.com/fatih/color v1.19.0 // indirect 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/go-jose/go-jose/v4 v4.1.3 // 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 github.com/gobwas/glob v0.2.3 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golobby/cast v1.3.3 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/go-querystring v1.1.0 // indirect + github.com/google/go-querystring v1.2.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect - github.com/googleapis/gax-go/v2 v2.17.0 // indirect + 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/hashicorp/errwrap v1.1.0 // indirect @@ -130,13 +132,13 @@ require ( github.com/hashicorp/hcl v1.0.1-vault-7 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/memberlist v0.5.4 // indirect - github.com/hashicorp/vault/api v1.22.0 // indirect + github.com/hashicorp/vault/api v1.23.0 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/itchyny/gojq v0.12.18 // indirect github.com/itchyny/timefmt-go v0.1.7 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/pgx/v5 v5.9.1 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect @@ -144,7 +146,7 @@ require ( github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect - github.com/klauspost/compress v1.18.4 // indirect + 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 @@ -157,77 +159,77 @@ 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.49.0 // indirect + github.com/nats-io/nats.go v1.50.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 github.com/oklog/run v1.2.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pierrec/lz4/v4 v4.1.25 // indirect + github.com/pierrec/lz4/v4 v4.1.26 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/redis/go-redis/v9 v9.18.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/reugn/go-quartz v0.15.2 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect 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.0.0 // indirect - github.com/tochemey/olric v0.3.8 // indirect + github.com/tochemey/goakt/v4 v4.1.1 // 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 github.com/x448/float16 v0.8.4 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect + 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.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.66.0 // indirect - go.opentelemetry.io/otel v1.42.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect - go.opentelemetry.io/otel/metric v1.42.0 // indirect - go.opentelemetry.io/otel/sdk v1.41.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect - go.opentelemetry.io/otel/trace v1.42.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // 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 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/crypto v0.48.0 // indirect - golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect - golang.org/x/mod v0.33.0 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/oauth2 v0.35.0 // 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/net v0.53.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect - golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.42.0 // indirect - google.golang.org/api v0.269.0 // indirect - google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect - google.golang.org/grpc v1.79.3 // 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 + 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/grpc v1.80.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.67.6 // indirect + modernc.org/libc v1.70.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.45.0 // indirect + modernc.org/sqlite v1.47.0 // indirect ) diff --git a/go.sum b/go.sum index 8ec621f..278bde3 100644 --- a/go.sum +++ b/go.sum @@ -3,28 +3,28 @@ cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= -cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= +cloud.google.com/go/auth v0.19.0 h1:DGYwtbcsGsT1ywuxsIoWi1u/vlks0moIblQHgSDgQkQ= +cloud.google.com/go/auth v0.19.0/go.mod h1:2Aph7BT2KnaSFOM0JDPyiYgNh6PL9vGMiP8CUIXZ+IY= 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/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= 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/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= -cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= +cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA= +cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak= cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= 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/storage v1.60.0 h1:oBfZrSOCimggVNz9Y/bXY35uUcts7OViubeddTTVzQ8= -cloud.google.com/go/storage v1.60.0/go.mod h1:q+5196hXfejkctrnx+VYU8RKQr/L3c0cBIlrjmiAKE0= +cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg= +cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk= cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= @@ -40,30 +40,28 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/datadog-go/v5 v5.4.0 h1:Ea3eXUVwrVV28F/fo3Dr3aa+TL/Z7Xi6SUPKW8L99aI= -github.com/DataDog/datadog-go/v5 v5.4.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= -github.com/GoCodeAlone/go-plugin v0.0.0-20260220090904-b4c35f0e4271 h1:/oxxpYJ41BuK+/5Gp9c+0PHybyNFWeBHyCzkSVLCoMk= -github.com/GoCodeAlone/go-plugin v0.0.0-20260220090904-b4c35f0e4271/go.mod h1:HbGQRZUIa+jbDfjsaZIMJYvrz+LnxL0mJpggfynSTMk= -github.com/GoCodeAlone/modular v1.12.3 h1:WcNqc1ZG+Lv/xzF8wTDavGIOeAvlV4wEd5HO2mVTUwE= -github.com/GoCodeAlone/modular v1.12.3/go.mod h1:nDdyW/eJu4gDFNueb6vWwLvti3bPHSZJHkWGiwEmi2I= -github.com/GoCodeAlone/modular/modules/auth v1.14.0 h1:Y+p4/HIcxkajlcNhcPlqpwAt1SCHjB4AaDMEys50E3I= -github.com/GoCodeAlone/modular/modules/auth v1.14.0/go.mod h1:fkwPn2svDsCHBI19gtUHxo064SL+EudjB+o7VjL9ug8= -github.com/GoCodeAlone/modular/modules/cache v1.14.0 h1:ykQRwXJGXaRtAsnW9Tgs0LvXExonkKr8P7XIHxPaYdY= -github.com/GoCodeAlone/modular/modules/cache v1.14.0/go.mod h1:tcIjHJHZ5fVU8sstILrXeVQgjpZcUkErnNjRaxkBSR8= -github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.7.0 h1:clGAyaOfyDc9iY63ONfZiHReVccVhK/yH19QEb14SSI= -github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.7.0/go.mod h1:0AnfWGVmrqyv91rduc6mrPqW6WQchDAa2WtM0Qmw/WA= -github.com/GoCodeAlone/modular/modules/jsonschema v1.14.0 h1:dCiPIO+NvJPizfCeUQqGXHD1WitOVYpKuL3fxMEjRlw= -github.com/GoCodeAlone/modular/modules/jsonschema v1.14.0/go.mod h1:5Hm+R9G41wwb0hKefx9+9PMqffjU1tA7roW3t3sTaLE= -github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.7.0 h1:TtVD+tE8ABN98n50MFVyMAvMsBM4JE86KRgCRDzPDC4= -github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.7.0/go.mod h1:N7d8aSV4eqr90qjlIOs/8EmW7avt9gwX06Uh+zKDr4s= -github.com/GoCodeAlone/modular/modules/scheduler v1.14.0 h1:JSrzo4FB7uGASExv+fCLRd6pXWULV1mJYvzmM9PzUeM= -github.com/GoCodeAlone/modular/modules/scheduler v1.14.0/go.mod h1:emkR2AnilabLJZv1rOTDO9eGpRBmZs487H00Lnp9jIc= -github.com/GoCodeAlone/workflow v0.3.56 h1:jsJmVwCRLz7XOTLQNoyE7POBDW3SmqbG/18VnAzbaEc= -github.com/GoCodeAlone/workflow v0.3.56/go.mod h1:uATRqpqPubm+g2jJtYoaMTMmjlUaXnLsSdZk5of+jW0= -github.com/GoCodeAlone/yaegi v0.17.1 h1:aPAwU29L9cGceRAff02c5pjQcT5KapDB4fWFZK9tElE= -github.com/GoCodeAlone/yaegi v0.17.1/go.mod h1:z5Pr6Wse6QJcQvpgxTxzMAevFarH0N37TG88Y9dprx0= -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/DataDog/datadog-go/v5 v5.8.3 h1:s58CUJ9s8lezjhTNJO/SxkPBv2qZjS3ktpRSqGF5n0s= +github.com/DataDog/datadog-go/v5 v5.8.3/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/GoCodeAlone/go-plugin v1.7.0 h1:EwnhqPlXiNmp85S+MXnKKvm3YlfA6O4NzBb4+GSlEVY= +github.com/GoCodeAlone/go-plugin v1.7.0/go.mod h1:HbGQRZUIa+jbDfjsaZIMJYvrz+LnxL0mJpggfynSTMk= +github.com/GoCodeAlone/modular v1.13.0 h1:UfsegfAmPWcPYQOqYZFsw/LNySBmMDcthiOQe5bscqE= +github.com/GoCodeAlone/modular v1.13.0/go.mod h1:b06Pvgcc8HsGxvl30iO39zGH2jIWz467QEj2+OQL2Do= +github.com/GoCodeAlone/modular/modules/auth v1.15.0 h1:pBSkPSf4k4GLSbUQFLuPa+nFbfoJXGzSz9q89VoapZk= +github.com/GoCodeAlone/modular/modules/auth v1.15.0/go.mod h1:vmIm/LQrcURS2p02YwaELb+CZoHPtT0XB0v1i+sj9i4= +github.com/GoCodeAlone/modular/modules/cache v1.15.0 h1:6Y2EJ5S7mb/TjyG/uN6dto5VUYJNDFYULUamRsqAKvo= +github.com/GoCodeAlone/modular/modules/cache v1.15.0/go.mod h1:PRun74dRZKfqlBM+f6QrvI9oa4joUU3j1hisiLyQ+oM= +github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0 h1:buYs0TGNbAZgtTq1Qb+dfmTv3+ZOBIN0HbvVBLyNqxE= +github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0/go.mod h1:329flAKmwrPq2JEwu9iltWv6A83H/Di82Xze+kvdKDw= +github.com/GoCodeAlone/modular/modules/jsonschema v1.15.0 h1:xb1mI4NZkzvNKQ2F6nkyXQvK/kEvvfs1z7FoGf3/LRA= +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.19.2 h1:WPcF3fio/uvREvjIm+pE4AX9gCxFJo/QzblV46NTh70= +github.com/GoCodeAlone/workflow v0.19.2/go.mod h1:ypkCqXTwnIPqNjS8h38KZfwzdVsgwgkS1d6Dq0lXyQQ= +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= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4= 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/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ= @@ -88,68 +86,68 @@ github.com/alicebob/miniredis/v2 v2.36.1 h1:Dvc5oAnNOr7BIfPn7tF269U8DvRW1dBG2D5n 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/antithesishq/antithesis-sdk-go v0.6.0 h1:v/YViLhFYkZOEEof4AXjD5AgGnGM84YHF4RqEwp6I2g= -github.com/antithesishq/antithesis-sdk-go v0.6.0/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +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.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= -github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6 h1:N4lRUXZpZ1KVEUn6hxtco/1d2lgYhNn1fHkkl8WhlyQ= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +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.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.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= +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.20 h1:qi3e/dmpdONhj1RyIZdi6DKKpDXS5Lb8ftr3p7cyHJc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c= +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/service/acm v1.32.1 h1:KAK08un+8LhHlG6OEUmDTqFpQth2tYA+6EX0NNocgl4= github.com/aws/aws-sdk-go-v2/service/acm v1.32.1/go.mod h1:3sKYAgRbuBa2QMYGh/WEclwnmfx+QoPhhX25PdSQSQM= 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.11 h1:sHMyvjsgVzzYNGdy5OdlYYQsNeEk1N+aui9R8JhP9HE= -github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.11/go.mod h1:Aa0zlfmZPQJnR3M1Kn7pGXKJ9qMR5zpNHBmXcjTh8Kc= +github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.13 h1:juPaAcploym78WhVwleVHNLPmgURO6gkObC442Hal1s= +github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.13/go.mod h1:HjgDVqI6lGR0azGz1GKmZTzGHkXuzhKzRUfG/p5Ug8s= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.55.2 h1:mleWBVIxwceEzyItUVoqMFiv6TmOP6ECPoN6WB/VWXc= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.55.2/go.mod h1:cMApt548kNgu87UsBTNWVv+fpzjbUTFRSFjD1688SBs= -github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.10 h1:f8Umf89E6+QciH5Fk4J23EFgcukyX/FkVu7urYUcW/k= -github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.10/go.mod h1:AqtqfJs5i0n0/SBo3/FD9rs3vnubrigU5B8iz+5YVHU= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.289.1 h1:wcrNo0Fn5z1CvdyiZ9ep+JWrCFg8ImRFSf1mcxJnx6w= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.289.1/go.mod h1:Uy+C+Sc58jozdoL1McQr8bDsEvNFx+/nBY+vpO1HVUY= +github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.12 h1:lQTVEv/YAk8Rw1Yf4XZS/jNNxF9klCN10WcSR3xlMtU= +github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.12/go.mod h1:yoa0R6Xku788EmJYkFiARzJBxt4A3hgFjQPRmMAttr0= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.0 h1:98Miqj16un1WLNyM1RjVDhXYumhqZrQfAeG8i4jPG6o= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.0/go.mod h1:T6ndRfdhnXLIY5oKBHjYZDVj706los2zGdpThppquvA= github.com/aws/aws-sdk-go-v2/service/ecr v1.44.2 h1:USCQWra7IXZiH25796EZizvSRmJeS5wJNrdv70JIk0o= github.com/aws/aws-sdk-go-v2/service/ecr v1.44.2/go.mod h1:H8cjdbuLk7oS/NbgIixh/QIPcuUgOfeK3+FiqqrSKE0= -github.com/aws/aws-sdk-go-v2/service/ecs v1.72.0 h1:hggRKpv26DpYMOik3wWo1Ty5MkANoXhNobjfWpC3G4M= -github.com/aws/aws-sdk-go-v2/service/ecs v1.72.0/go.mod h1:pMlGFDpHoLTJOIZHGdJOAWmi+xeIlQXuFTuQxs1epYE= -github.com/aws/aws-sdk-go-v2/service/eks v1.80.0 h1:moQGV8cPbVTN7r2Xte1Mybku35QDePSJEd3onYVmBtY= -github.com/aws/aws-sdk-go-v2/service/eks v1.80.0/go.mod h1:Qg678m+87sCuJhcsZojenz8mblYG+Tq86V4m3hjVz0s= +github.com/aws/aws-sdk-go-v2/service/ecs v1.76.0 h1:a5G/TgJNrpuCjZBTf8/PTN0C2B0do/ylaYVynxPSbUQ= +github.com/aws/aws-sdk-go-v2/service/ecs v1.76.0/go.mod h1:QkWmubOYmjj3cHn7A4CoUU7BKJhVeo39Gp6NH7IyhZw= +github.com/aws/aws-sdk-go-v2/service/eks v1.81.2 h1:6c/Jkyx1gYLiZGl6VPjApViaoPiYo7TDWXCMk/ZBq6c= +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/elasticache v1.47.1 h1:DIP+2UukVi9P4PHLUF2HXpZEtkbDLmqYcWILuU/m0IQ= github.com/aws/aws-sdk-go-v2/service/elasticache v1.47.1/go.mod h1:8LEhIVZFKc9OfOrug9sIsm9lTSmiS0KT121aXUnoTPo= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.54.6 h1:fQR1aeZKaiPkNPya0JMy2nhsoqoSgIWc3/QTiTiL1K0= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.54.6/go.mod h1:oJRLDix51wqBDlP9dv+blFkvvf7HESolQz5cdhdmV4A= -github.com/aws/aws-sdk-go-v2/service/iam v1.53.3 h1:boKZv8dNdHznhAA68hb/dqFz5pxoWmRAOJr9LtscVCI= -github.com/aws/aws-sdk-go-v2/service/iam v1.53.3/go.mod h1:E0QHh3aEwxYb7xshjvxYDELiOda7KBYJ77e/TvGhpcM= +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/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11/go.mod h1:aEUS4WrNk/+FxkBZZa7tVgp4pGH+kFGW40Y8rCPqt5g= +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/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.38.0 h1:8acX21qNMUs/QTHB3iNpixJViYsu7sSWSmZVzdriRcw= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.38.0/go.mod h1:No5RhgJ+mKYZKCSrJQOdDtyz+8dAfNaeYwMnTJBJV/Q= +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= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.4/go.mod h1:O2L6vGm4xacEuN2otHFMgn7yXXlgzFKzxrba0fy/yk8= github.com/aws/aws-sdk-go-v2/service/rds v1.115.0 h1:oNl6YghOtxu3MiFk1tQ86QlrYMIEJazGUDbBCg9nxLA= github.com/aws/aws-sdk-go-v2/service/rds v1.115.0/go.mod h1:JBRYWpz5oXQtHgQC+X8LX9lh0FBCwRHJlWEIT+TTLaE= -github.com/aws/aws-sdk-go-v2/service/route53 v1.62.2 h1:zoD/SoiVQi8l8tuQn//VexrXS2yorg/+717JNA4Ble8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.62.2/go.mod h1:Ll1DCasPTBFtHK5t/U5WIwGIyRuY3xY+x8/LmqIlqpM= -github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 h1:4ExZyubQ6LQQVuF2Qp9OsfEvsTdAWh5Gfwf6PgIdLdk= -github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4/go.mod h1:NF3JcMGOiARAss1ld3WGORCw71+4ExDD2cbbdKS5PpA= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.5 h1:Z+/OLsb85Kpq7TVLCspskqePaf68Tdv6GfmJP4kH6i0= +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= @@ -184,8 +182,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/cloudevents/sdk-go/v2 v2.16.2 h1:ZYDFrYke4FD+jM8TZTJJO6JhKHzOQl2oqpFK1D+NnQM= github.com/cloudevents/sdk-go/v2 v2.16.2/go.mod h1:laOcGImm4nVJEU+PHnUrKL56CKmRL65RlQF0kRmW/kg= -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/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -207,6 +205,8 @@ github.com/cucumber/godog v0.15.1 h1:rb/6oHDdvVZKS66hrhpjFQFHjthFSrQBCOI1LwshNTI github.com/cucumber/godog v0.15.1/go.mod h1:qju+SQDewOljHuq9NSM66s0xEhogx0q30flfxL4WUk8= github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= +github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= +github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -215,8 +215,8 @@ github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+Zlfu github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= 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.175.0 h1:tpfwJFkBzpePxvvFazOn69TXctdxuFlOs7DMVXsI7oU= -github.com/digitalocean/godo v1.175.0/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU= +github.com/digitalocean/godo v1.178.0 h1:+B4xGOaoFwwwpM7TKhoyGHdmFg5eF9zDB1YfOLvNJ2E= +github.com/digitalocean/godo v1.178.0/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= @@ -239,15 +239,17 @@ github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bF github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= -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/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -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/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= +github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= +github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowchartsman/retry v1.2.0 h1:qDhlw6RNufXz6RGr+IiYimFpMMkt77SUSHY5tgFaUCU= @@ -260,8 +262,8 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/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.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/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-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -310,8 +312,10 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= -github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -339,13 +343,13 @@ github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -357,18 +361,18 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 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.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= -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/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.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE= +github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 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.4 h1:AJkZp6qzgAYcMIU0+CjJ0Rb7+byfh0dazFK/gzlOcJk= -github.com/hashicorp/consul/api v1.33.4/go.mod h1:BkH3WEUzsnWvJJaHoDqKqoe2Q2EIixx7Gjj6MTwYnOA= +github.com/hashicorp/consul/api v1.33.7 h1:apLZVzX7O7BLgHyh4pvczcsBzPmYSVXGKZQbOaA1ae0= +github.com/hashicorp/consul/api v1.33.7/go.mod h1:SjR3cjwCUSLLDfVw5dFg76rnnKjOySxr8W8lC5s01C8= 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= @@ -380,8 +384,8 @@ github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= -github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= +github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+Nyo= +github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y= github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= github.com/hashicorp/go-msgpack/v2 v2.1.5 h1:Ue879bPnutj/hXfmUk6s/jtIK90XxgiUIcXRl656T44= @@ -416,8 +420,8 @@ github.com/hashicorp/memberlist v0.5.4 h1:40YY+3qq2tAUhZIMEK8kqusKZBBjdwJ3NUjvYk github.com/hashicorp/memberlist v0.5.4/go.mod h1:OgN6xiIo6RlHUWk+ALjP9e32xWCoQrsOCmHrWCm2MWA= github.com/hashicorp/serf v0.10.2 h1:m5IORhuNSjaxeljg5DeQVDlQyVkhRIjJDimbkCa8aAc= github.com/hashicorp/serf v0.10.2/go.mod h1:T1CmSGfSeGfnfNy/w0odXQUR1rfECGd2Qdsp84DjOiY= -github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= -github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= +github.com/hashicorp/vault/api v1.23.0 h1:gXgluBsSECfRWTSW9niY2jwg2e9mMJc4WoHNv4g3h6A= +github.com/hashicorp/vault/api v1.23.0/go.mod h1:zransKiB9ftp+kgY8ydjnvCU7Wk8i9L0DYWpXeMj9ko= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc= @@ -428,8 +432,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= @@ -457,8 +461,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V 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/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= -github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +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= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -474,8 +478,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-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM= -github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +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/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= @@ -489,8 +493,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D 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= -github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 h1:KGuD/pM2JpL9FAYvBrnBBeENKZNh6eNtjqytV6TYjnk= -github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/minio/highwayhash v1.0.4 h1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4= +github.com/minio/highwayhash v1.0.4/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= @@ -501,8 +505,8 @@ 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/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +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= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= @@ -529,12 +533,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= -github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.12.4 h1:ZnT10v2LU2Xcoiy8ek9X6Se4YG8EuMfIfvAEuFVx1Ts= -github.com/nats-io/nats-server/v2 v2.12.4/go.mod h1:5MCp/pqm5SEfsvVZ31ll1088ZTwEUdvRX1Hmh/mTTDg= -github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE= -github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw= +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/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= @@ -550,8 +554,8 @@ github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgr github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0= -github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= +github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -582,15 +586,15 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= @@ -606,8 +610,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +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= @@ -638,12 +642,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.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= -github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= -github.com/testcontainers/testcontainers-go/modules/consul v0.40.0 h1:dILouyNaXHjCGKiFvtAFgXJYJ4fGH+WmwQulfj/k6bI= -github.com/testcontainers/testcontainers-go/modules/consul v0.40.0/go.mod h1:bQNH35oDTt9ImPI2m+Y2Nf+cthcOGa/z/5c5vrgXc5E= -github.com/testcontainers/testcontainers-go/modules/etcd v0.40.0 h1:9uZrotowD6Z9qgpd8w46UXi1x5bkhOcpveK5rvWy5u0= -github.com/testcontainers/testcontainers-go/modules/etcd v0.40.0/go.mod h1:z5saei5a/cpuXYz3MJqJ91RMBYOqw7OXDueN8XKoALA= +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/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= @@ -657,10 +661,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.0.0 h1:+gYpo+54iWvlLUzppi/11fcVN6+r5Cr3F0nh3ggTrnA= -github.com/tochemey/goakt/v4 v4.0.0/go.mod h1:0lyUm16yq2rc7b3NxPSmkk+wUD4FFF0/YlTDIefaVKs= -github.com/tochemey/olric v0.3.8 h1:t9LMoyAcoeCfn8n9NRY6fCIJlfok06mzoagDHgICM48= -github.com/tochemey/olric v0.3.8/go.mod h1:bWN6wnNHaVFqz1KGWbvORsC6sfSLtncFEM19dUJHMdQ= +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/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= @@ -688,44 +692,46 @@ github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs= +github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= 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.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM= -go.etcd.io/etcd/api/v3 v3.6.8/go.mod h1:qyQj1HZPUV3B5cbAL8scG62+fyz5dSxxu0w8pn28N6Q= -go.etcd.io/etcd/client/pkg/v3 v3.6.8 h1:Qs/5C0LNFiqXxYf2GU8MVjYUEXJ6sZaYOz0zEqQgy50= -go.etcd.io/etcd/client/pkg/v3 v3.6.8/go.mod h1:GsiTRUZE2318PggZkAo6sWb6l8JLVrnckTNfbG8PWtw= -go.etcd.io/etcd/client/v3 v3.6.8 h1:B3G76t1UykqAOrbio7s/EPatixQDkQBevN8/mwiplrY= -go.etcd.io/etcd/client/v3 v3.6.8/go.mod h1:MVG4BpSIuumPi+ELF7wYtySETmoTWBHVcDoHdVupwt8= +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.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/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.66.0 h1:PnV4kVnw0zOmwwFkAzCN5O07fw1YOIQor120zrh0AVo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0/go.mod h1:ofAwF4uinaf8SXdVzzbL4OsxJ3VfeEg3f/F6CeF49/Y= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= -go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= -go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= -go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU= +go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -736,8 +742,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -746,15 +752,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -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-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +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/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.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +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/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= @@ -769,11 +775,11 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b 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= -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/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -811,47 +817,47 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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= 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= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -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.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +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/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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.269.0 h1:qDrTOxKUQ/P0MveH6a7vZ+DNHxJQjtGm/uvdbdGXCQg= -google.golang.org/api v0.269.0/go.mod h1:N8Wpcu23Tlccl0zSHEkcAZQKDLdquxK+l9r2LkwAauE= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA= +google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= -google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= -google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= -google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= -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.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +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/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= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -880,32 +886,32 @@ 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.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw= -k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60= -k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8= -k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o= -k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY= -k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= -k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +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/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/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= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= -modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= -modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= -modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= +modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= -modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= -modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= +modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= +modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= @@ -914,8 +920,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.45.0 h1:r51cSGzKpbptxnby+EIIz5fop4VuE4qFoVEjNvWoObs= -modernc.org/sqlite v1.45.0/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk= +modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/provider/provider.go b/provider/provider.go index 6723a85..3755ead 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -378,6 +378,21 @@ func (p *AWSProvider) ResolveSizing(resourceType string, size interfaces.Size, h return resolveSizing(resourceType, size, hints) } +// SupportedCanonicalKeys returns the full canonical IaC key set. Per the +// interfaces.IaCProvider doc, "built-in and stub providers return the full +// canonical key set"; this provider does not currently restrict the keys it +// understands at the provider level (per-driver validation happens in Diff). +func (p *AWSProvider) SupportedCanonicalKeys() []string { + return interfaces.CanonicalKeys() +} + +// BootstrapStateBackend is a no-op for this provider; state backends (S3 / GCS / Azure Blob) +// are managed via separate workflow paths rather than the provider interface. Returns (nil, nil) +// per interfaces.IaCProvider's documented contract for providers that do not manage state. +func (p *AWSProvider) BootstrapStateBackend(ctx context.Context, cfg map[string]any) (*interfaces.BootstrapResult, error) { + return nil, nil +} + func (p *AWSProvider) Close() error { p.mu.Lock() defer p.mu.Unlock() From cedc2241ea9c9b14e9923ba1c3614dec463ee38c Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Mon, 4 May 2026 10:22:10 -0400 Subject: [PATCH 2/6] =?UTF-8?q?fix(provider):=20address=20Copilot=20review?= =?UTF-8?q?=20=E2=80=94=20AWS-specific=20doc=20+=20underscore=20params?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replaced multi-cloud "S3/GCS/Azure Blob" doc copy with AWS-specific reference. - SupportedCanonicalKeys doc no longer claims per-driver Diff validation (drivers compare overlapping fields; do not reject unsupported keys). - BootstrapStateBackend params switched to _ per repo's no-op convention. Co-Authored-By: Claude Opus 4.7 (1M context) --- provider/provider.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/provider/provider.go b/provider/provider.go index 3755ead..7e9bdb6 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -380,16 +380,17 @@ func (p *AWSProvider) ResolveSizing(resourceType string, size interfaces.Size, h // SupportedCanonicalKeys returns the full canonical IaC key set. Per the // interfaces.IaCProvider doc, "built-in and stub providers return the full -// canonical key set"; this provider does not currently restrict the keys it -// understands at the provider level (per-driver validation happens in Diff). +// canonical key set"; this provider's drivers do not currently reject +// unsupported keys at the provider level. func (p *AWSProvider) SupportedCanonicalKeys() []string { return interfaces.CanonicalKeys() } -// BootstrapStateBackend is a no-op for this provider; state backends (S3 / GCS / Azure Blob) -// are managed via separate workflow paths rather than the provider interface. Returns (nil, nil) -// per interfaces.IaCProvider's documented contract for providers that do not manage state. -func (p *AWSProvider) BootstrapStateBackend(ctx context.Context, cfg map[string]any) (*interfaces.BootstrapResult, error) { +// BootstrapStateBackend is a no-op for this provider; AWS S3 state backends +// are managed via separate workflow paths rather than the provider interface. +// Returns (nil, nil) per interfaces.IaCProvider's documented contract for +// providers that do not manage state. +func (p *AWSProvider) BootstrapStateBackend(_ context.Context, _ map[string]any) (*interfaces.BootstrapResult, error) { return nil, nil } From 179fa71bdb4d542b582eed33e07c390ee7fe42fc Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 13 May 2026 06:49:30 -0400 Subject: [PATCH 3/6] =?UTF-8?q?feat(autoscaling=5Fgroup):=20add=20infra.au?= =?UTF-8?q?toscaling=5Fgroup=20driver=20=E2=80=94=20closes=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wraps AWS Application Auto Scaling (RegisterScalableTarget, DescribeScalableTargets, PutScalingPolicy, DescribeScalingPolicies, DeleteScalingPolicy, DeregisterScalableTarget). Supports ECS, DynamoDB, RDS, AppStream and any Application Auto Scaling namespace. Key design points: - ProviderID encodes "namespace|resource_id|dimension" for precise multi-key lookup across Read, Update, and Delete. - Scaling policies: both TargetTrackingScaling and StepScaling policy types are supported with distinct config shapes per adversarial check. - Update is idempotent (RegisterScalableTarget) and performs delete-on- removal for stale policies before upserting desired ones. - 24 unit tests covering Create/Read/Update/Delete happy paths, missing required fields, API errors, stale-policy removal, Diff, HealthCheck, SensitiveKeys, and interface compliance. Blocker note: workflow#653 Phase 1 must land before this driver can be exercised via wfctl strict-contracts gate. Co-Authored-By: Claude Sonnet 4.6 --- drivers/autoscaling_group.go | 432 +++++++++++++++++++++++++ drivers/autoscaling_group_test.go | 513 ++++++++++++++++++++++++++++++ go.mod | 10 +- go.sum | 20 +- plugin.json | 3 +- provider/provider.go | 2 + provider/provider_test.go | 5 +- 7 files changed, 967 insertions(+), 18 deletions(-) create mode 100644 drivers/autoscaling_group.go create mode 100644 drivers/autoscaling_group_test.go diff --git a/drivers/autoscaling_group.go b/drivers/autoscaling_group.go new file mode 100644 index 0000000..f94f4ac --- /dev/null +++ b/drivers/autoscaling_group.go @@ -0,0 +1,432 @@ +package drivers + +import ( + "context" + "fmt" + "strings" + + awssdk "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/applicationautoscaling" + aastypes "github.com/aws/aws-sdk-go-v2/service/applicationautoscaling/types" + + "github.com/GoCodeAlone/workflow/interfaces" +) + +// AutoScalingClient is the subset of Application Auto Scaling API used by AutoScalingGroupDriver. +type AutoScalingClient interface { + RegisterScalableTarget(ctx context.Context, params *applicationautoscaling.RegisterScalableTargetInput, optFns ...func(*applicationautoscaling.Options)) (*applicationautoscaling.RegisterScalableTargetOutput, error) + DescribeScalableTargets(ctx context.Context, params *applicationautoscaling.DescribeScalableTargetsInput, optFns ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DescribeScalableTargetsOutput, error) + DescribeScalingPolicies(ctx context.Context, params *applicationautoscaling.DescribeScalingPoliciesInput, optFns ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DescribeScalingPoliciesOutput, error) + PutScalingPolicy(ctx context.Context, params *applicationautoscaling.PutScalingPolicyInput, optFns ...func(*applicationautoscaling.Options)) (*applicationautoscaling.PutScalingPolicyOutput, error) + DeleteScalingPolicy(ctx context.Context, params *applicationautoscaling.DeleteScalingPolicyInput, optFns ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DeleteScalingPolicyOutput, error) + DeregisterScalableTarget(ctx context.Context, params *applicationautoscaling.DeregisterScalableTargetInput, optFns ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DeregisterScalableTargetOutput, error) +} + +// AutoScalingGroupDriver manages Application Auto Scaling targets (infra.autoscaling_group). +// +// Config keys: +// - service_namespace (string, required) — e.g., "ecs", "dynamodb", "rds" +// - resource_id (string, required) — e.g., "service/cluster/service-name" +// - scalable_dimension (string, required) — e.g., "ecs:service:DesiredCount" +// - min_capacity (int, required) +// - max_capacity (int, required) +// - role_arn (string, optional) +// - policies ([]any, optional) — each map with keys: policy_name, policy_type, +// target_value (TargetTracking), scale_in_cooldown, scale_out_cooldown (StepScaling) +type AutoScalingGroupDriver struct { + client AutoScalingClient +} + +// NewAutoScalingGroupDriver creates an AutoScalingGroupDriver from an AWS config. +func NewAutoScalingGroupDriver(cfg awssdk.Config) *AutoScalingGroupDriver { + return &AutoScalingGroupDriver{client: applicationautoscaling.NewFromConfig(cfg)} +} + +// NewAutoScalingGroupDriverWithClient creates an AutoScalingGroupDriver with a custom client (for tests). +func NewAutoScalingGroupDriverWithClient(client AutoScalingClient) *AutoScalingGroupDriver { + return &AutoScalingGroupDriver{client: client} +} + +func (d *AutoScalingGroupDriver) ResourceType() string { return "infra.autoscaling_group" } + +// Create registers a new scalable target and applies any declared scaling policies. +func (d *AutoScalingGroupDriver) Create(ctx context.Context, spec interfaces.ResourceSpec) (*interfaces.ResourceOutput, error) { + ns, resourceID, dim, err := requiredAutoScalingFields(spec) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: create %q: %w", spec.Name, err) + } + + minCap := int32(intProp(spec.Config, "min_capacity", 0)) + maxCap := int32(intProp(spec.Config, "max_capacity", 1)) + roleARN, _ := spec.Config["role_arn"].(string) + + in := &applicationautoscaling.RegisterScalableTargetInput{ + ServiceNamespace: aastypes.ServiceNamespace(ns), + ResourceId: awssdk.String(resourceID), + ScalableDimension: aastypes.ScalableDimension(dim), + MinCapacity: awssdk.Int32(minCap), + MaxCapacity: awssdk.Int32(maxCap), + } + if roleARN != "" { + in.RoleARN = awssdk.String(roleARN) + } + + out, err := d.client.RegisterScalableTarget(ctx, in) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: create %q: %w", spec.Name, err) + } + + targetARN := awssdk.ToString(out.ScalableTargetARN) + providerID := encodeProviderID(ns, resourceID, dim) + + if err := d.syncPolicies(ctx, spec, ns, resourceID, dim, nil); err != nil { + return nil, fmt.Errorf("autoscaling_group: create %q: apply policies: %w", spec.Name, err) + } + + return d.buildOutput(spec.Name, targetARN, providerID, int(minCap), int(maxCap), nil), nil +} + +// Read describes the scalable target and its policies. +func (d *AutoScalingGroupDriver) Read(ctx context.Context, ref interfaces.ResourceRef) (*interfaces.ResourceOutput, error) { + ns, resourceID, dim := decodeProviderID(ref.ProviderID) + + // If ProviderID is not set, we can only look up by namespace annotation stored in the ref. + // Fall back to the ref type context — caller must ensure ProviderID is populated for precise lookup. + if ns == "" { + // Use ref.Name as a best-effort resource_id if no ProviderID. + ns = "ecs" // default namespace for graceful degradation + } + + out, err := d.client.DescribeScalableTargets(ctx, &applicationautoscaling.DescribeScalableTargetsInput{ + ServiceNamespace: aastypes.ServiceNamespace(ns), + ResourceIds: resourceIDFilter(resourceID, ref.Name), + ScalableDimension: func() aastypes.ScalableDimension { + if dim != "" { + return aastypes.ScalableDimension(dim) + } + return "" + }(), + }) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: describe %q: %w", ref.Name, err) + } + if len(out.ScalableTargets) == 0 { + return nil, fmt.Errorf("autoscaling_group: scalable target %q not found", ref.Name) + } + target := out.ScalableTargets[0] + + policyNames := d.readPolicyNames(ctx, target) + minCap := int(awssdk.ToInt32(target.MinCapacity)) + maxCap := int(awssdk.ToInt32(target.MaxCapacity)) + providerID := encodeProviderID(string(target.ServiceNamespace), awssdk.ToString(target.ResourceId), string(target.ScalableDimension)) + targetARN := awssdk.ToString(target.ScalableTargetARN) + + return d.buildOutput(ref.Name, targetARN, providerID, minCap, maxCap, policyNames), nil +} + +// Update re-registers the scalable target (idempotent) and reconciles scaling policies. +func (d *AutoScalingGroupDriver) Update(ctx context.Context, ref interfaces.ResourceRef, spec interfaces.ResourceSpec) (*interfaces.ResourceOutput, error) { + ns, resourceID, dim, err := requiredAutoScalingFields(spec) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: update %q: %w", ref.Name, err) + } + + minCap := int32(intProp(spec.Config, "min_capacity", 0)) + maxCap := int32(intProp(spec.Config, "max_capacity", 1)) + roleARN, _ := spec.Config["role_arn"].(string) + + in := &applicationautoscaling.RegisterScalableTargetInput{ + ServiceNamespace: aastypes.ServiceNamespace(ns), + ResourceId: awssdk.String(resourceID), + ScalableDimension: aastypes.ScalableDimension(dim), + MinCapacity: awssdk.Int32(minCap), + MaxCapacity: awssdk.Int32(maxCap), + } + if roleARN != "" { + in.RoleARN = awssdk.String(roleARN) + } + + out, err := d.client.RegisterScalableTarget(ctx, in) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: update %q: %w", ref.Name, err) + } + + // Fetch current live policies so we can delete any that are removed from the spec. + livePolicies := d.fetchLivePolicies(ctx, ns, resourceID, dim) + + if err := d.syncPolicies(ctx, spec, ns, resourceID, dim, livePolicies); err != nil { + return nil, fmt.Errorf("autoscaling_group: update %q: sync policies: %w", ref.Name, err) + } + + targetARN := awssdk.ToString(out.ScalableTargetARN) + providerID := encodeProviderID(ns, resourceID, dim) + return d.buildOutput(ref.Name, targetARN, providerID, int(minCap), int(maxCap), nil), nil +} + +// Delete removes all scaling policies then deregisters the scalable target. +func (d *AutoScalingGroupDriver) Delete(ctx context.Context, ref interfaces.ResourceRef) error { + ns, resourceID, dim := decodeProviderID(ref.ProviderID) + if ns == "" { + return fmt.Errorf("autoscaling_group: delete %q: ProviderID is required (format: namespace|resource_id|dimension)", ref.Name) + } + + // Delete all existing policies first. + livePolicies := d.fetchLivePolicies(ctx, ns, resourceID, dim) + for _, p := range livePolicies { + if _, err := d.client.DeleteScalingPolicy(ctx, &applicationautoscaling.DeleteScalingPolicyInput{ + PolicyName: p.PolicyName, + ServiceNamespace: p.ServiceNamespace, + ResourceId: p.ResourceId, + ScalableDimension: p.ScalableDimension, + }); err != nil { + return fmt.Errorf("autoscaling_group: delete policy %q for %q: %w", awssdk.ToString(p.PolicyName), ref.Name, err) + } + } + + if _, err := d.client.DeregisterScalableTarget(ctx, &applicationautoscaling.DeregisterScalableTargetInput{ + ServiceNamespace: aastypes.ServiceNamespace(ns), + ResourceId: awssdk.String(resourceID), + ScalableDimension: aastypes.ScalableDimension(dim), + }); err != nil { + return fmt.Errorf("autoscaling_group: deregister %q: %w", ref.Name, err) + } + return nil +} + +// Diff computes whether the desired spec diverges from the current output. +func (d *AutoScalingGroupDriver) Diff(_ context.Context, desired interfaces.ResourceSpec, current *interfaces.ResourceOutput) (*interfaces.DiffResult, error) { + if current == nil { + return &interfaces.DiffResult{NeedsUpdate: true}, nil + } + // Compare the fields we surface in outputs. + want := map[string]any{ + "min_capacity": intProp(desired.Config, "min_capacity", 0), + "max_capacity": intProp(desired.Config, "max_capacity", 1), + } + changes := diffOutputs(want, current.Outputs) + return &interfaces.DiffResult{NeedsUpdate: len(changes) > 0, Changes: changes}, nil +} + +// HealthCheck returns healthy if the scalable target is readable. +func (d *AutoScalingGroupDriver) HealthCheck(ctx context.Context, ref interfaces.ResourceRef) (*interfaces.HealthResult, error) { + _, err := d.Read(ctx, ref) + if err != nil { + return &interfaces.HealthResult{Healthy: false, Message: err.Error()}, nil + } + return &interfaces.HealthResult{Healthy: true, Message: "scalable target registered"}, nil +} + +// Scale updates the max_capacity of the scalable target to the given replica count. +func (d *AutoScalingGroupDriver) Scale(_ context.Context, _ interfaces.ResourceRef, _ int) (*interfaces.ResourceOutput, error) { + return nil, fmt.Errorf("autoscaling_group: use Update with max_capacity to resize") +} + +// SensitiveKeys returns output keys whose values should be masked in logs. +func (d *AutoScalingGroupDriver) SensitiveKeys() []string { return nil } + +// ---- helpers ---- + +// requiredAutoScalingFields extracts and validates the three required config fields. +func requiredAutoScalingFields(spec interfaces.ResourceSpec) (ns, resourceID, dim string, err error) { + ns, _ = spec.Config["service_namespace"].(string) + if ns == "" { + return "", "", "", fmt.Errorf("service_namespace is required") + } + resourceID, _ = spec.Config["resource_id"].(string) + if resourceID == "" { + return "", "", "", fmt.Errorf("resource_id is required") + } + dim, _ = spec.Config["scalable_dimension"].(string) + if dim == "" { + return "", "", "", fmt.Errorf("scalable_dimension is required") + } + return ns, resourceID, dim, nil +} + +// encodeProviderID packs the three scalable-target identifiers into a single string. +// Format: "namespace|resource_id|scalable_dimension" +func encodeProviderID(ns, resourceID, dim string) string { + return ns + "|" + resourceID + "|" + dim +} + +// decodeProviderID unpacks a ProviderID produced by encodeProviderID. +func decodeProviderID(providerID string) (ns, resourceID, dim string) { + parts := strings.SplitN(providerID, "|", 3) + if len(parts) != 3 { + return "", "", "" + } + return parts[0], parts[1], parts[2] +} + +// resourceIDFilter returns a []string filter suitable for DescribeScalableTargets ResourceIds. +// If resourceID is non-empty, filter by it; otherwise use the ref name as a fallback. +func resourceIDFilter(resourceID, refName string) []string { + if resourceID != "" { + return []string{resourceID} + } + if refName != "" { + return []string{refName} + } + return nil +} + +// buildOutput constructs a ResourceOutput from a scalable target's fields. +func (d *AutoScalingGroupDriver) buildOutput(name, targetARN, providerID string, minCap, maxCap int, policyNames []string) *interfaces.ResourceOutput { + outputs := map[string]any{ + "min_capacity": minCap, + "max_capacity": maxCap, + } + if targetARN != "" { + outputs["arn"] = targetARN + } + if len(policyNames) > 0 { + outputs["policy_names"] = policyNames + } + return &interfaces.ResourceOutput{ + Name: name, + Type: "infra.autoscaling_group", + ProviderID: providerID, + Outputs: outputs, + Status: "running", + } +} + +// fetchLivePolicies returns the current scaling policies for a scalable target. +// Errors are swallowed — callers handle absence gracefully. +func (d *AutoScalingGroupDriver) fetchLivePolicies(ctx context.Context, ns, resourceID, dim string) []aastypes.ScalingPolicy { + out, err := d.client.DescribeScalingPolicies(ctx, &applicationautoscaling.DescribeScalingPoliciesInput{ + ServiceNamespace: aastypes.ServiceNamespace(ns), + ResourceId: awssdk.String(resourceID), + ScalableDimension: aastypes.ScalableDimension(dim), + }) + if err != nil || out == nil { + return nil + } + return out.ScalingPolicies +} + +// readPolicyNames fetches policy names for a ScalableTarget; returns nil on error. +func (d *AutoScalingGroupDriver) readPolicyNames(ctx context.Context, target aastypes.ScalableTarget) []string { + policies := d.fetchLivePolicies(ctx, string(target.ServiceNamespace), awssdk.ToString(target.ResourceId), string(target.ScalableDimension)) + var names []string + for _, p := range policies { + names = append(names, awssdk.ToString(p.PolicyName)) + } + return names +} + +// syncPolicies reconciles desired policies with live policies: +// 1. Delete live policies whose policy_name is absent from the desired list. +// 2. PutScalingPolicy for each desired policy. +func (d *AutoScalingGroupDriver) syncPolicies(ctx context.Context, spec interfaces.ResourceSpec, ns, resourceID, dim string, livePolicies []aastypes.ScalingPolicy) error { + desired := parsePolicies(spec.Config) + + // Build set of desired policy names. + desiredNames := make(map[string]struct{}, len(desired)) + for _, p := range desired { + desiredNames[p.policyName] = struct{}{} + } + + // Delete stale live policies. + for _, lp := range livePolicies { + if _, keep := desiredNames[awssdk.ToString(lp.PolicyName)]; !keep { + if _, err := d.client.DeleteScalingPolicy(ctx, &applicationautoscaling.DeleteScalingPolicyInput{ + PolicyName: lp.PolicyName, + ServiceNamespace: lp.ServiceNamespace, + ResourceId: lp.ResourceId, + ScalableDimension: lp.ScalableDimension, + }); err != nil { + return fmt.Errorf("delete stale policy %q: %w", awssdk.ToString(lp.PolicyName), err) + } + } + } + + // Upsert desired policies. + for _, p := range desired { + if err := d.putPolicy(ctx, ns, resourceID, dim, p); err != nil { + return err + } + } + return nil +} + +// policySpec is an internal parsed representation of one policy entry. +type policySpec struct { + policyName string + policyType string + targetValue float64 + scaleInCooldown int32 + scaleOutCooldown int32 +} + +// parsePolicies extracts the policies slice from config. +func parsePolicies(config map[string]any) []policySpec { + raw, ok := config["policies"] + if !ok { + return nil + } + items, ok := raw.([]any) + if !ok { + return nil + } + var result []policySpec + for _, item := range items { + m, ok := item.(map[string]any) + if !ok { + continue + } + p := policySpec{} + p.policyName, _ = m["policy_name"].(string) + p.policyType, _ = m["policy_type"].(string) + if tv, ok := m["target_value"].(float64); ok { + p.targetValue = tv + } + p.scaleInCooldown = int32(intProp(m, "scale_in_cooldown", 300)) + p.scaleOutCooldown = int32(intProp(m, "scale_out_cooldown", 300)) + if p.policyName != "" { + result = append(result, p) + } + } + return result +} + +// putPolicy calls PutScalingPolicy for a single policySpec. +// Both TargetTrackingScaling and StepScaling are supported. +func (d *AutoScalingGroupDriver) putPolicy(ctx context.Context, ns, resourceID, dim string, p policySpec) error { + in := &applicationautoscaling.PutScalingPolicyInput{ + PolicyName: awssdk.String(p.policyName), + ServiceNamespace: aastypes.ServiceNamespace(ns), + ResourceId: awssdk.String(resourceID), + ScalableDimension: aastypes.ScalableDimension(dim), + } + + switch p.policyType { + case "TargetTrackingScaling": + in.PolicyType = aastypes.PolicyTypeTargetTrackingScaling + cfg := &aastypes.TargetTrackingScalingPolicyConfiguration{ + TargetValue: awssdk.Float64(p.targetValue), + ScaleInCooldown: awssdk.Int32(p.scaleInCooldown), + ScaleOutCooldown: awssdk.Int32(p.scaleOutCooldown), + } + in.TargetTrackingScalingPolicyConfiguration = cfg + case "StepScaling": + in.PolicyType = aastypes.PolicyTypeStepScaling + cfg := &aastypes.StepScalingPolicyConfiguration{ + Cooldown: awssdk.Int32(p.scaleOutCooldown), + } + in.StepScalingPolicyConfiguration = cfg + default: + // Unknown policy type: default to TargetTracking so the API can validate. + in.PolicyType = aastypes.PolicyTypeTargetTrackingScaling + in.TargetTrackingScalingPolicyConfiguration = &aastypes.TargetTrackingScalingPolicyConfiguration{ + TargetValue: awssdk.Float64(p.targetValue), + } + } + + if _, err := d.client.PutScalingPolicy(ctx, in); err != nil { + return fmt.Errorf("put policy %q: %w", p.policyName, err) + } + return nil +} + +var _ interfaces.ResourceDriver = (*AutoScalingGroupDriver)(nil) diff --git a/drivers/autoscaling_group_test.go b/drivers/autoscaling_group_test.go new file mode 100644 index 0000000..5c32e0d --- /dev/null +++ b/drivers/autoscaling_group_test.go @@ -0,0 +1,513 @@ +package drivers_test + +import ( + "context" + "fmt" + "testing" + + awssdk "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/applicationautoscaling" + aastypes "github.com/aws/aws-sdk-go-v2/service/applicationautoscaling/types" + + "github.com/GoCodeAlone/workflow-plugin-aws/drivers" + "github.com/GoCodeAlone/workflow/interfaces" +) + +// mockAutoScalingClient implements AutoScalingClient for tests. +type mockAutoScalingClient struct { + registerOut *applicationautoscaling.RegisterScalableTargetOutput + registerErr error + describeOut *applicationautoscaling.DescribeScalableTargetsOutput + describeErr error + describePoliciesOut *applicationautoscaling.DescribeScalingPoliciesOutput + describePoliciesErr error + putPolicyOut *applicationautoscaling.PutScalingPolicyOutput + putPolicyErr error + deletePolicyErr error + deregisterErr error +} + +func (m *mockAutoScalingClient) RegisterScalableTarget(_ context.Context, _ *applicationautoscaling.RegisterScalableTargetInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.RegisterScalableTargetOutput, error) { + return m.registerOut, m.registerErr +} +func (m *mockAutoScalingClient) DescribeScalableTargets(_ context.Context, _ *applicationautoscaling.DescribeScalableTargetsInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DescribeScalableTargetsOutput, error) { + return m.describeOut, m.describeErr +} +func (m *mockAutoScalingClient) DescribeScalingPolicies(_ context.Context, _ *applicationautoscaling.DescribeScalingPoliciesInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DescribeScalingPoliciesOutput, error) { + return m.describePoliciesOut, m.describePoliciesErr +} +func (m *mockAutoScalingClient) PutScalingPolicy(_ context.Context, _ *applicationautoscaling.PutScalingPolicyInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.PutScalingPolicyOutput, error) { + return m.putPolicyOut, m.putPolicyErr +} +func (m *mockAutoScalingClient) DeleteScalingPolicy(_ context.Context, _ *applicationautoscaling.DeleteScalingPolicyInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DeleteScalingPolicyOutput, error) { + return &applicationautoscaling.DeleteScalingPolicyOutput{}, m.deletePolicyErr +} +func (m *mockAutoScalingClient) DeregisterScalableTarget(_ context.Context, _ *applicationautoscaling.DeregisterScalableTargetInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DeregisterScalableTargetOutput, error) { + return &applicationautoscaling.DeregisterScalableTargetOutput{}, m.deregisterErr +} + +// baseSpec returns a minimal valid ResourceSpec for infra.autoscaling_group. +func baseAutoScalingSpec(name string) interfaces.ResourceSpec { + return interfaces.ResourceSpec{ + Name: name, + Type: "infra.autoscaling_group", + Config: map[string]any{ + "service_namespace": "ecs", + "resource_id": "service/my-cluster/my-service", + "scalable_dimension": "ecs:service:DesiredCount", + "min_capacity": 1, + "max_capacity": 10, + }, + } +} + +// ---- ResourceType ---- + +func TestAutoScalingGroupDriver_ResourceType(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + if d.ResourceType() != "infra.autoscaling_group" { + t.Errorf("expected infra.autoscaling_group, got %s", d.ResourceType()) + } +} + +// ---- Create happy path ---- + +func TestAutoScalingGroupDriver_Create(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + out, err := d.Create(context.Background(), baseAutoScalingSpec("my-asg")) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + if out.Name != "my-asg" { + t.Errorf("expected name my-asg, got %s", out.Name) + } + if out.Type != "infra.autoscaling_group" { + t.Errorf("expected type infra.autoscaling_group, got %s", out.Type) + } + if out.ProviderID == "" { + t.Error("expected non-empty ProviderID") + } +} + +// ---- Create missing required fields ---- + +func TestAutoScalingGroupDriver_Create_MissingServiceNamespace(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + spec := interfaces.ResourceSpec{ + Name: "asg", + Config: map[string]any{"resource_id": "service/c/s", "scalable_dimension": "ecs:service:DesiredCount"}, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error for missing service_namespace") + } +} + +func TestAutoScalingGroupDriver_Create_MissingResourceID(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + spec := interfaces.ResourceSpec{ + Name: "asg", + Config: map[string]any{"service_namespace": "ecs", "scalable_dimension": "ecs:service:DesiredCount"}, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error for missing resource_id") + } +} + +func TestAutoScalingGroupDriver_Create_MissingScalableDimension(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + spec := interfaces.ResourceSpec{ + Name: "asg", + Config: map[string]any{"service_namespace": "ecs", "resource_id": "service/c/s"}, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error for missing scalable_dimension") + } +} + +// ---- Create with scaling policies ---- + +func TestAutoScalingGroupDriver_Create_WithTargetTrackingPolicy(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + putPolicyOut: &applicationautoscaling.PutScalingPolicyOutput{PolicyARN: awssdk.String("arn:aws:autoscaling:policy/xyz")}, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + spec := baseAutoScalingSpec("my-asg") + spec.Config["policies"] = []any{ + map[string]any{ + "policy_name": "cpu-tracking", + "policy_type": "TargetTrackingScaling", + "target_value": float64(75), + "scale_in_cooldown": int(300), + "scale_out_cooldown": int(60), + }, + } + out, err := d.Create(context.Background(), spec) + if err != nil { + t.Fatalf("Create with policy failed: %v", err) + } + if out == nil { + t.Fatal("expected non-nil output") + } +} + +func TestAutoScalingGroupDriver_Create_WithStepScalingPolicy(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + putPolicyOut: &applicationautoscaling.PutScalingPolicyOutput{PolicyARN: awssdk.String("arn:aws:autoscaling:policy/step")}, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + spec := baseAutoScalingSpec("my-asg") + spec.Config["policies"] = []any{ + map[string]any{ + "policy_name": "step-out", + "policy_type": "StepScaling", + "scale_out_cooldown": int(60), + }, + } + out, err := d.Create(context.Background(), spec) + if err != nil { + t.Fatalf("Create with step policy failed: %v", err) + } + if out == nil { + t.Fatal("expected non-nil output") + } +} + +// ---- Create API error ---- + +func TestAutoScalingGroupDriver_Create_RegisterError(t *testing.T) { + mock := &mockAutoScalingClient{ + registerErr: fmt.Errorf("validation exception: invalid resource"), + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + _, err := d.Create(context.Background(), baseAutoScalingSpec("my-asg")) + if err == nil { + t.Fatal("expected error when RegisterScalableTarget fails") + } +} + +func TestAutoScalingGroupDriver_Create_PutPolicyError(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + putPolicyErr: fmt.Errorf("invalid policy configuration"), + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + spec := baseAutoScalingSpec("my-asg") + spec.Config["policies"] = []any{ + map[string]any{ + "policy_name": "bad-policy", + "policy_type": "TargetTrackingScaling", + "target_value": float64(50), + }, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error when PutScalingPolicy fails") + } +} + +// ---- Read happy path ---- + +func TestAutoScalingGroupDriver_Read(t *testing.T) { + mock := &mockAutoScalingClient{ + describeOut: &applicationautoscaling.DescribeScalableTargetsOutput{ + ScalableTargets: []aastypes.ScalableTarget{ + { + ResourceId: awssdk.String("service/my-cluster/my-service"), + ScalableDimension: aastypes.ScalableDimensionECSServiceDesiredCount, + ServiceNamespace: aastypes.ServiceNamespaceEcs, + MinCapacity: awssdk.Int32(1), + MaxCapacity: awssdk.Int32(10), + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + }, + }, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + out, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "my-asg", Type: "infra.autoscaling_group"}) + if err != nil { + t.Fatalf("Read failed: %v", err) + } + if out.Name != "my-asg" { + t.Errorf("expected name my-asg, got %s", out.Name) + } +} + +func TestAutoScalingGroupDriver_Read_NotFound(t *testing.T) { + mock := &mockAutoScalingClient{ + describeOut: &applicationautoscaling.DescribeScalableTargetsOutput{ + ScalableTargets: []aastypes.ScalableTarget{}, + }, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + _, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "missing-asg", Type: "infra.autoscaling_group"}) + if err == nil { + t.Fatal("expected error for not-found scalable target") + } +} + +func TestAutoScalingGroupDriver_Read_DescribeError(t *testing.T) { + mock := &mockAutoScalingClient{ + describeErr: fmt.Errorf("service unavailable"), + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + _, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "my-asg", Type: "infra.autoscaling_group"}) + if err == nil { + t.Fatal("expected error when DescribeScalableTargets fails") + } +} + +// ---- Update happy path ---- + +func TestAutoScalingGroupDriver_Update(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + describeOut: &applicationautoscaling.DescribeScalableTargetsOutput{ + ScalableTargets: []aastypes.ScalableTarget{ + { + ResourceId: awssdk.String("service/my-cluster/my-service"), + ScalableDimension: aastypes.ScalableDimensionECSServiceDesiredCount, + ServiceNamespace: aastypes.ServiceNamespaceEcs, + MinCapacity: awssdk.Int32(1), + MaxCapacity: awssdk.Int32(10), + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + }, + }, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{ + ScalingPolicies: []aastypes.ScalingPolicy{}, + }, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + spec := baseAutoScalingSpec("my-asg") + spec.Config["max_capacity"] = 20 + out, err := d.Update(context.Background(), interfaces.ResourceRef{Name: "my-asg"}, spec) + if err != nil { + t.Fatalf("Update failed: %v", err) + } + if out == nil { + t.Fatal("expected non-nil output") + } +} + +// ---- Update removes stale policies ---- + +func TestAutoScalingGroupDriver_Update_RemovesStalePolicies(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + describeOut: &applicationautoscaling.DescribeScalableTargetsOutput{ + ScalableTargets: []aastypes.ScalableTarget{ + { + ResourceId: awssdk.String("service/my-cluster/my-service"), + ScalableDimension: aastypes.ScalableDimensionECSServiceDesiredCount, + ServiceNamespace: aastypes.ServiceNamespaceEcs, + MinCapacity: awssdk.Int32(1), + MaxCapacity: awssdk.Int32(10), + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + }, + }, + // Current live policies include "old-policy" which is absent from spec + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{ + ScalingPolicies: []aastypes.ScalingPolicy{ + { + PolicyName: awssdk.String("old-policy"), + PolicyARN: awssdk.String("arn:aws:autoscaling:policy/old"), + ResourceId: awssdk.String("service/my-cluster/my-service"), + ScalableDimension: aastypes.ScalableDimensionECSServiceDesiredCount, + ServiceNamespace: aastypes.ServiceNamespaceEcs, + }, + }, + }, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + // No policies in the desired spec — old-policy should be deleted + out, err := d.Update(context.Background(), interfaces.ResourceRef{Name: "my-asg"}, baseAutoScalingSpec("my-asg")) + if err != nil { + t.Fatalf("Update (remove stale policies) failed: %v", err) + } + if out == nil { + t.Fatal("expected non-nil output") + } +} + +func TestAutoScalingGroupDriver_Update_Error(t *testing.T) { + mock := &mockAutoScalingClient{ + registerErr: fmt.Errorf("invalid parameter combination"), + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + _, err := d.Update(context.Background(), interfaces.ResourceRef{Name: "my-asg"}, baseAutoScalingSpec("my-asg")) + if err == nil { + t.Fatal("expected error when RegisterScalableTarget fails during update") + } +} + +// ---- Delete happy path ---- + +func TestAutoScalingGroupDriver_Delete(t *testing.T) { + mock := &mockAutoScalingClient{ + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{ + ScalingPolicies: []aastypes.ScalingPolicy{ + { + PolicyName: awssdk.String("my-policy"), + PolicyARN: awssdk.String("arn:aws:autoscaling:policy/p1"), + ResourceId: awssdk.String("service/my-cluster/my-service"), + ScalableDimension: aastypes.ScalableDimensionECSServiceDesiredCount, + ServiceNamespace: aastypes.ServiceNamespaceEcs, + }, + }, + }, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + err := d.Delete(context.Background(), interfaces.ResourceRef{ + Name: "my-asg", + Type: "infra.autoscaling_group", + // ProviderID encodes "namespace/resource_id/scalable_dimension" + ProviderID: "ecs|service/my-cluster/my-service|ecs:service:DesiredCount", + }) + if err != nil { + t.Fatalf("Delete failed: %v", err) + } +} + +func TestAutoScalingGroupDriver_Delete_Error(t *testing.T) { + mock := &mockAutoScalingClient{ + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + deregisterErr: fmt.Errorf("scalable target not found"), + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + err := d.Delete(context.Background(), interfaces.ResourceRef{ + Name: "my-asg", + ProviderID: "ecs|service/my-cluster/my-service|ecs:service:DesiredCount", + }) + if err == nil { + t.Fatal("expected error when DeregisterScalableTarget fails") + } +} + +// ---- Diff ---- + +func TestAutoScalingGroupDriver_Diff_NilCurrent(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + diff, err := d.Diff(context.Background(), baseAutoScalingSpec("asg"), nil) + if err != nil { + t.Fatal(err) + } + if !diff.NeedsUpdate { + t.Error("expected NeedsUpdate=true for nil current") + } +} + +func TestAutoScalingGroupDriver_Diff_HasChanges(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + current := &interfaces.ResourceOutput{ + Name: "asg", + Type: "infra.autoscaling_group", + Outputs: map[string]any{"min_capacity": 1, "max_capacity": 5}, + } + spec := baseAutoScalingSpec("asg") + spec.Config["max_capacity"] = 20 + diff, err := d.Diff(context.Background(), spec, current) + if err != nil { + t.Fatal(err) + } + if !diff.NeedsUpdate { + t.Error("expected NeedsUpdate=true when max_capacity changes") + } +} + +func TestAutoScalingGroupDriver_Diff_NoChanges(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + current := &interfaces.ResourceOutput{ + Name: "asg", + Type: "infra.autoscaling_group", + Outputs: map[string]any{"min_capacity": 1, "max_capacity": 10}, + } + diff, err := d.Diff(context.Background(), baseAutoScalingSpec("asg"), current) + if err != nil { + t.Fatal(err) + } + if diff.NeedsUpdate { + t.Error("expected NeedsUpdate=false when config unchanged") + } +} + +// ---- HealthCheck ---- + +func TestAutoScalingGroupDriver_HealthCheck_Healthy(t *testing.T) { + mock := &mockAutoScalingClient{ + describeOut: &applicationautoscaling.DescribeScalableTargetsOutput{ + ScalableTargets: []aastypes.ScalableTarget{ + { + ResourceId: awssdk.String("service/my-cluster/my-service"), + ScalableDimension: aastypes.ScalableDimensionECSServiceDesiredCount, + ServiceNamespace: aastypes.ServiceNamespaceEcs, + MinCapacity: awssdk.Int32(1), + MaxCapacity: awssdk.Int32(10), + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + }, + }, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + health, err := d.HealthCheck(context.Background(), interfaces.ResourceRef{Name: "my-asg"}) + if err != nil { + t.Fatalf("HealthCheck failed: %v", err) + } + if !health.Healthy { + t.Errorf("expected healthy, got: %s", health.Message) + } +} + +func TestAutoScalingGroupDriver_HealthCheck_Unhealthy(t *testing.T) { + mock := &mockAutoScalingClient{ + describeErr: fmt.Errorf("resource not found"), + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + health, err := d.HealthCheck(context.Background(), interfaces.ResourceRef{Name: "my-asg"}) + if err != nil { + t.Fatalf("HealthCheck returned unexpected error: %v", err) + } + if health.Healthy { + t.Error("expected unhealthy when Read fails") + } +} + +// ---- SensitiveKeys ---- + +func TestAutoScalingGroupDriver_SensitiveKeys(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + if keys := d.SensitiveKeys(); keys != nil { + t.Errorf("expected nil sensitive keys, got %v", keys) + } +} + +// ---- Interface compliance ---- + +func TestAutoScalingGroupDriver_ImplementsResourceDriver(t *testing.T) { + var _ interfaces.ResourceDriver = (*drivers.AutoScalingGroupDriver)(nil) +} diff --git a/go.mod b/go.mod index 9de8270..46edea5 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,12 @@ go 1.26.0 require ( github.com/GoCodeAlone/workflow v0.19.2 - github.com/aws/aws-sdk-go-v2 v1.41.5 + github.com/aws/aws-sdk-go-v2 v1.41.7 github.com/aws/aws-sdk-go-v2/config v1.32.12 github.com/aws/aws-sdk-go-v2/credentials v1.19.12 github.com/aws/aws-sdk-go-v2/service/acm v1.32.1 github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.33.8 + github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.16 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.55.2 github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.0 github.com/aws/aws-sdk-go-v2/service/ecr v1.44.2 @@ -55,11 +56,10 @@ require ( 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/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/configsources v1.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // 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/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/internal/accept-encoding v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 // indirect @@ -70,7 +70,7 @@ require ( 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/smithy-go v1.25.1 // 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 diff --git a/go.sum b/go.sum index 278bde3..90f2856 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,8 @@ github.com/antithesishq/antithesis-sdk-go v0.7.0 h1:uWDG8BqLD1lI2ps38WDz2vXflrTX 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.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= 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= @@ -100,10 +100,10 @@ github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5 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/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= 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= @@ -112,8 +112,8 @@ github.com/aws/aws-sdk-go-v2/service/acm v1.32.1 h1:KAK08un+8LhHlG6OEUmDTqFpQth2 github.com/aws/aws-sdk-go-v2/service/acm v1.32.1/go.mod h1:3sKYAgRbuBa2QMYGh/WEclwnmfx+QoPhhX25PdSQSQM= 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= -github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.13/go.mod h1:HjgDVqI6lGR0azGz1GKmZTzGHkXuzhKzRUfG/p5Ug8s= +github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.16 h1:9ePxWacyZEGJQIBfYxumxYjDZvvpCcUAnFQUQy4GI2U= +github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.41.16/go.mod h1:gR1tnThD1DBemyG1rmZ9U5+WbfGoiLUaZDvsQ6wbAjM= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.55.2 h1:mleWBVIxwceEzyItUVoqMFiv6TmOP6ECPoN6WB/VWXc= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.55.2/go.mod h1:cMApt548kNgu87UsBTNWVv+fpzjbUTFRSFjD1688SBs= github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.12 h1:lQTVEv/YAk8Rw1Yf4XZS/jNNxF9klCN10WcSR3xlMtU= @@ -156,8 +156,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB 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/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= +github.com/aws/smithy-go v1.25.1/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= diff --git a/plugin.json b/plugin.json index 1129cb6..f9d4c13 100644 --- a/plugin.json +++ b/plugin.json @@ -17,6 +17,7 @@ "infra.firewall", "infra.iam_role", "infra.storage", - "infra.certificate" + "infra.certificate", + "infra.autoscaling_group" ] } diff --git a/provider/provider.go b/provider/provider.go index 7e9bdb6..b19e652 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -93,6 +93,7 @@ func (p *AWSProvider) registerDrivers(cfg awssdk.Config, ecsCluster, region stri drivers.NewIAMDriver(cfg), drivers.NewS3Driver(cfg, region), drivers.NewACMDriver(cfg), + drivers.NewAutoScalingGroupDriver(cfg), } for _, d := range driverList { p.driverMap[d.ResourceType()] = d @@ -114,6 +115,7 @@ func (p *AWSProvider) Capabilities() []interfaces.IaCCapabilityDeclaration { {ResourceType: "infra.iam_role", Tier: 1, Operations: []string{"create", "read", "update", "delete"}}, {ResourceType: "infra.storage", Tier: 2, Operations: []string{"create", "read", "update", "delete"}}, {ResourceType: "infra.certificate", Tier: 2, Operations: []string{"create", "read", "update", "delete"}}, + {ResourceType: "infra.autoscaling_group", Tier: 2, Operations: []string{"create", "read", "update", "delete"}}, } } diff --git a/provider/provider_test.go b/provider/provider_test.go index 9161c41..1e1d8f7 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -24,8 +24,8 @@ func TestNewAWSProvider(t *testing.T) { func TestAWSProvider_Capabilities(t *testing.T) { p := provider.NewAWSProvider() caps := p.Capabilities() - if len(caps) != 13 { - t.Errorf("expected 13 capabilities, got %d", len(caps)) + if len(caps) != 14 { + t.Errorf("expected 14 capabilities, got %d", len(caps)) } // Verify all required resource types are present @@ -43,6 +43,7 @@ func TestAWSProvider_Capabilities(t *testing.T) { "infra.iam_role", "infra.storage", "infra.certificate", + "infra.autoscaling_group", } capSet := make(map[string]bool) for _, c := range caps { From b82d4a6666364eaea6e7d3bf18401ccfa611f382 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 13 May 2026 07:05:39 -0400 Subject: [PATCH 4/6] =?UTF-8?q?fix(autoscaling=5Fgroup):=20address=20Copil?= =?UTF-8?q?ot=20review=20=E2=80=94=20validation,=20metric=20spec,=20stale-?= =?UTF-8?q?policy=20fetch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Require ProviderID for Read/HealthCheck (no more ecs-namespace default guessing) - Validate min_capacity/max_capacity presence and min<=max invariant in Create/Update - Include policy_names fingerprint in Diff so policy changes trigger reconciliation - TargetTrackingScaling: require predefined_metric_type (AWS rejects requests without it) - StepScaling: require adjustment_type + step_adjustments (AWS rejects incomplete config) - fetchLivePolicies now returns error; Delete aborts on fetch failure to avoid orphaned targets - Create fetches live policies before syncPolicies for idempotent re-runs - Embed noSensitiveKeys mixin instead of explicit SensitiveKeys method - Tests: pass realistic ProviderID to Read/HealthCheck; capture and assert PutScalingPolicyInput fields Co-Authored-By: Claude Sonnet 4.6 --- drivers/autoscaling_group.go | 268 ++++++++++++++++++++------- drivers/autoscaling_group_test.go | 298 ++++++++++++++++++++++++++---- 2 files changed, 463 insertions(+), 103 deletions(-) diff --git a/drivers/autoscaling_group.go b/drivers/autoscaling_group.go index f94f4ac..6b8a08e 100644 --- a/drivers/autoscaling_group.go +++ b/drivers/autoscaling_group.go @@ -3,6 +3,7 @@ package drivers import ( "context" "fmt" + "sort" "strings" awssdk "github.com/aws/aws-sdk-go-v2/aws" @@ -22,18 +23,32 @@ type AutoScalingClient interface { DeregisterScalableTarget(ctx context.Context, params *applicationautoscaling.DeregisterScalableTargetInput, optFns ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DeregisterScalableTargetOutput, error) } -// AutoScalingGroupDriver manages Application Auto Scaling targets (infra.autoscaling_group). +// AutoScalingGroupDriver manages AWS Application Auto Scaling targets (infra.autoscaling_group). +// +// This driver wraps the AWS Application Auto Scaling service, which manages scalable targets +// for services like ECS, DynamoDB, RDS, etc. It is NOT an EC2 Auto Scaling Group driver. // // Config keys: -// - service_namespace (string, required) — e.g., "ecs", "dynamodb", "rds" -// - resource_id (string, required) — e.g., "service/cluster/service-name" +// - service_namespace (string, required) — e.g., "ecs", "dynamodb", "rds" +// - resource_id (string, required) — e.g., "service/cluster/service-name" // - scalable_dimension (string, required) — e.g., "ecs:service:DesiredCount" -// - min_capacity (int, required) -// - max_capacity (int, required) -// - role_arn (string, optional) -// - policies ([]any, optional) — each map with keys: policy_name, policy_type, -// target_value (TargetTracking), scale_in_cooldown, scale_out_cooldown (StepScaling) +// - min_capacity (int, required) — must be >= 0 +// - max_capacity (int, required) — must be >= min_capacity +// - role_arn (string, optional) +// - policies ([]any, optional) — each map with keys: +// * policy_name (string, required) +// * policy_type (string, required) — "TargetTrackingScaling" or "StepScaling" +// For TargetTrackingScaling: +// * target_value (float64, required) +// * predefined_metric_type (string, required) — e.g., "ECSServiceAverageCPUUtilization" +// * scale_in_cooldown (int, optional, default 300) +// * scale_out_cooldown (int, optional, default 300) +// For StepScaling: +// * adjustment_type (string, required) — e.g., "ChangeInCapacity" +// * step_adjustments ([]any, required) — each map with metric_interval_lower_bound, scaling_adjustment +// * cooldown (int, optional, default 300) type AutoScalingGroupDriver struct { + noSensitiveKeys client AutoScalingClient } @@ -56,8 +71,10 @@ func (d *AutoScalingGroupDriver) Create(ctx context.Context, spec interfaces.Res return nil, fmt.Errorf("autoscaling_group: create %q: %w", spec.Name, err) } - minCap := int32(intProp(spec.Config, "min_capacity", 0)) - maxCap := int32(intProp(spec.Config, "max_capacity", 1)) + minCap, maxCap, err := validateCapacity(spec.Config) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: create %q: %w", spec.Name, err) + } roleARN, _ := spec.Config["role_arn"].(string) in := &applicationautoscaling.RegisterScalableTargetInput{ @@ -79,33 +96,31 @@ func (d *AutoScalingGroupDriver) Create(ctx context.Context, spec interfaces.Res targetARN := awssdk.ToString(out.ScalableTargetARN) providerID := encodeProviderID(ns, resourceID, dim) - if err := d.syncPolicies(ctx, spec, ns, resourceID, dim, nil); err != nil { + // Fetch live policies (handles idempotent re-runs) before syncing. + livePolicies, err := d.fetchLivePolicies(ctx, ns, resourceID, dim) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: create %q: fetch policies: %w", spec.Name, err) + } + if err := d.syncPolicies(ctx, spec, ns, resourceID, dim, livePolicies); err != nil { return nil, fmt.Errorf("autoscaling_group: create %q: apply policies: %w", spec.Name, err) } - return d.buildOutput(spec.Name, targetARN, providerID, int(minCap), int(maxCap), nil), nil + policyNames := desiredPolicyNames(spec.Config) + return d.buildOutput(spec.Name, targetARN, providerID, int(minCap), int(maxCap), policyNames), nil } // Read describes the scalable target and its policies. +// ProviderID is required (format: "namespace|resource_id|scalable_dimension"). func (d *AutoScalingGroupDriver) Read(ctx context.Context, ref interfaces.ResourceRef) (*interfaces.ResourceOutput, error) { ns, resourceID, dim := decodeProviderID(ref.ProviderID) - - // If ProviderID is not set, we can only look up by namespace annotation stored in the ref. - // Fall back to the ref type context — caller must ensure ProviderID is populated for precise lookup. if ns == "" { - // Use ref.Name as a best-effort resource_id if no ProviderID. - ns = "ecs" // default namespace for graceful degradation + return nil, fmt.Errorf("autoscaling_group: read %q: ProviderID is required (format: namespace|resource_id|dimension)", ref.Name) } out, err := d.client.DescribeScalableTargets(ctx, &applicationautoscaling.DescribeScalableTargetsInput{ - ServiceNamespace: aastypes.ServiceNamespace(ns), - ResourceIds: resourceIDFilter(resourceID, ref.Name), - ScalableDimension: func() aastypes.ScalableDimension { - if dim != "" { - return aastypes.ScalableDimension(dim) - } - return "" - }(), + ServiceNamespace: aastypes.ServiceNamespace(ns), + ResourceIds: []string{resourceID}, + ScalableDimension: aastypes.ScalableDimension(dim), }) if err != nil { return nil, fmt.Errorf("autoscaling_group: describe %q: %w", ref.Name, err) @@ -131,8 +146,10 @@ func (d *AutoScalingGroupDriver) Update(ctx context.Context, ref interfaces.Reso return nil, fmt.Errorf("autoscaling_group: update %q: %w", ref.Name, err) } - minCap := int32(intProp(spec.Config, "min_capacity", 0)) - maxCap := int32(intProp(spec.Config, "max_capacity", 1)) + minCap, maxCap, err := validateCapacity(spec.Config) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: update %q: %w", ref.Name, err) + } roleARN, _ := spec.Config["role_arn"].(string) in := &applicationautoscaling.RegisterScalableTargetInput{ @@ -152,7 +169,10 @@ func (d *AutoScalingGroupDriver) Update(ctx context.Context, ref interfaces.Reso } // Fetch current live policies so we can delete any that are removed from the spec. - livePolicies := d.fetchLivePolicies(ctx, ns, resourceID, dim) + livePolicies, err := d.fetchLivePolicies(ctx, ns, resourceID, dim) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: update %q: fetch policies: %w", ref.Name, err) + } if err := d.syncPolicies(ctx, spec, ns, resourceID, dim, livePolicies); err != nil { return nil, fmt.Errorf("autoscaling_group: update %q: sync policies: %w", ref.Name, err) @@ -160,7 +180,8 @@ func (d *AutoScalingGroupDriver) Update(ctx context.Context, ref interfaces.Reso targetARN := awssdk.ToString(out.ScalableTargetARN) providerID := encodeProviderID(ns, resourceID, dim) - return d.buildOutput(ref.Name, targetARN, providerID, int(minCap), int(maxCap), nil), nil + policyNames := desiredPolicyNames(spec.Config) + return d.buildOutput(ref.Name, targetARN, providerID, int(minCap), int(maxCap), policyNames), nil } // Delete removes all scaling policies then deregisters the scalable target. @@ -170,8 +191,11 @@ func (d *AutoScalingGroupDriver) Delete(ctx context.Context, ref interfaces.Reso return fmt.Errorf("autoscaling_group: delete %q: ProviderID is required (format: namespace|resource_id|dimension)", ref.Name) } - // Delete all existing policies first. - livePolicies := d.fetchLivePolicies(ctx, ns, resourceID, dim) + // Delete all existing policies first; errors here block the delete to avoid orphaned targets. + livePolicies, err := d.fetchLivePolicies(ctx, ns, resourceID, dim) + if err != nil { + return fmt.Errorf("autoscaling_group: delete %q: fetch policies: %w", ref.Name, err) + } for _, p := range livePolicies { if _, err := d.client.DeleteScalingPolicy(ctx, &applicationautoscaling.DeleteScalingPolicyInput{ PolicyName: p.PolicyName, @@ -194,20 +218,50 @@ func (d *AutoScalingGroupDriver) Delete(ctx context.Context, ref interfaces.Reso } // Diff computes whether the desired spec diverges from the current output. +// Compares capacity bounds and the sorted set of policy names. func (d *AutoScalingGroupDriver) Diff(_ context.Context, desired interfaces.ResourceSpec, current *interfaces.ResourceOutput) (*interfaces.DiffResult, error) { if current == nil { return &interfaces.DiffResult{NeedsUpdate: true}, nil } - // Compare the fields we surface in outputs. + + // Build a deterministic policy-name fingerprint for comparison. + wantPolicies := desiredPolicyNames(desired.Config) + sort.Strings(wantPolicies) + wantPolicyKey := strings.Join(wantPolicies, ",") + + // Extract current policy names from outputs. + var curPolicies []string + if pn, ok := current.Outputs["policy_names"]; ok { + switch v := pn.(type) { + case []string: + curPolicies = v + case []any: + for _, item := range v { + if s, ok := item.(string); ok { + curPolicies = append(curPolicies, s) + } + } + } + } + sort.Strings(curPolicies) + curPolicyKey := strings.Join(curPolicies, ",") + want := map[string]any{ "min_capacity": intProp(desired.Config, "min_capacity", 0), "max_capacity": intProp(desired.Config, "max_capacity", 1), + "policy_names": wantPolicyKey, + } + currentForDiff := map[string]any{ + "min_capacity": current.Outputs["min_capacity"], + "max_capacity": current.Outputs["max_capacity"], + "policy_names": curPolicyKey, } - changes := diffOutputs(want, current.Outputs) + changes := diffOutputs(want, currentForDiff) return &interfaces.DiffResult{NeedsUpdate: len(changes) > 0, Changes: changes}, nil } // HealthCheck returns healthy if the scalable target is readable. +// ProviderID is required. func (d *AutoScalingGroupDriver) HealthCheck(ctx context.Context, ref interfaces.ResourceRef) (*interfaces.HealthResult, error) { _, err := d.Read(ctx, ref) if err != nil { @@ -221,11 +275,34 @@ func (d *AutoScalingGroupDriver) Scale(_ context.Context, _ interfaces.ResourceR return nil, fmt.Errorf("autoscaling_group: use Update with max_capacity to resize") } -// SensitiveKeys returns output keys whose values should be masked in logs. -func (d *AutoScalingGroupDriver) SensitiveKeys() []string { return nil } - // ---- helpers ---- +// validateCapacity extracts min/max_capacity, enforcing presence and min<=max. +func validateCapacity(config map[string]any) (minCap, maxCap int32, err error) { + minRaw, minOK := config["min_capacity"] + maxRaw, maxOK := config["max_capacity"] + if !minOK { + return 0, 0, fmt.Errorf("min_capacity is required") + } + if !maxOK { + return 0, 0, fmt.Errorf("max_capacity is required") + } + minI := intProp(config, "min_capacity", -1) + maxI := intProp(config, "max_capacity", -1) + _ = minRaw + _ = maxRaw + if minI < 0 { + return 0, 0, fmt.Errorf("min_capacity must be a non-negative integer") + } + if maxI < 0 { + return 0, 0, fmt.Errorf("max_capacity must be a non-negative integer") + } + if minI > maxI { + return 0, 0, fmt.Errorf("min_capacity (%d) must not exceed max_capacity (%d)", minI, maxI) + } + return int32(minI), int32(maxI), nil +} + // requiredAutoScalingFields extracts and validates the three required config fields. func requiredAutoScalingFields(spec interfaces.ResourceSpec) (ns, resourceID, dim string, err error) { ns, _ = spec.Config["service_namespace"].(string) @@ -258,18 +335,6 @@ func decodeProviderID(providerID string) (ns, resourceID, dim string) { return parts[0], parts[1], parts[2] } -// resourceIDFilter returns a []string filter suitable for DescribeScalableTargets ResourceIds. -// If resourceID is non-empty, filter by it; otherwise use the ref name as a fallback. -func resourceIDFilter(resourceID, refName string) []string { - if resourceID != "" { - return []string{resourceID} - } - if refName != "" { - return []string{refName} - } - return nil -} - // buildOutput constructs a ResourceOutput from a scalable target's fields. func (d *AutoScalingGroupDriver) buildOutput(name, targetARN, providerID string, minCap, maxCap int, policyNames []string) *interfaces.ResourceOutput { outputs := map[string]any{ @@ -292,22 +357,28 @@ func (d *AutoScalingGroupDriver) buildOutput(name, targetARN, providerID string, } // fetchLivePolicies returns the current scaling policies for a scalable target. -// Errors are swallowed — callers handle absence gracefully. -func (d *AutoScalingGroupDriver) fetchLivePolicies(ctx context.Context, ns, resourceID, dim string) []aastypes.ScalingPolicy { +// Returns an error so callers can decide whether to abort destructive operations. +func (d *AutoScalingGroupDriver) fetchLivePolicies(ctx context.Context, ns, resourceID, dim string) ([]aastypes.ScalingPolicy, error) { out, err := d.client.DescribeScalingPolicies(ctx, &applicationautoscaling.DescribeScalingPoliciesInput{ ServiceNamespace: aastypes.ServiceNamespace(ns), ResourceId: awssdk.String(resourceID), ScalableDimension: aastypes.ScalableDimension(dim), }) - if err != nil || out == nil { - return nil + if err != nil { + return nil, err } - return out.ScalingPolicies + if out == nil { + return nil, nil + } + return out.ScalingPolicies, nil } // readPolicyNames fetches policy names for a ScalableTarget; returns nil on error. func (d *AutoScalingGroupDriver) readPolicyNames(ctx context.Context, target aastypes.ScalableTarget) []string { - policies := d.fetchLivePolicies(ctx, string(target.ServiceNamespace), awssdk.ToString(target.ResourceId), string(target.ScalableDimension)) + policies, err := d.fetchLivePolicies(ctx, string(target.ServiceNamespace), awssdk.ToString(target.ResourceId), string(target.ScalableDimension)) + if err != nil { + return nil + } var names []string for _, p := range policies { names = append(names, awssdk.ToString(p.PolicyName)) @@ -315,6 +386,16 @@ func (d *AutoScalingGroupDriver) readPolicyNames(ctx context.Context, target aas return names } +// desiredPolicyNames returns the policy_name values from the spec config. +func desiredPolicyNames(config map[string]any) []string { + policies := parsePolicies(config) + names := make([]string, 0, len(policies)) + for _, p := range policies { + names = append(names, p.policyName) + } + return names +} + // syncPolicies reconciles desired policies with live policies: // 1. Delete live policies whose policy_name is absent from the desired list. // 2. PutScalingPolicy for each desired policy. @@ -354,9 +435,22 @@ func (d *AutoScalingGroupDriver) syncPolicies(ctx context.Context, spec interfac type policySpec struct { policyName string policyType string - targetValue float64 - scaleInCooldown int32 - scaleOutCooldown int32 + // TargetTrackingScaling fields + targetValue float64 + predefinedMetricType string + scaleInCooldown int32 + scaleOutCooldown int32 + // StepScaling fields + adjustmentType string + stepAdjustments []stepAdjustment + cooldown int32 +} + +// stepAdjustment represents one step in a StepScaling policy. +type stepAdjustment struct { + metricIntervalLowerBound *float64 + metricIntervalUpperBound *float64 + scalingAdjustment int32 } // parsePolicies extracts the policies slice from config. @@ -381,8 +475,34 @@ func parsePolicies(config map[string]any) []policySpec { if tv, ok := m["target_value"].(float64); ok { p.targetValue = tv } + p.predefinedMetricType, _ = m["predefined_metric_type"].(string) p.scaleInCooldown = int32(intProp(m, "scale_in_cooldown", 300)) p.scaleOutCooldown = int32(intProp(m, "scale_out_cooldown", 300)) + p.adjustmentType, _ = m["adjustment_type"].(string) + p.cooldown = int32(intProp(m, "cooldown", 300)) + + // Parse step_adjustments for StepScaling. + if saRaw, ok := m["step_adjustments"]; ok { + if saItems, ok := saRaw.([]any); ok { + for _, saItem := range saItems { + saMap, ok := saItem.(map[string]any) + if !ok { + continue + } + sa := stepAdjustment{ + scalingAdjustment: int32(intProp(saMap, "scaling_adjustment", 0)), + } + if lb, ok := saMap["metric_interval_lower_bound"].(float64); ok { + sa.metricIntervalLowerBound = awssdk.Float64(lb) + } + if ub, ok := saMap["metric_interval_upper_bound"].(float64); ok { + sa.metricIntervalUpperBound = awssdk.Float64(ub) + } + p.stepAdjustments = append(p.stepAdjustments, sa) + } + } + } + if p.policyName != "" { result = append(result, p) } @@ -391,7 +511,7 @@ func parsePolicies(config map[string]any) []policySpec { } // putPolicy calls PutScalingPolicy for a single policySpec. -// Both TargetTrackingScaling and StepScaling are supported. +// Both TargetTrackingScaling and StepScaling are supported with their required fields. func (d *AutoScalingGroupDriver) putPolicy(ctx context.Context, ns, resourceID, dim string, p policySpec) error { in := &applicationautoscaling.PutScalingPolicyInput{ PolicyName: awssdk.String(p.policyName), @@ -402,25 +522,43 @@ func (d *AutoScalingGroupDriver) putPolicy(ctx context.Context, ns, resourceID, switch p.policyType { case "TargetTrackingScaling": + if p.predefinedMetricType == "" { + return fmt.Errorf("put policy %q: predefined_metric_type is required for TargetTrackingScaling (e.g., ECSServiceAverageCPUUtilization)", p.policyName) + } in.PolicyType = aastypes.PolicyTypeTargetTrackingScaling cfg := &aastypes.TargetTrackingScalingPolicyConfiguration{ - TargetValue: awssdk.Float64(p.targetValue), + TargetValue: awssdk.Float64(p.targetValue), + PredefinedMetricSpecification: &aastypes.PredefinedMetricSpecification{ + PredefinedMetricType: aastypes.MetricType(p.predefinedMetricType), + }, ScaleInCooldown: awssdk.Int32(p.scaleInCooldown), ScaleOutCooldown: awssdk.Int32(p.scaleOutCooldown), } in.TargetTrackingScalingPolicyConfiguration = cfg case "StepScaling": + if p.adjustmentType == "" { + return fmt.Errorf("put policy %q: adjustment_type is required for StepScaling (e.g., ChangeInCapacity)", p.policyName) + } + if len(p.stepAdjustments) == 0 { + return fmt.Errorf("put policy %q: step_adjustments is required for StepScaling (at least one step)", p.policyName) + } in.PolicyType = aastypes.PolicyTypeStepScaling + steps := make([]aastypes.StepAdjustment, len(p.stepAdjustments)) + for i, sa := range p.stepAdjustments { + steps[i] = aastypes.StepAdjustment{ + ScalingAdjustment: awssdk.Int32(sa.scalingAdjustment), + MetricIntervalLowerBound: sa.metricIntervalLowerBound, + MetricIntervalUpperBound: sa.metricIntervalUpperBound, + } + } cfg := &aastypes.StepScalingPolicyConfiguration{ - Cooldown: awssdk.Int32(p.scaleOutCooldown), + AdjustmentType: aastypes.AdjustmentType(p.adjustmentType), + StepAdjustments: steps, + Cooldown: awssdk.Int32(p.cooldown), } in.StepScalingPolicyConfiguration = cfg default: - // Unknown policy type: default to TargetTracking so the API can validate. - in.PolicyType = aastypes.PolicyTypeTargetTrackingScaling - in.TargetTrackingScalingPolicyConfiguration = &aastypes.TargetTrackingScalingPolicyConfiguration{ - TargetValue: awssdk.Float64(p.targetValue), - } + return fmt.Errorf("put policy %q: unsupported policy_type %q (must be TargetTrackingScaling or StepScaling)", p.policyName, p.policyType) } if _, err := d.client.PutScalingPolicy(ctx, in); err != nil { diff --git a/drivers/autoscaling_group_test.go b/drivers/autoscaling_group_test.go index 5c32e0d..7900bd6 100644 --- a/drivers/autoscaling_group_test.go +++ b/drivers/autoscaling_group_test.go @@ -25,6 +25,9 @@ type mockAutoScalingClient struct { putPolicyErr error deletePolicyErr error deregisterErr error + + // Captured inputs for assertion. + capturedPutPolicyInput *applicationautoscaling.PutScalingPolicyInput } func (m *mockAutoScalingClient) RegisterScalableTarget(_ context.Context, _ *applicationautoscaling.RegisterScalableTargetInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.RegisterScalableTargetOutput, error) { @@ -36,7 +39,8 @@ func (m *mockAutoScalingClient) DescribeScalableTargets(_ context.Context, _ *ap func (m *mockAutoScalingClient) DescribeScalingPolicies(_ context.Context, _ *applicationautoscaling.DescribeScalingPoliciesInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DescribeScalingPoliciesOutput, error) { return m.describePoliciesOut, m.describePoliciesErr } -func (m *mockAutoScalingClient) PutScalingPolicy(_ context.Context, _ *applicationautoscaling.PutScalingPolicyInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.PutScalingPolicyOutput, error) { +func (m *mockAutoScalingClient) PutScalingPolicy(_ context.Context, in *applicationautoscaling.PutScalingPolicyInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.PutScalingPolicyOutput, error) { + m.capturedPutPolicyInput = in return m.putPolicyOut, m.putPolicyErr } func (m *mockAutoScalingClient) DeleteScalingPolicy(_ context.Context, _ *applicationautoscaling.DeleteScalingPolicyInput, _ ...func(*applicationautoscaling.Options)) (*applicationautoscaling.DeleteScalingPolicyOutput, error) { @@ -46,21 +50,24 @@ func (m *mockAutoScalingClient) DeregisterScalableTarget(_ context.Context, _ *a return &applicationautoscaling.DeregisterScalableTargetOutput{}, m.deregisterErr } -// baseSpec returns a minimal valid ResourceSpec for infra.autoscaling_group. +// baseAutoScalingSpec returns a minimal valid ResourceSpec for infra.autoscaling_group. func baseAutoScalingSpec(name string) interfaces.ResourceSpec { return interfaces.ResourceSpec{ Name: name, Type: "infra.autoscaling_group", Config: map[string]any{ - "service_namespace": "ecs", - "resource_id": "service/my-cluster/my-service", - "scalable_dimension": "ecs:service:DesiredCount", - "min_capacity": 1, - "max_capacity": 10, + "service_namespace": "ecs", + "resource_id": "service/my-cluster/my-service", + "scalable_dimension": "ecs:service:DesiredCount", + "min_capacity": 1, + "max_capacity": 10, }, } } +// baseProviderID is the encoded ProviderID for baseAutoScalingSpec. +const baseProviderID = "ecs|service/my-cluster/my-service|ecs:service:DesiredCount" + // ---- ResourceType ---- func TestAutoScalingGroupDriver_ResourceType(t *testing.T) { @@ -101,7 +108,7 @@ func TestAutoScalingGroupDriver_Create_MissingServiceNamespace(t *testing.T) { d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) spec := interfaces.ResourceSpec{ Name: "asg", - Config: map[string]any{"resource_id": "service/c/s", "scalable_dimension": "ecs:service:DesiredCount"}, + Config: map[string]any{"resource_id": "service/c/s", "scalable_dimension": "ecs:service:DesiredCount", "min_capacity": 1, "max_capacity": 5}, } _, err := d.Create(context.Background(), spec) if err == nil { @@ -113,7 +120,7 @@ func TestAutoScalingGroupDriver_Create_MissingResourceID(t *testing.T) { d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) spec := interfaces.ResourceSpec{ Name: "asg", - Config: map[string]any{"service_namespace": "ecs", "scalable_dimension": "ecs:service:DesiredCount"}, + Config: map[string]any{"service_namespace": "ecs", "scalable_dimension": "ecs:service:DesiredCount", "min_capacity": 1, "max_capacity": 5}, } _, err := d.Create(context.Background(), spec) if err == nil { @@ -125,7 +132,7 @@ func TestAutoScalingGroupDriver_Create_MissingScalableDimension(t *testing.T) { d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) spec := interfaces.ResourceSpec{ Name: "asg", - Config: map[string]any{"service_namespace": "ecs", "resource_id": "service/c/s"}, + Config: map[string]any{"service_namespace": "ecs", "resource_id": "service/c/s", "min_capacity": 1, "max_capacity": 5}, } _, err := d.Create(context.Background(), spec) if err == nil { @@ -133,6 +140,42 @@ func TestAutoScalingGroupDriver_Create_MissingScalableDimension(t *testing.T) { } } +func TestAutoScalingGroupDriver_Create_MissingMinCapacity(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + spec := interfaces.ResourceSpec{ + Name: "asg", + Config: map[string]any{"service_namespace": "ecs", "resource_id": "service/c/s", "scalable_dimension": "ecs:service:DesiredCount", "max_capacity": 5}, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error for missing min_capacity") + } +} + +func TestAutoScalingGroupDriver_Create_MissingMaxCapacity(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + spec := interfaces.ResourceSpec{ + Name: "asg", + Config: map[string]any{"service_namespace": "ecs", "resource_id": "service/c/s", "scalable_dimension": "ecs:service:DesiredCount", "min_capacity": 1}, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error for missing max_capacity") + } +} + +func TestAutoScalingGroupDriver_Create_InvalidCapacityRange(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + spec := interfaces.ResourceSpec{ + Name: "asg", + Config: map[string]any{"service_namespace": "ecs", "resource_id": "service/c/s", "scalable_dimension": "ecs:service:DesiredCount", "min_capacity": 10, "max_capacity": 1}, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error when min_capacity > max_capacity") + } +} + // ---- Create with scaling policies ---- func TestAutoScalingGroupDriver_Create_WithTargetTrackingPolicy(t *testing.T) { @@ -147,11 +190,12 @@ func TestAutoScalingGroupDriver_Create_WithTargetTrackingPolicy(t *testing.T) { spec := baseAutoScalingSpec("my-asg") spec.Config["policies"] = []any{ map[string]any{ - "policy_name": "cpu-tracking", - "policy_type": "TargetTrackingScaling", - "target_value": float64(75), - "scale_in_cooldown": int(300), - "scale_out_cooldown": int(60), + "policy_name": "cpu-tracking", + "policy_type": "TargetTrackingScaling", + "target_value": float64(75), + "predefined_metric_type": "ECSServiceAverageCPUUtilization", + "scale_in_cooldown": int(300), + "scale_out_cooldown": int(60), }, } out, err := d.Create(context.Background(), spec) @@ -161,6 +205,48 @@ func TestAutoScalingGroupDriver_Create_WithTargetTrackingPolicy(t *testing.T) { if out == nil { t.Fatal("expected non-nil output") } + // Assert the PutScalingPolicyInput was built correctly. + captured := mock.capturedPutPolicyInput + if captured == nil { + t.Fatal("expected PutScalingPolicy to have been called") + } + if captured.PolicyType != aastypes.PolicyTypeTargetTrackingScaling { + t.Errorf("expected TargetTrackingScaling, got %v", captured.PolicyType) + } + ttCfg := captured.TargetTrackingScalingPolicyConfiguration + if ttCfg == nil { + t.Fatal("expected TargetTrackingScalingPolicyConfiguration to be set") + } + if awssdk.ToFloat64(ttCfg.TargetValue) != 75 { + t.Errorf("expected TargetValue=75, got %v", awssdk.ToFloat64(ttCfg.TargetValue)) + } + if ttCfg.PredefinedMetricSpecification == nil { + t.Fatal("expected PredefinedMetricSpecification to be set") + } + if string(ttCfg.PredefinedMetricSpecification.PredefinedMetricType) != "ECSServiceAverageCPUUtilization" { + t.Errorf("expected ECSServiceAverageCPUUtilization, got %v", ttCfg.PredefinedMetricSpecification.PredefinedMetricType) + } +} + +func TestAutoScalingGroupDriver_Create_TargetTracking_MissingMetricType(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc")}, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + spec := baseAutoScalingSpec("my-asg") + spec.Config["policies"] = []any{ + map[string]any{ + "policy_name": "cpu-tracking", + "policy_type": "TargetTrackingScaling", + "target_value": float64(75), + // predefined_metric_type intentionally omitted + }, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error when predefined_metric_type is missing for TargetTrackingScaling") + } } func TestAutoScalingGroupDriver_Create_WithStepScalingPolicy(t *testing.T) { @@ -175,9 +261,16 @@ func TestAutoScalingGroupDriver_Create_WithStepScalingPolicy(t *testing.T) { spec := baseAutoScalingSpec("my-asg") spec.Config["policies"] = []any{ map[string]any{ - "policy_name": "step-out", - "policy_type": "StepScaling", - "scale_out_cooldown": int(60), + "policy_name": "step-out", + "policy_type": "StepScaling", + "adjustment_type": "ChangeInCapacity", + "step_adjustments": []any{ + map[string]any{ + "metric_interval_lower_bound": float64(0), + "scaling_adjustment": int(2), + }, + }, + "cooldown": int(60), }, } out, err := d.Create(context.Background(), spec) @@ -187,6 +280,68 @@ func TestAutoScalingGroupDriver_Create_WithStepScalingPolicy(t *testing.T) { if out == nil { t.Fatal("expected non-nil output") } + // Assert PutScalingPolicyInput fields for StepScaling. + captured := mock.capturedPutPolicyInput + if captured == nil { + t.Fatal("expected PutScalingPolicy to have been called") + } + if captured.PolicyType != aastypes.PolicyTypeStepScaling { + t.Errorf("expected StepScaling, got %v", captured.PolicyType) + } + stCfg := captured.StepScalingPolicyConfiguration + if stCfg == nil { + t.Fatal("expected StepScalingPolicyConfiguration to be set") + } + if string(stCfg.AdjustmentType) != "ChangeInCapacity" { + t.Errorf("expected AdjustmentType=ChangeInCapacity, got %v", stCfg.AdjustmentType) + } + if len(stCfg.StepAdjustments) != 1 { + t.Errorf("expected 1 step adjustment, got %d", len(stCfg.StepAdjustments)) + } +} + +func TestAutoScalingGroupDriver_Create_StepScaling_MissingAdjustmentType(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc")}, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + spec := baseAutoScalingSpec("my-asg") + spec.Config["policies"] = []any{ + map[string]any{ + "policy_name": "step-out", + "policy_type": "StepScaling", + // adjustment_type intentionally omitted + "step_adjustments": []any{ + map[string]any{"metric_interval_lower_bound": float64(0), "scaling_adjustment": int(2)}, + }, + }, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error when adjustment_type is missing for StepScaling") + } +} + +func TestAutoScalingGroupDriver_Create_StepScaling_MissingStepAdjustments(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc")}, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + spec := baseAutoScalingSpec("my-asg") + spec.Config["policies"] = []any{ + map[string]any{ + "policy_name": "step-out", + "policy_type": "StepScaling", + "adjustment_type": "ChangeInCapacity", + // step_adjustments intentionally omitted + }, + } + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error when step_adjustments is missing for StepScaling") + } } // ---- Create API error ---- @@ -207,15 +362,17 @@ func TestAutoScalingGroupDriver_Create_PutPolicyError(t *testing.T) { registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), }, - putPolicyErr: fmt.Errorf("invalid policy configuration"), + putPolicyErr: fmt.Errorf("invalid policy configuration"), + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, } d := drivers.NewAutoScalingGroupDriverWithClient(mock) spec := baseAutoScalingSpec("my-asg") spec.Config["policies"] = []any{ map[string]any{ - "policy_name": "bad-policy", - "policy_type": "TargetTrackingScaling", - "target_value": float64(50), + "policy_name": "bad-policy", + "policy_type": "TargetTrackingScaling", + "target_value": float64(50), + "predefined_metric_type": "ECSServiceAverageCPUUtilization", }, } _, err := d.Create(context.Background(), spec) @@ -243,7 +400,12 @@ func TestAutoScalingGroupDriver_Read(t *testing.T) { describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, } d := drivers.NewAutoScalingGroupDriverWithClient(mock) - out, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "my-asg", Type: "infra.autoscaling_group"}) + // ProviderID is required for Read; pass a realistic encoded value. + out, err := d.Read(context.Background(), interfaces.ResourceRef{ + Name: "my-asg", + Type: "infra.autoscaling_group", + ProviderID: baseProviderID, + }) if err != nil { t.Fatalf("Read failed: %v", err) } @@ -252,6 +414,15 @@ func TestAutoScalingGroupDriver_Read(t *testing.T) { } } +func TestAutoScalingGroupDriver_Read_MissingProviderID(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + // Read without ProviderID must return an error. + _, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "my-asg", Type: "infra.autoscaling_group"}) + if err == nil { + t.Fatal("expected error when ProviderID is missing") + } +} + func TestAutoScalingGroupDriver_Read_NotFound(t *testing.T) { mock := &mockAutoScalingClient{ describeOut: &applicationautoscaling.DescribeScalableTargetsOutput{ @@ -259,7 +430,7 @@ func TestAutoScalingGroupDriver_Read_NotFound(t *testing.T) { }, } d := drivers.NewAutoScalingGroupDriverWithClient(mock) - _, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "missing-asg", Type: "infra.autoscaling_group"}) + _, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "missing-asg", Type: "infra.autoscaling_group", ProviderID: baseProviderID}) if err == nil { t.Fatal("expected error for not-found scalable target") } @@ -270,7 +441,7 @@ func TestAutoScalingGroupDriver_Read_DescribeError(t *testing.T) { describeErr: fmt.Errorf("service unavailable"), } d := drivers.NewAutoScalingGroupDriverWithClient(mock) - _, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "my-asg", Type: "infra.autoscaling_group"}) + _, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "my-asg", Type: "infra.autoscaling_group", ProviderID: baseProviderID}) if err == nil { t.Fatal("expected error when DescribeScalableTargets fails") } @@ -334,11 +505,11 @@ func TestAutoScalingGroupDriver_Update_RemovesStalePolicies(t *testing.T) { describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{ ScalingPolicies: []aastypes.ScalingPolicy{ { - PolicyName: awssdk.String("old-policy"), - PolicyARN: awssdk.String("arn:aws:autoscaling:policy/old"), - ResourceId: awssdk.String("service/my-cluster/my-service"), + PolicyName: awssdk.String("old-policy"), + PolicyARN: awssdk.String("arn:aws:autoscaling:policy/old"), + ResourceId: awssdk.String("service/my-cluster/my-service"), ScalableDimension: aastypes.ScalableDimensionECSServiceDesiredCount, - ServiceNamespace: aastypes.ServiceNamespaceEcs, + ServiceNamespace: aastypes.ServiceNamespaceEcs, }, }, }, @@ -383,10 +554,9 @@ func TestAutoScalingGroupDriver_Delete(t *testing.T) { } d := drivers.NewAutoScalingGroupDriverWithClient(mock) err := d.Delete(context.Background(), interfaces.ResourceRef{ - Name: "my-asg", - Type: "infra.autoscaling_group", - // ProviderID encodes "namespace/resource_id/scalable_dimension" - ProviderID: "ecs|service/my-cluster/my-service|ecs:service:DesiredCount", + Name: "my-asg", + Type: "infra.autoscaling_group", + ProviderID: baseProviderID, }) if err != nil { t.Fatalf("Delete failed: %v", err) @@ -401,13 +571,27 @@ func TestAutoScalingGroupDriver_Delete_Error(t *testing.T) { d := drivers.NewAutoScalingGroupDriverWithClient(mock) err := d.Delete(context.Background(), interfaces.ResourceRef{ Name: "my-asg", - ProviderID: "ecs|service/my-cluster/my-service|ecs:service:DesiredCount", + ProviderID: baseProviderID, }) if err == nil { t.Fatal("expected error when DeregisterScalableTarget fails") } } +func TestAutoScalingGroupDriver_Delete_FetchPoliciesError(t *testing.T) { + mock := &mockAutoScalingClient{ + describePoliciesErr: fmt.Errorf("api throttled"), + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + err := d.Delete(context.Background(), interfaces.ResourceRef{ + Name: "my-asg", + ProviderID: baseProviderID, + }) + if err == nil { + t.Fatal("expected error when DescribeScalingPolicies fails during delete") + } +} + // ---- Diff ---- func TestAutoScalingGroupDriver_Diff_NilCurrent(t *testing.T) { @@ -426,7 +610,7 @@ func TestAutoScalingGroupDriver_Diff_HasChanges(t *testing.T) { current := &interfaces.ResourceOutput{ Name: "asg", Type: "infra.autoscaling_group", - Outputs: map[string]any{"min_capacity": 1, "max_capacity": 5}, + Outputs: map[string]any{"min_capacity": 1, "max_capacity": 5, "policy_names": ""}, } spec := baseAutoScalingSpec("asg") spec.Config["max_capacity"] = 20 @@ -444,7 +628,7 @@ func TestAutoScalingGroupDriver_Diff_NoChanges(t *testing.T) { current := &interfaces.ResourceOutput{ Name: "asg", Type: "infra.autoscaling_group", - Outputs: map[string]any{"min_capacity": 1, "max_capacity": 10}, + Outputs: map[string]any{"min_capacity": 1, "max_capacity": 10, "policy_names": ""}, } diff, err := d.Diff(context.Background(), baseAutoScalingSpec("asg"), current) if err != nil { @@ -455,6 +639,32 @@ func TestAutoScalingGroupDriver_Diff_NoChanges(t *testing.T) { } } +func TestAutoScalingGroupDriver_Diff_PolicyChange(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + current := &interfaces.ResourceOutput{ + Name: "asg", + Type: "infra.autoscaling_group", + Outputs: map[string]any{"min_capacity": 1, "max_capacity": 10, "policy_names": []string{"old-policy"}}, + } + // Desired spec has a different policy set. + spec := baseAutoScalingSpec("asg") + spec.Config["policies"] = []any{ + map[string]any{ + "policy_name": "new-policy", + "policy_type": "TargetTrackingScaling", + "target_value": float64(75), + "predefined_metric_type": "ECSServiceAverageCPUUtilization", + }, + } + diff, err := d.Diff(context.Background(), spec, current) + if err != nil { + t.Fatal(err) + } + if !diff.NeedsUpdate { + t.Error("expected NeedsUpdate=true when policy set changes") + } +} + // ---- HealthCheck ---- func TestAutoScalingGroupDriver_HealthCheck_Healthy(t *testing.T) { @@ -474,7 +684,7 @@ func TestAutoScalingGroupDriver_HealthCheck_Healthy(t *testing.T) { describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, } d := drivers.NewAutoScalingGroupDriverWithClient(mock) - health, err := d.HealthCheck(context.Background(), interfaces.ResourceRef{Name: "my-asg"}) + health, err := d.HealthCheck(context.Background(), interfaces.ResourceRef{Name: "my-asg", ProviderID: baseProviderID}) if err != nil { t.Fatalf("HealthCheck failed: %v", err) } @@ -488,7 +698,7 @@ func TestAutoScalingGroupDriver_HealthCheck_Unhealthy(t *testing.T) { describeErr: fmt.Errorf("resource not found"), } d := drivers.NewAutoScalingGroupDriverWithClient(mock) - health, err := d.HealthCheck(context.Background(), interfaces.ResourceRef{Name: "my-asg"}) + health, err := d.HealthCheck(context.Background(), interfaces.ResourceRef{Name: "my-asg", ProviderID: baseProviderID}) if err != nil { t.Fatalf("HealthCheck returned unexpected error: %v", err) } @@ -497,6 +707,18 @@ func TestAutoScalingGroupDriver_HealthCheck_Unhealthy(t *testing.T) { } } +func TestAutoScalingGroupDriver_HealthCheck_MissingProviderID(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + health, err := d.HealthCheck(context.Background(), interfaces.ResourceRef{Name: "my-asg"}) + if err != nil { + t.Fatalf("HealthCheck returned unexpected error: %v", err) + } + // HealthCheck should return Healthy=false (not an error) when ProviderID is missing. + if health.Healthy { + t.Error("expected unhealthy when ProviderID is missing") + } +} + // ---- SensitiveKeys ---- func TestAutoScalingGroupDriver_SensitiveKeys(t *testing.T) { From 708ee6be275a3d5d3545760aa4e293e01ba5fc22 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 13 May 2026 07:15:15 -0400 Subject: [PATCH 5/6] =?UTF-8?q?fix(autoscaling=5Fgroup):=20address=20round?= =?UTF-8?q?-2=20Copilot=20review=20=E2=80=94=20identity,=20parsePolicies,?= =?UTF-8?q?=20target=5Fvalue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove dead code in validateCapacity (minRaw/maxRaw assigned then discarded) - Update(): validate ref.ProviderID matches spec identity; return clear error on mismatch to prevent orphaning the previously managed scalable target - Diff(): include provider_id in comparison to detect identity drift (ns/resource_id/dim changes) - parsePolicies(): return error on malformed input (wrong type / non-map entry) so syncPolicies fails safely instead of silently treating malformed input as "no policies" and destructively deleting all live policies - target_value: accept int/int64 in addition to float64 (common in YAML decoding); validate > 0 before PutScalingPolicy - readPolicyNames(): propagate DescribeScalingPolicies error so Read/HealthCheck callers can distinguish "target exists" from "policy enumeration failed" - Tests: add Diff identity-drift, Update identity-mismatch, int target_value, and malformed-policies-type cases Co-Authored-By: Claude Sonnet 4.6 --- drivers/autoscaling_group.go | 118 ++++++++++++++++++++---------- drivers/autoscaling_group_test.go | 112 +++++++++++++++++++++++++--- 2 files changed, 184 insertions(+), 46 deletions(-) diff --git a/drivers/autoscaling_group.go b/drivers/autoscaling_group.go index 6b8a08e..dcf560f 100644 --- a/drivers/autoscaling_group.go +++ b/drivers/autoscaling_group.go @@ -130,7 +130,10 @@ func (d *AutoScalingGroupDriver) Read(ctx context.Context, ref interfaces.Resour } target := out.ScalableTargets[0] - policyNames := d.readPolicyNames(ctx, target) + policyNames, err := d.readPolicyNames(ctx, target) + if err != nil { + return nil, fmt.Errorf("autoscaling_group: read %q: list policies: %w", ref.Name, err) + } minCap := int(awssdk.ToInt32(target.MinCapacity)) maxCap := int(awssdk.ToInt32(target.MaxCapacity)) providerID := encodeProviderID(string(target.ServiceNamespace), awssdk.ToString(target.ResourceId), string(target.ScalableDimension)) @@ -140,12 +143,24 @@ func (d *AutoScalingGroupDriver) Read(ctx context.Context, ref interfaces.Resour } // Update re-registers the scalable target (idempotent) and reconciles scaling policies. +// If ref.ProviderID is set, it must match the identity encoded in the spec +// (service_namespace|resource_id|scalable_dimension). A mismatch indicates that the +// identity fields have changed, which would target a different scalable target and +// orphan the previous one; callers should delete and re-create in that case. func (d *AutoScalingGroupDriver) Update(ctx context.Context, ref interfaces.ResourceRef, spec interfaces.ResourceSpec) (*interfaces.ResourceOutput, error) { ns, resourceID, dim, err := requiredAutoScalingFields(spec) if err != nil { return nil, fmt.Errorf("autoscaling_group: update %q: %w", ref.Name, err) } + // Validate identity consistency when ProviderID is known. + if ref.ProviderID != "" { + wantProviderID := encodeProviderID(ns, resourceID, dim) + if ref.ProviderID != wantProviderID { + return nil, fmt.Errorf("autoscaling_group: update %q: identity mismatch — ProviderID %q does not match spec identity %q; delete and re-create to change scalable target identity", ref.Name, ref.ProviderID, wantProviderID) + } + } + minCap, maxCap, err := validateCapacity(spec.Config) if err != nil { return nil, fmt.Errorf("autoscaling_group: update %q: %w", ref.Name, err) @@ -246,15 +261,23 @@ func (d *AutoScalingGroupDriver) Diff(_ context.Context, desired interfaces.Reso sort.Strings(curPolicies) curPolicyKey := strings.Join(curPolicies, ",") + // Compute the expected ProviderID from the desired spec to detect identity drift. + specNS, _ := desired.Config["service_namespace"].(string) + specResourceID, _ := desired.Config["resource_id"].(string) + specDim, _ := desired.Config["scalable_dimension"].(string) + wantProviderID := encodeProviderID(specNS, specResourceID, specDim) + want := map[string]any{ "min_capacity": intProp(desired.Config, "min_capacity", 0), "max_capacity": intProp(desired.Config, "max_capacity", 1), "policy_names": wantPolicyKey, + "provider_id": wantProviderID, } currentForDiff := map[string]any{ "min_capacity": current.Outputs["min_capacity"], "max_capacity": current.Outputs["max_capacity"], "policy_names": curPolicyKey, + "provider_id": current.ProviderID, } changes := diffOutputs(want, currentForDiff) return &interfaces.DiffResult{NeedsUpdate: len(changes) > 0, Changes: changes}, nil @@ -279,18 +302,14 @@ func (d *AutoScalingGroupDriver) Scale(_ context.Context, _ interfaces.ResourceR // validateCapacity extracts min/max_capacity, enforcing presence and min<=max. func validateCapacity(config map[string]any) (minCap, maxCap int32, err error) { - minRaw, minOK := config["min_capacity"] - maxRaw, maxOK := config["max_capacity"] - if !minOK { + if _, ok := config["min_capacity"]; !ok { return 0, 0, fmt.Errorf("min_capacity is required") } - if !maxOK { + if _, ok := config["max_capacity"]; !ok { return 0, 0, fmt.Errorf("max_capacity is required") } minI := intProp(config, "min_capacity", -1) maxI := intProp(config, "max_capacity", -1) - _ = minRaw - _ = maxRaw if minI < 0 { return 0, 0, fmt.Errorf("min_capacity must be a non-negative integer") } @@ -373,22 +392,25 @@ func (d *AutoScalingGroupDriver) fetchLivePolicies(ctx context.Context, ns, reso return out.ScalingPolicies, nil } -// readPolicyNames fetches policy names for a ScalableTarget; returns nil on error. -func (d *AutoScalingGroupDriver) readPolicyNames(ctx context.Context, target aastypes.ScalableTarget) []string { +// readPolicyNames fetches policy names for a ScalableTarget. +// Returns an error so callers can distinguish "target exists" from "policy enumeration failed." +func (d *AutoScalingGroupDriver) readPolicyNames(ctx context.Context, target aastypes.ScalableTarget) ([]string, error) { policies, err := d.fetchLivePolicies(ctx, string(target.ServiceNamespace), awssdk.ToString(target.ResourceId), string(target.ScalableDimension)) if err != nil { - return nil + return nil, err } var names []string for _, p := range policies { names = append(names, awssdk.ToString(p.PolicyName)) } - return names + return names, nil } // desiredPolicyNames returns the policy_name values from the spec config. +// Malformed policy entries are skipped to allow safe fingerprinting; callers +// that need strict validation use parsePolicies directly. func desiredPolicyNames(config map[string]any) []string { - policies := parsePolicies(config) + policies, _ := parsePolicies(config) names := make([]string, 0, len(policies)) for _, p := range policies { names = append(names, p.policyName) @@ -400,7 +422,10 @@ func desiredPolicyNames(config map[string]any) []string { // 1. Delete live policies whose policy_name is absent from the desired list. // 2. PutScalingPolicy for each desired policy. func (d *AutoScalingGroupDriver) syncPolicies(ctx context.Context, spec interfaces.ResourceSpec, ns, resourceID, dim string, livePolicies []aastypes.ScalingPolicy) error { - desired := parsePolicies(spec.Config) + desired, err := parsePolicies(spec.Config) + if err != nil { + return fmt.Errorf("parse policies: %w", err) + } // Build set of desired policy names. desiredNames := make(map[string]struct{}, len(desired)) @@ -454,27 +479,41 @@ type stepAdjustment struct { } // parsePolicies extracts the policies slice from config. -func parsePolicies(config map[string]any) []policySpec { +// Returns an error if the policies key exists but has an unexpected type or contains +// malformed entries, so reconciliation can fail safely without destructive deletes. +func parsePolicies(config map[string]any) ([]policySpec, error) { raw, ok := config["policies"] if !ok { - return nil + return nil, nil } items, ok := raw.([]any) if !ok { - return nil + return nil, fmt.Errorf("policies must be a list, got %T", raw) } var result []policySpec - for _, item := range items { + for i, item := range items { m, ok := item.(map[string]any) if !ok { - continue + return nil, fmt.Errorf("policies[%d] must be a map, got %T", i, item) } p := policySpec{} p.policyName, _ = m["policy_name"].(string) p.policyType, _ = m["policy_type"].(string) - if tv, ok := m["target_value"].(float64); ok { - p.targetValue = tv + + // target_value accepts float64, int, int64 (common in YAML decoding). + if tvRaw, ok := m["target_value"]; ok { + switch v := tvRaw.(type) { + case float64: + p.targetValue = v + case int: + p.targetValue = float64(v) + case int64: + p.targetValue = float64(v) + default: + return nil, fmt.Errorf("policies[%d]: target_value must be a number, got %T", i, tvRaw) + } } + p.predefinedMetricType, _ = m["predefined_metric_type"].(string) p.scaleInCooldown = int32(intProp(m, "scale_in_cooldown", 300)) p.scaleOutCooldown = int32(intProp(m, "scale_out_cooldown", 300)) @@ -483,23 +522,25 @@ func parsePolicies(config map[string]any) []policySpec { // Parse step_adjustments for StepScaling. if saRaw, ok := m["step_adjustments"]; ok { - if saItems, ok := saRaw.([]any); ok { - for _, saItem := range saItems { - saMap, ok := saItem.(map[string]any) - if !ok { - continue - } - sa := stepAdjustment{ - scalingAdjustment: int32(intProp(saMap, "scaling_adjustment", 0)), - } - if lb, ok := saMap["metric_interval_lower_bound"].(float64); ok { - sa.metricIntervalLowerBound = awssdk.Float64(lb) - } - if ub, ok := saMap["metric_interval_upper_bound"].(float64); ok { - sa.metricIntervalUpperBound = awssdk.Float64(ub) - } - p.stepAdjustments = append(p.stepAdjustments, sa) + saItems, ok := saRaw.([]any) + if !ok { + return nil, fmt.Errorf("policies[%d]: step_adjustments must be a list, got %T", i, saRaw) + } + for j, saItem := range saItems { + saMap, ok := saItem.(map[string]any) + if !ok { + return nil, fmt.Errorf("policies[%d].step_adjustments[%d] must be a map, got %T", i, j, saItem) } + sa := stepAdjustment{ + scalingAdjustment: int32(intProp(saMap, "scaling_adjustment", 0)), + } + if lb, ok := saMap["metric_interval_lower_bound"].(float64); ok { + sa.metricIntervalLowerBound = awssdk.Float64(lb) + } + if ub, ok := saMap["metric_interval_upper_bound"].(float64); ok { + sa.metricIntervalUpperBound = awssdk.Float64(ub) + } + p.stepAdjustments = append(p.stepAdjustments, sa) } } @@ -507,7 +548,7 @@ func parsePolicies(config map[string]any) []policySpec { result = append(result, p) } } - return result + return result, nil } // putPolicy calls PutScalingPolicy for a single policySpec. @@ -525,6 +566,9 @@ func (d *AutoScalingGroupDriver) putPolicy(ctx context.Context, ns, resourceID, if p.predefinedMetricType == "" { return fmt.Errorf("put policy %q: predefined_metric_type is required for TargetTrackingScaling (e.g., ECSServiceAverageCPUUtilization)", p.policyName) } + if p.targetValue <= 0 { + return fmt.Errorf("put policy %q: target_value must be > 0 for TargetTrackingScaling, got %v", p.policyName, p.targetValue) + } in.PolicyType = aastypes.PolicyTypeTargetTrackingScaling cfg := &aastypes.TargetTrackingScalingPolicyConfiguration{ TargetValue: awssdk.Float64(p.targetValue), diff --git a/drivers/autoscaling_group_test.go b/drivers/autoscaling_group_test.go index 7900bd6..df47465 100644 --- a/drivers/autoscaling_group_test.go +++ b/drivers/autoscaling_group_test.go @@ -608,9 +608,10 @@ func TestAutoScalingGroupDriver_Diff_NilCurrent(t *testing.T) { func TestAutoScalingGroupDriver_Diff_HasChanges(t *testing.T) { d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) current := &interfaces.ResourceOutput{ - Name: "asg", - Type: "infra.autoscaling_group", - Outputs: map[string]any{"min_capacity": 1, "max_capacity": 5, "policy_names": ""}, + Name: "asg", + Type: "infra.autoscaling_group", + ProviderID: baseProviderID, + Outputs: map[string]any{"min_capacity": 1, "max_capacity": 5, "policy_names": ""}, } spec := baseAutoScalingSpec("asg") spec.Config["max_capacity"] = 20 @@ -626,9 +627,10 @@ func TestAutoScalingGroupDriver_Diff_HasChanges(t *testing.T) { func TestAutoScalingGroupDriver_Diff_NoChanges(t *testing.T) { d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) current := &interfaces.ResourceOutput{ - Name: "asg", - Type: "infra.autoscaling_group", - Outputs: map[string]any{"min_capacity": 1, "max_capacity": 10, "policy_names": ""}, + Name: "asg", + Type: "infra.autoscaling_group", + ProviderID: baseProviderID, // matches baseAutoScalingSpec identity + Outputs: map[string]any{"min_capacity": 1, "max_capacity": 10, "policy_names": ""}, } diff, err := d.Diff(context.Background(), baseAutoScalingSpec("asg"), current) if err != nil { @@ -642,9 +644,10 @@ func TestAutoScalingGroupDriver_Diff_NoChanges(t *testing.T) { func TestAutoScalingGroupDriver_Diff_PolicyChange(t *testing.T) { d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) current := &interfaces.ResourceOutput{ - Name: "asg", - Type: "infra.autoscaling_group", - Outputs: map[string]any{"min_capacity": 1, "max_capacity": 10, "policy_names": []string{"old-policy"}}, + Name: "asg", + Type: "infra.autoscaling_group", + ProviderID: baseProviderID, + Outputs: map[string]any{"min_capacity": 1, "max_capacity": 10, "policy_names": []string{"old-policy"}}, } // Desired spec has a different policy set. spec := baseAutoScalingSpec("asg") @@ -728,6 +731,97 @@ func TestAutoScalingGroupDriver_SensitiveKeys(t *testing.T) { } } +// ---- Diff identity drift ---- + +func TestAutoScalingGroupDriver_Diff_IdentityDrift(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + // Current output has a different ProviderID (different resource_id). + current := &interfaces.ResourceOutput{ + Name: "asg", + Type: "infra.autoscaling_group", + ProviderID: "ecs|service/old-cluster/old-service|ecs:service:DesiredCount", + Outputs: map[string]any{"min_capacity": 1, "max_capacity": 10, "policy_names": ""}, + } + // Desired spec has a different identity — should trigger NeedsUpdate. + diff, err := d.Diff(context.Background(), baseAutoScalingSpec("asg"), current) + if err != nil { + t.Fatal(err) + } + if !diff.NeedsUpdate { + t.Error("expected NeedsUpdate=true when scalable target identity changes") + } +} + +// ---- Update identity mismatch ---- + +func TestAutoScalingGroupDriver_Update_IdentityMismatch(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + // ref.ProviderID encodes a different identity than the spec — should error. + _, err := d.Update(context.Background(), interfaces.ResourceRef{ + Name: "my-asg", + ProviderID: "ecs|service/old-cluster/old-service|ecs:service:DesiredCount", + }, baseAutoScalingSpec("my-asg")) + if err == nil { + t.Fatal("expected error when ProviderID does not match spec identity") + } +} + +// ---- Create with int target_value ---- + +func TestAutoScalingGroupDriver_Create_TargetTrackingPolicy_IntTargetValue(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + putPolicyOut: &applicationautoscaling.PutScalingPolicyOutput{PolicyARN: awssdk.String("arn:aws:autoscaling:policy/xyz")}, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + spec := baseAutoScalingSpec("my-asg") + // Use int (common in YAML decoding) instead of float64. + spec.Config["policies"] = []any{ + map[string]any{ + "policy_name": "cpu-tracking", + "policy_type": "TargetTrackingScaling", + "target_value": int(75), // int, not float64 + "predefined_metric_type": "ECSServiceAverageCPUUtilization", + }, + } + out, err := d.Create(context.Background(), spec) + if err != nil { + t.Fatalf("Create with int target_value failed: %v", err) + } + if out == nil { + t.Fatal("expected non-nil output") + } + captured := mock.capturedPutPolicyInput + if captured == nil { + t.Fatal("expected PutScalingPolicy to have been called") + } + if awssdk.ToFloat64(captured.TargetTrackingScalingPolicyConfiguration.TargetValue) != 75 { + t.Errorf("expected TargetValue=75, got %v", awssdk.ToFloat64(captured.TargetTrackingScalingPolicyConfiguration.TargetValue)) + } +} + +// ---- parsePolicies malformed input ---- + +func TestAutoScalingGroupDriver_Create_MalformedPoliciesType(t *testing.T) { + mock := &mockAutoScalingClient{ + registerOut: &applicationautoscaling.RegisterScalableTargetOutput{ + ScalableTargetARN: awssdk.String("arn:aws:application-autoscaling:us-east-1:123:scalable-target/abc"), + }, + describePoliciesOut: &applicationautoscaling.DescribeScalingPoliciesOutput{}, + } + d := drivers.NewAutoScalingGroupDriverWithClient(mock) + spec := baseAutoScalingSpec("my-asg") + // policies is a string, not a list — should fail safe rather than delete-all. + spec.Config["policies"] = "not-a-list" + _, err := d.Create(context.Background(), spec) + if err == nil { + t.Fatal("expected error when policies is malformed (wrong type)") + } +} + // ---- Interface compliance ---- func TestAutoScalingGroupDriver_ImplementsResourceDriver(t *testing.T) { From 46078e7e3a05eaac5488fdf1c70a30848d5becca Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 13 May 2026 07:22:56 -0400 Subject: [PATCH 6/6] =?UTF-8?q?fix(autoscaling=5Fgroup):=20address=20round?= =?UTF-8?q?-3=20Copilot=20review=20=E2=80=94=20policy=20config=20fingerpri?= =?UTF-8?q?nt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Diff now detects policy parameter changes (target_value, cooldowns, step_adjustments) by computing a deterministic policy_fingerprint from desired config and comparing against the stored fingerprint in current.Outputs; same-named policy with changed target_value now correctly triggers NeedsUpdate=true - buildOutputWithConfig stores policy_fingerprint in outputs for round-trip comparison - Create and Update use buildOutputWithConfig to persist the fingerprint - Add TestAutoScalingGroupDriver_Diff_PolicyConfigChange to cover this gap Co-Authored-By: Claude Sonnet 4.6 --- drivers/autoscaling_group.go | 74 ++++++++++++++++++++++++++----- drivers/autoscaling_group_test.go | 48 ++++++++++++++++++++ 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/drivers/autoscaling_group.go b/drivers/autoscaling_group.go index dcf560f..8e05329 100644 --- a/drivers/autoscaling_group.go +++ b/drivers/autoscaling_group.go @@ -106,7 +106,7 @@ func (d *AutoScalingGroupDriver) Create(ctx context.Context, spec interfaces.Res } policyNames := desiredPolicyNames(spec.Config) - return d.buildOutput(spec.Name, targetARN, providerID, int(minCap), int(maxCap), policyNames), nil + return d.buildOutputWithConfig(spec.Name, targetARN, providerID, int(minCap), int(maxCap), policyNames, spec.Config), nil } // Read describes the scalable target and its policies. @@ -196,7 +196,7 @@ func (d *AutoScalingGroupDriver) Update(ctx context.Context, ref interfaces.Reso targetARN := awssdk.ToString(out.ScalableTargetARN) providerID := encodeProviderID(ns, resourceID, dim) policyNames := desiredPolicyNames(spec.Config) - return d.buildOutput(ref.Name, targetARN, providerID, int(minCap), int(maxCap), policyNames), nil + return d.buildOutputWithConfig(ref.Name, targetARN, providerID, int(minCap), int(maxCap), policyNames, spec.Config), nil } // Delete removes all scaling policies then deregisters the scalable target. @@ -233,7 +233,10 @@ func (d *AutoScalingGroupDriver) Delete(ctx context.Context, ref interfaces.Reso } // Diff computes whether the desired spec diverges from the current output. -// Compares capacity bounds and the sorted set of policy names. +// Compares capacity bounds, policy names, and a policy-config fingerprint so +// changes to policy parameters (target_value, cooldowns, step_adjustments) are +// also detected. The fingerprint is stored in outputs["policy_fingerprint"] by +// buildOutput-callers that pass it. func (d *AutoScalingGroupDriver) Diff(_ context.Context, desired interfaces.ResourceSpec, current *interfaces.ResourceOutput) (*interfaces.DiffResult, error) { if current == nil { return &interfaces.DiffResult{NeedsUpdate: true}, nil @@ -267,17 +270,24 @@ func (d *AutoScalingGroupDriver) Diff(_ context.Context, desired interfaces.Reso specDim, _ := desired.Config["scalable_dimension"].(string) wantProviderID := encodeProviderID(specNS, specResourceID, specDim) + // Build a deterministic fingerprint of policy configs (not just names) so changes + // to policy parameters (target_value, cooldowns, step_adjustments) are detected. + wantPolicyFingerprint := policyConfigFingerprint(desired.Config) + want := map[string]any{ - "min_capacity": intProp(desired.Config, "min_capacity", 0), - "max_capacity": intProp(desired.Config, "max_capacity", 1), - "policy_names": wantPolicyKey, - "provider_id": wantProviderID, + "min_capacity": intProp(desired.Config, "min_capacity", 0), + "max_capacity": intProp(desired.Config, "max_capacity", 1), + "policy_names": wantPolicyKey, + "policy_fingerprint": wantPolicyFingerprint, + "provider_id": wantProviderID, } + curFingerprint, _ := current.Outputs["policy_fingerprint"].(string) currentForDiff := map[string]any{ - "min_capacity": current.Outputs["min_capacity"], - "max_capacity": current.Outputs["max_capacity"], - "policy_names": curPolicyKey, - "provider_id": current.ProviderID, + "min_capacity": current.Outputs["min_capacity"], + "max_capacity": current.Outputs["max_capacity"], + "policy_names": curPolicyKey, + "policy_fingerprint": curFingerprint, + "provider_id": current.ProviderID, } changes := diffOutputs(want, currentForDiff) return &interfaces.DiffResult{NeedsUpdate: len(changes) > 0, Changes: changes}, nil @@ -375,6 +385,48 @@ func (d *AutoScalingGroupDriver) buildOutput(name, targetARN, providerID string, } } +// buildOutputWithConfig is like buildOutput but also stores a policy_fingerprint +// derived from the spec config so Diff can detect policy parameter changes. +func (d *AutoScalingGroupDriver) buildOutputWithConfig(name, targetARN, providerID string, minCap, maxCap int, policyNames []string, config map[string]any) *interfaces.ResourceOutput { + out := d.buildOutput(name, targetARN, providerID, minCap, maxCap, policyNames) + out.Outputs["policy_fingerprint"] = policyConfigFingerprint(config) + return out +} + +// policyConfigFingerprint returns a deterministic string representation of the +// policies config for use in Diff comparisons. It encodes key policy parameters +// so changes to target_value, cooldowns, or step_adjustments are detected even +// when policy names stay the same. +func policyConfigFingerprint(config map[string]any) string { + policies, _ := parsePolicies(config) + if len(policies) == 0 { + return "" + } + // Sort by policy name for deterministic output. + sort.Slice(policies, func(i, j int) bool { + return policies[i].policyName < policies[j].policyName + }) + parts := make([]string, 0, len(policies)) + for _, p := range policies { + switch p.policyType { + case "TargetTrackingScaling": + parts = append(parts, fmt.Sprintf("%s:TT:%.2f:%s:%d:%d", + p.policyName, p.targetValue, p.predefinedMetricType, + p.scaleInCooldown, p.scaleOutCooldown)) + case "StepScaling": + steps := make([]string, len(p.stepAdjustments)) + for i, sa := range p.stepAdjustments { + steps[i] = fmt.Sprintf("%d", sa.scalingAdjustment) + } + parts = append(parts, fmt.Sprintf("%s:SS:%s:%d:%s", + p.policyName, p.adjustmentType, p.cooldown, strings.Join(steps, "+"))) + default: + parts = append(parts, p.policyName+":?:"+p.policyType) + } + } + return strings.Join(parts, "|") +} + // fetchLivePolicies returns the current scaling policies for a scalable target. // Returns an error so callers can decide whether to abort destructive operations. func (d *AutoScalingGroupDriver) fetchLivePolicies(ctx context.Context, ns, resourceID, dim string) ([]aastypes.ScalingPolicy, error) { diff --git a/drivers/autoscaling_group_test.go b/drivers/autoscaling_group_test.go index df47465..40b9353 100644 --- a/drivers/autoscaling_group_test.go +++ b/drivers/autoscaling_group_test.go @@ -731,6 +731,54 @@ func TestAutoScalingGroupDriver_SensitiveKeys(t *testing.T) { } } +// ---- Diff policy config change ---- + +func TestAutoScalingGroupDriver_Diff_PolicyConfigChange(t *testing.T) { + d := drivers.NewAutoScalingGroupDriverWithClient(&mockAutoScalingClient{}) + + // Build a "current" output as if Create had run with target_value=75. + specWithPolicy := baseAutoScalingSpec("asg") + specWithPolicy.Config["policies"] = []any{ + map[string]any{ + "policy_name": "cpu-policy", + "policy_type": "TargetTrackingScaling", + "target_value": float64(75), + "predefined_metric_type": "ECSServiceAverageCPUUtilization", + }, + } + // Simulate what Create stores: same policy name but the fingerprint encodes 75. + current := &interfaces.ResourceOutput{ + Name: "asg", + Type: "infra.autoscaling_group", + ProviderID: baseProviderID, + Outputs: map[string]any{ + "min_capacity": 1, + "max_capacity": 10, + "policy_names": "cpu-policy", + "policy_fingerprint": "cpu-policy:TT:75.00:ECSServiceAverageCPUUtilization:300:300", + }, + } + + // Desired spec changes target_value to 60 — same policy name, different config. + desired := baseAutoScalingSpec("asg") + desired.Config["policies"] = []any{ + map[string]any{ + "policy_name": "cpu-policy", + "policy_type": "TargetTrackingScaling", + "target_value": float64(60), + "predefined_metric_type": "ECSServiceAverageCPUUtilization", + }, + } + + diff, err := d.Diff(context.Background(), desired, current) + if err != nil { + t.Fatal(err) + } + if !diff.NeedsUpdate { + t.Error("expected NeedsUpdate=true when policy target_value changes (same name)") + } +} + // ---- Diff identity drift ---- func TestAutoScalingGroupDriver_Diff_IdentityDrift(t *testing.T) {