From 328b6f5907b9c1e724fbcd3cf68f8dfde291026a Mon Sep 17 00:00:00 2001 From: Cat C Date: Mon, 8 Sep 2025 22:42:11 -0700 Subject: [PATCH 01/12] Consolidate node peer annotations to address #1393 --- go.mod | 140 +++++---- go.sum | 293 +++++++++--------- .../routing/network_routes_controller.go | 293 ++++++++++++------ .../routing/network_routes_controller_test.go | 155 ++++++++- pkg/controllers/routing/utils.go | 20 +- 5 files changed, 578 insertions(+), 323 deletions(-) diff --git a/go.mod b/go.mod index a59bb82ea6..2e45613f57 100644 --- a/go.mod +++ b/go.mod @@ -1,89 +1,81 @@ module github.com/cloudnativelabs/kube-router/v2 require ( - github.com/aws/aws-sdk-go-v2 v1.40.0 - github.com/aws/aws-sdk-go-v2/config v1.31.17 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.274.0 - github.com/aws/smithy-go v1.23.2 - github.com/ccoveille/go-safecast/v2 v2.0.0 + github.com/aws/aws-sdk-go-v2 v1.38.3 + github.com/aws/aws-sdk-go-v2/config v1.31.6 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.249.0 + github.com/aws/smithy-go v1.23.0 + github.com/ccoveille/go-safecast v1.6.1 github.com/coreos/go-iptables v0.8.0 - github.com/docker/docker v28.5.2+incompatible + github.com/docker/docker v28.4.0+incompatible + github.com/goccy/go-yaml v1.18.0 + github.com/google/go-cmp v0.7.0 github.com/hashicorp/go-version v1.7.0 github.com/moby/ipvs v1.1.0 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.38.2 github.com/osrg/gobgp/v3 v3.37.0 - github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/client_golang v1.23.0 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 - golang.org/x/net v0.46.0 - golang.org/x/sys v0.38.0 - google.golang.org/grpc v1.76.0 - google.golang.org/protobuf v1.36.10 - k8s.io/api v0.34.2 - k8s.io/apimachinery v0.34.2 - k8s.io/client-go v0.34.2 - k8s.io/cri-api v0.34.2 + golang.org/x/net v0.43.0 + golang.org/x/sys v0.35.0 + google.golang.org/grpc v1.75.0 + google.golang.org/protobuf v1.36.8 + k8s.io/api v0.34.0 + k8s.io/apimachinery v0.34.0 + k8s.io/client-go v0.34.0 + k8s.io/cri-api v0.34.0 k8s.io/klog/v2 v2.130.1 - k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/yaml v1.6.0 ) require ( github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/eapache/channels v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect - github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.0 // indirect - github.com/go-openapi/jsonreference v0.21.1 // indirect - github.com/go-openapi/swag v0.24.1 // indirect - github.com/go-openapi/swag/cmdutils v0.24.0 // indirect - github.com/go-openapi/swag/conv v0.24.0 // indirect - github.com/go-openapi/swag/fileutils v0.24.0 // indirect - github.com/go-openapi/swag/jsonname v0.24.0 // indirect - github.com/go-openapi/swag/jsonutils v0.24.0 // indirect - github.com/go-openapi/swag/loading v0.24.0 // indirect - github.com/go-openapi/swag/mangling v0.24.0 // indirect - github.com/go-openapi/swag/netutils v0.24.0 // indirect - github.com/go-openapi/swag/stringutils v0.24.0 // indirect - github.com/go-openapi/swag/typeutils v0.24.0 // indirect - github.com/go-openapi/swag/yamlutils v0.24.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/k-sone/critbitgo v1.4.0 // indirect - github.com/mailru/easyjson v0.9.1 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect @@ -93,45 +85,51 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.17.0 // indirect - github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/viper v1.21.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/viper v1.19.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/oauth2 v0.31.0 // indirect - golang.org/x/term v0.36.0 // indirect - golang.org/x/text v0.30.0 // indirect - golang.org/x/time v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect - gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.4.0 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) -go 1.25.0 +go 1.24.0 -toolchain go1.25.1 +toolchain go1.24.1 diff --git a/go.sum b/go.sum index c9f59ff023..97b4ee560d 100644 --- a/go.sum +++ b/go.sum @@ -4,38 +4,38 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1 github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= -github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= -github.com/aws/aws-sdk-go-v2/config v1.31.17 h1:QFl8lL6RgakNK86vusim14P2k8BFSxjvUkcWLDjgz9Y= -github.com/aws/aws-sdk-go-v2/config v1.31.17/go.mod h1:V8P7ILjp/Uef/aX8TjGk6OHZN6IKPM5YW6S78QnRD5c= -github.com/aws/aws-sdk-go-v2/credentials v1.18.21 h1:56HGpsgnmD+2/KpG0ikvvR8+3v3COCwaF4r+oWwOeNA= -github.com/aws/aws-sdk-go-v2/credentials v1.18.21/go.mod h1:3YELwedmQbw7cXNaII2Wywd+YY58AmLPwX4LzARgmmA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.274.0 h1:Q2+WD4KSVRkd27QxD9I30nM3O7B4WYwE+ua5dm2NJY0= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.274.0/go.mod h1:QrV+/GjhSrJh6MRRuTO6ZEg4M2I0nwPakf0lZHSrE1o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 h1:0JPwLz1J+5lEOfy/g0SURC9cxhbQ1lIMHMa+AHZSzz0= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.1/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 h1:OWs0/j2UYR5LOGi88sD5/lhN6TDLG6SfA7CqsQO9zF0= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= -github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 h1:mLlUgHn02ue8whiR4BmxxGJLR2gwU6s6ZzJ5wDamBUs= -github.com/aws/aws-sdk-go-v2/service/sts v1.39.1/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= -github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= -github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk= +github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2/config v1.31.6 h1:a1t8fXY4GT4xjyJExz4knbuoxSCacB5hT/WgtfPyLjo= +github.com/aws/aws-sdk-go-v2/config v1.31.6/go.mod h1:5ByscNi7R+ztvOGzeUaIu49vkMk2soq5NaH5PYe33MQ= +github.com/aws/aws-sdk-go-v2/credentials v1.18.10 h1:xdJnXCouCx8Y0NncgoptztUocIYLKeQxrCgN6x9sdhg= +github.com/aws/aws-sdk-go-v2/credentials v1.18.10/go.mod h1:7tQk08ntj914F/5i9jC4+2HQTAuJirq7m1vZVIhEkWs= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 h1:wbjnrrMnKew78/juW7I2BtKQwa1qlf6EjQgS69uYY14= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6/go.mod h1:AtiqqNrDioJXuUgz3+3T0mBWN7Hro2n9wll2zRUc0ww= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 h1:uF68eJA6+S9iVr9WgX1NaRGyQ/6MdIyc4JNUo6TN1FA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6/go.mod h1:qlPeVZCGPiobx8wb1ft0GHT5l+dc6ldnwInDFaMvC7Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 h1:pa1DEC6JoI0zduhZePp3zmhWvk/xxm4NB8Hy/Tlsgos= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6/go.mod h1:gxEjPebnhWGJoaDdtDkA0JX46VRg1wcTHYe63OfX5pE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.249.0 h1:1wn3h1PKTKQ9tg7bzfm4x1iqKYsLY2qfmV4SsDmakkI= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.249.0/go.mod h1:SmMqzfS4HVsOD58lwLZ79oxF58f8zVe5YdK3o+/o1Ck= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 h1:LHS1YAIJXJ4K9zS+1d/xa9JAA9sL2QyXIQCQFQW/X08= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6/go.mod h1:c9PCiTEuh0wQID5/KqA32J+HAgZxN9tOGXKCiYJjTZI= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 h1:8OLZnVJPvjnrxEwHFg9hVUof/P4sibH+Ea4KKuqAGSg= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.1/go.mod h1:27M3BpVi0C02UiQh1w9nsBEit6pLhlaH3NHna6WUbDE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 h1:gKWSTnqudpo8dAxqBqZnDoDWCiEh/40FziUjr/mo6uA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2/go.mod h1:x7+rkNmRoEN1U13A6JE2fXne9EWyJy54o3n6d4mGaXQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 h1:YZPjhyaGzhDQEvsffDEcpycq49nl7fiGcfJTIo8BszI= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.2/go.mod h1:2dIN8qhQfv37BdUYGgEC8Q3tteM3zFxTI1MLO2O3J3c= +github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= +github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0= -github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs= +github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q= +github.com/ccoveille/go-safecast v1.6.1/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -52,30 +52,30 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= -github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 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/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= -github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= +github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= -github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -83,42 +83,20 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= -github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= -github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= -github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= -github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= -github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= -github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= -github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= -github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= -github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= -github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= -github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= -github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= -github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= -github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= -github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= -github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= -github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= -github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= -github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= -github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= -github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= -github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= -github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= -github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= -github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= -github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= -github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -147,6 +125,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -164,8 +144,12 @@ 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/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= -github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/ipvs v1.1.0 h1:ONN4pGaZQgAx+1Scz5RvWV4Q7Gb+mvfRh3NsPS+1XQQ= @@ -201,45 +185,55 @@ github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/osrg/gobgp/v3 v3.37.0 h1:+ObuOdvj7G7nxrT0fKFta+EAupdWf/q1WzbXydr8IOY= github.com/osrg/gobgp/v3 v3.37.0/go.mod h1:kVHVFy1/fyZHJ8P32+ctvPeJogn9qKwa1YCeMRXXrP0= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -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/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= -github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= -github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= -github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -252,37 +246,41 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -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/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -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.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -291,10 +289,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= -golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= -golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -312,52 +310,55 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= -golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 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/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= -google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= -google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= 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= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= -gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -368,22 +369,22 @@ 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.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/cri-api v0.34.2 h1:YtG6Ud62gH+5LYzOWFLeRCFz64SqFFEP5umr/I3PC0Q= -k8s.io/cri-api v0.34.2/go.mod h1:4qVUjidMg7/Z9YGZpqIDygbkPWkg3mkS1PvOx/kpHTE= +k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= +k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= +k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= +k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= +k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= +k8s.io/cri-api v0.34.0 h1:erzXelLqzDbNdryR7eVqxmR/1JfQeurE9U+HdKTgSpU= +k8s.io/cri-api v0.34.0/go.mod h1:4qVUjidMg7/Z9YGZpqIDygbkPWkg3mkS1PvOx/kpHTE= 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-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= diff --git a/pkg/controllers/routing/network_routes_controller.go b/pkg/controllers/routing/network_routes_controller.go index 235220089a..7a41c1ec8e 100644 --- a/pkg/controllers/routing/network_routes_controller.go +++ b/pkg/controllers/routing/network_routes_controller.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "github.com/goccy/go-yaml" "google.golang.org/protobuf/types/known/anypb" "github.com/ccoveille/go-safecast/v2" @@ -43,12 +44,14 @@ const ( nodeCustomImportRejectAnnotation = "kube-router.io/node.bgp.customimportreject" pathPrependASNAnnotation = "kube-router.io/path-prepend.as" pathPrependRepeatNAnnotation = "kube-router.io/path-prepend.repeat-n" - peerASNAnnotation = "kube-router.io/peer.asns" - peerIPAnnotation = "kube-router.io/peer.ips" - peerLocalIPAnnotation = "kube-router.io/peer.localips" + + peerASNAnnotation = "kube-router.io/peer.asns" + peerIPAnnotation = "kube-router.io/peer.ips" + peerLocalIPAnnotation = "kube-router.io/peer.localips" //nolint:gosec // this is not a hardcoded password peerPasswordAnnotation = "kube-router.io/peer.passwords" peerPortAnnotation = "kube-router.io/peer.ports" + peersAnnotation = "kube-router.io/peers" rrClientAnnotation = "kube-router.io/rr.client" rrServerAnnotation = "kube-router.io/rr.server" svcLocalAnnotation = "kube-router.io/service.local" @@ -157,7 +160,8 @@ type NetworkRoutingController struct { // Run runs forever until we are notified on stop channel func (nrc *NetworkRoutingController) Run(healthChan chan<- *healthcheck.ControllerHeartbeat, stopCh <-chan struct{}, - wg *sync.WaitGroup) { + wg *sync.WaitGroup, +) { var err error if nrc.enableCNI { nrc.updateCNIConfig() @@ -1098,106 +1102,22 @@ func (nrc *NetworkRoutingController) startBgpServer(grpcServer bool) error { // If the global routing peer is configured then peer with it // else attempt to get peers from node specific BGP annotations. if len(nrc.globalPeerRouters) == 0 { - // Get Global Peer Router ASN configs - nodeBgpPeerAsnsAnnotation, ok := node.Annotations[peerASNAnnotation] - if !ok { - klog.Infof("Could not find BGP peer info for the node in the node annotations so " + - "skipping configuring peer.") - return nil - } - - asnStrings := stringToSlice(nodeBgpPeerAsnsAnnotation, ",") - peerASNs, err := stringSliceToUInt32(asnStrings) + klog.V(2).Infof("Attempting to construct peer configs from annotation: %+v", node.Annotations) + peerCfgs, err := bgpPeerConfigsFromAnnotations(node.Annotations) if err != nil { err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) if err2 != nil { klog.Errorf("Failed to stop bgpServer: %s", err2) } - return fmt.Errorf("failed to parse node's Peer ASN Numbers Annotation: %s", err) + return err } - - // Get Global Peer Router IP Address configs - nodeBgpPeersAnnotation, ok := node.Annotations[peerIPAnnotation] - if !ok { - klog.Infof("Could not find BGP peer info for the node in the node annotations " + - "so skipping configuring peer.") + // Early exist because no BGP peer info was set in annotations for the node + if peerCfgs == nil { return nil } - ipStrings := stringToSlice(nodeBgpPeersAnnotation, ",") - peerIPs, err := stringSliceToIPs(ipStrings) - if err != nil { - err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) - if err2 != nil { - klog.Errorf("Failed to stop bgpServer: %s", err2) - } - - return fmt.Errorf("failed to parse node's Peer Addresses Annotation: %s", err) - } - - // Get Global Peer Router ASN configs - nodeBgpPeerPortsAnnotation, ok := node.Annotations[peerPortAnnotation] - // Default to default BGP port if port annotation is not found - var peerPorts = make([]uint32, 0) - if ok { - portStrings := stringToSlice(nodeBgpPeerPortsAnnotation, ",") - peerPorts, err = stringSliceToUInt32(portStrings) - if err != nil { - err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) - if err2 != nil { - klog.Errorf("Failed to stop bgpServer: %s", err2) - } - return fmt.Errorf("failed to parse node's Peer Port Numbers Annotation: %s", err) - } - } - - // Get Global Peer Router Password configs - var peerPasswords []string - nodeBGPPasswordsAnnotation, ok := node.Annotations[peerPasswordAnnotation] - if !ok { - klog.Infof("Could not find BGP peer password info in the node's annotations. Assuming no passwords.") - } else { - passStrings := stringToSlice(nodeBGPPasswordsAnnotation, ",") - peerPasswords, err = stringSliceB64Decode(passStrings) - if err != nil { - err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) - if err2 != nil { - klog.Errorf("Failed to stop bgpServer: %s", err2) - } - return fmt.Errorf("failed to parse node's Peer Passwords Annotation") - } - } - - // Get Global Peer Router LocalIP configs - var peerLocalIPs []string - nodeBGPPeerLocalIPs, ok := node.Annotations[peerLocalIPAnnotation] - if !ok { - klog.Infof("Could not find BGP peer local ip info in the node's annotations. Assuming node IP.") - } else { - peerLocalIPs = stringToSlice(nodeBGPPeerLocalIPs, ",") - err = func() error { - for _, s := range peerLocalIPs { - if s != "" { - ip := net.ParseIP(s) - if ip == nil { - return fmt.Errorf("could not parse \"%s\" as an IP", s) - } - } - } - - return nil - }() - if err != nil { - err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) - if err2 != nil { - klog.Errorf("Failed to stop bgpServer: %s", err2) - } - - return fmt.Errorf("failed to parse node's Peer Local Addresses Annotation: %s", err) - } - } // Create and set Global Peer Router complete configs - nrc.globalPeerRouters, err = newGlobalPeers(peerIPs, peerPorts, peerASNs, peerPasswords, peerLocalIPs, + nrc.globalPeerRouters, err = newGlobalPeers(peerCfgs.RemoteIPs(), peerCfgs.Ports(), peerCfgs.RemoteASNs(), peerCfgs.Passwords(), peerCfgs.LocalIPs(), nrc.bgpHoldtime, nrc.krNode.GetPrimaryNodeIP().String()) if err != nil { err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) @@ -1208,7 +1128,7 @@ func (nrc *NetworkRoutingController) startBgpServer(grpcServer bool) error { return fmt.Errorf("failed to process Global Peer Router configs: %s", err) } - nrc.nodePeerRouters = ipStrings + nrc.nodePeerRouters = peerCfgs.RemoteIPStrings() } if len(nrc.globalPeerRouters) != 0 { @@ -1284,8 +1204,8 @@ func (nrc *NetworkRoutingController) setupHandlers(node *v1core.Node) error { func NewNetworkRoutingController(clientset kubernetes.Interface, kubeRouterConfig *options.KubeRouterConfig, nodeInformer cache.SharedIndexInformer, svcInformer cache.SharedIndexInformer, - epSliceInformer cache.SharedIndexInformer, ipsetMutex *sync.Mutex) (*NetworkRoutingController, error) { - + epSliceInformer cache.SharedIndexInformer, ipsetMutex *sync.Mutex, +) (*NetworkRoutingController, error) { var err error nrc := NetworkRoutingController{ipsetMutex: ipsetMutex} @@ -1509,3 +1429,182 @@ func NewNetworkRoutingController(clientset kubernetes.Interface, return &nrc, nil } + +type bgpPeerConfig struct { + LocalIP *string `yaml:"localip"` + Password *base64String `yaml:"password"` + Port *uint32 `yaml:"port"` + RemoteASN *uint32 `yaml:"remoteasn"` + RemoteIP *net.IP `yaml:"remoteip"` + remoteIPString string +} + +type bgpPeerConfigs []bgpPeerConfig + +func (b bgpPeerConfigs) LocalIPs() []string { + localIPs := make([]string, 0) + for _, cfg := range b { + if cfg.LocalIP != nil { + localIPs = append(localIPs, *cfg.LocalIP) + } + } + return localIPs +} + +func (b bgpPeerConfigs) Passwords() []string { + passwords := make([]string, 0) + for _, cfg := range b { + if cfg.Password != nil { + passwords = append(passwords, string(*cfg.Password)) + } + } + return passwords +} + +func (b bgpPeerConfigs) Ports() []uint32 { + ports := make([]uint32, 0) + for _, cfg := range b { + if cfg.Port != nil { + ports = append(ports, *cfg.Port) + } + } + return ports +} + +func (b bgpPeerConfigs) RemoteASNs() []uint32 { + asns := make([]uint32, 0) + for _, cfg := range b { + if cfg.RemoteASN != nil { + asns = append(asns, *cfg.RemoteASN) + } + } + return asns +} + +func (b bgpPeerConfigs) RemoteIPs() []net.IP { + remoteIPs := make([]net.IP, 0) + for _, cfg := range b { + if cfg.RemoteIP != nil { + remoteIPs = append(remoteIPs, *cfg.RemoteIP) + } + } + return remoteIPs +} + +func (b bgpPeerConfigs) RemoteIPStrings() []string { + remoteIPs := make([]string, 0) + for _, cfg := range b { + if cfg.remoteIPString != "" { + remoteIPs = append(remoteIPs, cfg.remoteIPString) + } + } + return remoteIPs +} + +func bgpPeerConfigsFromAnnotations(nodeAnnotations map[string]string) (bgpPeerConfigs, error) { + nodeBgpPeersAnnotation, ok := nodeAnnotations[peersAnnotation] + if !ok { + klog.Infof("%s annotation not set, using individual node annotations to configure BGP peer info", peersAnnotation) + return bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations) + } + + var peerConfigs []bgpPeerConfig + if err := yaml.Unmarshal([]byte(nodeBgpPeersAnnotation), &peerConfigs); err != nil { + return nil, fmt.Errorf("failed to parse %s annotation: %w", peersAnnotation, err) + } + klog.Infof("Peer config from %s annotation: %+v", peersAnnotation, peerConfigs) + return peerConfigs, nil +} + +func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) (bgpPeerConfigs, error) { + // Get Global Peer Router ASN configs + nodeBgpPeerAsnsAnnotation, ok := nodeAnnotations[peerASNAnnotation] + if !ok { + klog.Infof("Could not find BGP peer info for the node in the node annotations so " + + "skipping configuring peer.") + return nil, nil + } + asnStrings := stringToSlice(nodeBgpPeerAsnsAnnotation, ",") + peerASNs, err := stringSliceToUInt32(asnStrings) + if err != nil { + return nil, fmt.Errorf("failed to parse node's Peer ASN Numbers Annotation: %w", err) + } + peerConfigs := make([]bgpPeerConfig, len(peerASNs)) + for i, peerASN := range peerASNs { + peerConfigs[i].RemoteASN = &peerASN + } + + // Get Global Peer Router IP Address configs + nodeBgpPeersAnnotation, ok := nodeAnnotations[peerIPAnnotation] + if !ok { + klog.Infof("Could not find BGP peer info for the node in the node annotations " + + "so skipping configuring peer.") + return nil, nil + } + ipStrings := stringToSlice(nodeBgpPeersAnnotation, ",") + peerIPs, err := stringSliceToIPs(ipStrings) + if err != nil { + return nil, fmt.Errorf("failed to parse node's Peer Addresses Annotation: %w", err) + } + for i, peerIP := range peerIPs { + peerConfigs[i].remoteIPString = ipStrings[i] + peerConfigs[i].RemoteIP = &peerIP + } + + // Get Global Peer Router ASN configs + nodeBgpPeerPortsAnnotation, ok := nodeAnnotations[peerPortAnnotation] + // Default to default BGP port if port annotation is not found + if ok { + var ports []uint32 + portStrings := stringToSlice(nodeBgpPeerPortsAnnotation, ",") + ports, err = stringSliceToUInt32(portStrings) + if err != nil { + return nil, fmt.Errorf("failed to parse node's Peer Port Numbers Annotation: %w", err) + } + for i, port := range ports { + peerConfigs[i].Port = &port + } + } + + // Get Global Peer Router Password configs + nodeBGPPasswordsAnnotation, ok := nodeAnnotations[peerPasswordAnnotation] + if !ok { + klog.Infof("Could not find BGP peer password info in the node's annotations. Assuming no passwords.") + } else { + var passwords []string + passStrings := stringToSlice(nodeBGPPasswordsAnnotation, ",") + passwords, err = stringSliceB64Decode(passStrings) + if err != nil { + return nil, fmt.Errorf("failed to parse node's Peer Passwords Annotation: %w", err) + } + for i, password := range passwords { + bpassword := base64String(password) + peerConfigs[i].Password = &bpassword + } + } + + // Get Global Peer Router LocalIP configs + nodeBGPPeerLocalIPs, ok := nodeAnnotations[peerLocalIPAnnotation] + if !ok { + klog.Infof("Could not find BGP peer local ip info in the node's annotations. Assuming node IP.") + } else { + localIPs := stringToSlice(nodeBGPPeerLocalIPs, ",") + err = func() error { + for i, s := range localIPs { + if s != "" { + ip := net.ParseIP(s) + if ip == nil { + return fmt.Errorf("could not parse \"%s\" as an IP", s) + } + peerConfigs[i].LocalIP = &s + } + } + return nil + }() + if err != nil { + return nil, fmt.Errorf("failed to parse node's Peer Local Addresses Annotation: %w", err) + } + } + + return peerConfigs, nil +} diff --git a/pkg/controllers/routing/network_routes_controller_test.go b/pkg/controllers/routing/network_routes_controller_test.go index 66ac257567..0f9532b407 100644 --- a/pkg/controllers/routing/network_routes_controller_test.go +++ b/pkg/controllers/routing/network_routes_controller_test.go @@ -12,7 +12,11 @@ import ( "github.com/cloudnativelabs/kube-router/v2/pkg/k8s/indexers" "github.com/cloudnativelabs/kube-router/v2/pkg/utils" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" . "github.com/onsi/ginkgo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1core "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -34,7 +38,6 @@ func Test_advertiseClusterIPs(t *testing.T) { // the key is the subnet from the watch event watchEvents map[string]bool }{ - { "add bgp path for service with ClusterIP", &NetworkRoutingController{ @@ -1818,11 +1821,11 @@ func Test_nodeHasEndpointsForService(t *testing.T) { Endpoints: []discoveryv1.Endpoint{ { Addresses: []string{"172.20.1.1"}, - NodeName: ptrToString("node-1"), + NodeName: valToPtr("node-1"), }, { Addresses: []string{"172.20.1.2"}, - NodeName: ptrToString("node-2"), + NodeName: valToPtr("node-2"), }, }, }, @@ -1863,11 +1866,11 @@ func Test_nodeHasEndpointsForService(t *testing.T) { Endpoints: []discoveryv1.Endpoint{ { Addresses: []string{"172.20.1.1"}, - NodeName: ptrToString("node-2"), + NodeName: valToPtr("node-2"), }, { Addresses: []string{"172.20.1.2"}, - NodeName: ptrToString("node-3"), + NodeName: valToPtr("node-3"), }, }, }, @@ -1902,7 +1905,6 @@ func Test_nodeHasEndpointsForService(t *testing.T) { t.Logf("actual nodeHasEndpoints: %v", nodeHasEndpoints) t.Error("unexpected nodeHasEndpoints") } - }) } } @@ -2618,7 +2620,133 @@ func Test_routeReflectorConfiguration(t *testing.T) { } }) } +} + +func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { + testCases := []struct { + name string + nodeAnnotations map[string]string + expectedBgpPeerConfigs bgpPeerConfigs + expectError bool + }{ + { + "node annotations are empty", + map[string]string{}, + nil, + false, + }, + { + "combined bgp peers config annotation", + map[string]string{ + peersAnnotation: `- remoteip: 10.0.0.1 + remoteasn: 64640 + password: cGFzc3dvcmQ= + localip: 192.168.0.1 +- remoteip: 10.0.0.2 + remoteasn: 64641 + password: cGFzc3dvcmQ= + localip: 192.168.0.2`, + }, + bgpPeerConfigs{ + bgpPeerConfig{ + RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: valToPtr(uint32(64640)), + Password: valToPtr(base64String("password")), + LocalIP: valToPtr("192.168.0.1"), + }, + bgpPeerConfig{ + RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: valToPtr(uint32(64641)), + Password: valToPtr(base64String("password")), + LocalIP: valToPtr("192.168.0.2"), + }, + }, + false, + }, + { + "combined bgp peers config annotation without all fields set", + map[string]string{ + peersAnnotation: `- remoteip: 10.0.0.1 + remoteasn: 64640 +- remoteip: 10.0.0.2 + remoteasn: 64641 + password: cGFzc3dvcmQ= + localip: 192.168.0.2`, + }, + bgpPeerConfigs{ + bgpPeerConfig{ + RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: valToPtr(uint32(64640)), + }, + bgpPeerConfig{ + RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: valToPtr(uint32(64641)), + Password: valToPtr(base64String("password")), + LocalIP: valToPtr("192.168.0.2"), + }, + }, + false, + }, + { + "individual bgp peers config annotations", + map[string]string{ + peerIPAnnotation: "10.0.0.1,10.0.0.2", + peerASNAnnotation: "64640,64641", + peerPasswordAnnotation: "cGFzc3dvcmQ=,cGFzc3dvcmQ=", + peerLocalIPAnnotation: "192.168.0.1,192.168.0.2", + }, + bgpPeerConfigs{ + bgpPeerConfig{ + RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: valToPtr(uint32(64640)), + Password: valToPtr(base64String("password")), + LocalIP: valToPtr("192.168.0.1"), + }, + bgpPeerConfig{ + RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: valToPtr(uint32(64641)), + Password: valToPtr(base64String("password")), + LocalIP: valToPtr("192.168.0.2"), + }, + }, + false, + }, + { + "individual bgp peers config annotations without peer ASN annotation", + map[string]string{ + peerASNAnnotation: "64640,64641", + peerPasswordAnnotation: "cGFzc3dvcmQ=,cGFzc3dvcmQ=", + peerLocalIPAnnotation: "192.168.0.1,192.168.0.2", + }, + nil, + false, + }, + { + "individual bgp peers config annotations without peer IP annotation", + map[string]string{ + peerIPAnnotation: "10.0.0.1,10.0.0.2", + peerPasswordAnnotation: "cGFzc3dvcmQ=,cGFzc3dvcmQ=", + peerLocalIPAnnotation: "192.168.0.1,192.168.0.2", + }, + nil, + false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bgpPeerCfgs, err := bgpPeerConfigsFromAnnotations(tc.nodeAnnotations) + if tc.expectError { + assert.Error(t, err) + return + } + require.NoError(t, err) + if !cmp.Equal(tc.expectedBgpPeerConfigs, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgpPeerConfig{})) { + diff := cmp.Diff(tc.expectedBgpPeerConfigs, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgpPeerConfig{})) + t.Errorf("BGP peer config mismatch:\n%s", diff) + } + }) + } } /* Disabling test for now. OnNodeUpdate() behaviour is changed. test needs to be adopted. @@ -2866,6 +2994,17 @@ func waitForListerWithTimeout(lister cache.Indexer, timeout time.Duration, t *te } } -func ptrToString(str string) *string { - return &str +type value interface { + string | uint32 | net.IP | base64String +} + +func valToPtr[V value](v V) *V { + return &v +} + +func ptrToVal[V value](v *V) V { + if v == nil { + return *new(V) + } + return *v } diff --git a/pkg/controllers/routing/utils.go b/pkg/controllers/routing/utils.go index 5c904afe56..a3bfbd5005 100644 --- a/pkg/controllers/routing/utils.go +++ b/pkg/controllers/routing/utils.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/goccy/go-yaml" gobgpapi "github.com/osrg/gobgp/v3/api" v1core "k8s.io/api/core/v1" @@ -110,6 +111,22 @@ func statementsEqualByName(a, b []*gobgpapi.Statement) bool { return true } +// Wrapper type to automatically handles decoding b64 encoded strings upon unmarshalling +type base64String string + +func (b *base64String) UnmarshalYAML(raw []byte) error { + var tmp string + if err := yaml.Unmarshal(raw, &tmp); err != nil { + return fmt.Errorf("failed to unmarshal string into base64string type: %w", err) + } + decoded, err := base64.StdEncoding.DecodeString(tmp) + if err != nil { + return fmt.Errorf("failed to base64 decode field: %w", err) + } + *b = base64String(string(decoded)) + return nil +} + // getPodCIDRsFromAllNodeSources gets the pod CIDRs for all available sources on a given node in a specific order. The // order of preference is: // 1. From the kube-router.io/pod-cidr annotation (preserves backwards compatibility) @@ -155,7 +172,8 @@ func getPodCIDRsFromAllNodeSources(node *v1core.Node) (podCIDRs []string) { // based upon whether it is an IPv4 address or an IPv6 address. Returns slash notation subnet as uint32 suitable for // sending to GoBGP and an error if it is unable to determine the subnet automatically func (nrc *NetworkRoutingController) getBGPRouteInfoForVIP(vip string) (subnet uint32, nh string, - afiFamily gobgpapi.Family_Afi, err error) { + afiFamily gobgpapi.Family_Afi, err error, +) { ip := net.ParseIP(vip) if ip == nil { err = fmt.Errorf("could not parse VIP: %s", vip) From a079c7c8016e2e10b7f1c572652278927f18008e Mon Sep 17 00:00:00 2001 From: Cat C Date: Sat, 11 Oct 2025 12:53:15 -0700 Subject: [PATCH 02/12] Re-organize structure of bgpPeerConfigs into separate packages --- pkg/bgp/peer_config.go | 107 ++++++++++++++++++ .../routing/network_routes_controller.go | 87 ++------------ .../routing/network_routes_controller_test.go | 37 +++--- pkg/controllers/routing/utils.go | 17 --- pkg/utils/base64.go | 25 ++++ 5 files changed, 160 insertions(+), 113 deletions(-) create mode 100644 pkg/bgp/peer_config.go create mode 100644 pkg/utils/base64.go diff --git a/pkg/bgp/peer_config.go b/pkg/bgp/peer_config.go new file mode 100644 index 0000000000..c9e658a886 --- /dev/null +++ b/pkg/bgp/peer_config.go @@ -0,0 +1,107 @@ +package bgp + +import ( + "fmt" + "net" + + "github.com/cloudnativelabs/kube-router/v2/pkg/utils" + "github.com/goccy/go-yaml" +) + +type PeerConfig struct { + LocalIP *string `yaml:"localip"` + Password *utils.Base64String `yaml:"password"` + Port *uint32 `yaml:"port"` + RemoteASN *uint32 `yaml:"remoteasn"` + RemoteIP *net.IP `yaml:"remoteip"` +} + +func (p *PeerConfig) UnmarshalYAML(raw []byte) error { + tmp := struct { + LocalIP *string `yaml:"localip"` + Password *utils.Base64String `yaml:"password"` + Port *uint32 `yaml:"port"` + RemoteASN *uint32 `yaml:"remoteasn"` + RemoteIP string `yaml:"remoteip"` + }{} + + if err := yaml.Unmarshal(raw, &tmp); err != nil { + return fmt.Errorf("failed to unmarshal peer config: %w", err) + } + + p.LocalIP = tmp.LocalIP + p.Password = tmp.Password + p.Port = tmp.Port + p.RemoteASN = tmp.RemoteASN + + if tmp.RemoteIP != "" { + ip := net.ParseIP(tmp.RemoteIP) + if ip == nil { + return fmt.Errorf("%s is not a valid IP address", tmp.RemoteIP) + } + p.RemoteIP = &ip + } + return nil +} + +type PeerConfigs []PeerConfig + +func (p PeerConfigs) LocalIPs() []string { + localIPs := make([]string, 0) + for _, cfg := range p { + if cfg.LocalIP != nil { + localIPs = append(localIPs, *cfg.LocalIP) + } + } + return localIPs +} + +func (p PeerConfigs) Passwords() []string { + passwords := make([]string, 0) + for _, cfg := range p { + if cfg.Password != nil { + passwords = append(passwords, string(*cfg.Password)) + } + } + return passwords +} + +func (p PeerConfigs) Ports() []uint32 { + ports := make([]uint32, 0) + for _, cfg := range p { + if cfg.Port != nil { + ports = append(ports, *cfg.Port) + } + } + return ports +} + +func (p PeerConfigs) RemoteASNs() []uint32 { + asns := make([]uint32, 0) + for _, cfg := range p { + if cfg.RemoteASN != nil { + asns = append(asns, *cfg.RemoteASN) + } + } + return asns +} + +func (p PeerConfigs) RemoteIPs() []net.IP { + remoteIPs := make([]net.IP, 0) + for _, cfg := range p { + if cfg.RemoteIP != nil { + remoteIPs = append(remoteIPs, *cfg.RemoteIP) + } + } + return remoteIPs +} + +func (p PeerConfigs) RemoteIPStrings() []string { + remoteIPs := make([]string, 0) + for _, cfg := range p { + if cfg.RemoteIP != nil { + remoteIPs = append(remoteIPs, cfg.RemoteIP.String()) + } + } + return remoteIPs +} diff --git a/pkg/controllers/routing/network_routes_controller.go b/pkg/controllers/routing/network_routes_controller.go index 7a41c1ec8e..5b88907ad8 100644 --- a/pkg/controllers/routing/network_routes_controller.go +++ b/pkg/controllers/routing/network_routes_controller.go @@ -45,13 +45,16 @@ const ( pathPrependASNAnnotation = "kube-router.io/path-prepend.as" pathPrependRepeatNAnnotation = "kube-router.io/path-prepend.repeat-n" + // Prefer using this consolidated BGP config annotation, as the + // individual annotation config options are deprecated. + peersAnnotation = "kube-router.io/peers" + peerASNAnnotation = "kube-router.io/peer.asns" peerIPAnnotation = "kube-router.io/peer.ips" peerLocalIPAnnotation = "kube-router.io/peer.localips" //nolint:gosec // this is not a hardcoded password peerPasswordAnnotation = "kube-router.io/peer.passwords" peerPortAnnotation = "kube-router.io/peer.ports" - peersAnnotation = "kube-router.io/peers" rrClientAnnotation = "kube-router.io/rr.client" rrServerAnnotation = "kube-router.io/rr.server" svcLocalAnnotation = "kube-router.io/service.local" @@ -1430,85 +1433,14 @@ func NewNetworkRoutingController(clientset kubernetes.Interface, return &nrc, nil } -type bgpPeerConfig struct { - LocalIP *string `yaml:"localip"` - Password *base64String `yaml:"password"` - Port *uint32 `yaml:"port"` - RemoteASN *uint32 `yaml:"remoteasn"` - RemoteIP *net.IP `yaml:"remoteip"` - remoteIPString string -} - -type bgpPeerConfigs []bgpPeerConfig - -func (b bgpPeerConfigs) LocalIPs() []string { - localIPs := make([]string, 0) - for _, cfg := range b { - if cfg.LocalIP != nil { - localIPs = append(localIPs, *cfg.LocalIP) - } - } - return localIPs -} - -func (b bgpPeerConfigs) Passwords() []string { - passwords := make([]string, 0) - for _, cfg := range b { - if cfg.Password != nil { - passwords = append(passwords, string(*cfg.Password)) - } - } - return passwords -} - -func (b bgpPeerConfigs) Ports() []uint32 { - ports := make([]uint32, 0) - for _, cfg := range b { - if cfg.Port != nil { - ports = append(ports, *cfg.Port) - } - } - return ports -} - -func (b bgpPeerConfigs) RemoteASNs() []uint32 { - asns := make([]uint32, 0) - for _, cfg := range b { - if cfg.RemoteASN != nil { - asns = append(asns, *cfg.RemoteASN) - } - } - return asns -} - -func (b bgpPeerConfigs) RemoteIPs() []net.IP { - remoteIPs := make([]net.IP, 0) - for _, cfg := range b { - if cfg.RemoteIP != nil { - remoteIPs = append(remoteIPs, *cfg.RemoteIP) - } - } - return remoteIPs -} - -func (b bgpPeerConfigs) RemoteIPStrings() []string { - remoteIPs := make([]string, 0) - for _, cfg := range b { - if cfg.remoteIPString != "" { - remoteIPs = append(remoteIPs, cfg.remoteIPString) - } - } - return remoteIPs -} - -func bgpPeerConfigsFromAnnotations(nodeAnnotations map[string]string) (bgpPeerConfigs, error) { +func bgpPeerConfigsFromAnnotations(nodeAnnotations map[string]string) (bgp.PeerConfigs, error) { nodeBgpPeersAnnotation, ok := nodeAnnotations[peersAnnotation] if !ok { klog.Infof("%s annotation not set, using individual node annotations to configure BGP peer info", peersAnnotation) return bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations) } - var peerConfigs []bgpPeerConfig + var peerConfigs bgp.PeerConfigs if err := yaml.Unmarshal([]byte(nodeBgpPeersAnnotation), &peerConfigs); err != nil { return nil, fmt.Errorf("failed to parse %s annotation: %w", peersAnnotation, err) } @@ -1516,7 +1448,7 @@ func bgpPeerConfigsFromAnnotations(nodeAnnotations map[string]string) (bgpPeerCo return peerConfigs, nil } -func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) (bgpPeerConfigs, error) { +func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) (bgp.PeerConfigs, error) { // Get Global Peer Router ASN configs nodeBgpPeerAsnsAnnotation, ok := nodeAnnotations[peerASNAnnotation] if !ok { @@ -1529,7 +1461,7 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) if err != nil { return nil, fmt.Errorf("failed to parse node's Peer ASN Numbers Annotation: %w", err) } - peerConfigs := make([]bgpPeerConfig, len(peerASNs)) + peerConfigs := make([]bgp.PeerConfig, len(peerASNs)) for i, peerASN := range peerASNs { peerConfigs[i].RemoteASN = &peerASN } @@ -1547,7 +1479,6 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) return nil, fmt.Errorf("failed to parse node's Peer Addresses Annotation: %w", err) } for i, peerIP := range peerIPs { - peerConfigs[i].remoteIPString = ipStrings[i] peerConfigs[i].RemoteIP = &peerIP } @@ -1578,7 +1509,7 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) return nil, fmt.Errorf("failed to parse node's Peer Passwords Annotation: %w", err) } for i, password := range passwords { - bpassword := base64String(password) + bpassword := utils.Base64String(password) peerConfigs[i].Password = &bpassword } } diff --git a/pkg/controllers/routing/network_routes_controller_test.go b/pkg/controllers/routing/network_routes_controller_test.go index 0f9532b407..2c3fd40472 100644 --- a/pkg/controllers/routing/network_routes_controller_test.go +++ b/pkg/controllers/routing/network_routes_controller_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/cloudnativelabs/kube-router/v2/pkg/bgp" "github.com/cloudnativelabs/kube-router/v2/pkg/k8s/indexers" "github.com/cloudnativelabs/kube-router/v2/pkg/utils" "github.com/google/go-cmp/cmp" @@ -2626,7 +2627,7 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { testCases := []struct { name string nodeAnnotations map[string]string - expectedBgpPeerConfigs bgpPeerConfigs + expectedBgpPeerConfigs bgp.PeerConfigs expectError bool }{ { @@ -2647,17 +2648,17 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { password: cGFzc3dvcmQ= localip: 192.168.0.2`, }, - bgpPeerConfigs{ - bgpPeerConfig{ + bgp.PeerConfigs{ + bgp.PeerConfig{ RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), RemoteASN: valToPtr(uint32(64640)), - Password: valToPtr(base64String("password")), + Password: valToPtr(utils.Base64String("password")), LocalIP: valToPtr("192.168.0.1"), }, - bgpPeerConfig{ + bgp.PeerConfig{ RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), RemoteASN: valToPtr(uint32(64641)), - Password: valToPtr(base64String("password")), + Password: valToPtr(utils.Base64String("password")), LocalIP: valToPtr("192.168.0.2"), }, }, @@ -2673,15 +2674,15 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { password: cGFzc3dvcmQ= localip: 192.168.0.2`, }, - bgpPeerConfigs{ - bgpPeerConfig{ + bgp.PeerConfigs{ + bgp.PeerConfig{ RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), RemoteASN: valToPtr(uint32(64640)), }, - bgpPeerConfig{ + bgp.PeerConfig{ RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), RemoteASN: valToPtr(uint32(64641)), - Password: valToPtr(base64String("password")), + Password: valToPtr(utils.Base64String("password")), LocalIP: valToPtr("192.168.0.2"), }, }, @@ -2695,17 +2696,17 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { peerPasswordAnnotation: "cGFzc3dvcmQ=,cGFzc3dvcmQ=", peerLocalIPAnnotation: "192.168.0.1,192.168.0.2", }, - bgpPeerConfigs{ - bgpPeerConfig{ + bgp.PeerConfigs{ + bgp.PeerConfig{ RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), RemoteASN: valToPtr(uint32(64640)), - Password: valToPtr(base64String("password")), + Password: valToPtr(utils.Base64String("password")), LocalIP: valToPtr("192.168.0.1"), }, - bgpPeerConfig{ + bgp.PeerConfig{ RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), RemoteASN: valToPtr(uint32(64641)), - Password: valToPtr(base64String("password")), + Password: valToPtr(utils.Base64String("password")), LocalIP: valToPtr("192.168.0.2"), }, }, @@ -2741,8 +2742,8 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { return } require.NoError(t, err) - if !cmp.Equal(tc.expectedBgpPeerConfigs, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgpPeerConfig{})) { - diff := cmp.Diff(tc.expectedBgpPeerConfigs, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgpPeerConfig{})) + if !cmp.Equal(tc.expectedBgpPeerConfigs, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgp.PeerConfig{})) { + diff := cmp.Diff(tc.expectedBgpPeerConfigs, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgp.PeerConfig{})) t.Errorf("BGP peer config mismatch:\n%s", diff) } }) @@ -2995,7 +2996,7 @@ func waitForListerWithTimeout(lister cache.Indexer, timeout time.Duration, t *te } type value interface { - string | uint32 | net.IP | base64String + string | uint32 | net.IP | utils.Base64String } func valToPtr[V value](v V) *V { diff --git a/pkg/controllers/routing/utils.go b/pkg/controllers/routing/utils.go index a3bfbd5005..1d03033c50 100644 --- a/pkg/controllers/routing/utils.go +++ b/pkg/controllers/routing/utils.go @@ -7,7 +7,6 @@ import ( "strconv" "strings" - "github.com/goccy/go-yaml" gobgpapi "github.com/osrg/gobgp/v3/api" v1core "k8s.io/api/core/v1" @@ -111,22 +110,6 @@ func statementsEqualByName(a, b []*gobgpapi.Statement) bool { return true } -// Wrapper type to automatically handles decoding b64 encoded strings upon unmarshalling -type base64String string - -func (b *base64String) UnmarshalYAML(raw []byte) error { - var tmp string - if err := yaml.Unmarshal(raw, &tmp); err != nil { - return fmt.Errorf("failed to unmarshal string into base64string type: %w", err) - } - decoded, err := base64.StdEncoding.DecodeString(tmp) - if err != nil { - return fmt.Errorf("failed to base64 decode field: %w", err) - } - *b = base64String(string(decoded)) - return nil -} - // getPodCIDRsFromAllNodeSources gets the pod CIDRs for all available sources on a given node in a specific order. The // order of preference is: // 1. From the kube-router.io/pod-cidr annotation (preserves backwards compatibility) diff --git a/pkg/utils/base64.go b/pkg/utils/base64.go new file mode 100644 index 0000000000..5b1a869c29 --- /dev/null +++ b/pkg/utils/base64.go @@ -0,0 +1,25 @@ +package utils + +import ( + "encoding/base64" + "fmt" + + "github.com/goccy/go-yaml" +) + +// Base64String is a wrapper type that handles automatic b64 decoding of a +// string when unmarshalling +type Base64String string + +func (b *Base64String) UnmarshalYAML(raw []byte) error { + var tmp string + if err := yaml.Unmarshal(raw, &tmp); err != nil { + return fmt.Errorf("failed to unmarshal string into base64string type: %w", err) + } + decoded, err := base64.StdEncoding.DecodeString(tmp) + if err != nil { + return fmt.Errorf("failed to base64 decode field: %w", err) + } + *b = Base64String(string(decoded)) + return nil +} From 08610c4b658e053ab36a38b7f53587c3a1b4bd15 Mon Sep 17 00:00:00 2001 From: Cat C Date: Tue, 21 Oct 2025 19:59:59 -0700 Subject: [PATCH 03/12] Break out bgp peer config into separate package, add tests --- pkg/bgp/peer_config.go | 98 +++++++++++++++++++ pkg/bgp/peer_config_test.go | 68 +++++++++++++ pkg/controllers/routing/bgp_peers.go | 35 ++----- .../routing/network_routes_controller.go | 47 ++++----- .../routing/network_routes_controller_test.go | 28 +++++- 5 files changed, 220 insertions(+), 56 deletions(-) create mode 100644 pkg/bgp/peer_config_test.go diff --git a/pkg/bgp/peer_config.go b/pkg/bgp/peer_config.go index c9e658a886..f25b32f496 100644 --- a/pkg/bgp/peer_config.go +++ b/pkg/bgp/peer_config.go @@ -1,9 +1,12 @@ package bgp import ( + "errors" "fmt" "net" + "strconv" + "github.com/cloudnativelabs/kube-router/v2/pkg/options" "github.com/cloudnativelabs/kube-router/v2/pkg/utils" "github.com/goccy/go-yaml" ) @@ -56,6 +59,7 @@ func (p PeerConfigs) LocalIPs() []string { return localIPs } +// Returns b64 decoded passwords func (p PeerConfigs) Passwords() []string { passwords := make([]string, 0) for _, cfg := range p { @@ -105,3 +109,97 @@ func (p PeerConfigs) RemoteIPStrings() []string { } return remoteIPs } + +func (p *PeerConfigs) UnmarshalYAML(raw []byte) error { + type tmpPeerConfigs PeerConfigs + tmp := (*tmpPeerConfigs)(p) + + if err := yaml.Unmarshal(raw, tmp); err != nil { + return err + } + + return p.Validate() +} + +func (p PeerConfigs) Validate() error { + return validatePeerConfigs(p.RemoteIPStrings(), p.RemoteASNs(), p.Ports(), p.Passwords(), p.LocalIPs(), "") +} + +func NewPeerConfigs( + remoteIPs []string, + remoteASNs []uint32, + ports []uint32, + b64EncodedPasswords []string, + localIPs []string, + localAddress string, +) (PeerConfigs, error) { + if err := validatePeerConfigs(remoteIPs, remoteASNs, ports, b64EncodedPasswords, localIPs, localAddress); err != nil { + return nil, err + } + + peerCfgs := make(PeerConfigs, len(remoteIPs)) + for i, remoteIP := range remoteIPs { + ip := net.ParseIP(remoteIP) + if ip == nil { + return nil, fmt.Errorf("invalid IP address: %s", remoteIP) + } + peerCfgs[i].RemoteIP = &ip + peerCfgs[i].RemoteASN = &remoteASNs[i] + + if len(ports) != 0 { + peerCfgs[i].Port = &ports[i] + } + + if len(b64EncodedPasswords) != 0 { + pw := utils.Base64String(b64EncodedPasswords[i]) + peerCfgs[i].Password = &pw + } + + if len(localIPs) != 0 && localIPs[i] != "" { + peerCfgs[i].LocalIP = &localIPs[i] + } + } + + return peerCfgs, nil +} + +func validatePeerConfigs( + remoteIPs []string, + remoteASNs []uint32, + ports []uint32, + b64EncodedPasswords []string, + localIPs []string, + localAddress string, +) error { + if len(remoteIPs) != len(remoteASNs) { + return errors.New("invalid peer router config, the number of IPs and ASN numbers must be equal") + } + if len(remoteIPs) != len(b64EncodedPasswords) && len(b64EncodedPasswords) != 0 { + return errors.New("invalid peer router config. The number of passwords should either be zero, or " + + "one per peer router. Use blank items if a router doesn't expect a password. Example: \"pass,,pass\" " + + "OR [\"pass\",\"\",\"pass\"]") + } + if len(remoteIPs) != len(ports) && len(ports) != 0 { + return fmt.Errorf("invalid peer router config. The number of ports should either be zero, or "+ + "one per peer router. If blank items are used, it will default to standard BGP port, %s. "+ + "Example: \"port,,port\" OR [\"port\",\"\",\"port\"]", strconv.Itoa(options.DefaultBgpPort)) + } + if len(remoteIPs) != len(localIPs) && len(localIPs) != 0 { + return fmt.Errorf("invalid peer router config. The number of localIPs should either be zero, or "+ + "one per peer router. If blank items are used, it will default to nodeIP, %s. "+ + "Example: \"10.1.1.1,,10.1.1.2\" OR [\"10.1.1.1\",\"\",\"10.1.1.2\"]", localAddress) + } + + for _, asn := range remoteASNs { + if (asn < 1 || asn > 23455) && + (asn < 23457 || asn > 63999) && + (asn < 64512 || asn > 65534) && + (asn < 131072 || asn > 4199999999) && + (asn < 4200000000 || asn > 4294967294) { + return fmt.Errorf("reserved ASN number \"%d\" for global BGP peer", + asn) + } + } + + return nil +} diff --git a/pkg/bgp/peer_config_test.go b/pkg/bgp/peer_config_test.go new file mode 100644 index 0000000000..da06545c98 --- /dev/null +++ b/pkg/bgp/peer_config_test.go @@ -0,0 +1,68 @@ +package bgp + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_NewPeerConfigs(t *testing.T) { + tcs := []struct { + name string + remoteIPs []string + remoteASNs []uint32 + ports []uint32 + b64EncodedPasswords []string + localIPs []string + localAddress string + errContains string + }{ + { + name: "all fields set to nil", + }, + { + name: "number of remote IPs and remote ASNs don't match", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234}, + errContains: "the number of IPs and ASN numbers must be equal", + }, + { + name: "number of remote IPs and passwords don't match", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234, 2345}, + b64EncodedPasswords: []string{"fakepassword"}, + errContains: "number of passwords", + }, + { + name: "number of remote IPs and ports don't match", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234, 2345}, + ports: []uint32{8080}, + errContains: "number of ports", + }, + { + name: "number of remote IPs and local IPs don't match", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234, 2345}, + localIPs: []string{"1.1.1.1"}, + errContains: "number of localIPs", + }, + { + name: "remoteASN contains a reserved ASN number", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{0, 2345}, + errContains: "reserved ASN", + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + _, err := NewPeerConfigs(tc.remoteIPs, tc.remoteASNs, tc.ports, tc.b64EncodedPasswords, tc.localIPs, tc.localAddress) + if tc.errContains != "" { + assert.ErrorContains(t, err, tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/controllers/routing/bgp_peers.go b/pkg/controllers/routing/bgp_peers.go index 3f290575f0..9ef5cf0dc5 100644 --- a/pkg/controllers/routing/bgp_peers.go +++ b/pkg/controllers/routing/bgp_peers.go @@ -2,13 +2,13 @@ package routing import ( "context" - "errors" "fmt" "net" "strconv" "strings" "time" + "github.com/cloudnativelabs/kube-router/v2/pkg/bgp" "github.com/cloudnativelabs/kube-router/v2/pkg/metrics" "github.com/cloudnativelabs/kube-router/v2/pkg/options" "github.com/cloudnativelabs/kube-router/v2/pkg/utils" @@ -205,7 +205,8 @@ func (nrc *NetworkRoutingController) syncInternalPeers() { // connectToExternalBGPPeers adds all the configured eBGP peers (global or node specific) as neighbours func (nrc *NetworkRoutingController) connectToExternalBGPPeers(server *gobgp.BgpServer, peerNeighbors []*gobgpapi.Peer, bgpGracefulRestart bool, bgpGracefulRestartDeferralTime time.Duration, bgpGracefulRestartTime time.Duration, - peerMultihopTTL uint8) error { + peerMultihopTTL uint8, +) error { for _, n := range peerNeighbors { neighborIPStr := n.Conf.NeighborAddress neighborIP := net.ParseIP(neighborIPStr) @@ -285,32 +286,14 @@ func (nrc *NetworkRoutingController) connectToExternalBGPPeers(server *gobgp.Bgp } // Does validation and returns neighbor configs -func newGlobalPeers(ips []net.IP, ports []uint32, asns []uint32, passwords []string, localips []string, - holdtime float64, localAddress string) ([]*gobgpapi.Peer, error) { +func newGlobalPeers(peerConfigs bgp.PeerConfigs, holdtime float64, localAddress string) ([]*gobgpapi.Peer, error) { peers := make([]*gobgpapi.Peer, 0) - // Validations - if len(ips) != len(asns) { - return nil, errors.New("invalid peer router config, the number of IPs and ASN numbers must be equal") - } - - if len(ips) != len(passwords) && len(passwords) != 0 { - return nil, errors.New("invalid peer router config. The number of passwords should either be zero, or " + - "one per peer router. Use blank items if a router doesn't expect a password. Example: \"pass,,pass\" " + - "OR [\"pass\",\"\",\"pass\"]") - } - - if len(ips) != len(ports) && len(ports) != 0 { - return nil, fmt.Errorf("invalid peer router config. The number of ports should either be zero, or "+ - "one per peer router. If blank items are used, it will default to standard BGP port, %s. "+ - "Example: \"port,,port\" OR [\"port\",\"\",\"port\"]", strconv.Itoa(options.DefaultBgpPort)) - } - - if len(ips) != len(localips) && len(localips) != 0 { - return nil, fmt.Errorf("invalid peer router config. The number of localIPs should either be zero, or "+ - "one per peer router. If blank items are used, it will default to nodeIP, %s. "+ - "Example: \"10.1.1.1,,10.1.1.2\" OR [\"10.1.1.1\",\"\",\"10.1.1.2\"]", localAddress) - } + ips := peerConfigs.RemoteIPs() + asns := peerConfigs.RemoteASNs() + passwords := peerConfigs.Passwords() + ports := peerConfigs.Ports() + localips := peerConfigs.LocalIPs() for i := 0; i < len(ips); i++ { if (asns[i] < 1 || asns[i] > 23455) && diff --git a/pkg/controllers/routing/network_routes_controller.go b/pkg/controllers/routing/network_routes_controller.go index 5b88907ad8..feeb3192de 100644 --- a/pkg/controllers/routing/network_routes_controller.go +++ b/pkg/controllers/routing/network_routes_controller.go @@ -1120,8 +1120,7 @@ func (nrc *NetworkRoutingController) startBgpServer(grpcServer bool) error { } // Create and set Global Peer Router complete configs - nrc.globalPeerRouters, err = newGlobalPeers(peerCfgs.RemoteIPs(), peerCfgs.Ports(), peerCfgs.RemoteASNs(), peerCfgs.Passwords(), peerCfgs.LocalIPs(), - nrc.bgpHoldtime, nrc.krNode.GetPrimaryNodeIP().String()) + nrc.globalPeerRouters, err = newGlobalPeers(peerCfgs, nrc.bgpHoldtime, nrc.krNode.GetPrimaryNodeIP().String()) if err != nil { err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) if err2 != nil { @@ -1387,8 +1386,17 @@ func NewNetworkRoutingController(clientset kubernetes.Interface, } } - nrc.globalPeerRouters, err = newGlobalPeers(kubeRouterConfig.PeerRouters, peerPorts, - peerASNs, peerPasswords, nil, nrc.bgpHoldtime, nrc.krNode.GetPrimaryNodeIP().String()) + peerRouterIPs := make([]string, len(kubeRouterConfig.PeerRouters)) + for i, pr := range kubeRouterConfig.PeerRouters { + peerRouterIPs[i] = pr.String() + } + + peerCfgs, err := bgp.NewPeerConfigs(peerRouterIPs, peerASNs, peerPorts, peerPasswords, nil, nrc.krNode.GetPrimaryNodeIP().String()) + if err != nil { + return nil, err + } + + nrc.globalPeerRouters, err = newGlobalPeers(peerCfgs, nrc.bgpHoldtime, nrc.krNode.GetPrimaryNodeIP().String()) if err != nil { return nil, fmt.Errorf("error processing Global Peer Router configs: %s", err) } @@ -1461,10 +1469,6 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) if err != nil { return nil, fmt.Errorf("failed to parse node's Peer ASN Numbers Annotation: %w", err) } - peerConfigs := make([]bgp.PeerConfig, len(peerASNs)) - for i, peerASN := range peerASNs { - peerConfigs[i].RemoteASN = &peerASN - } // Get Global Peer Router IP Address configs nodeBgpPeersAnnotation, ok := nodeAnnotations[peerIPAnnotation] @@ -1474,60 +1478,46 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) return nil, nil } ipStrings := stringToSlice(nodeBgpPeersAnnotation, ",") - peerIPs, err := stringSliceToIPs(ipStrings) - if err != nil { - return nil, fmt.Errorf("failed to parse node's Peer Addresses Annotation: %w", err) - } - for i, peerIP := range peerIPs { - peerConfigs[i].RemoteIP = &peerIP - } // Get Global Peer Router ASN configs + var ports []uint32 nodeBgpPeerPortsAnnotation, ok := nodeAnnotations[peerPortAnnotation] // Default to default BGP port if port annotation is not found if ok { - var ports []uint32 portStrings := stringToSlice(nodeBgpPeerPortsAnnotation, ",") ports, err = stringSliceToUInt32(portStrings) if err != nil { return nil, fmt.Errorf("failed to parse node's Peer Port Numbers Annotation: %w", err) } - for i, port := range ports { - peerConfigs[i].Port = &port - } } + var passwords []string // Get Global Peer Router Password configs nodeBGPPasswordsAnnotation, ok := nodeAnnotations[peerPasswordAnnotation] if !ok { klog.Infof("Could not find BGP peer password info in the node's annotations. Assuming no passwords.") } else { - var passwords []string passStrings := stringToSlice(nodeBGPPasswordsAnnotation, ",") passwords, err = stringSliceB64Decode(passStrings) if err != nil { return nil, fmt.Errorf("failed to parse node's Peer Passwords Annotation: %w", err) } - for i, password := range passwords { - bpassword := utils.Base64String(password) - peerConfigs[i].Password = &bpassword - } } // Get Global Peer Router LocalIP configs + var localIPs []string nodeBGPPeerLocalIPs, ok := nodeAnnotations[peerLocalIPAnnotation] if !ok { klog.Infof("Could not find BGP peer local ip info in the node's annotations. Assuming node IP.") } else { - localIPs := stringToSlice(nodeBGPPeerLocalIPs, ",") + localIPs = stringToSlice(nodeBGPPeerLocalIPs, ",") err = func() error { - for i, s := range localIPs { + for _, s := range localIPs { if s != "" { ip := net.ParseIP(s) if ip == nil { return fmt.Errorf("could not parse \"%s\" as an IP", s) } - peerConfigs[i].LocalIP = &s } } return nil @@ -1537,5 +1527,6 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) } } - return peerConfigs, nil + // TODO: Add local address to the end arg? + return bgp.NewPeerConfigs(ipStrings, peerASNs, ports, passwords, localIPs, "") } diff --git a/pkg/controllers/routing/network_routes_controller_test.go b/pkg/controllers/routing/network_routes_controller_test.go index 2c3fd40472..856e9c757b 100644 --- a/pkg/controllers/routing/network_routes_controller_test.go +++ b/pkg/controllers/routing/network_routes_controller_test.go @@ -2665,7 +2665,7 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { false, }, { - "combined bgp peers config annotation without all fields set", + "combined bgp peers config annotation without matching number of peer config fields set", map[string]string{ peersAnnotation: `- remoteip: 10.0.0.1 remoteasn: 64640 @@ -2686,7 +2686,7 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { LocalIP: valToPtr("192.168.0.2"), }, }, - false, + true, }, { "individual bgp peers config annotations", @@ -2712,6 +2712,30 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { }, false, }, + { + "individual bgp peers config annotations without matching number of peer config fields set", + map[string]string{ + peerIPAnnotation: "10.0.0.1,10.0.0.2", + peerASNAnnotation: "64640,64641", + peerPasswordAnnotation: "cGFzc3dvcmQ=", + peerLocalIPAnnotation: "192.168.0.2", + }, + bgp.PeerConfigs{ + bgp.PeerConfig{ + RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: valToPtr(uint32(64640)), + Password: valToPtr(utils.Base64String("password")), + LocalIP: valToPtr("192.168.0.1"), + }, + bgp.PeerConfig{ + RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: valToPtr(uint32(64641)), + Password: valToPtr(utils.Base64String("password")), + LocalIP: valToPtr("192.168.0.2"), + }, + }, + true, + }, { "individual bgp peers config annotations without peer ASN annotation", map[string]string{ From 2b86c85f6d6a3665997ad6b9533c426250b02245 Mon Sep 17 00:00:00 2001 From: Cat C Date: Tue, 21 Oct 2025 21:30:28 -0700 Subject: [PATCH 04/12] Update markdown docs --- docs/bgp.md | 78 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/docs/bgp.md b/docs/bgp.md index a68b444c1f..45690cc1b7 100644 --- a/docs/bgp.md +++ b/docs/bgp.md @@ -10,7 +10,7 @@ pod IPs, service IPs, etc.). This is the default mode. All nodes in the clusters form iBGP peering relationships with rest of the nodes forming a full node-to-node mesh. Each node advertise the pod CIDR allocated to the nodes with its peers (the rest of the nodes in -the cluster). There is no configuration required in this mode. All the nodes in the cluster are associated with the +the cluster). There is no configuration required in this mode. All the nodes in the cluster are associated with the private ASN 64512 implicitly (which can be configured with `--cluster-asn` flag) and users are transparent to use of iBGP. This mode is suitable in public cloud environments or small cluster deployments. @@ -30,7 +30,7 @@ kubectl annotate node "kube-router.io/node.asn=64512" Only nodes within same ASN form full mesh. Two nodes with different ASNs never get peered. -### Route-Reflector setup Without Full Mesh +### Route-Reflector setup Without Full Mesh This model supports the common scheme of using a Route Reflector Server node to concentrate peering from client peers. This has the big advantage of not needing full mesh, and will scale better. In this mode kube-router expects each node @@ -75,11 +75,45 @@ For example: ### Node Specific External BGP Peers -Alternatively, each node can be configured with one or more node specific BGP peers. Information regarding node specific -BGP peer is read from node API object annotations: +Each node can be configured with one or more node specific BGP peers using the `kube-router.io/peers` node annotation. +Previously, these settings were configured using individual `kube-router.io/peer.*` annotations. +While these individual annotations are still supported, they're now deprecated and +will be removed in a future release. + +#### Using Consolidated Annotation + +The `kube-router.io/peers` annotation accepts peer configurations in YAML format with the following fields: + +- `remoteip` (required): The IP address of the peer +- `remoteasn` (required): The ASN of the peer +- `localip` (optional): Local IP address to use for this peer connection +- `password` (optional): Base64 encoded password for BGP authentication +- `port` (optional): BGP port (defaults to 179 if not specified) + +```shell +kubectl annotate node \ +kube-router.io/peers="$(cat <<'EOF' +- remoteip: 192.168.1.99 + remoteasn: 65000 + password: U2VjdXJlUGFzc3dvcmQK, +- remoteip: 192.168.1.100 + remoteasn: 65000' + password: U2VjdXJlUGFzc3dvcmQK, +EOF +)" +``` + +#### Using Individual Annotations (Deprecated) + +> **NOTE:** The individual peer annotations listed below are deprecated in favor of the consolidated `kube-router.io/peers` +> annotation. They are maintained for backward compatibility but will be removed in a future release. + +Node-specific BGP peer configs can also be set via individual node API object annotations: - `kube-router.io/peer.ips` - `kube-router.io/peer.asns` +- `kube-router.io/peer.passwords` +- `kube-router.io/peer.localips` For example, users can annotate node object with below commands: @@ -106,26 +140,23 @@ kubectl annotate node "kube-router.io/path-prepend.repeat-n=5" ### BGP Peer Local IP configuration -In some setups it might be desirable to set a local IP address used for connecting external BGP peers. This can be -accomplished on nodes with annotations: +In some setups it might be desirable to set a local IP address used for connecting external BGP peers. -- `kube-router.io/peer.localips` - -If set, this must be a list with a local IP address for each peer, or left empty to use nodeIP. +When using the `kube-router.io/peers` annotation, specify the `localip` field for each peer as shown in the +[Node Specific External BGP Peers](#node-specific-external-bgp-peers) section above. -Example: +When using individual annotations, you can specify the local IP address using `kube-router.io/peer.localips`: ```shell kubectl annotate node "kube-router.io/peer.localips=10.1.1.1,10.1.1.2" ``` -This will instruct kube-router to use IP `10.1.1.1` for first BGP peer as a local address, and use `10.1.1.2`for the -second. +If set, this must be a list with a local IP address for each peer, or left empty to use nodeIP. ### BGP Peer Password Authentication -The examples above have assumed there is no password authentication with BGP peer routers. If you need to use a password -for peering, you can use the `--peer-router-passwords` command-line option, the `kube-router.io/peer.passwords` node +If you need to use a password for peering with BGP peer routers, you can configure it using the `kube-router.io/peers` +annotation, the `--peer-router-passwords` command-line option, the deprecated `kube-router.io/peer.passwords` node annotation, or the `--peer-router-passwords-file` command-line option. #### Base64 Encoding Passwords @@ -142,7 +173,14 @@ U2VjdXJlUGFzc3dvcmQ= #### Password Configuration Examples -In this CLI flag example the first router (192.168.1.99) uses a password, while the second (192.168.1.100) does not. +**Using the consolidated annotation (recommended):** + +When using the `kube-router.io/peers` annotation, specify the `password` field with a base64 encoded password for each +peer that requires authentication. See the [Node Specific External BGP Peers](#node-specific-external-bgp-peers) section for an example. + +**Using CLI flags:** + +In this example the first router (192.168.1.99) uses a password, while the second (192.168.1.100) does not: ```sh --peer-router-ips="192.168.1.99,192.168.1.100" @@ -152,7 +190,9 @@ In this CLI flag example the first router (192.168.1.99) uses a password, while Note the comma indicating the end of the first password. -Here's the same example but configured as node annotations: +**Using individual annotations (deprecated):** + +Here's the same example but configured with individual node annotations: ```shell kubectl annotate node "kube-router.io/peer.ips=192.168.1.99,192.168.1.100" @@ -160,6 +200,8 @@ kubectl annotate node "kube-router.io/peer.asns=65000,65000" kubectl annotate node "kube-router.io/peer.passwords=U2VjdXJlUGFzc3dvcmQK," ``` +**Using a password file:** + Finally, to include peer passwords as a file you would run kube-router with the following option: ```shell @@ -168,8 +210,8 @@ Finally, to include peer passwords as a file you would run kube-router with the --peer-router-passwords-file="/etc/kube-router/bgp-passwords.conf" ``` -The password file, closely follows the syntax of the command-line and node annotation options. -Here, the first peer IP (192.168.1.99) would be configured with a password, while the second would not. +The password file closely follows the syntax of the command-line and node annotation options. +Here, the first peer IP (192.168.1.99) would be configured with a password, while the second would not: ```sh U2VjdXJlUGFzc3dvcmQK, From 6adf22e7ac3281f61db25dbf5c6e00b0a98fd1f7 Mon Sep 17 00:00:00 2001 From: Cat C Date: Mon, 27 Oct 2025 19:03:43 -0700 Subject: [PATCH 05/12] Update docs changes --- docs/bgp.md | 3 +- pkg/bgp/peer_config_test.go | 44 +++++++++---------- .../routing/network_routes_controller.go | 11 +++-- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/bgp.md b/docs/bgp.md index 45690cc1b7..330de66df9 100644 --- a/docs/bgp.md +++ b/docs/bgp.md @@ -176,7 +176,8 @@ U2VjdXJlUGFzc3dvcmQ= **Using the consolidated annotation (recommended):** When using the `kube-router.io/peers` annotation, specify the `password` field with a base64 encoded password for each -peer that requires authentication. See the [Node Specific External BGP Peers](#node-specific-external-bgp-peers) section for an example. +peer that requires authentication. See the +[Node Specific External BGP Peers](#node-specific-external-bgp-peers) section for an example. **Using CLI flags:** diff --git a/pkg/bgp/peer_config_test.go b/pkg/bgp/peer_config_test.go index da06545c98..d3f4f69dbe 100644 --- a/pkg/bgp/peer_config_test.go +++ b/pkg/bgp/peer_config_test.go @@ -15,51 +15,51 @@ func Test_NewPeerConfigs(t *testing.T) { b64EncodedPasswords []string localIPs []string localAddress string - errContains string + errStringContains string }{ { name: "all fields set to nil", }, { - name: "number of remote IPs and remote ASNs don't match", - remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, - remoteASNs: []uint32{1234}, - errContains: "the number of IPs and ASN numbers must be equal", + name: "number of remote IPs and remote ASNs don't match", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234}, + errStringContains: "the number of IPs and ASN numbers must be equal", }, { name: "number of remote IPs and passwords don't match", remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, remoteASNs: []uint32{1234, 2345}, b64EncodedPasswords: []string{"fakepassword"}, - errContains: "number of passwords", + errStringContains: "number of passwords", }, { - name: "number of remote IPs and ports don't match", - remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, - remoteASNs: []uint32{1234, 2345}, - ports: []uint32{8080}, - errContains: "number of ports", + name: "number of remote IPs and ports don't match", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234, 2345}, + ports: []uint32{8080}, + errStringContains: "number of ports", }, { - name: "number of remote IPs and local IPs don't match", - remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, - remoteASNs: []uint32{1234, 2345}, - localIPs: []string{"1.1.1.1"}, - errContains: "number of localIPs", + name: "number of remote IPs and local IPs don't match", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234, 2345}, + localIPs: []string{"1.1.1.1"}, + errStringContains: "number of localIPs", }, { - name: "remoteASN contains a reserved ASN number", - remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, - remoteASNs: []uint32{0, 2345}, - errContains: "reserved ASN", + name: "remoteASN contains a reserved ASN number", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{0, 2345}, + errStringContains: "reserved ASN", }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { _, err := NewPeerConfigs(tc.remoteIPs, tc.remoteASNs, tc.ports, tc.b64EncodedPasswords, tc.localIPs, tc.localAddress) - if tc.errContains != "" { - assert.ErrorContains(t, err, tc.errContains) + if tc.errStringContains != "" { + assert.ErrorContains(t, err, tc.errStringContains) } else { assert.NoError(t, err) } diff --git a/pkg/controllers/routing/network_routes_controller.go b/pkg/controllers/routing/network_routes_controller.go index feeb3192de..46bf203cac 100644 --- a/pkg/controllers/routing/network_routes_controller.go +++ b/pkg/controllers/routing/network_routes_controller.go @@ -1106,7 +1106,7 @@ func (nrc *NetworkRoutingController) startBgpServer(grpcServer bool) error { // else attempt to get peers from node specific BGP annotations. if len(nrc.globalPeerRouters) == 0 { klog.V(2).Infof("Attempting to construct peer configs from annotation: %+v", node.Annotations) - peerCfgs, err := bgpPeerConfigsFromAnnotations(node.Annotations) + peerCfgs, err := bgpPeerConfigsFromAnnotations(node.Annotations, nrc.krNode.GetPrimaryNodeIP().String()) if err != nil { err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) if err2 != nil { @@ -1441,11 +1441,11 @@ func NewNetworkRoutingController(clientset kubernetes.Interface, return &nrc, nil } -func bgpPeerConfigsFromAnnotations(nodeAnnotations map[string]string) (bgp.PeerConfigs, error) { +func bgpPeerConfigsFromAnnotations(nodeAnnotations map[string]string, localAddress string) (bgp.PeerConfigs, error) { nodeBgpPeersAnnotation, ok := nodeAnnotations[peersAnnotation] if !ok { klog.Infof("%s annotation not set, using individual node annotations to configure BGP peer info", peersAnnotation) - return bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations) + return bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations, localAddress) } var peerConfigs bgp.PeerConfigs @@ -1456,7 +1456,7 @@ func bgpPeerConfigsFromAnnotations(nodeAnnotations map[string]string) (bgp.PeerC return peerConfigs, nil } -func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) (bgp.PeerConfigs, error) { +func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string, localAddress string) (bgp.PeerConfigs, error) { // Get Global Peer Router ASN configs nodeBgpPeerAsnsAnnotation, ok := nodeAnnotations[peerASNAnnotation] if !ok { @@ -1527,6 +1527,5 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string) } } - // TODO: Add local address to the end arg? - return bgp.NewPeerConfigs(ipStrings, peerASNs, ports, passwords, localIPs, "") + return bgp.NewPeerConfigs(ipStrings, peerASNs, ports, passwords, localIPs, localAddress) } From dba861de985dabca770850d1c0c9d18f74768eb8 Mon Sep 17 00:00:00 2001 From: Cat C Date: Sun, 2 Nov 2025 12:36:38 -0800 Subject: [PATCH 06/12] Fix extraneous commas and quotes in README. Implement Stringer interface for PeerConfig structs. Break out ValToPtr functions into a testutils package. --- docs/bgp.md | 6 +- internal/testutils/pointers.go | 22 +++ pkg/bgp/peer_config.go | 29 ++++ pkg/bgp/peer_config_test.go | 130 ++++++++++++++++++ .../routing/network_routes_controller_test.go | 86 +++++------- 5 files changed, 220 insertions(+), 53 deletions(-) create mode 100644 internal/testutils/pointers.go diff --git a/docs/bgp.md b/docs/bgp.md index 330de66df9..96f1a88ab4 100644 --- a/docs/bgp.md +++ b/docs/bgp.md @@ -95,10 +95,10 @@ kubectl annotate node \ kube-router.io/peers="$(cat <<'EOF' - remoteip: 192.168.1.99 remoteasn: 65000 - password: U2VjdXJlUGFzc3dvcmQK, + password: U2VjdXJlUGFzc3dvcmQK - remoteip: 192.168.1.100 - remoteasn: 65000' - password: U2VjdXJlUGFzc3dvcmQK, + remoteasn: 65000 + password: U2VjdXJlUGFzc3dvcmQK EOF )" ``` diff --git a/internal/testutils/pointers.go b/internal/testutils/pointers.go new file mode 100644 index 0000000000..232dd0a3ea --- /dev/null +++ b/internal/testutils/pointers.go @@ -0,0 +1,22 @@ +package testutils + +import ( + "net" + + "github.com/cloudnativelabs/kube-router/v2/pkg/utils" +) + +type TestValue interface { + string | uint32 | net.IP | utils.Base64String +} + +func ValToPtr[V TestValue](v V) *V { + return &v +} + +func PtrToVal[V TestValue](v *V) V { + if v == nil { + return *new(V) + } + return *v +} diff --git a/pkg/bgp/peer_config.go b/pkg/bgp/peer_config.go index f25b32f496..9ed7ced656 100644 --- a/pkg/bgp/peer_config.go +++ b/pkg/bgp/peer_config.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "strconv" + "strings" "github.com/cloudnativelabs/kube-router/v2/pkg/options" "github.com/cloudnativelabs/kube-router/v2/pkg/utils" @@ -19,6 +20,25 @@ type PeerConfig struct { RemoteIP *net.IP `yaml:"remoteip"` } +// Custom Stringer to prevent leaking passwords when printed +func (p PeerConfig) String() string { + var fields []string + + if p.LocalIP != nil { + fields = append(fields, fmt.Sprintf("LocalIP: %s", *p.LocalIP)) + } + if p.Port != nil { + fields = append(fields, fmt.Sprintf("Port: %d", *p.Port)) + } + if p.RemoteASN != nil { + fields = append(fields, fmt.Sprintf("RemoteASN: %d", *p.RemoteASN)) + } + if p.RemoteIP != nil { + fields = append(fields, fmt.Sprintf("RemoteIP: %v", *p.RemoteIP)) + } + return fmt.Sprintf("PeerConfig{%s}", strings.Join(fields, ", ")) +} + func (p *PeerConfig) UnmarshalYAML(raw []byte) error { tmp := struct { LocalIP *string `yaml:"localip"` @@ -110,6 +130,15 @@ func (p PeerConfigs) RemoteIPStrings() []string { return remoteIPs } +// Prints the PeerConfigs without the passwords leaking +func (p PeerConfigs) String() string { + pcs := make([]string, len(p)) + for i, pc := range p { + pcs[i] = pc.String() + } + return fmt.Sprintf("PeerConfigs[%s]", strings.Join(pcs, ",")) +} + func (p *PeerConfigs) UnmarshalYAML(raw []byte) error { type tmpPeerConfigs PeerConfigs tmp := (*tmpPeerConfigs)(p) diff --git a/pkg/bgp/peer_config_test.go b/pkg/bgp/peer_config_test.go index d3f4f69dbe..92edffedfd 100644 --- a/pkg/bgp/peer_config_test.go +++ b/pkg/bgp/peer_config_test.go @@ -1,11 +1,141 @@ package bgp import ( + "net" "testing" + "github.com/cloudnativelabs/kube-router/v2/internal/testutils" + "github.com/cloudnativelabs/kube-router/v2/pkg/utils" "github.com/stretchr/testify/assert" ) +func TestPeerConfig_String(t *testing.T) { + tests := []struct { + name string + config PeerConfig + expected string + }{ + { + name: "empty PeerConfig", + config: PeerConfig{}, + expected: "PeerConfig{}", + }, + { + name: "LocalIP", + config: PeerConfig{ + LocalIP: testutils.ValToPtr("192.168.1.1"), + }, + expected: "PeerConfig{LocalIP: 192.168.1.1}", + }, + { + name: "Port", + config: PeerConfig{ + Port: testutils.ValToPtr(uint32(179)), + }, + expected: "PeerConfig{Port: 179}", + }, + { + name: "RemoteASN", + config: PeerConfig{ + RemoteASN: testutils.ValToPtr(uint32(65000)), + }, + expected: "PeerConfig{RemoteASN: 65000}", + }, + { + name: "RemoteIP", + config: PeerConfig{ + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + }, + expected: "PeerConfig{RemoteIP: 10.0.0.1}", + }, + { + name: "RemoteIP with IPv6", + config: PeerConfig{ + RemoteIP: testutils.ValToPtr(net.ParseIP("2001:db8::1")), + }, + expected: "PeerConfig{RemoteIP: 2001:db8::1}", + }, + { + name: "Password - should not be printed", + config: PeerConfig{ + Password: testutils.ValToPtr(utils.Base64String("password")), + }, + expected: "PeerConfig{}", + }, + { + name: "all fields - Password should not be printed", + config: PeerConfig{ + LocalIP: testutils.ValToPtr("192.168.1.1"), + Password: testutils.ValToPtr(utils.Base64String("password")), + Port: testutils.ValToPtr(uint32(179)), + RemoteASN: testutils.ValToPtr(uint32(65000)), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + }, + expected: "PeerConfig{LocalIP: 192.168.1.1, Port: 179, RemoteASN: 65000, RemoteIP: 10.0.0.1}", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.config.String() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestPeerConfigs_String(t *testing.T) { + tests := []struct { + name string + configs PeerConfigs + expected string + }{ + { + name: "empty PeerConfigs", + configs: PeerConfigs{}, + expected: "PeerConfigs[]", + }, + { + name: "PeerConfig - password should not be printed", + configs: PeerConfigs{ + { + LocalIP: testutils.ValToPtr("192.168.1.1"), + Password: testutils.ValToPtr(utils.Base64String("secret")), + Port: testutils.ValToPtr(uint32(179)), + RemoteASN: testutils.ValToPtr(uint32(65000)), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + }, + }, + expected: "PeerConfigs[PeerConfig{LocalIP: 192.168.1.1, Port: 179, RemoteASN: 65000, RemoteIP: 10.0.0.1}]", + }, + { + name: "multiple PeerConfigs - passwords should not be printed", + configs: PeerConfigs{ + { + LocalIP: testutils.ValToPtr("192.168.1.1"), + Password: testutils.ValToPtr(utils.Base64String("secret")), + RemoteASN: testutils.ValToPtr(uint32(65000)), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + }, + { + Port: testutils.ValToPtr(uint32(1790)), + RemoteASN: testutils.ValToPtr(uint32(65001)), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), + }, + { + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.3")), + }, + }, + expected: "PeerConfigs[PeerConfig{LocalIP: 192.168.1.1, RemoteASN: 65000, RemoteIP: 10.0.0.1},PeerConfig{Port: 1790, RemoteASN: 65001, RemoteIP: 10.0.0.2},PeerConfig{RemoteIP: 10.0.0.3}]", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.configs.String()) + }) + } +} + func Test_NewPeerConfigs(t *testing.T) { tcs := []struct { name string diff --git a/pkg/controllers/routing/network_routes_controller_test.go b/pkg/controllers/routing/network_routes_controller_test.go index 856e9c757b..c9b102a649 100644 --- a/pkg/controllers/routing/network_routes_controller_test.go +++ b/pkg/controllers/routing/network_routes_controller_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/cloudnativelabs/kube-router/v2/internal/testutils" "github.com/cloudnativelabs/kube-router/v2/pkg/bgp" "github.com/cloudnativelabs/kube-router/v2/pkg/k8s/indexers" "github.com/cloudnativelabs/kube-router/v2/pkg/utils" @@ -1822,11 +1823,11 @@ func Test_nodeHasEndpointsForService(t *testing.T) { Endpoints: []discoveryv1.Endpoint{ { Addresses: []string{"172.20.1.1"}, - NodeName: valToPtr("node-1"), + NodeName: testutils.ValToPtr("node-1"), }, { Addresses: []string{"172.20.1.2"}, - NodeName: valToPtr("node-2"), + NodeName: testutils.ValToPtr("node-2"), }, }, }, @@ -1867,11 +1868,11 @@ func Test_nodeHasEndpointsForService(t *testing.T) { Endpoints: []discoveryv1.Endpoint{ { Addresses: []string{"172.20.1.1"}, - NodeName: valToPtr("node-2"), + NodeName: testutils.ValToPtr("node-2"), }, { Addresses: []string{"172.20.1.2"}, - NodeName: valToPtr("node-3"), + NodeName: testutils.ValToPtr("node-3"), }, }, }, @@ -2650,16 +2651,16 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { }, bgp.PeerConfigs{ bgp.PeerConfig{ - RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), - RemoteASN: valToPtr(uint32(64640)), - Password: valToPtr(utils.Base64String("password")), - LocalIP: valToPtr("192.168.0.1"), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: testutils.ValToPtr(uint32(64640)), + Password: testutils.ValToPtr(utils.Base64String("password")), + LocalIP: testutils.ValToPtr("192.168.0.1"), }, bgp.PeerConfig{ - RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), - RemoteASN: valToPtr(uint32(64641)), - Password: valToPtr(utils.Base64String("password")), - LocalIP: valToPtr("192.168.0.2"), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: testutils.ValToPtr(uint32(64641)), + Password: testutils.ValToPtr(utils.Base64String("password")), + LocalIP: testutils.ValToPtr("192.168.0.2"), }, }, false, @@ -2676,14 +2677,14 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { }, bgp.PeerConfigs{ bgp.PeerConfig{ - RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), - RemoteASN: valToPtr(uint32(64640)), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: testutils.ValToPtr(uint32(64640)), }, bgp.PeerConfig{ - RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), - RemoteASN: valToPtr(uint32(64641)), - Password: valToPtr(utils.Base64String("password")), - LocalIP: valToPtr("192.168.0.2"), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: testutils.ValToPtr(uint32(64641)), + Password: testutils.ValToPtr(utils.Base64String("password")), + LocalIP: testutils.ValToPtr("192.168.0.2"), }, }, true, @@ -2698,16 +2699,16 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { }, bgp.PeerConfigs{ bgp.PeerConfig{ - RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), - RemoteASN: valToPtr(uint32(64640)), - Password: valToPtr(utils.Base64String("password")), - LocalIP: valToPtr("192.168.0.1"), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: testutils.ValToPtr(uint32(64640)), + Password: testutils.ValToPtr(utils.Base64String("password")), + LocalIP: testutils.ValToPtr("192.168.0.1"), }, bgp.PeerConfig{ - RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), - RemoteASN: valToPtr(uint32(64641)), - Password: valToPtr(utils.Base64String("password")), - LocalIP: valToPtr("192.168.0.2"), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: testutils.ValToPtr(uint32(64641)), + Password: testutils.ValToPtr(utils.Base64String("password")), + LocalIP: testutils.ValToPtr("192.168.0.2"), }, }, false, @@ -2722,16 +2723,16 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { }, bgp.PeerConfigs{ bgp.PeerConfig{ - RemoteIP: valToPtr(net.ParseIP("10.0.0.1")), - RemoteASN: valToPtr(uint32(64640)), - Password: valToPtr(utils.Base64String("password")), - LocalIP: valToPtr("192.168.0.1"), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: testutils.ValToPtr(uint32(64640)), + Password: testutils.ValToPtr(utils.Base64String("password")), + LocalIP: testutils.ValToPtr("192.168.0.1"), }, bgp.PeerConfig{ - RemoteIP: valToPtr(net.ParseIP("10.0.0.2")), - RemoteASN: valToPtr(uint32(64641)), - Password: valToPtr(utils.Base64String("password")), - LocalIP: valToPtr("192.168.0.2"), + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: testutils.ValToPtr(uint32(64641)), + Password: testutils.ValToPtr(utils.Base64String("password")), + LocalIP: testutils.ValToPtr("192.168.0.2"), }, }, true, @@ -2760,7 +2761,7 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - bgpPeerCfgs, err := bgpPeerConfigsFromAnnotations(tc.nodeAnnotations) + bgpPeerCfgs, err := bgpPeerConfigsFromAnnotations(tc.nodeAnnotations, "") if tc.expectError { assert.Error(t, err) return @@ -3018,18 +3019,3 @@ func waitForListerWithTimeout(lister cache.Indexer, timeout time.Duration, t *te } } } - -type value interface { - string | uint32 | net.IP | utils.Base64String -} - -func valToPtr[V value](v V) *V { - return &v -} - -func ptrToVal[V value](v *V) V { - if v == nil { - return *new(V) - } - return *v -} From ff1e971e6a0295a3bf0a2d6964425daed04bb3f8 Mon Sep 17 00:00:00 2001 From: Cat C Date: Sun, 9 Nov 2025 22:23:53 -0800 Subject: [PATCH 07/12] Update stringifying configs --- pkg/bgp/peer_config.go | 14 ++++------ pkg/bgp/peer_config_test.go | 26 +++++++++---------- .../routing/network_routes_controller.go | 1 - 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/pkg/bgp/peer_config.go b/pkg/bgp/peer_config.go index 9ed7ced656..2ab3261b5f 100644 --- a/pkg/bgp/peer_config.go +++ b/pkg/bgp/peer_config.go @@ -15,7 +15,7 @@ import ( type PeerConfig struct { LocalIP *string `yaml:"localip"` Password *utils.Base64String `yaml:"password"` - Port *uint32 `yaml:"port"` + Port uint32 `yaml:"port"` RemoteASN *uint32 `yaml:"remoteasn"` RemoteIP *net.IP `yaml:"remoteip"` } @@ -23,13 +23,11 @@ type PeerConfig struct { // Custom Stringer to prevent leaking passwords when printed func (p PeerConfig) String() string { var fields []string + fields = append(fields, fmt.Sprintf("Port: %d", p.Port)) if p.LocalIP != nil { fields = append(fields, fmt.Sprintf("LocalIP: %s", *p.LocalIP)) } - if p.Port != nil { - fields = append(fields, fmt.Sprintf("Port: %d", *p.Port)) - } if p.RemoteASN != nil { fields = append(fields, fmt.Sprintf("RemoteASN: %d", *p.RemoteASN)) } @@ -43,7 +41,7 @@ func (p *PeerConfig) UnmarshalYAML(raw []byte) error { tmp := struct { LocalIP *string `yaml:"localip"` Password *utils.Base64String `yaml:"password"` - Port *uint32 `yaml:"port"` + Port uint32 `yaml:"port"` RemoteASN *uint32 `yaml:"remoteasn"` RemoteIP string `yaml:"remoteip"` }{} @@ -93,9 +91,7 @@ func (p PeerConfigs) Passwords() []string { func (p PeerConfigs) Ports() []uint32 { ports := make([]uint32, 0) for _, cfg := range p { - if cfg.Port != nil { - ports = append(ports, *cfg.Port) - } + ports = append(ports, cfg.Port) } return ports } @@ -176,7 +172,7 @@ func NewPeerConfigs( peerCfgs[i].RemoteASN = &remoteASNs[i] if len(ports) != 0 { - peerCfgs[i].Port = &ports[i] + peerCfgs[i].Port = ports[i] } if len(b64EncodedPasswords) != 0 { diff --git a/pkg/bgp/peer_config_test.go b/pkg/bgp/peer_config_test.go index 92edffedfd..64e988343d 100644 --- a/pkg/bgp/peer_config_test.go +++ b/pkg/bgp/peer_config_test.go @@ -18,19 +18,19 @@ func TestPeerConfig_String(t *testing.T) { { name: "empty PeerConfig", config: PeerConfig{}, - expected: "PeerConfig{}", + expected: "PeerConfig{Port: 0}", }, { name: "LocalIP", config: PeerConfig{ LocalIP: testutils.ValToPtr("192.168.1.1"), }, - expected: "PeerConfig{LocalIP: 192.168.1.1}", + expected: "PeerConfig{Port: 0, LocalIP: 192.168.1.1}", }, { name: "Port", config: PeerConfig{ - Port: testutils.ValToPtr(uint32(179)), + Port: 179, }, expected: "PeerConfig{Port: 179}", }, @@ -39,39 +39,39 @@ func TestPeerConfig_String(t *testing.T) { config: PeerConfig{ RemoteASN: testutils.ValToPtr(uint32(65000)), }, - expected: "PeerConfig{RemoteASN: 65000}", + expected: "PeerConfig{Port: 0, RemoteASN: 65000}", }, { name: "RemoteIP", config: PeerConfig{ RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), }, - expected: "PeerConfig{RemoteIP: 10.0.0.1}", + expected: "PeerConfig{Port: 0, RemoteIP: 10.0.0.1}", }, { name: "RemoteIP with IPv6", config: PeerConfig{ RemoteIP: testutils.ValToPtr(net.ParseIP("2001:db8::1")), }, - expected: "PeerConfig{RemoteIP: 2001:db8::1}", + expected: "PeerConfig{Port: 0, RemoteIP: 2001:db8::1}", }, { name: "Password - should not be printed", config: PeerConfig{ Password: testutils.ValToPtr(utils.Base64String("password")), }, - expected: "PeerConfig{}", + expected: "PeerConfig{Port: 0}", }, { name: "all fields - Password should not be printed", config: PeerConfig{ LocalIP: testutils.ValToPtr("192.168.1.1"), Password: testutils.ValToPtr(utils.Base64String("password")), - Port: testutils.ValToPtr(uint32(179)), + Port: 179, RemoteASN: testutils.ValToPtr(uint32(65000)), RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), }, - expected: "PeerConfig{LocalIP: 192.168.1.1, Port: 179, RemoteASN: 65000, RemoteIP: 10.0.0.1}", + expected: "PeerConfig{Port: 179, LocalIP: 192.168.1.1, RemoteASN: 65000, RemoteIP: 10.0.0.1}", }, } @@ -100,12 +100,12 @@ func TestPeerConfigs_String(t *testing.T) { { LocalIP: testutils.ValToPtr("192.168.1.1"), Password: testutils.ValToPtr(utils.Base64String("secret")), - Port: testutils.ValToPtr(uint32(179)), + Port: 179, RemoteASN: testutils.ValToPtr(uint32(65000)), RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), }, }, - expected: "PeerConfigs[PeerConfig{LocalIP: 192.168.1.1, Port: 179, RemoteASN: 65000, RemoteIP: 10.0.0.1}]", + expected: "PeerConfigs[PeerConfig{Port: 179, LocalIP: 192.168.1.1, RemoteASN: 65000, RemoteIP: 10.0.0.1}]", }, { name: "multiple PeerConfigs - passwords should not be printed", @@ -117,7 +117,7 @@ func TestPeerConfigs_String(t *testing.T) { RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), }, { - Port: testutils.ValToPtr(uint32(1790)), + Port: 179, RemoteASN: testutils.ValToPtr(uint32(65001)), RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), }, @@ -125,7 +125,7 @@ func TestPeerConfigs_String(t *testing.T) { RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.3")), }, }, - expected: "PeerConfigs[PeerConfig{LocalIP: 192.168.1.1, RemoteASN: 65000, RemoteIP: 10.0.0.1},PeerConfig{Port: 1790, RemoteASN: 65001, RemoteIP: 10.0.0.2},PeerConfig{RemoteIP: 10.0.0.3}]", + expected: "PeerConfigs[PeerConfig{Port: 0, LocalIP: 192.168.1.1, RemoteASN: 65000, RemoteIP: 10.0.0.1},PeerConfig{Port: 179, RemoteASN: 65001, RemoteIP: 10.0.0.2},PeerConfig{Port: 0, RemoteIP: 10.0.0.3}]", }, } diff --git a/pkg/controllers/routing/network_routes_controller.go b/pkg/controllers/routing/network_routes_controller.go index 46bf203cac..9d3b94217d 100644 --- a/pkg/controllers/routing/network_routes_controller.go +++ b/pkg/controllers/routing/network_routes_controller.go @@ -1482,7 +1482,6 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string, // Get Global Peer Router ASN configs var ports []uint32 nodeBgpPeerPortsAnnotation, ok := nodeAnnotations[peerPortAnnotation] - // Default to default BGP port if port annotation is not found if ok { portStrings := stringToSlice(nodeBgpPeerPortsAnnotation, ",") ports, err = stringSliceToUInt32(portStrings) From 0b7b33cbf1afbd9e0ba1e057bdbc50d46a163a31 Mon Sep 17 00:00:00 2001 From: Cat C Date: Mon, 10 Nov 2025 21:00:24 -0800 Subject: [PATCH 08/12] Add b64 type test --- pkg/utils/base64_test.go | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 pkg/utils/base64_test.go diff --git a/pkg/utils/base64_test.go b/pkg/utils/base64_test.go new file mode 100644 index 0000000000..b4b3843103 --- /dev/null +++ b/pkg/utils/base64_test.go @@ -0,0 +1,48 @@ +package utils + +import ( + "fmt" + "testing" + + "github.com/goccy/go-yaml" + "github.com/stretchr/testify/assert" +) + +func TestBase64String(t *testing.T) { + type testStruct struct { + Password Base64String `yaml:"password"` + } + + tcs := []struct { + name string + input []byte + shouldError bool + errorContains string + }{ + { + name: "happy path", + // b64: hello world + input: []byte(`password: "aGVsbG8gd29ybGQ="`), + }, + { + name: "invalid base64 encoding", + input: []byte(`password: "notbase64"`), + shouldError: true, + errorContains: "failed to base64 decode", + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(tt *testing.T) { + var ts testStruct + err := yaml.Unmarshal(tc.input, &ts) + fmt.Printf("TS: %+v\n", ts) + if tc.shouldError { + assert.ErrorContains(tt, err, tc.errorContains) + } else { + assert.NoError(tt, err) + assert.NotEmpty(tt, ts.Password) + } + }) + } +} From 052c3d504fe81e7340d4ff48058f01266cb15f40 Mon Sep 17 00:00:00 2001 From: Cat C Date: Tue, 18 Nov 2025 00:04:11 -0800 Subject: [PATCH 09/12] Fix port and localip defaults logic, add more test cases --- pkg/bgp/peer_config.go | 44 ++-- pkg/bgp/peer_config_test.go | 237 ++++++++++++++---- pkg/controllers/routing/bgp_peers.go | 9 - .../routing/network_routes_controller.go | 2 +- .../routing/network_routes_controller_test.go | 60 ++--- 5 files changed, 247 insertions(+), 105 deletions(-) diff --git a/pkg/bgp/peer_config.go b/pkg/bgp/peer_config.go index 2ab3261b5f..19828957e1 100644 --- a/pkg/bgp/peer_config.go +++ b/pkg/bgp/peer_config.go @@ -15,7 +15,7 @@ import ( type PeerConfig struct { LocalIP *string `yaml:"localip"` Password *utils.Base64String `yaml:"password"` - Port uint32 `yaml:"port"` + Port *uint32 `yaml:"port"` RemoteASN *uint32 `yaml:"remoteasn"` RemoteIP *net.IP `yaml:"remoteip"` } @@ -23,8 +23,9 @@ type PeerConfig struct { // Custom Stringer to prevent leaking passwords when printed func (p PeerConfig) String() string { var fields []string - fields = append(fields, fmt.Sprintf("Port: %d", p.Port)) - + if p.Port != nil { + fields = append(fields, fmt.Sprintf("Port: %d", *p.Port)) + } if p.LocalIP != nil { fields = append(fields, fmt.Sprintf("LocalIP: %s", *p.LocalIP)) } @@ -41,27 +42,32 @@ func (p *PeerConfig) UnmarshalYAML(raw []byte) error { tmp := struct { LocalIP *string `yaml:"localip"` Password *utils.Base64String `yaml:"password"` - Port uint32 `yaml:"port"` + Port *uint32 `yaml:"port"` RemoteASN *uint32 `yaml:"remoteasn"` - RemoteIP string `yaml:"remoteip"` + RemoteIP *string `yaml:"remoteip"` }{} if err := yaml.Unmarshal(raw, &tmp); err != nil { return fmt.Errorf("failed to unmarshal peer config: %w", err) } + if tmp.RemoteIP == nil { + return errors.New("remoteip cannot be empty") + } + if tmp.RemoteASN == nil { + return errors.New("remoteasn cannot be empty") + } + p.LocalIP = tmp.LocalIP p.Password = tmp.Password p.Port = tmp.Port p.RemoteASN = tmp.RemoteASN - if tmp.RemoteIP != "" { - ip := net.ParseIP(tmp.RemoteIP) - if ip == nil { - return fmt.Errorf("%s is not a valid IP address", tmp.RemoteIP) - } - p.RemoteIP = &ip + ip := net.ParseIP(*tmp.RemoteIP) + if ip == nil { + return fmt.Errorf("%s is not a valid IP address", *tmp.RemoteIP) } + p.RemoteIP = &ip return nil } @@ -72,6 +78,8 @@ func (p PeerConfigs) LocalIPs() []string { for _, cfg := range p { if cfg.LocalIP != nil { localIPs = append(localIPs, *cfg.LocalIP) + } else { + localIPs = append(localIPs, "") } } return localIPs @@ -91,7 +99,11 @@ func (p PeerConfigs) Passwords() []string { func (p PeerConfigs) Ports() []uint32 { ports := make([]uint32, 0) for _, cfg := range p { - ports = append(ports, cfg.Port) + if cfg.Port != nil { + ports = append(ports, *cfg.Port) + } else { + ports = append(ports, options.DefaultBgpPort) + } } return ports } @@ -172,7 +184,7 @@ func NewPeerConfigs( peerCfgs[i].RemoteASN = &remoteASNs[i] if len(ports) != 0 { - peerCfgs[i].Port = ports[i] + peerCfgs[i].Port = &ports[i] } if len(b64EncodedPasswords) != 0 { @@ -206,13 +218,11 @@ func validatePeerConfigs( } if len(remoteIPs) != len(ports) && len(ports) != 0 { return fmt.Errorf("invalid peer router config. The number of ports should either be zero, or "+ - "one per peer router. If blank items are used, it will default to standard BGP port, %s. "+ - "Example: \"port,,port\" OR [\"port\",\"\",\"port\"]", strconv.Itoa(options.DefaultBgpPort)) + "one per peer router. If blank items are used, it will default to standard BGP port, %s. ", strconv.Itoa(options.DefaultBgpPort)) } if len(remoteIPs) != len(localIPs) && len(localIPs) != 0 { return fmt.Errorf("invalid peer router config. The number of localIPs should either be zero, or "+ - "one per peer router. If blank items are used, it will default to nodeIP, %s. "+ - "Example: \"10.1.1.1,,10.1.1.2\" OR [\"10.1.1.1\",\"\",\"10.1.1.2\"]", localAddress) + "one per peer router. If blank items are used, it will default to nodeIP, %s. ", localAddress) } for _, asn := range remoteASNs { diff --git a/pkg/bgp/peer_config_test.go b/pkg/bgp/peer_config_test.go index 64e988343d..f0431be059 100644 --- a/pkg/bgp/peer_config_test.go +++ b/pkg/bgp/peer_config_test.go @@ -5,7 +5,9 @@ import ( "testing" "github.com/cloudnativelabs/kube-router/v2/internal/testutils" + "github.com/cloudnativelabs/kube-router/v2/pkg/options" "github.com/cloudnativelabs/kube-router/v2/pkg/utils" + "github.com/goccy/go-yaml" "github.com/stretchr/testify/assert" ) @@ -18,19 +20,19 @@ func TestPeerConfig_String(t *testing.T) { { name: "empty PeerConfig", config: PeerConfig{}, - expected: "PeerConfig{Port: 0}", + expected: "PeerConfig{}", }, { name: "LocalIP", config: PeerConfig{ LocalIP: testutils.ValToPtr("192.168.1.1"), }, - expected: "PeerConfig{Port: 0, LocalIP: 192.168.1.1}", + expected: "PeerConfig{LocalIP: 192.168.1.1}", }, { name: "Port", config: PeerConfig{ - Port: 179, + Port: testutils.ValToPtr(uint32(179)), }, expected: "PeerConfig{Port: 179}", }, @@ -39,35 +41,35 @@ func TestPeerConfig_String(t *testing.T) { config: PeerConfig{ RemoteASN: testutils.ValToPtr(uint32(65000)), }, - expected: "PeerConfig{Port: 0, RemoteASN: 65000}", + expected: "PeerConfig{RemoteASN: 65000}", }, { name: "RemoteIP", config: PeerConfig{ RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), }, - expected: "PeerConfig{Port: 0, RemoteIP: 10.0.0.1}", + expected: "PeerConfig{RemoteIP: 10.0.0.1}", }, { name: "RemoteIP with IPv6", config: PeerConfig{ RemoteIP: testutils.ValToPtr(net.ParseIP("2001:db8::1")), }, - expected: "PeerConfig{Port: 0, RemoteIP: 2001:db8::1}", + expected: "PeerConfig{RemoteIP: 2001:db8::1}", }, { - name: "Password - should not be printed", + name: "Password should not be printed", config: PeerConfig{ Password: testutils.ValToPtr(utils.Base64String("password")), }, - expected: "PeerConfig{Port: 0}", + expected: "PeerConfig{}", }, { name: "all fields - Password should not be printed", config: PeerConfig{ LocalIP: testutils.ValToPtr("192.168.1.1"), Password: testutils.ValToPtr(utils.Base64String("password")), - Port: 179, + Port: testutils.ValToPtr(uint32(179)), RemoteASN: testutils.ValToPtr(uint32(65000)), RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), }, @@ -76,9 +78,64 @@ func TestPeerConfig_String(t *testing.T) { } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.name, func(st *testing.T) { result := tt.config.String() - assert.Equal(t, tt.expected, result) + assert.Equal(st, tt.expected, result) + }) + } +} + +func TestPeerConfig_UnmarshalYAML(t *testing.T) { + tests := []struct { + name string + input []byte + expected PeerConfig + errorContains string + }{ + { + name: "empty YAML", + expected: PeerConfig{}, + }, + { + name: "remote IP not set returns error", + input: []byte(`remoteasn: 1234`), + errorContains: "remoteip cannot be empty", + }, + { + name: "remote asn not set returns error", + input: []byte(`remoteip: 1.1.1.1`), + errorContains: "remoteasn cannot be empty", + }, + { + name: "invalid remote IP", + input: []byte(`remoteip: 1234.12 +remoteasn: 1234`), + errorContains: "is not a valid IP address", + }, + { + name: "valid peer config YAML", + input: []byte(`remoteip: 1.1.1.1 +remoteasn: 1234 +password: aGVsbG8= +`), + expected: PeerConfig{ + Password: testutils.ValToPtr(utils.Base64String("hello")), + RemoteIP: testutils.ValToPtr(net.ParseIP("1.1.1.1")), + RemoteASN: testutils.ValToPtr(uint32(1234)), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(st *testing.T) { + var actual PeerConfig + err := yaml.Unmarshal(tt.input, &actual) + if tt.errorContains != "" { + assert.ErrorContains(st, err, tt.errorContains) + } else { + assert.NoError(st, err) + assert.Equal(st, tt.expected, actual) + } }) } } @@ -100,7 +157,7 @@ func TestPeerConfigs_String(t *testing.T) { { LocalIP: testutils.ValToPtr("192.168.1.1"), Password: testutils.ValToPtr(utils.Base64String("secret")), - Port: 179, + Port: testutils.ValToPtr(uint32(179)), RemoteASN: testutils.ValToPtr(uint32(65000)), RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), }, @@ -117,7 +174,7 @@ func TestPeerConfigs_String(t *testing.T) { RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), }, { - Port: 179, + Port: testutils.ValToPtr(uint32(179)), RemoteASN: testutils.ValToPtr(uint32(65001)), RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), }, @@ -125,19 +182,111 @@ func TestPeerConfigs_String(t *testing.T) { RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.3")), }, }, - expected: "PeerConfigs[PeerConfig{Port: 0, LocalIP: 192.168.1.1, RemoteASN: 65000, RemoteIP: 10.0.0.1},PeerConfig{Port: 179, RemoteASN: 65001, RemoteIP: 10.0.0.2},PeerConfig{Port: 0, RemoteIP: 10.0.0.3}]", + expected: "PeerConfigs[PeerConfig{LocalIP: 192.168.1.1, RemoteASN: 65000, RemoteIP: 10.0.0.1},PeerConfig{Port: 179, RemoteASN: 65001, RemoteIP: 10.0.0.2},PeerConfig{RemoteIP: 10.0.0.3}]", }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expected, tt.configs.String()) + t.Run(tt.name, func(st *testing.T) { + assert.Equal(st, tt.expected, tt.configs.String()) + }) + } +} + +func TestPeerConfigs_LocalIPs(t *testing.T) { + tests := []struct { + name string + pcs PeerConfigs + expected []string + }{ + { + name: "peer configs with no local IP set returns empty strings", + pcs: PeerConfigs{ + { + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: testutils.ValToPtr(uint32(1234)), + }, + { + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: testutils.ValToPtr(uint32(1235)), + }, + }, + expected: []string{"", ""}, + }, + { + name: "peer configs with local IPs returns list of IPs as strings", + pcs: PeerConfigs{ + { + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: testutils.ValToPtr(uint32(1234)), + LocalIP: testutils.ValToPtr("192.168.0.1"), + }, + { + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: testutils.ValToPtr(uint32(1235)), + LocalIP: testutils.ValToPtr("192.168.0.2"), + }, + }, + expected: []string{"192.168.0.1", "192.168.0.2"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(st *testing.T) { + actual := tt.pcs.LocalIPs() + assert.Equal(st, tt.expected, actual) + }) + } +} + +func TestPeerConfigs_Ports(t *testing.T) { + tests := []struct { + name string + pcs PeerConfigs + expected []uint32 + }{ + { + name: "peer configs with no ports set returns default ports", + pcs: PeerConfigs{ + { + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: testutils.ValToPtr(uint32(1234)), + }, + { + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: testutils.ValToPtr(uint32(1235)), + }, + }, + expected: []uint32{options.DefaultBgpPort, options.DefaultBgpPort}, + }, + { + name: "peer configs with ports set returns list of ports", + pcs: PeerConfigs{ + { + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), + RemoteASN: testutils.ValToPtr(uint32(1234)), + Port: testutils.ValToPtr(uint32(1790)), + }, + { + RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.2")), + RemoteASN: testutils.ValToPtr(uint32(1235)), + Port: testutils.ValToPtr(uint32(1791)), + }, + }, + expected: []uint32{1790, 1791}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(st *testing.T) { + actual := tt.pcs.Ports() + assert.Equal(st, tt.expected, actual) }) } } func Test_NewPeerConfigs(t *testing.T) { - tcs := []struct { + tests := []struct { name string remoteIPs []string remoteASNs []uint32 @@ -145,51 +294,51 @@ func Test_NewPeerConfigs(t *testing.T) { b64EncodedPasswords []string localIPs []string localAddress string - errStringContains string + errorContains string }{ { - name: "all fields set to nil", + name: "all fields set to nil returns nothing", }, { - name: "number of remote IPs and remote ASNs don't match", - remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, - remoteASNs: []uint32{1234}, - errStringContains: "the number of IPs and ASN numbers must be equal", + name: "number of remote IPs and remote ASNs don't match returns error", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234}, + errorContains: "the number of IPs and ASN numbers must be equal", }, { - name: "number of remote IPs and passwords don't match", + name: "number of remote IPs and passwords don't match returns error", remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, remoteASNs: []uint32{1234, 2345}, b64EncodedPasswords: []string{"fakepassword"}, - errStringContains: "number of passwords", + errorContains: "number of passwords", }, { - name: "number of remote IPs and ports don't match", - remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, - remoteASNs: []uint32{1234, 2345}, - ports: []uint32{8080}, - errStringContains: "number of ports", + name: "number of remote IPs and ports don't match returns error", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234, 2345}, + ports: []uint32{8080}, + errorContains: "number of ports", }, { - name: "number of remote IPs and local IPs don't match", - remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, - remoteASNs: []uint32{1234, 2345}, - localIPs: []string{"1.1.1.1"}, - errStringContains: "number of localIPs", + name: "number of remote IPs and local IPs don't match returns error", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{1234, 2345}, + localIPs: []string{"1.1.1.1"}, + errorContains: "number of localIPs", }, { - name: "remoteASN contains a reserved ASN number", - remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, - remoteASNs: []uint32{0, 2345}, - errStringContains: "reserved ASN", + name: "remoteASN contains a reserved ASN number returns error", + remoteIPs: []string{"10.0.0.1", "10.0.0.2"}, + remoteASNs: []uint32{0, 2345}, + errorContains: "reserved ASN", }, } - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - _, err := NewPeerConfigs(tc.remoteIPs, tc.remoteASNs, tc.ports, tc.b64EncodedPasswords, tc.localIPs, tc.localAddress) - if tc.errStringContains != "" { - assert.ErrorContains(t, err, tc.errStringContains) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := NewPeerConfigs(tt.remoteIPs, tt.remoteASNs, tt.ports, tt.b64EncodedPasswords, tt.localIPs, tt.localAddress) + if tt.errorContains != "" { + assert.ErrorContains(t, err, tt.errorContains) } else { assert.NoError(t, err) } diff --git a/pkg/controllers/routing/bgp_peers.go b/pkg/controllers/routing/bgp_peers.go index 9ef5cf0dc5..e2f6adb734 100644 --- a/pkg/controllers/routing/bgp_peers.go +++ b/pkg/controllers/routing/bgp_peers.go @@ -296,15 +296,6 @@ func newGlobalPeers(peerConfigs bgp.PeerConfigs, holdtime float64, localAddress localips := peerConfigs.LocalIPs() for i := 0; i < len(ips); i++ { - if (asns[i] < 1 || asns[i] > 23455) && - (asns[i] < 23457 || asns[i] > 63999) && - (asns[i] < 64512 || asns[i] > 65534) && - (asns[i] < 131072 || asns[i] > 4199999999) && - (asns[i] < 4200000000 || asns[i] > 4294967294) { - return nil, fmt.Errorf("reserved ASN number \"%d\" for global BGP peer", - asns[i]) - } - // explicitly set neighbors.transport.config.local-address with primaryIP which is configured // as their neighbor address at the remote peers. // this prevents the controller from initiating connection to its peers with a different IP address diff --git a/pkg/controllers/routing/network_routes_controller.go b/pkg/controllers/routing/network_routes_controller.go index 9d3b94217d..dd2aa231b1 100644 --- a/pkg/controllers/routing/network_routes_controller.go +++ b/pkg/controllers/routing/network_routes_controller.go @@ -1480,7 +1480,7 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string, ipStrings := stringToSlice(nodeBgpPeersAnnotation, ",") // Get Global Peer Router ASN configs - var ports []uint32 + ports := make([]uint32, 0) nodeBgpPeerPortsAnnotation, ok := nodeAnnotations[peerPortAnnotation] if ok { portStrings := stringToSlice(nodeBgpPeerPortsAnnotation, ",") diff --git a/pkg/controllers/routing/network_routes_controller_test.go b/pkg/controllers/routing/network_routes_controller_test.go index c9b102a649..ecdcd4fd59 100644 --- a/pkg/controllers/routing/network_routes_controller_test.go +++ b/pkg/controllers/routing/network_routes_controller_test.go @@ -2626,20 +2626,18 @@ func Test_routeReflectorConfiguration(t *testing.T) { func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { testCases := []struct { - name string - nodeAnnotations map[string]string - expectedBgpPeerConfigs bgp.PeerConfigs - expectError bool + name string + nodeAnnotations map[string]string + expected bgp.PeerConfigs + expectError bool }{ { - "node annotations are empty", - map[string]string{}, - nil, - false, + name: "node annotations are empty", + nodeAnnotations: map[string]string{}, }, { - "combined bgp peers config annotation", - map[string]string{ + name: "combined bgp peers config annotation", + nodeAnnotations: map[string]string{ peersAnnotation: `- remoteip: 10.0.0.1 remoteasn: 64640 password: cGFzc3dvcmQ= @@ -2649,7 +2647,7 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { password: cGFzc3dvcmQ= localip: 192.168.0.2`, }, - bgp.PeerConfigs{ + expected: bgp.PeerConfigs{ bgp.PeerConfig{ RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), RemoteASN: testutils.ValToPtr(uint32(64640)), @@ -2663,11 +2661,10 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { LocalIP: testutils.ValToPtr("192.168.0.2"), }, }, - false, }, { - "combined bgp peers config annotation without matching number of peer config fields set", - map[string]string{ + name: "combined bgp peers config annotation without matching number of peer config fields set", + nodeAnnotations: map[string]string{ peersAnnotation: `- remoteip: 10.0.0.1 remoteasn: 64640 - remoteip: 10.0.0.2 @@ -2675,7 +2672,7 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { password: cGFzc3dvcmQ= localip: 192.168.0.2`, }, - bgp.PeerConfigs{ + expected: bgp.PeerConfigs{ bgp.PeerConfig{ RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), RemoteASN: testutils.ValToPtr(uint32(64640)), @@ -2687,17 +2684,17 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { LocalIP: testutils.ValToPtr("192.168.0.2"), }, }, - true, + expectError: true, }, { - "individual bgp peers config annotations", - map[string]string{ + name: "individual bgp peers config annotations", + nodeAnnotations: map[string]string{ peerIPAnnotation: "10.0.0.1,10.0.0.2", peerASNAnnotation: "64640,64641", peerPasswordAnnotation: "cGFzc3dvcmQ=,cGFzc3dvcmQ=", peerLocalIPAnnotation: "192.168.0.1,192.168.0.2", }, - bgp.PeerConfigs{ + expected: bgp.PeerConfigs{ bgp.PeerConfig{ RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), RemoteASN: testutils.ValToPtr(uint32(64640)), @@ -2711,17 +2708,16 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { LocalIP: testutils.ValToPtr("192.168.0.2"), }, }, - false, }, { - "individual bgp peers config annotations without matching number of peer config fields set", - map[string]string{ + name: "individual bgp peers config annotations without matching number of peer config fields set", + nodeAnnotations: map[string]string{ peerIPAnnotation: "10.0.0.1,10.0.0.2", peerASNAnnotation: "64640,64641", peerPasswordAnnotation: "cGFzc3dvcmQ=", peerLocalIPAnnotation: "192.168.0.2", }, - bgp.PeerConfigs{ + expected: bgp.PeerConfigs{ bgp.PeerConfig{ RemoteIP: testutils.ValToPtr(net.ParseIP("10.0.0.1")), RemoteASN: testutils.ValToPtr(uint32(64640)), @@ -2735,27 +2731,23 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { LocalIP: testutils.ValToPtr("192.168.0.2"), }, }, - true, + expectError: true, }, { - "individual bgp peers config annotations without peer ASN annotation", - map[string]string{ + name: "individual bgp peers config annotations without peer ASN annotation", + nodeAnnotations: map[string]string{ peerASNAnnotation: "64640,64641", peerPasswordAnnotation: "cGFzc3dvcmQ=,cGFzc3dvcmQ=", peerLocalIPAnnotation: "192.168.0.1,192.168.0.2", }, - nil, - false, }, { - "individual bgp peers config annotations without peer IP annotation", - map[string]string{ + name: "individual bgp peers config annotations without peer IP annotation", + nodeAnnotations: map[string]string{ peerIPAnnotation: "10.0.0.1,10.0.0.2", peerPasswordAnnotation: "cGFzc3dvcmQ=,cGFzc3dvcmQ=", peerLocalIPAnnotation: "192.168.0.1,192.168.0.2", }, - nil, - false, }, } @@ -2767,8 +2759,8 @@ func Test_bgpPeerConfigsFromAnnotations(t *testing.T) { return } require.NoError(t, err) - if !cmp.Equal(tc.expectedBgpPeerConfigs, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgp.PeerConfig{})) { - diff := cmp.Diff(tc.expectedBgpPeerConfigs, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgp.PeerConfig{})) + if !cmp.Equal(tc.expected, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgp.PeerConfig{})) { + diff := cmp.Diff(tc.expected, bgpPeerCfgs, cmpopts.IgnoreUnexported(bgp.PeerConfig{})) t.Errorf("BGP peer config mismatch:\n%s", diff) } }) From e2d256852af4df1594cc7c0569b091e12956a472 Mon Sep 17 00:00:00 2001 From: Cat C Date: Sat, 22 Nov 2025 12:37:24 -0800 Subject: [PATCH 10/12] Fixes according to linter --- pkg/bgp/peer_config.go | 3 +- pkg/controllers/routing/bgp_peers.go | 75 +++++++--- .../routing/network_routes_controller.go | 139 ++++++++++++++---- 3 files changed, 166 insertions(+), 51 deletions(-) diff --git a/pkg/bgp/peer_config.go b/pkg/bgp/peer_config.go index 19828957e1..18b54971dd 100644 --- a/pkg/bgp/peer_config.go +++ b/pkg/bgp/peer_config.go @@ -218,7 +218,8 @@ func validatePeerConfigs( } if len(remoteIPs) != len(ports) && len(ports) != 0 { return fmt.Errorf("invalid peer router config. The number of ports should either be zero, or "+ - "one per peer router. If blank items are used, it will default to standard BGP port, %s. ", strconv.Itoa(options.DefaultBgpPort)) + "one per peer router. If blank items are used, it will default to standard BGP port, %s. ", + strconv.Itoa(options.DefaultBgpPort)) } if len(remoteIPs) != len(localIPs) && len(localIPs) != 0 { return fmt.Errorf("invalid peer router config. The number of localIPs should either be zero, or "+ diff --git a/pkg/controllers/routing/bgp_peers.go b/pkg/controllers/routing/bgp_peers.go index e2f6adb734..220d6531c5 100644 --- a/pkg/controllers/routing/bgp_peers.go +++ b/pkg/controllers/routing/bgp_peers.go @@ -93,9 +93,13 @@ func (nrc *NetworkRoutingController) syncInternalPeers() { sourceNodeIsIPv4 := nrc.krNode.GetPrimaryNodeIP().To4() != nil if targetNodeIsIPv4 != sourceNodeIsIPv4 { - klog.Warningf("Not peering with Node %s as it's primary IP (%s) uses a different protocol than "+ - "our primary IP (%s)", node.Name, targetNode.GetPrimaryNodeIP(), - nrc.krNode.GetPrimaryNodeIP()) + klog.Warningf( + "Not peering with Node %s as it's primary IP (%s) uses a different protocol than "+ + "our primary IP (%s)", + node.Name, + targetNode.GetPrimaryNodeIP(), + nrc.krNode.GetPrimaryNodeIP(), + ) continue } @@ -128,7 +132,10 @@ func (nrc *NetworkRoutingController) syncInternalPeers() { if targetNode.IsIPv4Capable() { afiSafi := gobgpapi.AfiSafi{ Config: &gobgpapi.AfiSafiConfig{ - Family: &gobgpapi.Family{Afi: gobgpapi.Family_AFI_IP, Safi: gobgpapi.Family_SAFI_UNICAST}, + Family: &gobgpapi.Family{ + Afi: gobgpapi.Family_AFI_IP, + Safi: gobgpapi.Family_SAFI_UNICAST, + }, Enabled: true, }, MpGracefulRestart: &gobgpapi.MpGracefulRestart{ @@ -143,7 +150,10 @@ func (nrc *NetworkRoutingController) syncInternalPeers() { if targetNode.IsIPv6Capable() { afiSafi := gobgpapi.AfiSafi{ Config: &gobgpapi.AfiSafiConfig{ - Family: &gobgpapi.Family{Afi: gobgpapi.Family_AFI_IP6, Safi: gobgpapi.Family_SAFI_UNICAST}, + Family: &gobgpapi.Family{ + Afi: gobgpapi.Family_AFI_IP6, + Safi: gobgpapi.Family_SAFI_UNICAST, + }, Enabled: true, }, MpGracefulRestart: &gobgpapi.MpGracefulRestart{ @@ -173,7 +183,11 @@ func (nrc *NetworkRoutingController) syncInternalPeers() { Peer: n, }); err != nil { if !strings.Contains(err.Error(), "can't overwrite the existing peer") { - klog.Errorf("Failed to add node %s as peer due to %s", targetNode.GetPrimaryNodeIP(), err) + klog.Errorf( + "Failed to add node %s as peer due to %s", + targetNode.GetPrimaryNodeIP(), + err, + ) } } } @@ -203,8 +217,12 @@ func (nrc *NetworkRoutingController) syncInternalPeers() { } // connectToExternalBGPPeers adds all the configured eBGP peers (global or node specific) as neighbours -func (nrc *NetworkRoutingController) connectToExternalBGPPeers(server *gobgp.BgpServer, peerNeighbors []*gobgpapi.Peer, - bgpGracefulRestart bool, bgpGracefulRestartDeferralTime time.Duration, bgpGracefulRestartTime time.Duration, +func (nrc *NetworkRoutingController) connectToExternalBGPPeers( + server *gobgp.BgpServer, + peerNeighbors []*gobgpapi.Peer, + bgpGracefulRestart bool, + bgpGracefulRestartDeferralTime time.Duration, + bgpGracefulRestartTime time.Duration, peerMultihopTTL uint8, ) error { for _, n := range peerNeighbors { @@ -217,16 +235,23 @@ func (nrc *NetworkRoutingController) connectToExternalBGPPeers(server *gobgp.Bgp } peeringAddressForNeighbor := net.ParseIP(n.Transport.LocalAddress) if peeringAddressForNeighbor == nil { - klog.Errorf("unable to parse our local address for peer (%s), not peering with this peer (%s)", - n.Transport.LocalAddress, neighborIPStr) + klog.Errorf( + "unable to parse our local address for peer (%s), not peering with this peer (%s)", + n.Transport.LocalAddress, + neighborIPStr, + ) } neighborIsIPv4 := neighborIP.To4() != nil peeringAddressIsIPv4 := peeringAddressForNeighbor.To4() != nil if neighborIsIPv4 != peeringAddressIsIPv4 { - klog.Warningf("Not peering with configured peer as it's primary IP (%s) uses a different "+ - "protocol than our configured local-address (%s). Its possible that this can be resolved by setting "+ - "the local address appropriately", neighborIP, peeringAddressForNeighbor) + klog.Warningf( + "Not peering with configured peer as it's primary IP (%s) uses a different "+ + "protocol than our configured local-address (%s). Its possible that this can be resolved by setting "+ + "the local address appropriately", + neighborIP, + peeringAddressForNeighbor, + ) continue } @@ -242,7 +267,10 @@ func (nrc *NetworkRoutingController) connectToExternalBGPPeers(server *gobgp.Bgp n.AfiSafis = []*gobgpapi.AfiSafi{ { Config: &gobgpapi.AfiSafiConfig{ - Family: &gobgpapi.Family{Afi: gobgpapi.Family_AFI_IP, Safi: gobgpapi.Family_SAFI_UNICAST}, + Family: &gobgpapi.Family{ + Afi: gobgpapi.Family_AFI_IP, + Safi: gobgpapi.Family_SAFI_UNICAST, + }, Enabled: true, }, MpGracefulRestart: &gobgpapi.MpGracefulRestart{ @@ -256,7 +284,10 @@ func (nrc *NetworkRoutingController) connectToExternalBGPPeers(server *gobgp.Bgp if nrc.krNode.IsIPv6Capable() { afiSafi := gobgpapi.AfiSafi{ Config: &gobgpapi.AfiSafiConfig{ - Family: &gobgpapi.Family{Afi: gobgpapi.Family_AFI_IP6, Safi: gobgpapi.Family_SAFI_UNICAST}, + Family: &gobgpapi.Family{ + Afi: gobgpapi.Family_AFI_IP6, + Safi: gobgpapi.Family_SAFI_UNICAST, + }, Enabled: true, }, MpGracefulRestart: &gobgpapi.MpGracefulRestart{ @@ -286,7 +317,11 @@ func (nrc *NetworkRoutingController) connectToExternalBGPPeers(server *gobgp.Bgp } // Does validation and returns neighbor configs -func newGlobalPeers(peerConfigs bgp.PeerConfigs, holdtime float64, localAddress string) ([]*gobgpapi.Peer, error) { +func newGlobalPeers( + peerConfigs bgp.PeerConfigs, + holdtime float64, + localAddress string, +) []*gobgpapi.Peer { peers := make([]*gobgpapi.Peer, 0) ips := peerConfigs.RemoteIPs() @@ -330,7 +365,7 @@ func newGlobalPeers(peerConfigs bgp.PeerConfigs, holdtime float64, localAddress peers = append(peers, peer) } - return peers, nil + return peers } func (nrc *NetworkRoutingController) newNodeEventHandler() cache.ResourceEventHandler { @@ -367,8 +402,10 @@ func (nrc *NetworkRoutingController) newNodeEventHandler() cache.ResourceEventHa // In this case even if we can't get the NodeIP that's alright as the node is being removed anyway and // future node lister operations that happen in OnNodeUpdate won't be affected as the node won't be returned if err == nil && targetNode != nil { - klog.Infof("Received node %s removed update from watch API, so remove node from peer", - targetNode.GetPrimaryNodeIP()) + klog.Infof( + "Received node %s removed update from watch API, so remove node from peer", + targetNode.GetPrimaryNodeIP(), + ) } else { klog.Infof("Received node (IP unavailable) removed update from watch API, so remove node " + "from peer") diff --git a/pkg/controllers/routing/network_routes_controller.go b/pkg/controllers/routing/network_routes_controller.go index dd2aa231b1..79000008f9 100644 --- a/pkg/controllers/routing/network_routes_controller.go +++ b/pkg/controllers/routing/network_routes_controller.go @@ -82,7 +82,11 @@ const ( type RouteSyncer interface { AddInjectedRoute(dst *net.IPNet, route *netlink.Route) DelInjectedRoute(dst *net.IPNet) - Run(healthChan chan<- *healthcheck.ControllerHeartbeat, stopCh <-chan struct{}, wg *sync.WaitGroup) + Run( + healthChan chan<- *healthcheck.ControllerHeartbeat, + stopCh <-chan struct{}, + wg *sync.WaitGroup, + ) SyncLocalRouteTable() error } @@ -162,7 +166,9 @@ type NetworkRoutingController struct { } // Run runs forever until we are notified on stop channel -func (nrc *NetworkRoutingController) Run(healthChan chan<- *healthcheck.ControllerHeartbeat, stopCh <-chan struct{}, +func (nrc *NetworkRoutingController) Run( + healthChan chan<- *healthcheck.ControllerHeartbeat, + stopCh <-chan struct{}, wg *sync.WaitGroup, ) { var err error @@ -252,26 +258,39 @@ func (nrc *NetworkRoutingController) Run(healthChan chan<- *healthcheck.Controll linkAttrs.Name = "kube-bridge" bridge := &netlink.Bridge{LinkAttrs: linkAttrs} if err = netlink.LinkAdd(bridge); err != nil { - klog.Errorf("Failed to create `kube-router` bridge due to %s. Will be created by CNI bridge "+ - "plugin when pod is launched.", err.Error()) + klog.Errorf( + "Failed to create `kube-router` bridge due to %s. Will be created by CNI bridge "+ + "plugin when pod is launched.", + err.Error(), + ) } kubeBridgeIf, err = netlink.LinkByName("kube-bridge") if err != nil { - klog.Errorf("Failed to find created `kube-router` bridge due to %s. Will be created by CNI "+ - "bridge plugin when pod is launched.", err.Error()) + klog.Errorf( + "Failed to find created `kube-router` bridge due to %s. Will be created by CNI "+ + "bridge plugin when pod is launched.", + err.Error(), + ) } err = netlink.LinkSetUp(kubeBridgeIf) if err != nil { - klog.Errorf("Failed to bring `kube-router` bridge up due to %s. Will be created by CNI bridge "+ - "plugin at later point when pod is launched.", err.Error()) + klog.Errorf( + "Failed to bring `kube-router` bridge up due to %s. Will be created by CNI bridge "+ + "plugin at later point when pod is launched.", + err.Error(), + ) } } if nrc.autoMTU { mtu, err := nrc.krNode.GetNodeMTU() if err != nil { - klog.Errorf("Failed to find MTU for node IP: %s for intelligently setting the kube-bridge MTU "+ - "due to %s.", nrc.krNode.GetPrimaryNodeIP(), err.Error()) + klog.Errorf( + "Failed to find MTU for node IP: %s for intelligently setting the kube-bridge MTU "+ + "due to %s.", + nrc.krNode.GetPrimaryNodeIP(), + err.Error(), + ) } if mtu > 0 { klog.Infof("Setting MTU of kube-bridge interface to: %d", mtu) @@ -279,12 +298,17 @@ func (nrc *NetworkRoutingController) Run(healthChan chan<- *healthcheck.Controll if err != nil { klog.Errorf( "Failed to set MTU for kube-bridge interface due to: %s (kubeBridgeIf: %#v, mtu: %v)", - err.Error(), kubeBridgeIf, mtu, + err.Error(), + kubeBridgeIf, + mtu, ) // need to correct kuberouter.conf because autoConfigureMTU() may have set an invalid value! currentMTU := kubeBridgeIf.Attrs().MTU if currentMTU > 0 && currentMTU != mtu { - klog.Warningf("Updating config file with current MTU for kube-bridge: %d", currentMTU) + klog.Warningf( + "Updating config file with current MTU for kube-bridge: %d", + currentMTU, + ) cniNetConf, err := utils.NewCNINetworkConfig(nrc.cniConfFile) if err == nil { cniNetConf.SetMTU(currentMTU) @@ -309,6 +333,10 @@ func (nrc *NetworkRoutingController) Run(healthChan chan<- *healthcheck.Controll "not work: %s", err.Error()) } }() + if _, err := exec.Command("modprobe", "br_netfilter").CombinedOutput(); err != nil { + klog.Errorf("Failed to enable netfilter for bridge. Network policies and service proxy may "+ + "not work: %s", err.Error()) + } sysctlErr := utils.SetSysctl(utils.BridgeNFCallIPTables, 1) if sysctlErr != nil { klog.Errorf("Failed to enable iptables for bridge. Network policies and service proxy may "+ @@ -445,6 +473,7 @@ func (nrc *NetworkRoutingController) updateCNIConfig() { err = cniNetConf.InsertPodCIDRIntoIPAM(ipv6CIDR) if err != nil { klog.Fatalf("failed to insert IPv6 `subnet`(pod CIDR) '%s' into CNI conf file: %v", ipv6CIDR, err) + ) } } @@ -477,7 +506,8 @@ func (nrc *NetworkRoutingController) watchBgpUpdates() { if path.NeighborIp == "" { return } - klog.V(2).Infof("Processing bgp route advertisement from peer: %s", path.NeighborIp) + klog.V(2). + Infof("Processing bgp route advertisement from peer: %s", path.NeighborIp) if err := nrc.injectRoute(path); err != nil { klog.Errorf("failed to inject routes due to: %v", err) } @@ -551,8 +581,10 @@ func (nrc *NetworkRoutingController) advertisePodRoute() error { if nrc.krNode.IsIPv6Capable() { nodePrimaryIPv6IP := nrc.krNode.FindBestIPv6NodeAddress() if nodePrimaryIPv6IP == nil { - return fmt.Errorf("previous logic marked this node as IPv6 capable, but we couldn't find any " + - "available IPv6 node IPs, this shouldn't happen") + return fmt.Errorf( + "previous logic marked this node as IPv6 capable, but we couldn't find any " + + "available IPv6 node IPs, this shouldn't happen", + ) } for _, cidr := range nrc.podIPv6CIDRs { @@ -1070,8 +1102,11 @@ func (nrc *NetworkRoutingController) startBgpServer(grpcServer bool) error { // Make sure that the address type matches what we're capable of before listening if ip.To4() != nil { if !nrc.krNode.IsIPv4Capable() { - klog.Warningf("was configured to listen on %s, but node is not enabled for IPv4 or does not "+ - "have any IPv4 addresses configured for it, skipping", addr) + klog.Warningf( + "was configured to listen on %s, but node is not enabled for IPv4 or does not "+ + "have any IPv4 addresses configured for it, skipping", + addr, + ) continue } } else { @@ -1105,8 +1140,12 @@ func (nrc *NetworkRoutingController) startBgpServer(grpcServer bool) error { // If the global routing peer is configured then peer with it // else attempt to get peers from node specific BGP annotations. if len(nrc.globalPeerRouters) == 0 { - klog.V(2).Infof("Attempting to construct peer configs from annotation: %+v", node.Annotations) - peerCfgs, err := bgpPeerConfigsFromAnnotations(node.Annotations, nrc.krNode.GetPrimaryNodeIP().String()) + klog.V(2). + Infof("Attempting to construct peer configs from annotation: %+v", node.Annotations) + peerCfgs, err := bgpPeerConfigsFromAnnotations( + node.Annotations, + nrc.krNode.GetPrimaryNodeIP().String(), + ) if err != nil { err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) if err2 != nil { @@ -1120,7 +1159,11 @@ func (nrc *NetworkRoutingController) startBgpServer(grpcServer bool) error { } // Create and set Global Peer Router complete configs - nrc.globalPeerRouters, err = newGlobalPeers(peerCfgs, nrc.bgpHoldtime, nrc.krNode.GetPrimaryNodeIP().String()) + nrc.globalPeerRouters = newGlobalPeers( + peerCfgs, + nrc.bgpHoldtime, + nrc.krNode.GetPrimaryNodeIP().String(), + ) if err != nil { err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) if err2 != nil { @@ -1134,8 +1177,14 @@ func (nrc *NetworkRoutingController) startBgpServer(grpcServer bool) error { } if len(nrc.globalPeerRouters) != 0 { - err := nrc.connectToExternalBGPPeers(nrc.bgpServer, nrc.globalPeerRouters, nrc.bgpGracefulRestart, - nrc.bgpGracefulRestartDeferralTime, nrc.bgpGracefulRestartTime, nrc.peerMultihopTTL) + err := nrc.connectToExternalBGPPeers( + nrc.bgpServer, + nrc.globalPeerRouters, + nrc.bgpGracefulRestart, + nrc.bgpGracefulRestartDeferralTime, + nrc.bgpGracefulRestartTime, + nrc.peerMultihopTTL, + ) if err != nil { err2 := nrc.bgpServer.StopBgp(context.Background(), &gobgpapi.StopBgpRequest{}) if err2 != nil { @@ -1244,6 +1293,7 @@ func NewNetworkRoutingController(clientset kubernetes.Interface, if nrc.bgpHoldtime > 65536 || nrc.bgpHoldtime < 3 { return nil, errors.New("this is an incorrect BGP holdtime range, holdtime must be in the range " + "3s to 18h12m16s") + ) } nrc.hostnameOverride = kubeRouterConfig.HostnameOverride @@ -1391,12 +1441,23 @@ func NewNetworkRoutingController(clientset kubernetes.Interface, peerRouterIPs[i] = pr.String() } - peerCfgs, err := bgp.NewPeerConfigs(peerRouterIPs, peerASNs, peerPorts, peerPasswords, nil, nrc.krNode.GetPrimaryNodeIP().String()) + peerCfgs, err := bgp.NewPeerConfigs( + peerRouterIPs, + peerASNs, + peerPorts, + peerPasswords, + nil, + nrc.krNode.GetPrimaryNodeIP().String(), + ) if err != nil { return nil, err } - nrc.globalPeerRouters, err = newGlobalPeers(peerCfgs, nrc.bgpHoldtime, nrc.krNode.GetPrimaryNodeIP().String()) + nrc.globalPeerRouters = newGlobalPeers( + peerCfgs, + nrc.bgpHoldtime, + nrc.krNode.GetPrimaryNodeIP().String(), + ) if err != nil { return nil, fmt.Errorf("error processing Global Peer Router configs: %s", err) } @@ -1409,8 +1470,11 @@ func NewNetworkRoutingController(clientset kubernetes.Interface, if nrc.krNode.IsIPv6Capable() { nrc.localAddressList = append(nrc.localAddressList, nrc.krNode.FindBestIPv6NodeAddress().String()) } - klog.Infof("Could not find annotation `kube-router.io/bgp-local-addresses` on node object so BGP "+ - "will listen on node IP: %s addresses.", nrc.localAddressList) + klog.Infof( + "Could not find annotation `kube-router.io/bgp-local-addresses` on node object so BGP "+ + "will listen on node IP: %s addresses.", + nrc.localAddressList, + ) } else { klog.Infof("Found annotation `kube-router.io/bgp-local-addresses` on node object so BGP will listen "+ "on local IP's: %s", bgpLocalAddressListAnnotation) @@ -1441,10 +1505,16 @@ func NewNetworkRoutingController(clientset kubernetes.Interface, return &nrc, nil } -func bgpPeerConfigsFromAnnotations(nodeAnnotations map[string]string, localAddress string) (bgp.PeerConfigs, error) { +func bgpPeerConfigsFromAnnotations( + nodeAnnotations map[string]string, + localAddress string, +) (bgp.PeerConfigs, error) { nodeBgpPeersAnnotation, ok := nodeAnnotations[peersAnnotation] if !ok { - klog.Infof("%s annotation not set, using individual node annotations to configure BGP peer info", peersAnnotation) + klog.Infof( + "%s annotation not set, using individual node annotations to configure BGP peer info", + peersAnnotation, + ) return bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations, localAddress) } @@ -1456,7 +1526,10 @@ func bgpPeerConfigsFromAnnotations(nodeAnnotations map[string]string, localAddre return peerConfigs, nil } -func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string, localAddress string) (bgp.PeerConfigs, error) { +func bgpPeerConfigsFromIndividualAnnotations( + nodeAnnotations map[string]string, + localAddress string, +) (bgp.PeerConfigs, error) { // Get Global Peer Router ASN configs nodeBgpPeerAsnsAnnotation, ok := nodeAnnotations[peerASNAnnotation] if !ok { @@ -1494,7 +1567,9 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string, // Get Global Peer Router Password configs nodeBGPPasswordsAnnotation, ok := nodeAnnotations[peerPasswordAnnotation] if !ok { - klog.Infof("Could not find BGP peer password info in the node's annotations. Assuming no passwords.") + klog.Infof( + "Could not find BGP peer password info in the node's annotations. Assuming no passwords.", + ) } else { passStrings := stringToSlice(nodeBGPPasswordsAnnotation, ",") passwords, err = stringSliceB64Decode(passStrings) @@ -1507,7 +1582,9 @@ func bgpPeerConfigsFromIndividualAnnotations(nodeAnnotations map[string]string, var localIPs []string nodeBGPPeerLocalIPs, ok := nodeAnnotations[peerLocalIPAnnotation] if !ok { - klog.Infof("Could not find BGP peer local ip info in the node's annotations. Assuming node IP.") + klog.Infof( + "Could not find BGP peer local ip info in the node's annotations. Assuming node IP.", + ) } else { localIPs = stringToSlice(nodeBGPPeerLocalIPs, ",") err = func() error { From 850179bc22f3d499d6e9b0d5018aaa483d360d17 Mon Sep 17 00:00:00 2001 From: Cat C Date: Sat, 22 Nov 2025 13:13:47 -0800 Subject: [PATCH 11/12] Cleanup with another round of go mod tidy and some minor fixes from rebasing --- go.mod | 3 +-- go.sum | 4 ++-- pkg/controllers/routing/network_routes_controller.go | 6 ------ 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 2e45613f57..c74c96723b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 github.com/aws/aws-sdk-go-v2/service/ec2 v1.249.0 github.com/aws/smithy-go v1.23.0 - github.com/ccoveille/go-safecast v1.6.1 + github.com/ccoveille/go-safecast/v2 v2.0.0 github.com/coreos/go-iptables v0.8.0 github.com/docker/docker v28.4.0+incompatible github.com/goccy/go-yaml v1.18.0 @@ -127,7 +127,6 @@ require ( sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect - sigs.k8s.io/yaml v1.6.0 // indirect ) go 1.24.0 diff --git a/go.sum b/go.sum index 97b4ee560d..95580d9d12 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q= -github.com/ccoveille/go-safecast v1.6.1/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8= +github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0= +github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= diff --git a/pkg/controllers/routing/network_routes_controller.go b/pkg/controllers/routing/network_routes_controller.go index 79000008f9..69b2c97aff 100644 --- a/pkg/controllers/routing/network_routes_controller.go +++ b/pkg/controllers/routing/network_routes_controller.go @@ -333,10 +333,6 @@ func (nrc *NetworkRoutingController) Run( "not work: %s", err.Error()) } }() - if _, err := exec.Command("modprobe", "br_netfilter").CombinedOutput(); err != nil { - klog.Errorf("Failed to enable netfilter for bridge. Network policies and service proxy may "+ - "not work: %s", err.Error()) - } sysctlErr := utils.SetSysctl(utils.BridgeNFCallIPTables, 1) if sysctlErr != nil { klog.Errorf("Failed to enable iptables for bridge. Network policies and service proxy may "+ @@ -473,7 +469,6 @@ func (nrc *NetworkRoutingController) updateCNIConfig() { err = cniNetConf.InsertPodCIDRIntoIPAM(ipv6CIDR) if err != nil { klog.Fatalf("failed to insert IPv6 `subnet`(pod CIDR) '%s' into CNI conf file: %v", ipv6CIDR, err) - ) } } @@ -1293,7 +1288,6 @@ func NewNetworkRoutingController(clientset kubernetes.Interface, if nrc.bgpHoldtime > 65536 || nrc.bgpHoldtime < 3 { return nil, errors.New("this is an incorrect BGP holdtime range, holdtime must be in the range " + "3s to 18h12m16s") - ) } nrc.hostnameOverride = kubeRouterConfig.HostnameOverride From dbed2c1d8ab07843af64ec0185a3adb19fc7c8d0 Mon Sep 17 00:00:00 2001 From: Cat C Date: Sat, 22 Nov 2025 13:18:09 -0800 Subject: [PATCH 12/12] Revert some formatting changes to files that there weren't any real changes to --- pkg/controllers/routing/utils.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/controllers/routing/utils.go b/pkg/controllers/routing/utils.go index 1d03033c50..5c904afe56 100644 --- a/pkg/controllers/routing/utils.go +++ b/pkg/controllers/routing/utils.go @@ -155,8 +155,7 @@ func getPodCIDRsFromAllNodeSources(node *v1core.Node) (podCIDRs []string) { // based upon whether it is an IPv4 address or an IPv6 address. Returns slash notation subnet as uint32 suitable for // sending to GoBGP and an error if it is unable to determine the subnet automatically func (nrc *NetworkRoutingController) getBGPRouteInfoForVIP(vip string) (subnet uint32, nh string, - afiFamily gobgpapi.Family_Afi, err error, -) { + afiFamily gobgpapi.Family_Afi, err error) { ip := net.ParseIP(vip) if ip == nil { err = fmt.Errorf("could not parse VIP: %s", vip)