From 8c50b29f86295f2bcdf5fa0d6d25a162d261d607 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 16 Oct 2026 09:14:45 -0500 Subject: [PATCH 1/6] Test NSO preservation of Galactic attachment API group --- .../networkinterfaceclaim_controller_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/controller/networkinterfaceclaim_controller_test.go b/internal/controller/networkinterfaceclaim_controller_test.go index 6e7a72e8..24414976 100644 --- a/internal/controller/networkinterfaceclaim_controller_test.go +++ b/internal/controller/networkinterfaceclaim_controller_test.go @@ -1955,6 +1955,7 @@ func TestExternalAttachmentStatusSurvivesNSO(t *testing.T) { } require.NoError(t, s.client.Status().Update(s.ctx, iface)) + expectedGroup := "cloud.datumapis.com" requireAttachmentReported := func(stage string) { t.Helper() reported, err := s.getInterface("slot-0-eth0") @@ -1965,6 +1966,8 @@ func TestExternalAttachmentStatusSurvivesNSO(t *testing.T) { "%s reverted the condition the data plane owns", stage) require.Equal(t, "0000000ju", reported.Status.VPC, "%s cleared status.vpc", stage) require.NotNil(t, reported.Status.AttachmentRef, "%s cleared status.attachmentRef", stage) + require.Equal(t, expectedGroup, reported.Status.AttachmentRef.APIGroup, + "%s changed the data plane's attachment API group", stage) } s.reconcile(s.getClaim("slot-0-eth0")) @@ -1973,6 +1976,14 @@ func TestExternalAttachmentStatusSurvivesNSO(t *testing.T) { s.reconcileInterface("slot-0-eth0") requireAttachmentReported("an interface reconcile") + // Galactic takes over the attachment under its consolidated API group. + // NSO must preserve the replacement reference through release and rebind. + iface, err = s.getInterface("slot-0-eth0") + require.NoError(t, err) + iface.Status.AttachmentRef.APIGroup = "galactic.datumapis.com" + require.NoError(t, s.client.Status().Update(s.ctx, iface)) + expectedGroup = "galactic.datumapis.com" + // Retain unbinds the interface and a replacement claim adopts it. s.deleteClaim(s.getClaim("slot-0-eth0")) requireAttachmentReported("release under Retain") From 32431f5e94bad72869fa60705faf43e52a5c98bb Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 16 Oct 2026 09:51:13 -0500 Subject: [PATCH 2/6] Verify NSO preserves Galactic attachment migration status --- docs/enhancements/network-interfaces.md | 4 +-- .../networkinterfaceclaim_controller_test.go | 25 ++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/enhancements/network-interfaces.md b/docs/enhancements/network-interfaces.md index dfb14e30..804ec7e1 100644 --- a/docs/enhancements/network-interfaces.md +++ b/docs/enhancements/network-interfaces.md @@ -221,7 +221,7 @@ status: networkContextRef: name: default-us-central-1 attachmentRef: - apiGroup: cloud.datumapis.com + apiGroup: galactic.datumapis.com kind: VPCAttachment name: nic-4f2a9c1e vpc: 3kF9qP2x @@ -399,7 +399,7 @@ status: # The data-plane realization, once one exists. attachmentRef: - apiGroup: cloud.datumapis.com + apiGroup: galactic.datumapis.com kind: VPCAttachment name: nic-4f2a9c1e diff --git a/internal/controller/networkinterfaceclaim_controller_test.go b/internal/controller/networkinterfaceclaim_controller_test.go index 24414976..805d810f 100644 --- a/internal/controller/networkinterfaceclaim_controller_test.go +++ b/internal/controller/networkinterfaceclaim_controller_test.go @@ -1956,11 +1956,12 @@ func TestExternalAttachmentStatusSurvivesNSO(t *testing.T) { require.NoError(t, s.client.Status().Update(s.ctx, iface)) expectedGroup := "cloud.datumapis.com" + expectedProgrammed := metav1.ConditionTrue requireAttachmentReported := func(stage string) { t.Helper() reported, err := s.getInterface("slot-0-eth0") require.NoError(t, err) - require.Equal(t, metav1.ConditionTrue, + require.Equal(t, expectedProgrammed, apimeta.FindStatusCondition(reported.Status.Conditions, networkingv1alpha.NetworkInterfaceProgrammed).Status, "%s reverted the condition the data plane owns", stage) @@ -1981,8 +1982,17 @@ func TestExternalAttachmentStatusSurvivesNSO(t *testing.T) { iface, err = s.getInterface("slot-0-eth0") require.NoError(t, err) iface.Status.AttachmentRef.APIGroup = "galactic.datumapis.com" + apimeta.SetStatusCondition(&iface.Status.Conditions, metav1.Condition{ + Type: networkingv1alpha.NetworkInterfaceProgrammed, + Status: metav1.ConditionUnknown, + Reason: "APIGroupMigration", + Message: "waiting for the Galactic attachment to be programmed", + }) require.NoError(t, s.client.Status().Update(s.ctx, iface)) expectedGroup = "galactic.datumapis.com" + expectedProgrammed = metav1.ConditionUnknown + s.reconcile(s.getClaim("slot-0-eth0")) + requireAttachmentReported("a migration requeue") // Retain unbinds the interface and a replacement claim adopts it. s.deleteClaim(s.getClaim("slot-0-eth0")) @@ -1990,6 +2000,19 @@ func TestExternalAttachmentStatusSurvivesNSO(t *testing.T) { s.reconcile(s.createClaim("slot-0-eth0", spec)) requireAttachmentReported("a rebind") + + // Galactic reports success only after its new advertisement is live. + iface, err = s.getInterface("slot-0-eth0") + require.NoError(t, err) + apimeta.SetStatusCondition(&iface.Status.Conditions, metav1.Condition{ + Type: networkingv1alpha.NetworkInterfaceProgrammed, + Status: metav1.ConditionTrue, + Reason: "Advertised", + }) + require.NoError(t, s.client.Status().Update(s.ctx, iface)) + expectedProgrammed = metav1.ConditionTrue + s.reconcile(s.getClaim("slot-0-eth0")) + requireAttachmentReported("new Galactic programming") } // A retained interface outlives the holder that reported it, and the next claim From 8368bb5c327a344bdb00ac6b3cf1b8660a340db4 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 16 Oct 2026 10:24:03 -0500 Subject: [PATCH 3/6] Move Galactic cell VPC programming into NSO --- config/rbac/role.yaml | 42 ++ docs/enhancements/network-interfaces.md | 6 +- go.mod | 132 ++--- go.sum | 298 ++++++------ internal/cmd/cell/cell.go | 28 +- internal/config/cell.go | 16 + internal/config/cell_test.go | 13 + internal/config/zz_generated.deepcopy.go | 1 + .../controller/networkcontext_controller.go | 251 ++++++++++ .../networkcontext_controller_test.go | 243 ++++++++++ .../controller/networkinterface_controller.go | 459 ++++++++++++++++++ .../networkinterface_controller_test.go | 235 +++++++++ .../galactic/controller/subnet_controller.go | 180 +++++++ .../controller/subnet_controller_test.go | 163 +++++++ internal/galactic/galactic/galactic.go | 214 ++++++++ internal/galactic/galactic/galactic_test.go | 171 +++++++ internal/galactic/identifier/identifier.go | 95 ++++ .../galactic/identifier/identifier_test.go | 87 ++++ 18 files changed, 2418 insertions(+), 216 deletions(-) create mode 100644 internal/galactic/controller/networkcontext_controller.go create mode 100644 internal/galactic/controller/networkcontext_controller_test.go create mode 100644 internal/galactic/controller/networkinterface_controller.go create mode 100644 internal/galactic/controller/networkinterface_controller_test.go create mode 100644 internal/galactic/controller/subnet_controller.go create mode 100644 internal/galactic/controller/subnet_controller_test.go create mode 100644 internal/galactic/galactic/galactic.go create mode 100644 internal/galactic/galactic/galactic_test.go create mode 100644 internal/galactic/identifier/identifier.go create mode 100644 internal/galactic/identifier/identifier_test.go diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 356c583b..f073056f 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -176,6 +176,36 @@ rules: - get - patch - update +- apiGroups: + - galactic.datumapis.com + resources: + - networkfabricidentities + verbs: + - get + - list + - watch +- apiGroups: + - galactic.datumapis.com + resources: + - vpcattachments + - vpcs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - galactic.datumapis.com + resources: + - vpcattachments/status + - vpcs/status + verbs: + - get + - patch + - update - apiGroups: - gateway.envoyproxy.io resources: @@ -246,6 +276,18 @@ rules: - get - patch - update +- apiGroups: + - k8s.cni.cncf.io + resources: + - network-attachment-definitions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - locations.miloapis.com resources: diff --git a/docs/enhancements/network-interfaces.md b/docs/enhancements/network-interfaces.md index 804ec7e1..f5101195 100644 --- a/docs/enhancements/network-interfaces.md +++ b/docs/enhancements/network-interfaces.md @@ -489,12 +489,12 @@ the allocator to preserve. ### Reaching the data plane A `NetworkInterface` says what an interface must be. `VPCAttachment`, in the -[cloud](https://github.com/datum-cloud/cloud) API group, is where it becomes real, and the +`galactic.datumapis.com` API group, is where it becomes real, and the split is deliberate: an interface is allocated as soon as a claim exists, before an instance has been scheduled to any node, while what a node ends up carrying — a container ID, a host device, a VRF — is only knowable once the sandbox exists. -The attachment is therefore **written as intent by the infrastructure provider and reported +The attachment is therefore **written as intent by NSO's cell manager and reported on by the node**, not created by the node. It has to be, because the data plane reads it before the sandbox exists: the CNI configuration a `VPCAttachment` produces must already be in place when the pod's sandbox is created, so an attachment that only came into being as a @@ -507,7 +507,7 @@ NetworkInterfaceClaim compute's intent created per instance, per interfac NetworkInterface NSO's answer addresses, gateway, MTU │ realized by ▼ -VPCAttachment spec: the provider intent, written before the pod +VPCAttachment spec: NSO intent, written before the pod │ status: the node node, containerID, VRF, host device │ attaches to ▼ diff --git a/go.mod b/go.mod index 88378902..2eccabe3 100644 --- a/go.mod +++ b/go.mod @@ -6,44 +6,46 @@ require ( github.com/alicebob/miniredis/v2 v2.35.0 github.com/cert-manager/cert-manager v1.17.2 github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/domainr/whois v0.1.0 github.com/envoyproxy/gateway v1.8.1 github.com/envoyproxy/gateway/test v0.0.0-20260617000843-7e4492d9a99e github.com/envoyproxy/go-control-plane/contrib v1.36.1-0.20260409050421-3f47accd6e14 github.com/envoyproxy/go-control-plane/envoy v1.37.1-0.20260409050421-3f47accd6e14 - github.com/go-logr/logr v1.4.3 + github.com/go-logr/logr v1.4.4 github.com/go-redis/redis/v7 v7.4.1 github.com/google/cel-go v0.27.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 + github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 + github.com/kenshaw/baseconv v0.1.1 github.com/modelcontextprotocol/go-sdk v1.7.0 - github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/ginkgo/v2 v2.28.2 github.com/openrdap/rdap v0.9.1 - github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/client_golang v1.25.0 github.com/spf13/cobra v1.10.2 - github.com/stretchr/testify v1.11.1 + github.com/stretchr/testify v1.12.1 go.datum.net/datumctl v0.18.1 + go.datum.net/galactic v0.5.8-0.20261016143526-45d3c3514bec go.miloapis.com/activity v0.7.1 go.miloapis.com/dns-operator v0.5.1 - go.miloapis.com/locations v0.0.0-20260825185141-507ac2cbd48c + go.miloapis.com/locations v0.0.1 go.miloapis.com/milo v0.36.2 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 - go.opentelemetry.io/otel v1.44.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 - go.opentelemetry.io/otel/sdk v1.44.0 - go.opentelemetry.io/otel/trace v1.44.0 - golang.org/x/net v0.56.0 - golang.org/x/sync v0.22.0 - google.golang.org/grpc v1.81.1 - google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af - k8s.io/api v0.36.1 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0 + go.opentelemetry.io/otel v1.47.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 + go.opentelemetry.io/otel/sdk v1.47.0 + go.opentelemetry.io/otel/trace v1.47.0 + golang.org/x/net v0.61.0 + golang.org/x/sync v0.24.0 + google.golang.org/grpc v1.84.0 + google.golang.org/protobuf v1.36.12 + k8s.io/api v0.36.3 k8s.io/apiextensions-apiserver v0.36.1 - k8s.io/apimachinery v0.36.1 - k8s.io/client-go v0.36.1 - k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 + k8s.io/apimachinery v0.36.3 + k8s.io/client-go v0.36.3 + k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/gateway-api v1.5.1 sigs.k8s.io/gateway-api/conformance v1.5.1 @@ -53,11 +55,14 @@ require ( require ( github.com/clipperhouse/uax29/v2 v2.7.0 // indirect - github.com/go-openapi/swag/cmdutils v0.25.4 // indirect - github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-openapi/swag/cmdutils v0.28.0 // indirect + github.com/go-openapi/swag/netutils v0.28.0 // indirect + github.com/go-openapi/swag/pools v0.28.0 // indirect github.com/google/jsonschema-go v0.4.3 // indirect github.com/segmentio/encoding v0.5.4 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + go.opentelemetry.io/otel/log v1.47.0 // indirect ) require ( @@ -114,7 +119,7 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.3 // indirect - github.com/cilium/ebpf v0.21.0 // indirect + github.com/cilium/ebpf v0.22.0 // indirect github.com/containerd/cgroups/v3 v3.1.3 // indirect github.com/containerd/containerd v1.7.32 // indirect github.com/containerd/errdefs v1.0.0 // indirect @@ -152,24 +157,23 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-openapi/analysis v0.25.1 // indirect - github.com/go-openapi/errors v0.22.7 // indirect - github.com/go-openapi/jsonpointer v0.23.1 // indirect - github.com/go-openapi/jsonreference v0.21.6 // indirect - github.com/go-openapi/loads v0.23.3 // indirect - github.com/go-openapi/spec v0.22.5 // indirect - github.com/go-openapi/strfmt v0.26.3 // indirect - github.com/go-openapi/swag v0.25.4 // indirect - github.com/go-openapi/swag/conv v0.26.0 // indirect - github.com/go-openapi/swag/fileutils v0.26.0 // indirect - github.com/go-openapi/swag/jsonname v0.26.0 // indirect - github.com/go-openapi/swag/jsonutils v0.26.0 // indirect - github.com/go-openapi/swag/loading v0.26.0 // indirect - github.com/go-openapi/swag/mangling v0.26.0 // indirect - github.com/go-openapi/swag/stringutils v0.26.0 // indirect - github.com/go-openapi/swag/typeutils v0.26.0 // indirect - github.com/go-openapi/swag/yamlutils v0.26.0 // indirect - github.com/go-openapi/validate v0.25.3 // indirect + github.com/go-openapi/analysis v0.25.5 // indirect + github.com/go-openapi/errors v0.22.8 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/loads v0.25.0 // indirect + github.com/go-openapi/spec v0.22.9 // indirect + github.com/go-openapi/strfmt v0.27.0 // indirect + github.com/go-openapi/swag v0.28.0 // indirect + github.com/go-openapi/swag/conv v0.28.0 // indirect + github.com/go-openapi/swag/fileutils v0.28.0 // indirect + github.com/go-openapi/swag/jsonutils v0.28.0 // indirect + github.com/go-openapi/swag/loading v0.28.0 // indirect + github.com/go-openapi/swag/mangling v0.28.0 // indirect + github.com/go-openapi/swag/stringutils v0.28.0 // indirect + github.com/go-openapi/swag/typeutils v0.28.0 // indirect + github.com/go-openapi/swag/yamlutils v0.28.0 // indirect + github.com/go-openapi/validate v0.26.1 // indirect github.com/go-sql-driver/mysql v1.10.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect @@ -187,7 +191,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gosuri/uitable v0.0.4 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect @@ -197,7 +201,7 @@ require ( github.com/jackc/pgx/v5 v5.9.2 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/compress v1.20.1 // indirect github.com/kortschak/goroutine v1.1.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect @@ -243,10 +247,10 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/client_model v0.6.3 + github.com/prometheus/common v0.72.0 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect - github.com/prometheus/procfs v0.20.1 // indirect + github.com/prometheus/procfs v0.22.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.1 // indirect github.com/replicatedhq/troubleshoot v0.129.1 // indirect @@ -268,7 +272,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.4.0 // indirect github.com/tklauser/numcpus v0.12.0 // indirect - github.com/vishvananda/netlink v1.3.1 // indirect + github.com/vishvananda/netlink v1.3.2-0.20261002201547-1d1c62507e3c // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect @@ -282,31 +286,31 @@ require ( go.miloapis.com/ipam v0.4.0 go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.66.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/otel/metric v1.47.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.47.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 go.yaml.in/yaml/v2 v2.4.4 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.54.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/crypto v0.58.0 // indirect golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/mod v0.38.0 // indirect - golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/term v0.45.0 - golang.org/x/text v0.40.0 // indirect - golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.47.0 // indirect + golang.org/x/mod v0.41.0 // indirect + golang.org/x/oauth2 v0.37.0 // indirect + golang.org/x/sys v0.49.0 // indirect + golang.org/x/term v0.47.0 + golang.org/x/text v0.43.0 // indirect + golang.org/x/time v0.16.0 // indirect + golang.org/x/tools v0.51.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -319,7 +323,7 @@ require ( k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 // indirect k8s.io/kubectl v0.36.1 // indirect k8s.io/metrics v0.36.1 // indirect - k8s.io/streaming v0.36.1 // indirect + k8s.io/streaming v0.36.3 // indirect oras.land/oras-go/v2 v2.6.0 // indirect periph.io/x/host/v3 v3.8.5 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect @@ -329,7 +333,7 @@ require ( sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect sigs.k8s.io/mcs-api v0.5.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect ) replace sigs.k8s.io/multicluster-runtime => github.com/datum-cloud/multicluster-runtime v0.24.2-0.20261008054817-748176e5c835 diff --git a/go.sum b/go.sum index 4c749d46..c54671b3 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80= github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/cilium/ebpf v0.21.0 h1:4dpx1J/B/1apeTmWBH5BkVLayHTkFrMovVPnHEk+l3k= -github.com/cilium/ebpf v0.21.0/go.mod h1:1kHKv6Kvh5a6TePP5vvvoMa1bclRyzUXELSs272fmIQ= +github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY= +github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10bQt4= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= @@ -247,8 +247,8 @@ github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpj github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -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/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/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-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -256,52 +256,52 @@ github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-openapi/analysis v0.25.1 h1:We0FXVvCY5XNSb8GrE+k+wsgkUqTJqnkqsVAC9EGdrM= -github.com/go-openapi/analysis v0.25.1/go.mod h1:/S7h3rOOTHDGoPwyyjTGQpGeLKxWecr8QWSBdE68UgM= -github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA= -github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w= -github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= -github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y= -github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY= -github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ= -github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA= -github.com/go-openapi/spec v0.22.5 h1:KhO7RBlKQfonUWX2WzQCoLIXVA6AcNqDGZ3a1Dutdlo= -github.com/go-openapi/spec v0.22.5/go.mod h1:vxpOtMya5TXtENXKE5bKqv5NjocVhyhxHrlZfvKnZ74= -github.com/go-openapi/strfmt v0.26.3 h1:rzmslHarJgBbf2qfGge+X3htclQfmXqBZMm0Too0HhU= -github.com/go-openapi/strfmt v0.26.3/go.mod h1:a5nsUw0oRpQzZeOwx8bi6cKbzFZslpbCKt1LEot+KnQ= -github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= -github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= -github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= -github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= -github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= -github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= -github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= -github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= -github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= -github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= -github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= -github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= -github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= -github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= -github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= -github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= -github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= -github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= -github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= -github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= -github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= -github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= -github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8= -github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk= -github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo= -github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= -github.com/go-openapi/validate v0.25.3 h1:4nzAIavcJ7WveHK2+V1UAkZK3kWcjzxZCzjfZAfavKs= -github.com/go-openapi/validate v0.25.3/go.mod h1:GemfuGMyYpIaBoKpX3z8sLywrmxpzWVOoJ7R0VeAVuk= +github.com/go-openapi/analysis v0.25.5 h1:xPYEvTb90o1y0epuiOPAoG4QqahjP3cdp5xNlHeKJRI= +github.com/go-openapi/analysis v0.25.5/go.mod h1:d3UGtQC5uq5Kqqqis2VH09Km/v3vwsWrYkbp4gdm+Rc= +github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= +github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/loads v0.25.0 h1:74Bc2snfaVlsHzwdQj/3gsA9XJz3daXTJVs+4ZaK7jI= +github.com/go-openapi/loads v0.25.0/go.mod h1:JFBw4SIB9+PTIFHDfcXuSSy5h6aWzjtUCrPYyx3qWU8= +github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w= +github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0= +github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= +github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= +github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw= +github.com/go-openapi/swag v0.28.0/go.mod h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg= +github.com/go-openapi/swag/cmdutils v0.28.0 h1:7TOeNtkYru1SG8Y34tDh9WBbLsMqGnptuxWiHREPZ4Q= +github.com/go-openapi/swag/cmdutils v0.28.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= +github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= +github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= +github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= +github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= +github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= +github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= +github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.28.0 h1:YXN6TALEi2pzts8/8GNm6T61HTAZsieukGZidap989k= +github.com/go-openapi/swag/netutils v0.28.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= +github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= +github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= +github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= +github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/validate v0.26.1 h1:pZSbvtRO8G2R2FpWTYRn3w8LrsNwbtaVhP2dWiBa0Us= +github.com/go-openapi/validate v0.26.1/go.mod h1:B8UMgXiQiwwQWIbmuROlwJZDPGlikPuh7iHV1vPX9Oo= github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI= @@ -366,8 +366,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -396,8 +396,6 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -406,11 +404,15 @@ github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCX github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 h1:z4P744DR+PIpkjwXSEc6TvN3L6LVzmUquFgmNm8wSUc= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7/go.mod h1:CM7HAH5PNuIsqjMN0fGc1ydM74Uj+0VZFhob620nklw= +github.com/kenshaw/baseconv v0.1.1 h1:oAu/C7ipUT2PqT9DT0mZDGDg4URIglizZMjPv9oCu0E= +github.com/kenshaw/baseconv v0.1.1/go.mod h1:yy9zGmnnR6vgOxOQb702nVdAG30JhyYZpj/5/m0siRI= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/goroutine v1.1.3 h1:kELvAfi7jpVD7a+MPWjmIxuQVJVYo/RELaOeGJZBb88= github.com/kortschak/goroutine v1.1.3/go.mod h1:zKpXs1FWN/6mXasDQzfl7g0LrGFIOiA6cLs9eXKyaMY= @@ -448,10 +450,10 @@ github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhg github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= -github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/mdlayher/netlink v1.11.2 h1:HKh2jqe+omdSWcQ88nrT7INE61B0NXfiSPFdgL4YbNI= +github.com/mdlayher/netlink v1.11.2/go.mod h1:uT2Yc/QLaZubzDpZIBi9d4GoeLwtp3x1AMeqSRrK2sA= +github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU= +github.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18= github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY= @@ -514,8 +516,8 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= -github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= +github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= @@ -553,16 +555,16 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -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_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.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= -github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/client_golang v1.25.0 h1:cDtW1TcFsMi1RJc8khwCKB4VhzwV04aKqbYtNOXuiK0= +github.com/prometheus/client_golang v1.25.0/go.mod h1:j2X/4/kaQHLPnmyEU8zjmkndo1T317VpCIQEml8LTKk= +github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo= +github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM= +github.com/prometheus/common v0.72.0 h1:tAYsE+sPJxIncDAobm4H5aQjmox9ZxEIIqPbiffa8G4= +github.com/prometheus/common v0.72.0/go.mod h1:77NWqAQ2tXT7BIK40qjJdw5Acrsrg1TlHAnsQi3i6mk= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= -github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= -github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics= +github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= @@ -623,8 +625,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -633,8 +636,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= @@ -652,8 +655,8 @@ github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqo github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= -github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= -github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= +github.com/vishvananda/netlink v1.3.2-0.20261002201547-1d1c62507e3c h1:88GLXNKgc6pwIU/4yq+gleF5ISv3AsJkYmFlS1GjHvo= +github.com/vishvananda/netlink v1.3.2-0.20261002201547-1d1c62507e3c/go.mod h1:lEui7SPMd9fgxzHVGRAvTxsBGCF6PRH81o2kLWLWHgw= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= @@ -684,6 +687,8 @@ github.com/zonedb/zonedb v1.0.3544 h1:u5a3xOaI338FclecJ/H6J7fiImVEZ/qZomJnVYIlTe github.com/zonedb/zonedb v1.0.3544/go.mod h1:h9mfHV/S6lboOkltULrbNY52cd7JZo6MbxIiqKMWPLg= go.datum.net/datumctl v0.18.1 h1:MP83cRkJ94aq4lYyOm5dSLU60JNnmR486LIeZnaCw8s= go.datum.net/datumctl v0.18.1/go.mod h1:0L0BBgFfud8ZuV9sbsSmeEBUPoK2q9g4boU5yczF92o= +go.datum.net/galactic v0.5.8-0.20261016143526-45d3c3514bec h1:voR/MqaNhUuh9OylFZ3IamgoSkWGsKi+9Rta+PqlHc4= +go.datum.net/galactic v0.5.8-0.20261016143526-45d3c3514bec/go.mod h1:g9KA3v6fQ4fMFBoWxFNA2ABlacKecn2l6ftJQeUdVE4= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.etcd.io/etcd/api/v3 v3.6.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM= @@ -704,10 +709,8 @@ go.miloapis.com/dns-operator v0.5.1 h1:3HmturI/Opl+l4/KurN6B0/XEeVefoSvYU0ewcZ8B go.miloapis.com/dns-operator v0.5.1/go.mod h1:yQSBOx1ZhSzZgg5RyNKyNHfZgjXRKhxkcGD+jvbebQE= go.miloapis.com/ipam v0.4.0 h1:U+mg3RMFXj0c2eQ0Lm15CI0bYBPFDqy8IsGLN8v/eGs= go.miloapis.com/ipam v0.4.0/go.mod h1:Jj7xg4lJi9psE0+4PuOg/GQOG8rG13h112xYoM994rc= -go.miloapis.com/locations v0.0.0-20260825185141-507ac2cbd48c h1:+BQirT3wYCgv7H2lEZtAH+dpMiWu9j/Wa+c8s4jJUIA= -go.miloapis.com/locations v0.0.0-20260825185141-507ac2cbd48c/go.mod h1:gzfAfHhSMwl/N68k/uSNYXOKK3IOBJCdXYaBgCE3gdE= -go.miloapis.com/milo v0.31.0 h1:gBZ9NaplTCFhgWUZJ18N38JsFDCVtdKmypwJvffSdUU= -go.miloapis.com/milo v0.31.0/go.mod h1:GKK3afjCwshfZfvhjNe1wp/H45z4m7x5oG/8xbSgU1M= +go.miloapis.com/locations v0.0.1 h1:voJKqBzyLX5x96M3+5y/ga7fgq9/+vypTizd+bBvqUY= +go.miloapis.com/locations v0.0.1/go.mod h1:gzfAfHhSMwl/N68k/uSNYXOKK3IOBJCdXYaBgCE3gdE= go.miloapis.com/milo v0.36.2 h1:ZXKX6KIQ9O6KF87IVHfXoQ0xmF9MztiP91fFLWasznQ= go.miloapis.com/milo v0.36.2/go.mod h1:zMzwGXexUlPlFaZ9/4U+2DDbzPj4xzs9I76Z901g2sE= go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= @@ -717,12 +720,12 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXv go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0 h1:Tq+E65HaNbZTeqcLw6QUhsV+/EIWdkfUH2CQzcrlE5M= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0/go.mod h1:A+EVhDakAj1waJBPhmTszZaSr08ww8MRRKy1nB1rqck= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04= +go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM= +go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -731,10 +734,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 h1:SUp go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0/go.mod h1:ho2g4N+ane+swq5I/VBkKWnRDY4kUINH3FuqyZqX/Ug= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 h1:RuynHbfU8JUEw7DyONgkVYg2SVtsoF28y0LGIr69jgA= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0/go.mod h1:qZF+/lBs71APw8mlnEZcqZHMzqrYrsFiJOv83lX1OGo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 h1:julhjPeUH/q/7hinbSdDdqt5h7Zw9YWmRlWRhI0jd54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0/go.mod h1:Ao2mz688LH/tFf0yMAenidq6k2YNSx6SIY2q6jDACck= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 h1:UFpxOpYPmMNUtOWhdb+nC1WELIgVf8z9higURrbzYU4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0/go.mod h1:YBGjxe3lt0jtXoEXxGrGhv/jM7oUBUDVK7zhAvNyEjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/prometheus v0.66.0 h1:vkrK8PAznv2NKt2r+kdu252ccGzkEqLc2aSXbQIALYQ= @@ -743,24 +746,24 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/B go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= -go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= -go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= -go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0 h1:lsA/S1bxgdbyFGkTj+3meEdJ6ADVU7QoFstV6MXgE68= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0/go.mod h1:L7u+MirGoB1bjeLH66+xDykF4RC8C3RN7lIFpBiewUo= +go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI= +go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw= +go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY= +go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU= +go.opentelemetry.io/otel/metric/x v0.69.0 h1:DjRLr15H83v+hCW7JA9NoJvOkYTtmq5YoDRbe9deYpM= +go.opentelemetry.io/otel/metric/x v0.69.0/go.mod h1:uVvsMPMFFyj/HUQfrUnH3JjnOQ1dwFDorgFLRBasM0k= +go.opentelemetry.io/otel/sdk v1.47.0 h1:zWXEr4j2lFefG87TU6Yg8a7ngfohIKFZHKp0Hf5hC6I= +go.opentelemetry.io/otel/sdk v1.47.0/go.mod h1:VUc24kiOeoGsxG8G9ULx3fWKvB7jMhnGE8Oi607lgR0= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/otel/sdk/metric v1.47.0 h1:lfISg2j93VT6yqdk9OfUaZmw/GfcZqCCV3jdXtsPnKw= +go.opentelemetry.io/otel/sdk/metric v1.47.0/go.mod h1:ypLp+mW1Nt2x+Szt3b5/i1syodyts49lMOwxpDI3VGw= +go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM= +go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0= +go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= +go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= @@ -771,15 +774,16 @@ go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= +golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w= golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb h1:Iu0p/klM0SM7atONioa/bPhLS7cjhnip99x1OIGibwg= golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= @@ -787,8 +791,8 @@ golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aI 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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -799,17 +803,17 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= -golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= -golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= +golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= +golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= +golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= 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= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -829,29 +833,27 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/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.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM= +golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= +golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= +golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= +golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= 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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI= +golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA= 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= @@ -860,16 +862,16 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc h1:rZHRz0ogq4Pid1IKtA/ivHi8XONVF5tqUQXA2TtgpkE= +google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:K/n0XLazCJHjD73YuMyNJsN38BPPy1kfFnKxUXaThFU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8 h1:0SXBqli5dnuCKMjI/X3FM226zEaEa0GUcSCJtN1vL2o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= -google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -896,18 +898,18 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= helm.sh/helm/v3 v3.21.0 h1:9TRbaXQH+BIKLLDYlu++JsyWodS5kBBOLF7C7HY5+cs= helm.sh/helm/v3 v3.21.0/go.mod h1:5IvU6Ae6ruB/vasVHhnC1IU5RvqFM349vLYS1BiHqeY= -k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= -k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= +k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks= k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8= -k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= -k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= +k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= +k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY= k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw= k8s.io/cli-runtime v0.36.1 h1:yuC/BGnnj1YYPh6D1P+pZnzinCs6DvMq86yAeNqoqzM= k8s.io/cli-runtime v0.36.1/go.mod h1:ZQWHGt8xAF7KnviB79vX0lYNyUUqKIpU+LQg7exuFAw= -k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= -k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= +k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA= k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= @@ -920,10 +922,10 @@ k8s.io/kubectl v0.36.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI= k8s.io/kubectl v0.36.1/go.mod h1:/DGPAIewKsFWF9VFgGvkPhao2Ev4SNuE3BioZo8yPbk= k8s.io/metrics v0.36.1 h1:MQPb+G4RhrKEpt8NETPssbW8QgGUc4Jbqu1jx+kPqGk= k8s.io/metrics v0.36.1/go.mod h1:xqS8XcWLjDzo6E7DJm/GfjKpRKdN5/MtJAQFuV6nLUc= -k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4= -k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/streaming v0.36.3 h1:9rAaqBk0C0Pc7+/fqGekj07NV+/Xrew58p647A0JT8w= +k8s.io/streaming v0.36.3/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= periph.io/x/host/v3 v3.8.5 h1:g4g5xE1XZtDiGl1UAJaUur1aT7uNiFLMkyMEiZ7IHII= @@ -950,7 +952,7 @@ sigs.k8s.io/mcs-api v0.5.0 h1:UMgKuFFIRVEy4LR2LQovPnBkV9rgmZMT+sJO7MkaVj4= sigs.k8s.io/mcs-api v0.5.0/go.mod h1:zZ5CK8uS6HaLkxY4HqsmcBHfzHuNMrY2uJy8T7jffK4= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw= +sigs.k8s.io/structured-merge-diff/v6 v6.3.3/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/cmd/cell/cell.go b/internal/cmd/cell/cell.go index a89dd94f..b4c490b3 100644 --- a/internal/cmd/cell/cell.go +++ b/internal/cmd/cell/cell.go @@ -7,6 +7,7 @@ import ( "fmt" "os" + nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" "k8s.io/apimachinery/pkg/runtime" @@ -22,11 +23,13 @@ import ( ipamv1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" locationsv1alpha1 "go.miloapis.com/locations/api/v1alpha1" + cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1" networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha" networkingv1alpha1 "go.datum.net/network-services-operator/api/v1alpha1" "go.datum.net/network-services-operator/internal/cmd/clusterdiscovery" "go.datum.net/network-services-operator/internal/config" "go.datum.net/network-services-operator/internal/controller" + galacticcontroller "go.datum.net/network-services-operator/internal/galactic/controller" "go.datum.net/network-services-operator/internal/logging" ) @@ -46,6 +49,8 @@ func init() { utilruntime.Must(locationsv1alpha1.AddToScheme(scheme)) utilruntime.Must(networkingv1alpha1.AddToScheme(scheme)) utilruntime.Must(ipamv1alpha1.AddToScheme(scheme)) + utilruntime.Must(cloudv1alpha1.AddToScheme(scheme)) + utilruntime.Must(nadv1.AddToScheme(scheme)) } // NewCommand builds the cell controller manager command. @@ -285,7 +290,28 @@ func controllerRegistrations( ipamClients controller.IPAMClientFactory, hubCluster cluster.Cluster, ) []namedSetup { - registrations := make([]namedSetup, 0, 5) + registrations := make([]namedSetup, 0, 8) + if serverConfig.Galactic.Enabled { + registrations = append(registrations, + namedSetup{"galactic-networkcontext", func() (bool, error) { + return true, (&galacticcontroller.NetworkContextReconciler{ + Client: mgr.GetLocalManager().GetClient(), Scheme: mgr.GetLocalManager().GetScheme(), + }).SetupWithManager(mgr.GetLocalManager()) + }}, + namedSetup{"galactic-subnet", func() (bool, error) { + return true, (&galacticcontroller.SubnetReconciler{ + Client: mgr.GetLocalManager().GetClient(), Scheme: mgr.GetLocalManager().GetScheme(), + }).SetupWithManager(mgr.GetLocalManager()) + }}, + namedSetup{"galactic-networkinterface", func() (bool, error) { + return true, (&galacticcontroller.NetworkInterfaceReconciler{ + Client: mgr.GetLocalManager().GetClient(), Scheme: mgr.GetLocalManager().GetScheme(), + APIReader: mgr.GetLocalManager().GetAPIReader(), + AttachmentMode: cloudv1alpha1.VPCAttachmentInterfaceMode(serverConfig.Galactic.AttachmentMode), + }).SetupWithManager(mgr.GetLocalManager()) + }}, + ) + } registrations = append(registrations, namedSetup{"networkinterfaceclaim", func() (bool, error) { return true, (&controller.NetworkInterfaceClaimReconciler{ diff --git a/internal/config/cell.go b/internal/config/cell.go index bf34e5af..20459b4e 100644 --- a/internal/config/cell.go +++ b/internal/config/cell.go @@ -58,6 +58,15 @@ type CellControllerManager struct { // This field is temporary. It goes away once no cell reports Configured for // nso_cell_location_identity_source. Location LocationConfig `json:"location,omitempty"` + + // Galactic enables this cell to reconcile downstream Galactic VPCs and + // attachments once the Galactic CRDs are installed. + Galactic GalacticCellConfig `json:"galactic,omitempty"` +} + +type GalacticCellConfig struct { + Enabled bool `json:"enabled,omitempty"` + AttachmentMode string `json:"attachmentMode,omitempty"` } // Validate reports whether the cell configuration can serve a cell. @@ -68,5 +77,12 @@ func (c *CellControllerManager) Validate() error { if err := c.Federation.validate(); err != nil { return fmt.Errorf("federation: %w", err) } + if c.Galactic.Enabled { + switch c.Galactic.AttachmentMode { + case "Netns", "Hypervisor", "HypervisorDeclared": + default: + return fmt.Errorf("galactic.attachmentMode must be Netns, Hypervisor, or HypervisorDeclared") + } + } return nil } diff --git a/internal/config/cell_test.go b/internal/config/cell_test.go index d5007d1a..3943a9bd 100644 --- a/internal/config/cell_test.go +++ b/internal/config/cell_test.go @@ -76,6 +76,19 @@ func TestCellControllerManager_Validate(t *testing.T) { mutate: func(c *CellControllerManager) { c.Federation.KubeconfigPath = "" }, wantSub: "federation: kubeconfigPath is required", }, + { + name: "galactic enabled for hypervisor cell", + mutate: func(c *CellControllerManager) { + c.Galactic = GalacticCellConfig{Enabled: true, AttachmentMode: "Hypervisor"} + }, + }, + { + name: "galactic enabled without attachment mode", + mutate: func(c *CellControllerManager) { + c.Galactic.Enabled = true + }, + wantSub: "galactic.attachmentMode", + }, } for _, tt := range tests { diff --git a/internal/config/zz_generated.deepcopy.go b/internal/config/zz_generated.deepcopy.go index 1ac184cc..6caf90ce 100644 --- a/internal/config/zz_generated.deepcopy.go +++ b/internal/config/zz_generated.deepcopy.go @@ -43,6 +43,7 @@ func (in *CellControllerManager) DeepCopyInto(out *CellControllerManager) { out.IPAM = in.IPAM out.Federation = in.Federation out.Location = in.Location + out.Galactic = in.Galactic } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CellControllerManager. diff --git a/internal/galactic/controller/networkcontext_controller.go b/internal/galactic/controller/networkcontext_controller.go new file mode 100644 index 00000000..6877668a --- /dev/null +++ b/internal/galactic/controller/networkcontext_controller.go @@ -0,0 +1,251 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package controller + +import ( + "context" + "fmt" + "slices" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1" + networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha" + "go.datum.net/network-services-operator/internal/galactic/identifier" +) + +const reasonAwaitingFabricIdentity = "AwaitingFabricIdentity" + +// NetworkContextReconciler gives a network's presence in one location its +// data-plane identity: one VPC per NetworkContext, carrying the base62 VPC +// identifier the whole galactic fabric keys on. +type NetworkContextReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkcontexts,verbs=get;list;watch +// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=networkfabricidentities,verbs=get;list;watch +// +kubebuilder:rbac:groups=networking.datumapis.com,resources=subnets,verbs=get;list;watch +// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcs,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcs/status,verbs=get;update;patch + +func (r *NetworkContextReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var networkContext networkingv1alpha.NetworkContext + if err := r.Get(ctx, req.NamespacedName, &networkContext); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + if !networkContext.DeletionTimestamp.IsZero() { + return ctrl.Result{}, nil + } + + networks, err := r.networksForContext(ctx, &networkContext) + if err != nil { + return ctrl.Result{}, err + } + if len(networks) == 0 { + // The VPC address space comes from the Subnets IPAM allocated for this + // location, and VPCSpec is immutable once written. + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + + vpc := &cloudv1alpha1.VPC{ + ObjectMeta: metav1.ObjectMeta{ + Name: networkContext.Name, + Namespace: networkContext.Namespace, + }, + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, vpc, func() error { + if vpc.CreationTimestamp.IsZero() { + vpc.Spec.Networks = networks + } + return controllerutil.SetControllerReference(&networkContext, vpc, r.Scheme) + }); err != nil { + return ctrl.Result{}, fmt.Errorf("reconcile VPC %s: %w", vpc.Name, err) + } + + // Everything below is conditioned on the VPC not having an identifier yet. A + // VPC that already has one keeps it: the VRF device is named from it and the + // Route Target derives from it, so renumbering a live VPC would rename its + // interface and change its routes under running traffic. + if vpc.Status.VPC == "" { + identity, found, err := r.fabricIdentity(ctx, &networkContext) + if err != nil { + return ctrl.Result{}, err + } + if !found { + return ctrl.Result{}, r.markAwaitingFabricIdentity(ctx, vpc) + } + encoded, err := identifier.VPCBase62(uint64(identity)) + if err != nil { + return ctrl.Result{}, fmt.Errorf("encode fabric identity %d for VPC %s: %w", identity, vpc.Name, err) + } + vpc.Status.VPC = encoded + } + vpc.Status.ObservedGeneration = vpc.Generation + meta.SetStatusCondition(&vpc.Status.Conditions, metav1.Condition{ + Type: cloudv1alpha1.ConditionTypeReady, + Status: metav1.ConditionTrue, + Reason: "IdentifierAllocated", + Message: fmt.Sprintf("VPC identifier %s allocated", vpc.Status.VPC), + ObservedGeneration: vpc.Generation, + }) + if err := r.Status().Update(ctx, vpc); err != nil { + return ctrl.Result{}, fmt.Errorf("update VPC %s status: %w", vpc.Name, err) + } + + return ctrl.Result{}, nil +} + +// fabricIdentity reads the identity carried to this cell for the context's +// network. It is one object per network, named after the network, in the +// network's namespace. A missing one is an ordinary answer, not a failure. +// +// The identity is allocated centrally, once for the whole network, and carried +// to each cell the network reaches. Deriving the VPC identifier from it is what +// makes two locations of one network the same network on the fabric; drawing a +// random value per location, which is what this used to do, made them two. +// +// A VPC whose identity has not arrived waits, and there is deliberately nothing +// else it can do. The identifier is immutable, so a random value taken while +// waiting is permanent, and a network whose locations then disagree is worse +// than one with no VPC at all: a NetworkService spanning them binds no VRF and +// fails every request, including through healthy members. Waiting ends the +// moment the identity lands. A random draw never ends. +func (r *NetworkContextReconciler) fabricIdentity( + ctx context.Context, networkContext *networkingv1alpha.NetworkContext, +) (int64, bool, error) { + networkName := networkContext.Spec.Network.Name + if networkName == "" { + return 0, false, nil + } + + var identity cloudv1alpha1.NetworkFabricIdentity + key := client.ObjectKey{Namespace: networkContext.Namespace, Name: networkName} + if err := r.Get(ctx, key, &identity); err != nil { + if apierrors.IsNotFound(err) { + return 0, false, nil + } + return 0, false, fmt.Errorf("read the fabric identity for network %q: %w", networkName, err) + } + if identity.Spec.Identity == 0 { + return 0, false, nil + } + return identity.Spec.Identity, true, nil +} + +// markAwaitingFabricIdentity says out loud that the VPC has no identifier yet +// and why, so a network stuck waiting on a central allocation is visible as +// that rather than as a VPC that silently never became ready. +func (r *NetworkContextReconciler) markAwaitingFabricIdentity( + ctx context.Context, vpc *cloudv1alpha1.VPC, +) error { + vpc.Status.ObservedGeneration = vpc.Generation + meta.SetStatusCondition(&vpc.Status.Conditions, metav1.Condition{ + Type: cloudv1alpha1.ConditionTypeReady, + Status: metav1.ConditionFalse, + Reason: reasonAwaitingFabricIdentity, + Message: "Waiting for the identity the fabric knows this network by", + ObservedGeneration: vpc.Generation, + }) + if err := r.Status().Update(ctx, vpc); err != nil { + return fmt.Errorf("update VPC %s status: %w", vpc.Name, err) + } + return nil +} + +// networksForContext collects the CIDRs IPAM allocated for this location. +func (r *NetworkContextReconciler) networksForContext( + ctx context.Context, networkContext *networkingv1alpha.NetworkContext, +) ([]cloudv1alpha1.Network, error) { + var subnets networkingv1alpha.SubnetList + if err := r.List(ctx, &subnets, client.InNamespace(networkContext.Namespace)); err != nil { + return nil, fmt.Errorf("list subnets: %w", err) + } + + networks := make([]cloudv1alpha1.Network, 0, len(subnets.Items)) + for _, subnet := range subnets.Items { + if subnet.Spec.NetworkContext.Name != networkContext.Name { + continue + } + start, prefixLength, ok := subnetRange(&subnet) + if !ok { + continue + } + networks = append(networks, cloudv1alpha1.Network( + fmt.Sprintf("%s/%d", start, prefixLength))) + } + slices.Sort(networks) + return networks, nil +} + +// subnetRange reads a subnet's allocated range, preferring status and falling +// back to spec. A location's copy arrives by propagation, which carries spec +// and never status. +func subnetRange(subnet *networkingv1alpha.Subnet) (string, int32, bool) { + if subnet.Status.StartAddress != nil && subnet.Status.PrefixLength != nil { + return *subnet.Status.StartAddress, *subnet.Status.PrefixLength, true + } + if subnet.Spec.StartAddress != "" && subnet.Spec.PrefixLength != 0 { + return subnet.Spec.StartAddress, subnet.Spec.PrefixLength, true + } + return "", 0, false +} + +// SetupWithManager registers the reconciler with the manager. +func (r *NetworkContextReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&networkingv1alpha.NetworkContext{}). + Owns(&cloudv1alpha1.VPC{}). + Watches(&cloudv1alpha1.NetworkFabricIdentity{}, + handler.EnqueueRequestsFromMapFunc(r.contextsForFabricIdentity)). + Named("networkcontext"). + Complete(r) +} + +// contextsForFabricIdentity wakes a network's presences the moment its identity +// reaches this cell, so a VPC waiting on one takes it immediately instead of +// sitting out its poll interval. +func (r *NetworkContextReconciler) contextsForFabricIdentity( + ctx context.Context, object client.Object, +) []reconcile.Request { + var contexts networkingv1alpha.NetworkContextList + if err := r.List(ctx, &contexts, client.InNamespace(object.GetNamespace())); err != nil { + return nil + } + + requests := make([]reconcile.Request, 0, len(contexts.Items)) + for i := range contexts.Items { + if contexts.Items[i].Spec.Network.Name != object.GetName() { + continue + } + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKeyFromObject(&contexts.Items[i]), + }) + } + return requests +} diff --git a/internal/galactic/controller/networkcontext_controller_test.go b/internal/galactic/controller/networkcontext_controller_test.go new file mode 100644 index 00000000..c86139e4 --- /dev/null +++ b/internal/galactic/controller/networkcontext_controller_test.go @@ -0,0 +1,243 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package controller + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1" + networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha" +) + +// A location's Subnet arrives by propagation, which carries spec and never +// status, so the allocated range has to be readable from either. +func TestSubnetRangePrefersStatusAndFallsBackToSpec(t *testing.T) { + start := "fd00::" + var length int32 = 48 + + withStatus := &networkingv1alpha.Subnet{ + Spec: networkingv1alpha.SubnetSpec{StartAddress: "fd20::", PrefixLength: 64}, + Status: networkingv1alpha.SubnetStatus{StartAddress: &start, PrefixLength: &length}, + } + gotStart, gotLength, ok := subnetRange(withStatus) + if !ok || gotStart != "fd00::" || gotLength != 48 { + t.Fatalf("status should win when present, got %q/%d ok=%v", gotStart, gotLength, ok) + } + + specOnly := &networkingv1alpha.Subnet{ + Spec: networkingv1alpha.SubnetSpec{StartAddress: "fd20::", PrefixLength: 64}, + } + gotStart, gotLength, ok = subnetRange(specOnly) + if !ok || gotStart != "fd20::" || gotLength != 64 { + t.Fatalf("a propagated copy carries spec only, got %q/%d ok=%v", gotStart, gotLength, ok) + } + + if _, _, ok := subnetRange(&networkingv1alpha.Subnet{}); ok { + t.Fatal("an unallocated subnet should yield nothing") + } +} + +const ( + vpcTestNamespace = "ns-project" + vpcTestNetwork = "prod" + vpcTestLocation = "us-central-1" + + // The identity the fabric knows the test network by, and the identifier a + // VPC derives from it. + vpcTestIdentity = 16 + vpcTestIdentifier = "g" +) + +type vpcFixture struct { + t *testing.T + ctx context.Context + client client.Client + reconciler *NetworkContextReconciler +} + +// newVPCFixture stands up one network present at one location, with the subnet +// that gives the VPC an address space to be created for. +func newVPCFixture(t *testing.T, objects ...client.Object) *vpcFixture { + t.Helper() + + scheme := runtime.NewScheme() + if err := networkingv1alpha.AddToScheme(scheme); err != nil { + t.Fatalf("build the networking scheme: %v", err) + } + if err := cloudv1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("build the cloud scheme: %v", err) + } + + presence := &networkingv1alpha.NetworkContext{} + presence.Namespace = vpcTestNamespace + presence.Name = vpcTestNetwork + "-" + vpcTestLocation + presence.Spec.Network = networkingv1alpha.LocalNetworkRef{Name: vpcTestNetwork} + + subnet := &networkingv1alpha.Subnet{} + subnet.Namespace = vpcTestNamespace + subnet.Name = presence.Name + subnet.Spec.NetworkContext = networkingv1alpha.LocalNetworkContextRef{Name: presence.Name} + subnet.Spec.StartAddress = "fd00::" + subnet.Spec.PrefixLength = 48 + + all := append([]client.Object{presence, subnet}, objects...) + fakeClient := fake.NewClientBuilder().WithScheme(scheme). + WithStatusSubresource(&cloudv1alpha1.VPC{}). + WithObjects(all...).Build() + + return &vpcFixture{ + t: t, + ctx: context.Background(), + client: fakeClient, + reconciler: &NetworkContextReconciler{Client: fakeClient, Scheme: scheme}, + } +} + +func (f *vpcFixture) reconcile() ctrl.Result { + f.t.Helper() + result, err := f.reconciler.Reconcile(f.ctx, ctrl.Request{NamespacedName: types.NamespacedName{ + Namespace: vpcTestNamespace, + Name: vpcTestNetwork + "-" + vpcTestLocation, + }}) + if err != nil { + f.t.Fatalf("reconcile: %v", err) + } + return result +} + +func (f *vpcFixture) vpc() *cloudv1alpha1.VPC { + f.t.Helper() + vpc := &cloudv1alpha1.VPC{} + key := types.NamespacedName{Namespace: vpcTestNamespace, Name: vpcTestNetwork + "-" + vpcTestLocation} + if err := f.client.Get(f.ctx, key, vpc); err != nil { + f.t.Fatalf("read the VPC: %v", err) + } + return vpc +} + +func fabricIdentityObject() *cloudv1alpha1.NetworkFabricIdentity { + object := &cloudv1alpha1.NetworkFabricIdentity{} + object.Namespace = vpcTestNamespace + object.Name = vpcTestNetwork + object.Spec.Identity = vpcTestIdentity + object.Spec.NetworkRef = cloudv1alpha1.NetworkFabricIdentityNetworkRef{Name: vpcTestNetwork} + return object +} + +// The whole point: every location of one network reaches the same identifier, +// because every one of them reads the same centrally allocated identity. +func TestVPCTakesItsIdentifierFromTheNetworksFabricIdentity(t *testing.T) { + fixture := newVPCFixture(t, fabricIdentityObject()) + + fixture.reconcile() + + vpc := fixture.vpc() + if vpc.Status.VPC != vpcTestIdentifier { + t.Fatalf("VPC identifier: got %q, want %q", vpc.Status.VPC, vpcTestIdentifier) + } + if condition := meta.FindStatusCondition(vpc.Status.Conditions, cloudv1alpha1.ConditionTypeReady); condition == nil || + condition.Status != metav1.ConditionTrue { + t.Fatalf("a VPC with an identifier should be ready, got %+v", condition) + } +} + +// A network whose identity has not reached this cell yet waits, and writes +// nothing. The identifier is immutable, so a value taken while waiting is +// permanent, and locations that disagree bind no VRF at all. +func TestVPCWaitsForAnIdentityThatHasNotArrived(t *testing.T) { + fixture := newVPCFixture(t) + + if result := fixture.reconcile(); result.RequeueAfter != 0 { + t.Fatalf("the identity's arrival is the trigger, not a poll, got %v", result) + } + + vpc := fixture.vpc() + if vpc.Status.VPC != "" { + t.Fatalf("no identifier should be written while waiting, got %q", vpc.Status.VPC) + } + condition := meta.FindStatusCondition(vpc.Status.Conditions, cloudv1alpha1.ConditionTypeReady) + if condition == nil || condition.Status != metav1.ConditionFalse || + condition.Reason != "AwaitingFabricIdentity" { + t.Fatalf("the wait should be visible on the VPC, got %+v", condition) + } +} + +// The identity landing is what ends the wait, and the identifier that follows +// is the derived one rather than a random draw. +func TestVPCTakesTheIdentityOnceItArrives(t *testing.T) { + fixture := newVPCFixture(t) + + fixture.reconcile() + if got := fixture.vpc().Status.VPC; got != "" { + t.Fatalf("no identifier should be written while waiting, got %q", got) + } + + if err := fixture.client.Create(fixture.ctx, fabricIdentityObject()); err != nil { + t.Fatalf("publish the identity: %v", err) + } + fixture.reconcile() + + if got := fixture.vpc().Status.VPC; got != vpcTestIdentifier { + t.Fatalf("VPC identifier: got %q, want %q", got, vpcTestIdentifier) + } +} + +// Renumbering a live VPC would rename its VRF device and change its Route +// Target under running traffic, so an identifier already written stays written +// even when it disagrees with the identity that later arrived. +func TestVPCKeepsAnIdentifierItAlreadyHas(t *testing.T) { + existing := &cloudv1alpha1.VPC{} + existing.Namespace = vpcTestNamespace + existing.Name = vpcTestNetwork + "-" + vpcTestLocation + existing.Status.VPC = "R2POk4jT" + + fixture := newVPCFixture(t, existing, fabricIdentityObject()) + + fixture.reconcile() + + if got := fixture.vpc().Status.VPC; got != "R2POk4jT" { + t.Fatalf("an allocated VPC must keep its identifier, got %q", got) + } +} + +// An identity reaching the cell has to wake the presences waiting on it, or a +// VPC would sit out its poll interval for a value already there. +func TestFabricIdentityWakesTheNetworksPresences(t *testing.T) { + fixture := newVPCFixture(t) + + requests := fixture.reconciler.contextsForFabricIdentity(fixture.ctx, fabricIdentityObject()) + + if len(requests) != 1 || requests[0].Name != vpcTestNetwork+"-"+vpcTestLocation { + t.Fatalf("the network's presence should be enqueued, got %v", requests) + } + + other := fabricIdentityObject() + other.Name = "unrelated" + if requests := fixture.reconciler.contextsForFabricIdentity(fixture.ctx, other); len(requests) != 0 { + t.Fatalf("another network's identity should enqueue nothing, got %v", requests) + } +} diff --git a/internal/galactic/controller/networkinterface_controller.go b/internal/galactic/controller/networkinterface_controller.go new file mode 100644 index 00000000..416c4f2f --- /dev/null +++ b/internal/galactic/controller/networkinterface_controller.go @@ -0,0 +1,459 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package controller + +import ( + "context" + "fmt" + "time" + + nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + + cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1" + networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha" + "go.datum.net/network-services-operator/internal/galactic/galactic" + "go.datum.net/network-services-operator/internal/galactic/identifier" +) + +const ( + // LabelVPC records the base62 VPC identifier a NAD attaches to. + LabelVPC = "galactic.datumapis.com/vpc" + + // LabelVPCAttachment records the base62 attachment identifier a NAD holds. + // The NAD is the allocation record for that identifier. + LabelVPCAttachment = "galactic.datumapis.com/vpc-attachment" + legacyLabelVPC = "cloud.datumapis.com/vpc" + legacyLabelVPCAttachment = "cloud.datumapis.com/vpc-attachment" + legacyAttachmentKind = "VPCAttachment" + reasonAPIGroupMigration = "APIGroupMigration" + + // AnnotationHostInterface records the host device this attachment will get, + // on the NAD, at the moment the NAD is written. The name is derived from the + // VPC and the attachment identifier, so it is known here — well before the + // CNI ADD that creates the device. A runtime that must name the device when + // it asks for an interface, rather than learn it from the ADD result, has + // nowhere else to read it in time. galactic writes the same key during ADD + // from the same inputs, so the two always agree. + AnnotationHostInterface = "k8s.v1.cni.cncf.io/host-interface" + + // ConditionTypePrepared reports that the data plane's pre-Pod artifacts exist. + // Unlike Programmed, which only becomes true at CNI ADD, it is safe to gate + // Pod creation on. network-services-operator is adding the type in parallel. + ConditionTypePrepared = "Prepared" +) + +// maxIdentifierAttempts bounds the retry loop that draws an unused identifier. +const maxIdentifierAttempts = 100 + +// NetworkInterfaceReconciler realizes a fulfilled NetworkInterface claim on the +// galactic data plane. +// +// It creates the VPCAttachment and the NetworkAttachmentDefinition in one pass, +// so it already holds every render input and never has to look sideways. Both +// objects are per-interface, which keeps the attachment identifier and the tap +// device name stable across instance replacement, and it publishes the +// annotations a workload must carry so no infrastructure provider has to know +// what a NAD is. +type NetworkInterfaceReconciler struct { + client.Client + Scheme *runtime.Scheme + + // APIReader bypasses the cache when listing allocated identifiers, so a NAD + // written moments ago cannot be missed and its identifier reissued. + APIReader client.Reader + + // AttachmentMode is how guests in this cell consume an interface. It is + // required configuration standing in for a capability class that does not + // exist yet, so a cell states what it is rather than defaulting. It covers + // every interface that states no mode of its own. + AttachmentMode cloudv1alpha1.VPCAttachmentInterfaceMode +} + +// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkinterfaces,verbs=get;list;watch +// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkinterfaces/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkinterfaceclaims,verbs=get;list;watch +// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkinterfaceclaims/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcs,verbs=get;list;watch +// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcattachments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcattachments/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch;create;update;patch;delete + +func (r *NetworkInterfaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var networkInterface networkingv1alpha.NetworkInterface + if err := r.Get(ctx, req.NamespacedName, &networkInterface); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + if !networkInterface.DeletionTimestamp.IsZero() { + return ctrl.Result{}, nil + } + if !claimFulfilled(&networkInterface) { + return ctrl.Result{}, nil + } + + var vpc cloudv1alpha1.VPC + vpcKey := types.NamespacedName{ + Namespace: networkInterface.Namespace, + Name: networkInterface.Status.NetworkContextRef.Name, + } + if err := r.Get(ctx, vpcKey, &vpc); err != nil { + if apierrors.IsNotFound(err) { + return ctrl.Result{RequeueAfter: 10 * time.Second}, r.markPrepared(ctx, &networkInterface, + metav1.ConditionFalse, "AwaitingVPC", fmt.Sprintf("VPC %s does not exist yet", vpcKey.Name)) + } + return ctrl.Result{}, fmt.Errorf("get VPC %s: %w", vpcKey, err) + } + if vpc.Status.VPC == "" { + return ctrl.Result{RequeueAfter: 10 * time.Second}, r.markPrepared(ctx, &networkInterface, + metav1.ConditionFalse, "AwaitingVPCIdentifier", + fmt.Sprintf("VPC %s has no identifier yet", vpc.Name)) + } + + attachment, err := r.reconcileAttachment(ctx, &networkInterface, &vpc) + if err != nil { + return ctrl.Result{}, err + } + nad, err := r.reconcileNAD(ctx, attachment, &vpc, &networkInterface) + if err != nil { + return ctrl.Result{}, err + } + if err := r.publishAttachmentStatus(ctx, attachment, &vpc, nad); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, r.publishToInterface(ctx, &networkInterface, attachment, &vpc) +} + +// claimFulfilled reports whether an interface is bound to a claim and holds +// every address it must carry. Nothing can be rendered before that. +func claimFulfilled(networkInterface *networkingv1alpha.NetworkInterface) bool { + if networkInterface.Status.Phase != networkingv1alpha.NetworkInterfacePhaseBound { + return false + } + if networkInterface.Status.NetworkContextRef == nil { + return false + } + return meta.IsStatusConditionTrue(networkInterface.Status.Conditions, + networkingv1alpha.NetworkInterfaceAllocated) +} + +// reconcileAttachment creates the VPCAttachment for an interface. The controller +// owns this object, not the infrastructure provider: it is the only component +// that speaks both the workload vocabulary and the data plane's. +func (r *NetworkInterfaceReconciler) reconcileAttachment( + ctx context.Context, networkInterface *networkingv1alpha.NetworkInterface, vpc *cloudv1alpha1.VPC, +) (*cloudv1alpha1.VPCAttachment, error) { + attachment := &cloudv1alpha1.VPCAttachment{ + ObjectMeta: metav1.ObjectMeta{Name: networkInterface.Name, Namespace: networkInterface.Namespace}, + } + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, attachment, func() error { + attachment.Spec.VPC = cloudv1alpha1.VPCRef{Name: vpc.Name} + attachment.Spec.InterfaceRef = &cloudv1alpha1.NetworkInterfaceRef{Name: networkInterface.Name} + attachment.Spec.Interface.Name = networkInterface.Spec.InterfaceName + attachment.Spec.Interface.Mode = r.attachmentMode(networkInterface) + attachment.Spec.Interface.Addresses = interfaceAddresses(networkInterface) + return controllerutil.SetControllerReference(networkInterface, attachment, r.Scheme) + }); err != nil { + return nil, fmt.Errorf("reconcile VPC attachment %s: %w", attachment.Name, err) + } + return attachment, nil +} + +// reconcileNAD creates or updates the NAD the attachment owns. +func (r *NetworkInterfaceReconciler) reconcileNAD( + ctx context.Context, + attachment *cloudv1alpha1.VPCAttachment, + vpc *cloudv1alpha1.VPC, + networkInterface *networkingv1alpha.NetworkInterface, +) (*nadv1.NetworkAttachmentDefinition, error) { + nad := &nadv1.NetworkAttachmentDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: attachment.Name, Namespace: attachment.Namespace}, + } + + attachmentID := "" + if err := r.Get(ctx, client.ObjectKeyFromObject(nad), nad); err == nil { + attachmentID = nad.Labels[LabelVPCAttachment] + if attachmentID == "" { + attachmentID = nad.Labels[legacyLabelVPCAttachment] + } + } else if !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("get NetworkAttachmentDefinition %s: %w", nad.Name, err) + } + if attachmentID == "" { + allocated, err := r.allocateAttachmentIdentifier(ctx, vpc.Status.VPC) + if err != nil { + return nil, err + } + attachmentID = allocated + } + + addresses := make([]galactic.Address, 0, len(networkInterface.Spec.Addresses)) + for _, address := range networkInterface.Spec.Addresses { + addresses = append(addresses, galactic.Address{ + Address: address.Address, + Gateway: address.Gateway, + }) + } + config, err := galactic.ConflistJSON(attachment.Name, masterPlugin(attachment.Spec.Interface.Mode), + vpc.Status.VPC, attachmentID, networkInterface.Spec.MTU, addresses, + declaresDevice(attachment.Spec.Interface.Mode)) + if err != nil { + return nil, err + } + + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, nad, func() error { + if nad.Labels == nil { + nad.Labels = map[string]string{} + } + nad.Labels[LabelVPC] = vpc.Status.VPC + nad.Labels[LabelVPCAttachment] = attachmentID + if nad.Annotations == nil { + nad.Annotations = map[string]string{} + } + nad.Annotations[AnnotationHostInterface] = galactic.HostInterfaceName(vpc.Status.VPC, attachmentID) + nad.Spec.Config = config + // The former cloud-group attachment owned this NAD. Its controller + // must be stopped before rollout; transfer ownership in the same write + // as the group/label update while retaining its stable attachment ID. + refs := nad.OwnerReferences[:0] + for _, ref := range nad.OwnerReferences { + if ref.APIVersion == "cloud.datumapis.com/v1alpha1" && ref.Kind == legacyAttachmentKind && ref.Name == attachment.Name { + continue + } + refs = append(refs, ref) + } + nad.OwnerReferences = refs + return controllerutil.SetControllerReference(attachment, nad, r.Scheme) + }); err != nil { + return nil, fmt.Errorf("reconcile NetworkAttachmentDefinition %s: %w", nad.Name, err) + } + return nad, nil +} + +// attachmentMode resolves how one guest consumes its interface. An interface +// that states a mode carries the workload's own requirement, so it wins. The +// cell-wide mode covers everything else, which is every interface written +// before a workload could state one. +func (r *NetworkInterfaceReconciler) attachmentMode( + networkInterface *networkingv1alpha.NetworkInterface, +) cloudv1alpha1.VPCAttachmentInterfaceMode { + switch networkInterface.Spec.AttachmentMode { + case networkingv1alpha.NetworkInterfaceAttachmentModeNetns: + return cloudv1alpha1.VPCAttachmentInterfaceModeNetns + case networkingv1alpha.NetworkInterfaceAttachmentModeHypervisor: + return cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor + case networkingv1alpha.NetworkInterfaceAttachmentModeHypervisorDeclared: + return cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared + default: + return r.AttachmentMode + } +} + +// masterPlugin translates how a guest consumes an interface into the galactic +// binary that realizes it. This is the only place the two vocabularies meet. +func masterPlugin(mode cloudv1alpha1.VPCAttachmentInterfaceMode) string { + switch mode { + case cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor, + cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared: + return galactic.PluginTap + default: + return galactic.PluginVeth + } +} + +// declaresDevice reports whether the tap plugin describes the device to the +// hypervisor instead of leaving the hypervisor to discover it. +func declaresDevice(mode cloudv1alpha1.VPCAttachmentInterfaceMode) bool { + return mode == cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared +} + +// interfaceAddresses copies the addresses NSO allocated onto the attachment, so +// the attachment describes itself without a second read. +func interfaceAddresses(networkInterface *networkingv1alpha.NetworkInterface) []cloudv1alpha1.IPAddress { + addresses := make([]cloudv1alpha1.IPAddress, 0, len(networkInterface.Spec.Addresses)) + for _, address := range networkInterface.Spec.Addresses { + addresses = append(addresses, cloudv1alpha1.IPAddress(address.Address)) + } + return addresses +} + +// allocateAttachmentIdentifier draws a random identifier unused within the VPC. +// Random rather than lowest-free, so a freed identifier is not immediately +// reissued while its BGPAdvertisement is still being garbage collected. +func (r *NetworkInterfaceReconciler) allocateAttachmentIdentifier(ctx context.Context, vpc string) (string, error) { + used := make(map[string]struct{}) + for _, label := range []string{LabelVPC, legacyLabelVPC} { + var nads nadv1.NetworkAttachmentDefinitionList + if err := r.APIReader.List(ctx, &nads, client.MatchingLabels{label: vpc}); err != nil { + return "", fmt.Errorf("list NetworkAttachmentDefinitions for VPC %s: %w", vpc, err) + } + for _, nad := range nads.Items { + for _, key := range []string{LabelVPCAttachment, legacyLabelVPCAttachment} { + if id := nad.Labels[key]; id != "" { + used[id] = struct{}{} + } + } + } + } + + for range maxIdentifierAttempts { + candidate, err := identifier.RandomVPCAttachmentBase62() + if err != nil { + return "", err + } + if _, taken := used[candidate]; !taken { + return candidate, nil + } + } + return "", fmt.Errorf("no unused attachment identifier found in VPC %s after %d attempts", + vpc, maxIdentifierAttempts) +} + +// publishAttachmentStatus records the allocated identifiers on the attachment. +func (r *NetworkInterfaceReconciler) publishAttachmentStatus( + ctx context.Context, + attachment *cloudv1alpha1.VPCAttachment, + vpc *cloudv1alpha1.VPC, + nad *nadv1.NetworkAttachmentDefinition, +) error { + attachment.Status.VPC = vpc.Status.VPC + attachment.Status.VPCAttachment = nad.Labels[LabelVPCAttachment] + attachment.Status.NetworkAttachmentDefinition = nad.Name + attachment.Status.ObservedGeneration = attachment.Generation + meta.SetStatusCondition(&attachment.Status.Conditions, metav1.Condition{ + Type: cloudv1alpha1.ConditionTypeReady, + Status: metav1.ConditionTrue, + Reason: "AttachmentDefinitionReady", + Message: fmt.Sprintf("NetworkAttachmentDefinition %s is ready for use", nad.Name), + ObservedGeneration: attachment.Generation, + }) + if err := r.Status().Update(ctx, attachment); err != nil { + return fmt.Errorf("update VPC attachment %s status: %w", client.ObjectKeyFromObject(attachment), err) + } + return nil +} + +// publishToInterface records what realizes the interface and which VPC it landed +// in, then reports Prepared so compute can release the Pod. +func (r *NetworkInterfaceReconciler) publishToInterface( + ctx context.Context, + networkInterface *networkingv1alpha.NetworkInterface, + attachment *cloudv1alpha1.VPCAttachment, + vpc *cloudv1alpha1.VPC, +) error { + ref := &networkingv1alpha.NetworkInterfaceAttachmentRef{ + APIGroup: cloudv1alpha1.GroupVersion.Group, + Kind: legacyAttachmentKind, + Name: attachment.Name, + } + if old := networkInterface.Status.AttachmentRef; old != nil && old.APIGroup != ref.APIGroup { + // NSO preserves the provider-owned Programmed condition. A condition + // earned by the former API group cannot certify this new attachment; + // the new BGPAdvertisement reconciler will report it again. + meta.SetStatusCondition(&networkInterface.Status.Conditions, metav1.Condition{ + Type: networkingv1alpha.NetworkInterfaceProgrammed, + Status: metav1.ConditionUnknown, + Reason: reasonAPIGroupMigration, + Message: "waiting for the Galactic attachment to be programmed", + ObservedGeneration: networkInterface.Generation, + }) + } + networkInterface.Status.AttachmentRef = ref + networkInterface.Status.VPC = vpc.Status.VPC + return r.markPrepared(ctx, networkInterface, metav1.ConditionTrue, "AttachmentReady", + fmt.Sprintf("VPCAttachment %s and its attachment definition exist", attachment.Name)) +} + +// markPrepared reports whether the pre-Pod artifacts exist, on the interface and +// on the claim holding it, which is what compute gates Pod creation on. +func (r *NetworkInterfaceReconciler) markPrepared( + ctx context.Context, + networkInterface *networkingv1alpha.NetworkInterface, + status metav1.ConditionStatus, + reason, message string, +) error { + condition := metav1.Condition{ + Type: ConditionTypePrepared, + Status: status, + Reason: reason, + Message: message, + ObservedGeneration: networkInterface.Generation, + } + meta.SetStatusCondition(&networkInterface.Status.Conditions, condition) + if err := r.Status().Update(ctx, networkInterface); err != nil { + return fmt.Errorf("update network interface %s status: %w", + client.ObjectKeyFromObject(networkInterface), err) + } + + if networkInterface.Spec.ClaimRef == nil { + return nil + } + var claim networkingv1alpha.NetworkInterfaceClaim + key := types.NamespacedName{Namespace: networkInterface.Namespace, Name: networkInterface.Spec.ClaimRef.Name} + if err := r.Get(ctx, key, &claim); err != nil { + return client.IgnoreNotFound(err) + } + claimCondition := condition + claimCondition.ObservedGeneration = claim.Generation + meta.SetStatusCondition(&claim.Status.Conditions, claimCondition) + // A claim can have Programmed and Ready from the old attachment even after + // its interface points at Galactic. Invalidate both until the new group's + // advertisement reports that the replacement attachment is carrying traffic. + if programmed := meta.FindStatusCondition(networkInterface.Status.Conditions, + networkingv1alpha.NetworkInterfaceProgrammed); programmed != nil && + programmed.Status == metav1.ConditionUnknown && programmed.Reason == reasonAPIGroupMigration { + claimProgrammed := *programmed + claimProgrammed.Type = networkingv1alpha.NetworkInterfaceClaimProgrammed + claimProgrammed.ObservedGeneration = claim.Generation + meta.SetStatusCondition(&claim.Status.Conditions, claimProgrammed) + meta.SetStatusCondition(&claim.Status.Conditions, metav1.Condition{ + Type: networkingv1alpha.NetworkInterfaceClaimReady, + Status: metav1.ConditionUnknown, + Reason: "NotProgrammed", + Message: "waiting for the Galactic attachment to be programmed", + ObservedGeneration: claim.Generation, + }) + } + if err := r.Status().Update(ctx, &claim); err != nil { + return fmt.Errorf("update network interface claim %s status: %w", key, err) + } + return nil +} + +// SetupWithManager registers the reconciler with the manager. +func (r *NetworkInterfaceReconciler) SetupWithManager(mgr ctrl.Manager) error { + // A NAD is owned by the attachment rather than the interface, but all three + // share a name and namespace, so mapping one back is an identity. + nadToInterface := func(_ context.Context, obj client.Object) []ctrl.Request { + return []ctrl.Request{{NamespacedName: client.ObjectKeyFromObject(obj)}} + } + + return ctrl.NewControllerManagedBy(mgr). + For(&networkingv1alpha.NetworkInterface{}). + Owns(&cloudv1alpha1.VPCAttachment{}). + Watches(&nadv1.NetworkAttachmentDefinition{}, handler.EnqueueRequestsFromMapFunc(nadToInterface)). + Named("networkinterface"). + Complete(r) +} diff --git a/internal/galactic/controller/networkinterface_controller_test.go b/internal/galactic/controller/networkinterface_controller_test.go new file mode 100644 index 00000000..c8d35113 --- /dev/null +++ b/internal/galactic/controller/networkinterface_controller_test.go @@ -0,0 +1,235 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package controller + +import ( + "context" + "testing" + + nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1" + networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha" + "go.datum.net/network-services-operator/internal/galactic/galactic" +) + +func TestReconcileNADTakesOverLegacyAttachment(t *testing.T) { + scheme := runtime.NewScheme() + if err := cloudv1alpha1.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + if err := nadv1.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + controller := true + old := &nadv1.NetworkAttachmentDefinition{ObjectMeta: metav1.ObjectMeta{ + Name: "nic", Namespace: "tenant", + Labels: map[string]string{legacyLabelVPC: "1", legacyLabelVPCAttachment: "2"}, + OwnerReferences: []metav1.OwnerReference{{APIVersion: "cloud.datumapis.com/v1alpha1", Kind: "VPCAttachment", Name: "nic", UID: types.UID("old"), Controller: &controller}}, + }} + cl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(old).Build() + r := &NetworkInterfaceReconciler{Client: cl, APIReader: cl, Scheme: scheme} + attachment := &cloudv1alpha1.VPCAttachment{ObjectMeta: metav1.ObjectMeta{Name: "nic", Namespace: "tenant", UID: types.UID("new")}} + attachment.Spec.Interface.Mode = cloudv1alpha1.VPCAttachmentInterfaceModeNetns + vpc := &cloudv1alpha1.VPC{} + vpc.Status.VPC = "1" + iface := &networkingv1alpha.NetworkInterface{} + iface.Spec.MTU = 1500 + got, err := r.reconcileNAD(context.Background(), attachment, vpc, iface) + if err != nil { + t.Fatal(err) + } + if got.Labels[LabelVPCAttachment] != "2" { + t.Fatalf("attachment ID changed: %q", got.Labels[LabelVPCAttachment]) + } + if len(got.OwnerReferences) != 1 || got.OwnerReferences[0].APIVersion != cloudv1alpha1.GroupVersion.String() || got.OwnerReferences[0].UID != attachment.UID { + t.Fatalf("owner references = %#v", got.OwnerReferences) + } +} + +func TestPublishToInterfaceReprogramsNSOAttachmentGroup(t *testing.T) { + scheme := runtime.NewScheme() + if err := networkingv1alpha.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + if err := cloudv1alpha1.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + iface := &networkingv1alpha.NetworkInterface{ObjectMeta: metav1.ObjectMeta{Name: "nic", Namespace: "tenant"}} + iface.Spec.ClaimRef = &networkingv1alpha.NetworkInterfaceClaimRef{Name: "claim"} + claim := &networkingv1alpha.NetworkInterfaceClaim{ObjectMeta: metav1.ObjectMeta{Name: "claim", Namespace: "tenant"}} + claim.Status.Conditions = []metav1.Condition{ + {Type: networkingv1alpha.NetworkInterfaceClaimProgrammed, Status: metav1.ConditionTrue, Reason: "OldGroupProgrammed", LastTransitionTime: metav1.Now()}, + {Type: networkingv1alpha.NetworkInterfaceClaimReady, Status: metav1.ConditionTrue, Reason: "OldGroupReady", LastTransitionTime: metav1.Now()}, + } + iface.Status.AttachmentRef = &networkingv1alpha.NetworkInterfaceAttachmentRef{ + APIGroup: "cloud.datumapis.com", Kind: "VPCAttachment", Name: "nic", + } + iface.Status.Conditions = []metav1.Condition{{ + Type: networkingv1alpha.NetworkInterfaceProgrammed, + Status: metav1.ConditionTrue, Reason: "OldGroupProgrammed", LastTransitionTime: metav1.Now(), + }} + cl := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(iface, claim).WithObjects(iface, claim).Build() + r := &NetworkInterfaceReconciler{Client: cl} + attachment := &cloudv1alpha1.VPCAttachment{ObjectMeta: metav1.ObjectMeta{Name: "nic", Namespace: "tenant"}} + vpc := &cloudv1alpha1.VPC{} + vpc.Status.VPC = "vpc-1" + if err := r.publishToInterface(context.Background(), iface, attachment, vpc); err != nil { + t.Fatal(err) + } + var got networkingv1alpha.NetworkInterface + if err := cl.Get(context.Background(), client.ObjectKeyFromObject(iface), &got); err != nil { + t.Fatal(err) + } + if got.Status.AttachmentRef == nil || got.Status.AttachmentRef.APIGroup != "galactic.datumapis.com" { + t.Fatalf("attachment reference = %#v, want Galactic API group", got.Status.AttachmentRef) + } + condition := apimeta.FindStatusCondition(got.Status.Conditions, networkingv1alpha.NetworkInterfaceProgrammed) + if condition == nil || condition.Status != metav1.ConditionUnknown || condition.Reason != "APIGroupMigration" { + t.Fatalf("Programmed condition = %#v, want Unknown/APIGroupMigration", condition) + } + var gotClaim networkingv1alpha.NetworkInterfaceClaim + if err := cl.Get(context.Background(), client.ObjectKeyFromObject(claim), &gotClaim); err != nil { + t.Fatal(err) + } + for _, conditionType := range []string{networkingv1alpha.NetworkInterfaceClaimProgrammed, networkingv1alpha.NetworkInterfaceClaimReady} { + condition := apimeta.FindStatusCondition(gotClaim.Status.Conditions, conditionType) + if condition == nil || condition.Status != metav1.ConditionUnknown { + t.Fatalf("claim %s condition = %#v, want Unknown during migration", conditionType, condition) + } + } +} + +func TestMasterPlugin(t *testing.T) { + tests := []struct { + mode cloudv1alpha1.VPCAttachmentInterfaceMode + want string + }{ + {cloudv1alpha1.VPCAttachmentInterfaceModeNetns, galactic.PluginVeth}, + {cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor, galactic.PluginTap}, + {cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared, galactic.PluginTap}, + } + for _, test := range tests { + t.Run(string(test.mode), func(t *testing.T) { + if got := masterPlugin(test.mode); got != test.want { + t.Errorf("got %q, want %q", got, test.want) + } + }) + } +} + +func TestClaimFulfilled(t *testing.T) { + newInterface := func(phase networkingv1alpha.NetworkInterfacePhase, allocated metav1.ConditionStatus, + contextRef *networkingv1alpha.LocalNetworkContextRef) *networkingv1alpha.NetworkInterface { + return &networkingv1alpha.NetworkInterface{ + Status: networkingv1alpha.NetworkInterfaceStatus{ + Phase: phase, + NetworkContextRef: contextRef, + Conditions: []metav1.Condition{{ + Type: networkingv1alpha.NetworkInterfaceAllocated, + Status: allocated, + Reason: "Test", + }}, + }, + } + } + contextRef := &networkingv1alpha.LocalNetworkContextRef{Name: "default-us-central-1"} + + tests := []struct { + name string + networkInterface *networkingv1alpha.NetworkInterface + want bool + }{ + {"bound and allocated", newInterface( + networkingv1alpha.NetworkInterfacePhaseBound, metav1.ConditionTrue, contextRef), true}, + {"available", newInterface( + networkingv1alpha.NetworkInterfacePhaseAvailable, metav1.ConditionTrue, contextRef), false}, + {"not allocated", newInterface( + networkingv1alpha.NetworkInterfacePhaseBound, metav1.ConditionFalse, contextRef), false}, + {"no network context", newInterface( + networkingv1alpha.NetworkInterfacePhaseBound, metav1.ConditionTrue, nil), false}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got := claimFulfilled(test.networkInterface); got != test.want { + t.Errorf("got %v, want %v", got, test.want) + } + }) + } +} + +// The cell-wide mode is the only signal every attachment written until now +// carries, so it has to keep deciding for all of them. An interface that +// states its own mode is the exception. +func TestAttachmentModeFallsBackToTheCell(t *testing.T) { + r := &NetworkInterfaceReconciler{ + AttachmentMode: cloudv1alpha1.VPCAttachmentInterfaceModeNetns, + } + + tests := []struct { + name string + iface networkingv1alpha.NetworkInterfaceAttachmentMode + want cloudv1alpha1.VPCAttachmentInterfaceMode + }{ + {"unset", "", cloudv1alpha1.VPCAttachmentInterfaceModeNetns}, + {"netns", networkingv1alpha.NetworkInterfaceAttachmentModeNetns, + cloudv1alpha1.VPCAttachmentInterfaceModeNetns}, + {"hypervisor", networkingv1alpha.NetworkInterfaceAttachmentModeHypervisor, + cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor}, + {"declared", networkingv1alpha.NetworkInterfaceAttachmentModeHypervisorDeclared, + cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + networkInterface := &networkingv1alpha.NetworkInterface{ + Spec: networkingv1alpha.NetworkInterfaceSpec{AttachmentMode: test.iface}, + } + if got := r.attachmentMode(networkInterface); got != test.want { + t.Errorf("got %q, want %q", got, test.want) + } + }) + } +} + +// Only the declared mode asks the tap plugin to describe the device. +// TestHostInterfaceAnnotationKey pins the annotation key this controller writes +// on the NAD. The key is a contract with the data plane: galactic writes the +// same one during CNI ADD, and a runtime reads it to learn its host device. +// Renaming it here alone would leave that runtime with nothing to read, and +// nothing else in this repository would fail. +func TestHostInterfaceAnnotationKey(t *testing.T) { + const want = "k8s.v1.cni.cncf.io/host-interface" + if AnnotationHostInterface != want { + t.Errorf("AnnotationHostInterface = %q, want %q", AnnotationHostInterface, want) + } +} + +func TestDeclaresDevice(t *testing.T) { + if declaresDevice(cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor) { + t.Error("a discovered hypervisor attachment must not ask for a description") + } + if !declaresDevice(cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared) { + t.Error("a declared hypervisor attachment must ask for a description") + } +} diff --git a/internal/galactic/controller/subnet_controller.go b/internal/galactic/controller/subnet_controller.go new file mode 100644 index 00000000..46c735dd --- /dev/null +++ b/internal/galactic/controller/subnet_controller.go @@ -0,0 +1,180 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package controller + +import ( + "context" + "fmt" + "slices" + "strconv" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + + cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1" + networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha" +) + +// upstreamGenerationAnnotation names the generation of the project's subnet +// that the copy propagated here was taken from. The operator that owns the +// subnet stamps it on the copy, and reads a report back only for the +// generation it stamped. The key is the operator's; it is spelled out here +// because the operator version this module pins does not export it yet. +const upstreamGenerationAnnotation = "networking.datumapis.com/upstream-generation" + +// SubnetReconciler reports whether a location's address range is in place on +// this cell: the VPC built for the subnet's network context carries the range +// and is ready. It closes the Programmed condition the networking operator +// leaves for the data plane, on the copy of the Subnet propagated to this cell, +// and the federation carries the report back to the project. +// +// The range is in place when traffic can be routed in it. A VPC carries the +// range the moment it is built, because its address space is fixed at +// creation; what it waits for is the identity the fabric routes it by. +type SubnetReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=networking.datumapis.com,resources=subnets,verbs=get;list;watch +// +kubebuilder:rbac:groups=networking.datumapis.com,resources=subnets/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcs,verbs=get;list;watch + +func (r *SubnetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var subnet networkingv1alpha.Subnet + if err := r.Get(ctx, req.NamespacedName, &subnet); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + if !subnet.DeletionTimestamp.IsZero() { + return ctrl.Result{}, nil + } + + var vpc *cloudv1alpha1.VPC + if subnet.Spec.NetworkContext.Name != "" { + vpc = &cloudv1alpha1.VPC{} + key := client.ObjectKey{Namespace: subnet.Namespace, Name: subnet.Spec.NetworkContext.Name} + if err := r.Get(ctx, key, vpc); err != nil { + if !apierrors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("read the VPC for subnet %s: %w", req.NamespacedName, err) + } + vpc = nil + } + } + + condition := subnetProgrammedCondition(&subnet, vpc) + condition.ObservedGeneration = reportedGeneration(&subnet) + if !meta.SetStatusCondition(&subnet.Status.Conditions, condition) { + return ctrl.Result{}, nil + } + if err := r.Status().Update(ctx, &subnet); err != nil { + return ctrl.Result{}, fmt.Errorf("update subnet %s status: %w", req.NamespacedName, err) + } + return ctrl.Result{}, nil +} + +// subnetProgrammedCondition says whether the subnet's range is in place on this +// cell, given the VPC built for its network context, or nil for none yet. +func subnetProgrammedCondition(subnet *networkingv1alpha.Subnet, vpc *cloudv1alpha1.VPC) metav1.Condition { + condition := metav1.Condition{ + Type: networkingv1alpha.SubnetProgrammed, + Status: metav1.ConditionFalse, + } + + start, prefixLength, ok := subnetRange(subnet) + if !ok { + condition.Reason = networkingv1alpha.SubnetProgrammedReasonNotProgrammed + condition.Message = "the subnet states no range to put in place" + return condition + } + network := cloudv1alpha1.Network(fmt.Sprintf("%s/%d", start, prefixLength)) + + if vpc == nil { + condition.Reason = networkingv1alpha.SubnetProgrammedReasonProgrammingInProgress + condition.Message = fmt.Sprintf("waiting for the VPC for network context %q to be built", subnet.Spec.NetworkContext.Name) + return condition + } + + // The address space is fixed when the VPC is built. A range that was not + // there then is not routed by it now, and no amount of waiting changes that. + if !slices.Contains(vpc.Spec.Networks, network) { + condition.Reason = networkingv1alpha.SubnetProgrammedReasonNotProgrammed + condition.Message = fmt.Sprintf("VPC %q was built without %s; its address space is fixed at creation", vpc.Name, network) + return condition + } + + ready := meta.FindStatusCondition(vpc.Status.Conditions, cloudv1alpha1.ConditionTypeReady) + if ready == nil || ready.Status != metav1.ConditionTrue || vpc.Status.VPC == "" { + condition.Reason = networkingv1alpha.SubnetProgrammedReasonProgrammingInProgress + condition.Message = fmt.Sprintf("VPC %q carries %s but is not ready yet", vpc.Name, network) + if ready != nil && ready.Message != "" { + condition.Message += ": " + ready.Message + } + return condition + } + + condition.Status = metav1.ConditionTrue + condition.Reason = networkingv1alpha.SubnetProgrammedReasonProgrammed + condition.Message = fmt.Sprintf("%s is routed by VPC %s", network, vpc.Status.VPC) + return condition +} + +// reportedGeneration is the generation the report is about. Each API server +// counts generation on its own, so the copy's own generation says nothing to +// the project; the report is made against the upstream generation stamped on +// the copy, and falls back to the copy's generation for one that carries none. +func reportedGeneration(subnet *networkingv1alpha.Subnet) int64 { + if generation, err := strconv.ParseInt(subnet.Annotations[upstreamGenerationAnnotation], 10, 64); err == nil { + return generation + } + return subnet.Generation +} + +// SetupWithManager registers the reconciler with the manager. +func (r *SubnetReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&networkingv1alpha.Subnet{}). + Watches(&cloudv1alpha1.VPC{}, handler.EnqueueRequestsFromMapFunc(r.subnetsForVPC)). + Named("subnet"). + Complete(r) +} + +// subnetsForVPC names the subnets a VPC was built for, so its becoming ready +// is reported on each of them. +func (r *SubnetReconciler) subnetsForVPC(ctx context.Context, obj client.Object) []ctrl.Request { + vpc, ok := obj.(*cloudv1alpha1.VPC) + if !ok { + return nil + } + var subnets networkingv1alpha.SubnetList + if err := r.List(ctx, &subnets, client.InNamespace(vpc.Namespace)); err != nil { + return nil + } + requests := make([]ctrl.Request, 0, len(subnets.Items)) + for i := range subnets.Items { + if subnets.Items[i].Spec.NetworkContext.Name != vpc.Name { + continue + } + requests = append(requests, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(&subnets.Items[i])}) + } + return requests +} diff --git a/internal/galactic/controller/subnet_controller_test.go b/internal/galactic/controller/subnet_controller_test.go new file mode 100644 index 00000000..384179d0 --- /dev/null +++ b/internal/galactic/controller/subnet_controller_test.go @@ -0,0 +1,163 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package controller + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1" + networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha" +) + +// propagatedSubnet is the copy of a project's subnet a cell is handed: spec +// only, with the upstream generation the operator stamped on it. +func propagatedSubnet(upstreamGeneration string) *networkingv1alpha.Subnet { + subnet := &networkingv1alpha.Subnet{} + subnet.Namespace = vpcTestNamespace + subnet.Name = vpcTestNetwork + "-" + vpcTestLocation + "-ipv6" + subnet.Generation = 7 + if upstreamGeneration != "" { + subnet.Annotations = map[string]string{upstreamGenerationAnnotation: upstreamGeneration} + } + subnet.Spec.NetworkContext = networkingv1alpha.LocalNetworkContextRef{Name: vpcTestNetwork + "-" + vpcTestLocation} + subnet.Spec.StartAddress = "fd00::" + subnet.Spec.PrefixLength = 48 + return subnet +} + +func vpcFor(networks []cloudv1alpha1.Network, ready bool) *cloudv1alpha1.VPC { + vpc := &cloudv1alpha1.VPC{} + vpc.Namespace = vpcTestNamespace + vpc.Name = vpcTestNetwork + "-" + vpcTestLocation + vpc.Spec.Networks = networks + if ready { + vpc.Status.VPC = vpcTestIdentifier + vpc.Status.Conditions = []metav1.Condition{{ + Type: cloudv1alpha1.ConditionTypeReady, Status: metav1.ConditionTrue, Reason: "IdentifierAllocated", + }} + } else { + vpc.Status.Conditions = []metav1.Condition{{ + Type: cloudv1alpha1.ConditionTypeReady, Status: metav1.ConditionFalse, Reason: "AwaitingFabricIdentity", + Message: "Waiting for the identity the fabric knows this network by", + }} + } + return vpc +} + +func reconcileSubnet(t *testing.T, objects ...client.Object) *networkingv1alpha.Subnet { + t.Helper() + + scheme := runtime.NewScheme() + if err := networkingv1alpha.AddToScheme(scheme); err != nil { + t.Fatalf("build the networking scheme: %v", err) + } + if err := cloudv1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("build the cloud scheme: %v", err) + } + cl := fake.NewClientBuilder().WithScheme(scheme). + WithStatusSubresource(&networkingv1alpha.Subnet{}). + WithObjects(objects...).Build() + + reconciler := &SubnetReconciler{Client: cl, Scheme: scheme} + key := types.NamespacedName{Namespace: vpcTestNamespace, Name: vpcTestNetwork + "-" + vpcTestLocation + "-ipv6"} + if _, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: key}); err != nil { + t.Fatalf("reconcile: %v", err) + } + + subnet := &networkingv1alpha.Subnet{} + if err := cl.Get(context.Background(), key, subnet); err != nil { + t.Fatalf("read the subnet: %v", err) + } + return subnet +} + +func programmedOf(t *testing.T, subnet *networkingv1alpha.Subnet) metav1.Condition { + t.Helper() + condition := meta.FindStatusCondition(subnet.Status.Conditions, networkingv1alpha.SubnetProgrammed) + if condition == nil { + t.Fatalf("the cell should have reported on the subnet, got %+v", subnet.Status.Conditions) + } + return *condition +} + +// The signal: a range is in place once the VPC built for its network context +// carries it and is routed by the fabric. The report is made against the +// generation the project knows, not the copy's own. +func TestSubnetIsProgrammedWhenAReadyVPCCarriesItsRange(t *testing.T) { + subnet := reconcileSubnet(t, propagatedSubnet("3"), vpcFor([]cloudv1alpha1.Network{"fd00::/48"}, true)) + + condition := programmedOf(t, subnet) + if condition.Status != metav1.ConditionTrue || condition.Reason != networkingv1alpha.SubnetProgrammedReasonProgrammed { + t.Fatalf("a ready VPC carrying the range should read as programmed, got %+v", condition) + } + if condition.ObservedGeneration != 3 { + t.Fatalf("the report is about the project's generation 3, got %d", condition.ObservedGeneration) + } +} + +// A VPC that carries the range but has no identity yet is on its way, and +// says so, rather than claiming the range is routed. +func TestSubnetWaitsForTheVPCToBecomeReady(t *testing.T) { + subnet := reconcileSubnet(t, propagatedSubnet("3"), vpcFor([]cloudv1alpha1.Network{"fd00::/48"}, false)) + + condition := programmedOf(t, subnet) + if condition.Status != metav1.ConditionFalse || condition.Reason != networkingv1alpha.SubnetProgrammedReasonProgrammingInProgress { + t.Fatalf("a VPC awaiting its identity should read as in progress, got %+v", condition) + } +} + +// No VPC yet is the same wait: the network context reconciler builds it once +// the subnet it reads from is there. +func TestSubnetWaitsForTheVPCToBeBuilt(t *testing.T) { + subnet := reconcileSubnet(t, propagatedSubnet("3")) + + condition := programmedOf(t, subnet) + if condition.Status != metav1.ConditionFalse || condition.Reason != networkingv1alpha.SubnetProgrammedReasonProgrammingInProgress { + t.Fatalf("a VPC not built yet should read as in progress, got %+v", condition) + } +} + +// A VPC's address space is fixed when it is built. A range it was built +// without is not routed by it, and that is a verdict, not a wait. +func TestSubnetIsNotProgrammedWhenTheVPCWasBuiltWithoutIt(t *testing.T) { + subnet := reconcileSubnet(t, propagatedSubnet("3"), vpcFor([]cloudv1alpha1.Network{"fd00:1::/48"}, true)) + + condition := programmedOf(t, subnet) + if condition.Status != metav1.ConditionFalse || condition.Reason != networkingv1alpha.SubnetProgrammedReasonNotProgrammed { + t.Fatalf("a range the VPC was built without should read as not programmed, got %+v", condition) + } +} + +// A copy with no upstream generation stamped on it reports against its own, +// so a project running an older operator still sees a report. +func TestSubnetReportFallsBackToTheCopysOwnGeneration(t *testing.T) { + subnet := reconcileSubnet(t, propagatedSubnet(""), vpcFor([]cloudv1alpha1.Network{"fd00::/48"}, true)) + + if got := programmedOf(t, subnet).ObservedGeneration; got != 7 { + t.Fatalf("an unstamped copy reports against its own generation 7, got %d", got) + } +} diff --git a/internal/galactic/galactic/galactic.go b/internal/galactic/galactic/galactic.go new file mode 100644 index 00000000..9865ce51 --- /dev/null +++ b/internal/galactic/galactic/galactic.go @@ -0,0 +1,214 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +// Package galactic is the vocabulary this operator shares with the galactic +// data plane: the CNI conflist a NetworkAttachmentDefinition carries, the +// kernel interface names derived from a (vpc, attachment) pair, and the +// BGPAdvertisement annotations galactic publishes back. +package galactic + +import ( + "encoding/json" + "fmt" + "strings" + + "go.datum.net/network-services-operator/internal/galactic/identifier" +) + +const ( + // CNIVersion is the only conflist version galactic-bgp reconstructs a + // prevResult from; "1.1.0" also works, anything older fails every ADD. + CNIVersion = "1.0.0" + + // PluginVeth is the master plugin for container attachments. + PluginVeth = "galactic-veth" + + // PluginTap is the master plugin for virtual machine guests. + PluginTap = "galactic-tap" + + // PluginBGP publishes the attachment into BGP and SRv6. + PluginBGP = "galactic-bgp" + + // PluginIPAM is the delegated IPAM binary. + PluginIPAM = "galactic-ipam" + + // SystemNamespace holds the BGP CRDs the chain reads and writes. + SystemNamespace = "galactic-system" +) + +const ( + // AnnotationNetNS is the BGPAdvertisement annotation key prefix carrying a + // container's netns path. + AnnotationNetNS = "galactic.datum.net/netns" + + // AnnotationAllocatedSubnetIPv6 is the annotation key prefix carrying a + // container's allocated IPv6 pod subnet. + AnnotationAllocatedSubnetIPv6 = "galactic.datum.net/allocated-subnet-ipv6" + + // AnnotationAllocatedSubnetIPv4 is the annotation key prefix carrying a + // container's allocated IPv4 pod address. + AnnotationAllocatedSubnetIPv4 = "galactic.datum.net/allocated-subnet-ipv4" + + // AnnotationNoAddressing marks an advertisement whose guest manages its own + // addressing, so an empty prefix list is intentional. + AnnotationNoAddressing = "galactic.datum.net/no-addressing" + + // ConditionAdvertised is set on a BGPAdvertisement from live GoBGP state. + ConditionAdvertised = "Advertised" +) + +// NetConfList is a CNI conflist, the payload of a NAD's spec.config. +type NetConfList struct { + CNIVersion string `json:"cniVersion"` + Name string `json:"name"` + Plugins []any `json:"plugins"` +} + +// MasterPlugin is the galactic-veth or galactic-tap stanza. +type MasterPlugin struct { + Type string `json:"type"` + VPC string `json:"vpc"` + VPCAttachment string `json:"vpcattachment"` + Namespace string `json:"namespace"` + MTU int32 `json:"mtu,omitempty"` + // DAN asks the tap plugin to describe the device to the hypervisor in a + // file the hypervisor reads, rather than leaving it to be discovered from + // the node. Only the tap plugin reads it, and it is omitted when false so + // every conflist rendered until now is byte-identical. + DAN bool `json:"dan,omitempty"` + IPAM *IPAM `json:"ipam,omitempty"` +} + +// BGPPlugin is the galactic-bgp stanza. It is never optional: the master plugin +// fetches its own NAD and fails ADD before creating kernel state without it. +type BGPPlugin struct { + Type string `json:"type"` + VPC string `json:"vpc"` + VPCAttachment string `json:"vpcattachment"` + Namespace string `json:"namespace"` +} + +// IPAM is the delegated IPAM block. Presence alone decides whether IPAM runs. +type IPAM struct { + Type string `json:"type"` + Addresses []Address `json:"addresses,omitempty"` +} + +// Address is one pre-decided address, in CIDR notation, with the next hop the +// interface routes through for its family. Without the gateway the guest has an +// address but no route off its own link, and the host installs neither the tap +// gateway address nor the route to the attachment's prefix. +type Address struct { + Address string `json:"address"` + Gateway string `json:"gateway,omitempty"` +} + +// Conflist renders the conflist for one attachment. Addresses are the addresses +// NSO already allocated; an empty list means the guest addresses itself and no +// IPAM block is emitted. Set dan for a guest whose hypervisor is handed the +// device rather than discovering it. +func Conflist(name, plugin, vpc, vpcAttachment string, mtu int32, addresses []Address, dan bool) NetConfList { + master := MasterPlugin{ + Type: plugin, + VPC: vpc, + VPCAttachment: vpcAttachment, + Namespace: SystemNamespace, + MTU: mtu, + DAN: dan, + } + if len(addresses) > 0 { + master.IPAM = &IPAM{Type: PluginIPAM, Addresses: addresses} + } + return NetConfList{ + CNIVersion: CNIVersion, + Name: name, + Plugins: []any{ + master, + BGPPlugin{Type: PluginBGP, VPC: vpc, VPCAttachment: vpcAttachment, Namespace: SystemNamespace}, + }, + } +} + +// ConflistJSON renders the conflist as the string a NAD's spec.config holds. +func ConflistJSON(name, plugin, vpc, vpcAttachment string, mtu int32, addresses []Address, dan bool) (string, error) { + raw, err := json.Marshal(Conflist(name, plugin, vpc, vpcAttachment, mtu, addresses, dan)) + if err != nil { + return "", fmt.Errorf("marshal CNI conflist: %w", err) + } + return string(raw), nil +} + +// HostInterfaceName returns the host-side veth or tap device name. +func HostInterfaceName(vpc, vpcAttachment string) string { + return fmt.Sprintf("G%09s%03sH", vpc, vpcAttachment) +} + +// GuestInterfaceName returns the guest-side veth device name. +func GuestInterfaceName(vpc, vpcAttachment string) string { + return fmt.Sprintf("G%09s%03sG", vpc, vpcAttachment) +} + +// VRFInterfaceName returns the VRF device name, which is per-VPC rather than +// per-attachment. +func VRFInterfaceName(vpc string) string { + return fmt.Sprintf("G%09sV", vpc) +} + +// AdvertisementName returns the hex-encoded identity prefix shared by every +// node-qualified BGPAdvertisement galactic derives from an attachment. +func AdvertisementName(vpc, vpcAttachment string) string { + vpcHex, err := identifier.Base62ToHex(vpc) + if err != nil { + return "" + } + attachmentHex, err := identifier.Base62ToHex(vpcAttachment) + if err != nil { + return "" + } + return fmt.Sprintf("%s-%s", vpcHex, attachmentHex) +} + +// SplitAdvertisementName recovers the base62 (vpc, attachment) pair from a +// current node-qualified advertisement name. +func SplitAdvertisementName(name string) (vpc, vpcAttachment string, ok bool) { + parts := strings.SplitN(name, "-", 3) + if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" { + return "", "", false + } + vpc, err := identifier.HexToBase62(parts[0]) + if err != nil || vpc == "" { + return "", "", false + } + vpcAttachment, err = identifier.HexToBase62(parts[1]) + if err != nil || vpcAttachment == "" { + return "", "", false + } + return vpc, vpcAttachment, true +} + +// AllocatedSubnets returns every allocated subnet recorded on an advertisement, +// across both families and every container ID. +func AllocatedSubnets(annotations map[string]string) []string { + var subnets []string + for key, value := range annotations { + if strings.HasPrefix(key, AnnotationAllocatedSubnetIPv6+".") || + strings.HasPrefix(key, AnnotationAllocatedSubnetIPv4+".") { + subnets = append(subnets, value) + } + } + return subnets +} diff --git a/internal/galactic/galactic/galactic_test.go b/internal/galactic/galactic/galactic_test.go new file mode 100644 index 00000000..d718ff21 --- /dev/null +++ b/internal/galactic/galactic/galactic_test.go @@ -0,0 +1,171 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package galactic + +import ( + "encoding/json" + "testing" +) + +func TestConflistChainIsComplete(t *testing.T) { + conflist := Conflist("web-eth0", PluginTap, "0000000jU", "01a", 1400, + []Address{ + {Address: "fd00:10:ff01:0:1::1/96", Gateway: "fd00:10:ff01::1"}, + {Address: "172.20.1.7/32", Gateway: "172.20.1.1"}, + }, false) + + if conflist.CNIVersion != "1.0.0" { + t.Errorf("cniVersion: got %q, want %q", conflist.CNIVersion, "1.0.0") + } + if len(conflist.Plugins) != 2 { + t.Fatalf("plugin count: got %d, want 2", len(conflist.Plugins)) + } + + master, ok := conflist.Plugins[0].(MasterPlugin) + if !ok { + t.Fatalf("first plugin: got %T, want MasterPlugin", conflist.Plugins[0]) + } + if master.Type != PluginTap { + t.Errorf("master plugin: got %q, want %q", master.Type, PluginTap) + } + if master.IPAM == nil || len(master.IPAM.Addresses) != 2 { + t.Fatalf("ipam addresses: got %v, want two entries", master.IPAM) + } + // Without the gateway the guest has an address it cannot route off. + for _, address := range master.IPAM.Addresses { + if address.Gateway == "" { + t.Errorf("address %q carries no gateway", address.Address) + } + } + + // The master plugin fails ADD before creating kernel state without this. + bgp, ok := conflist.Plugins[1].(BGPPlugin) + if !ok { + t.Fatalf("second plugin: got %T, want BGPPlugin", conflist.Plugins[1]) + } + if bgp.Type != PluginBGP { + t.Errorf("bgp plugin: got %q, want %q", bgp.Type, PluginBGP) + } +} + +func TestConflistOmitsIPAMForSelfAddressingGuest(t *testing.T) { + raw, err := ConflistJSON("web-eth0", PluginTap, "0000000jU", "01a", 0, nil, false) + if err != nil { + t.Fatalf("ConflistJSON: %v", err) + } + + var decoded struct { + Plugins []map[string]any `json:"plugins"` + } + if err := json.Unmarshal([]byte(raw), &decoded); err != nil { + t.Fatalf("unmarshal conflist: %v", err) + } + if _, present := decoded.Plugins[0]["ipam"]; present { + t.Errorf("ipam block present for a guest managing its own addressing: %s", raw) + } + if _, present := decoded.Plugins[0]["mtu"]; present { + t.Errorf("mtu emitted when unset: %s", raw) + } +} + +func TestInterfaceNames(t *testing.T) { + tests := []struct { + name string + got string + want string + }{ + {"host", HostInterfaceName("jU", "1a"), "G0000000jU01aH"}, + {"guest", GuestInterfaceName("jU", "1a"), "G0000000jU01aG"}, + {"vrf", VRFInterfaceName("jU"), "G0000000jUV"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.got != test.want { + t.Errorf("got %q, want %q", test.got, test.want) + } + if len(test.got) > 15 { + t.Errorf("interface name %q exceeds the 15 character kernel limit", test.got) + } + }) + } +} + +func TestSplitAdvertisementName(t *testing.T) { + tests := []struct { + input string + vpc string + vpcAttachment string + ok bool + }{ + {"e008-d54-compute-c21d3c-us-east-1-staging-lab", "eV2", "T2", true}, + {"e007-d53-edge-567e29-us-east-1", "eV1", "T1", true}, + {"e008-d54", "", "", false}, + {"-d54-worker", "", "", false}, + {"e008--worker", "", "", false}, + } + for _, test := range tests { + t.Run(test.input, func(t *testing.T) { + vpc, vpcAttachment, ok := SplitAdvertisementName(test.input) + if vpc != test.vpc || vpcAttachment != test.vpcAttachment || ok != test.ok { + t.Errorf("got (%q, %q, %v), want (%q, %q, %v)", + vpc, vpcAttachment, ok, test.vpc, test.vpcAttachment, test.ok) + } + }) + } +} + +func TestAdvertisementNameEncodesBase62IdentityAsHex(t *testing.T) { + if got, want := AdvertisementName("eV2", "T2"), "e008-d54"; got != want { + t.Errorf("AdvertisementName() = %q, want %q", got, want) + } + if got := AdvertisementName("not-valid!", "T2"); got != "" { + t.Errorf("AdvertisementName() with invalid VPC = %q, want empty", got) + } +} + +// The tap plugin reads this one field to decide whether it describes the device +// to the hypervisor. Every attachment rendered until now leaves it out, so its +// absence has to stay the default. +func TestConflistCarriesTheDeclaredDeviceRequest(t *testing.T) { + declared, err := ConflistJSON("vm-eth0", PluginTap, "0000000jU", "01a", 1400, nil, true) + if err != nil { + t.Fatalf("ConflistJSON: %v", err) + } + discovered, err := ConflistJSON("vm-eth0", PluginTap, "0000000jU", "01a", 1400, nil, false) + if err != nil { + t.Fatalf("ConflistJSON: %v", err) + } + + if got := masterStanza(t, declared)["dan"]; got != true { + t.Errorf("declared attachment: got %v, want true", got) + } + if _, present := masterStanza(t, discovered)["dan"]; present { + t.Errorf("discovered attachment carries the field: %s", discovered) + } +} + +func masterStanza(t *testing.T, raw string) map[string]any { + t.Helper() + var decoded struct { + Plugins []map[string]any `json:"plugins"` + } + if err := json.Unmarshal([]byte(raw), &decoded); err != nil { + t.Fatalf("unmarshal conflist: %v", err) + } + return decoded.Plugins[0] +} diff --git a/internal/galactic/identifier/identifier.go b/internal/galactic/identifier/identifier.go new file mode 100644 index 00000000..657d22d5 --- /dev/null +++ b/internal/galactic/identifier/identifier.go @@ -0,0 +1,95 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +// Package identifier allocates the VPC and VPC attachment identifiers the +// galactic data plane keys on. Identifiers are generated as hex and published +// as base62, which is what keeps a kernel interface name inside 15 characters. +package identifier + +import ( + "crypto/rand" + "fmt" + "math/big" + "strconv" + "strings" + + "github.com/kenshaw/baseconv" +) + +const ( + // MaxVPC is the largest VPC identifier: 48 bits, nine base62 characters. + MaxVPC uint64 = 0xFFFFFFFFFFFF + + // MaxVPCAttachment is the largest attachment identifier: 16 bits, three + // base62 characters. + MaxVPCAttachment uint64 = 0xFFFF +) + +// Hex renders value as a zero-padded hex string wide enough to hold max. +// Zero and max are reserved. +func Hex(value, max uint64) (string, error) { + if value == 0 || value == max { + return "", fmt.Errorf("%d is a reserved identifier value", value) + } + if value > max { + return "", fmt.Errorf("%d exceeds the maximum identifier value %d", value, max) + } + return fmt.Sprintf("%0*x", len(strconv.FormatUint(max, 16)), value), nil +} + +// Random returns a random hex identifier in (0, max). +func Random(max uint64) (string, error) { + n, err := rand.Int(rand.Reader, big.NewInt(int64(max-1))) + if err != nil { + return "", fmt.Errorf("draw random identifier: %w", err) + } + return Hex(n.Uint64()+1, max) +} + +// RandomVPCAttachment returns a random 16-bit attachment identifier in hex. +func RandomVPCAttachment() (string, error) { + return Random(MaxVPCAttachment) +} + +// HexToBase62 converts a hex identifier to the base62 form the CNI chain reads. +func HexToBase62(value string) (string, error) { + return baseconv.Convert(strings.ToLower(value), baseconv.DigitsHex, baseconv.Digits62) +} + +// Base62ToHex converts a base62 identifier back to lowercase hex. +func Base62ToHex(value string) (string, error) { + return baseconv.Convert(value, baseconv.Digits62, baseconv.DigitsHex) +} + +// VPCBase62 renders a VPC identifier in base62, with the reserved-value guards +// and the width that keeps a kernel interface name inside fifteen characters. +func VPCBase62(value uint64) (string, error) { + hex, err := Hex(value, MaxVPC) + if err != nil { + return "", err + } + return HexToBase62(hex) +} + +// RandomVPCAttachmentBase62 returns a random attachment identifier in base62. +func RandomVPCAttachmentBase62() (string, error) { + hex, err := RandomVPCAttachment() + if err != nil { + return "", err + } + return HexToBase62(hex) +} diff --git a/internal/galactic/identifier/identifier_test.go b/internal/galactic/identifier/identifier_test.go new file mode 100644 index 00000000..5e7cda33 --- /dev/null +++ b/internal/galactic/identifier/identifier_test.go @@ -0,0 +1,87 @@ +/* +Copyright © 2026 Datum Technology, Inc. All rights reserved. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package identifier + +import "testing" + +func TestHexRejectsReservedValues(t *testing.T) { + for _, value := range []uint64{0, MaxVPC, MaxVPC + 1} { + if _, err := Hex(value, MaxVPC); err == nil { + t.Errorf("Hex(%d): got no error, want one", value) + } + } +} + +func TestRandomIdentifiersFitTheirInterfaceNameSegment(t *testing.T) { + for range 200 { + attachment, err := RandomVPCAttachmentBase62() + if err != nil { + t.Fatalf("RandomVPCAttachmentBase62: %v", err) + } + if len(attachment) > 3 { + t.Errorf("attachment identifier %q exceeds three base62 characters", attachment) + } + } +} + +func TestBase62RoundTrip(t *testing.T) { + hex, err := Random(MaxVPC) + if err != nil { + t.Fatalf("Random(MaxVPC): %v", err) + } + base62, err := HexToBase62(hex) + if err != nil { + t.Fatalf("HexToBase62(%q): %v", hex, err) + } + back, err := Base62ToHex(base62) + if err != nil { + t.Fatalf("Base62ToHex(%q): %v", base62, err) + } + if want := trimLeadingZeros(hex); back != want { + t.Errorf("round trip: got %q, want %q", back, want) + } +} + +func trimLeadingZeros(value string) string { + for i := range value { + if value[i] != '0' { + return value[i:] + } + } + return "0" +} + +func TestVPCBase62RejectsReservedValuesAndFitsItsSegment(t *testing.T) { + for _, value := range []uint64{0, MaxVPC, MaxVPC + 1} { + if _, err := VPCBase62(value); err == nil { + t.Errorf("VPCBase62(%d): got no error, want one", value) + } + } + + // The widest fabric identity is 32 bits, well inside the 48 the VPC + // identifier holds, so nothing an identity carries can overflow the segment. + for _, value := range []uint64{1, 16, 1 << 31, 1<<32 - 1} { + encoded, err := VPCBase62(value) + if err != nil { + t.Fatalf("VPCBase62(%d): %v", value, err) + } + if len(encoded) > 9 { + t.Errorf("VPCBase62(%d) = %q exceeds nine base62 characters", value, encoded) + } + } +} From 165f6c81370c77929b5b043b477d0eee2f3f6d3d Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 16 Oct 2026 10:25:31 -0500 Subject: [PATCH 4/6] Regenerate dependency notices for Galactic API --- NOTICE | 1284 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 1218 insertions(+), 66 deletions(-) diff --git a/NOTICE b/NOTICE index 1c8bc8fc..557e25eb 100644 --- a/NOTICE +++ b/NOTICE @@ -2036,7 +2036,7 @@ SOFTWARE. ## github.com/go-logr/logr * Name: github.com/go-logr/logr -* Version: v1.4.3 +* Version: v1.4.4 * License: Apache-2.0 ``` @@ -2669,7 +2669,7 @@ SOFTWARE. ## github.com/go-openapi/jsonpointer * Name: github.com/go-openapi/jsonpointer -* Version: v0.23.1 +* Version: v1.0.0 * License: Apache-2.0 ``` @@ -2880,7 +2880,7 @@ SOFTWARE. ## github.com/go-openapi/jsonreference * Name: github.com/go-openapi/jsonreference -* Version: v0.21.6 +* Version: v1.0.0 * License: Apache-2.0 ``` @@ -3092,7 +3092,7 @@ SOFTWARE. ## github.com/go-openapi/swag * Name: github.com/go-openapi/swag -* Version: v0.25.4 +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -3304,7 +3304,7 @@ SOFTWARE. ## github.com/go-openapi/swag/cmdutils * Name: github.com/go-openapi/swag/cmdutils -* Version: v0.25.4 +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -3516,7 +3516,7 @@ SOFTWARE. ## github.com/go-openapi/swag/conv * Name: github.com/go-openapi/swag/conv -* Version: v0.26.0 +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -3728,7 +3728,7 @@ SOFTWARE. ## github.com/go-openapi/swag/fileutils * Name: github.com/go-openapi/swag/fileutils -* Version: v0.26.0 +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -3937,10 +3937,10 @@ SOFTWARE. ``` -## github.com/go-openapi/swag/jsonname +## github.com/go-openapi/swag/jsonutils -* Name: github.com/go-openapi/swag/jsonname -* Version: v0.26.0 +* Name: github.com/go-openapi/swag/jsonutils +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -4149,10 +4149,10 @@ SOFTWARE. ``` -## github.com/go-openapi/swag/jsonutils +## github.com/go-openapi/swag/loading -* Name: github.com/go-openapi/swag/jsonutils -* Version: v0.26.0 +* Name: github.com/go-openapi/swag/loading +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -4361,10 +4361,10 @@ SOFTWARE. ``` -## github.com/go-openapi/swag/loading +## github.com/go-openapi/swag/mangling -* Name: github.com/go-openapi/swag/loading -* Version: v0.26.0 +* Name: github.com/go-openapi/swag/mangling +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -4573,10 +4573,10 @@ SOFTWARE. ``` -## github.com/go-openapi/swag/mangling +## github.com/go-openapi/swag/netutils -* Name: github.com/go-openapi/swag/mangling -* Version: v0.26.0 +* Name: github.com/go-openapi/swag/netutils +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -4785,10 +4785,10 @@ SOFTWARE. ``` -## github.com/go-openapi/swag/netutils +## github.com/go-openapi/swag/pools -* Name: github.com/go-openapi/swag/netutils -* Version: v0.25.4 +* Name: github.com/go-openapi/swag/pools +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -5000,7 +5000,7 @@ SOFTWARE. ## github.com/go-openapi/swag/stringutils * Name: github.com/go-openapi/swag/stringutils -* Version: v0.26.0 +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -5212,7 +5212,7 @@ SOFTWARE. ## github.com/go-openapi/swag/typeutils * Name: github.com/go-openapi/swag/typeutils -* Version: v0.26.0 +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -5424,7 +5424,7 @@ SOFTWARE. ## github.com/go-openapi/swag/yamlutils * Name: github.com/go-openapi/swag/yamlutils -* Version: v0.26.0 +* Version: v0.28.0 * License: Apache-2.0 ``` @@ -6652,7 +6652,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## github.com/grpc-ecosystem/grpc-gateway/v2 * Name: github.com/grpc-ecosystem/grpc-gateway/v2 -* Version: v2.29.0 +* Version: v2.30.0 * License: BSD-3-Clause ``` @@ -6717,6 +6717,248 @@ SOFTWARE. ``` +## github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io + +* Name: github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io +* Version: v1.7.7 +* License: Apache-2.0 + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +``` + +## github.com/kenshaw/baseconv + +* Name: github.com/kenshaw/baseconv +* Version: v0.1.1 +* License: MIT + +``` +The MIT License (MIT) + +Copyright (c) 2015 Kenneth Shaw + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +``` + ## github.com/mitchellh/go-homedir * Name: github.com/mitchellh/go-homedir @@ -7440,7 +7682,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## github.com/onsi/ginkgo/v2 * Name: github.com/onsi/ginkgo/v2 -* Version: v2.28.1 +* Version: v2.28.2 * License: MIT ``` @@ -7536,7 +7778,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil * Name: github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil -* Version: v1.23.2 +* Version: v1.25.0 * License: BSD-3-Clause ``` @@ -7573,7 +7815,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## github.com/prometheus/client_golang/prometheus * Name: github.com/prometheus/client_golang/prometheus -* Version: v1.23.2 +* Version: v1.25.0 * License: Apache-2.0 ``` @@ -7784,7 +8026,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## github.com/prometheus/client_model/go * Name: github.com/prometheus/client_model/go -* Version: v0.6.2 +* Version: v0.6.3 * License: Apache-2.0 ``` @@ -7995,7 +8237,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## github.com/prometheus/common * Name: github.com/prometheus/common -* Version: v0.67.5 +* Version: v0.72.0 * License: Apache-2.0 ``` @@ -8206,7 +8448,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## github.com/prometheus/procfs * Name: github.com/prometheus/procfs -* Version: v0.20.1 +* Version: v0.22.0 * License: Apache-2.0 ``` @@ -9113,6 +9355,676 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` +## go.datum.net/galactic/api/cloud/v1alpha1 + +* Name: go.datum.net/galactic/api/cloud/v1alpha1 +* Version: v0.5.8-0.20261016143526-45d3c3514bec +* License: AGPL-3.0 + +``` + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. +``` + ## go.miloapis.com/dns-operator/api/v1alpha1 * Name: go.miloapis.com/dns-operator/api/v1alpha1 @@ -9156,7 +10068,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## go.miloapis.com/locations * Name: go.miloapis.com/locations -* Version: v0.0.0-20260825185141-507ac2cbd48c +* Version: v0.0.1 * License: AGPL-3.0 ``` @@ -10708,7 +11620,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see ## go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc * Name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc -* Version: v0.67.0 +* Version: v0.72.0 * License: Apache-2.0 ``` @@ -10948,7 +11860,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp * Name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp -* Version: v0.69.0 +* Version: v0.70.0 * License: Apache-2.0 ``` @@ -11188,7 +12100,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## go.opentelemetry.io/otel * Name: go.opentelemetry.io/otel -* Version: v1.44.0 +* Version: v1.47.0 * License: Apache-2.0 ``` @@ -11428,7 +12340,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## go.opentelemetry.io/otel/exporters/otlp/otlptrace * Name: go.opentelemetry.io/otel/exporters/otlp/otlptrace -* Version: v1.44.0 +* Version: v1.47.0 * License: Apache-2.0 ``` @@ -11668,7 +12580,247 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc * Name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc -* Version: v1.44.0 +* Version: v1.47.0 +* License: Apache-2.0 + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## go.opentelemetry.io/otel/log + +* Name: go.opentelemetry.io/otel/log +* Version: v1.47.0 * License: Apache-2.0 ``` @@ -11908,7 +13060,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## go.opentelemetry.io/otel/metric * Name: go.opentelemetry.io/otel/metric -* Version: v1.44.0 +* Version: v1.47.0 * License: Apache-2.0 ``` @@ -12148,7 +13300,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## go.opentelemetry.io/otel/sdk * Name: go.opentelemetry.io/otel/sdk -* Version: v1.44.0 +* Version: v1.47.0 * License: Apache-2.0 ``` @@ -12388,7 +13540,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## go.opentelemetry.io/otel/trace * Name: go.opentelemetry.io/otel/trace -* Version: v1.44.0 +* Version: v1.47.0 * License: Apache-2.0 ``` @@ -12628,7 +13780,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## go.opentelemetry.io/proto/otlp * Name: go.opentelemetry.io/proto/otlp -* Version: v1.10.0 +* Version: v1.11.0 * License: Apache-2.0 ``` @@ -13108,7 +14260,7 @@ THE SOFTWARE. ## go.yaml.in/yaml/v3 * Name: go.yaml.in/yaml/v3 -* Version: v3.0.4 +* Version: v3.0.5 * License: MIT ``` @@ -13168,7 +14320,7 @@ limitations under the License. ## golang.org/x/crypto/pkcs12 * Name: golang.org/x/crypto/pkcs12 -* Version: v0.54.0 +* Version: v0.58.0 * License: BSD-3-Clause ``` @@ -13242,7 +14394,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## golang.org/x/mod/semver * Name: golang.org/x/mod/semver -* Version: v0.38.0 +* Version: v0.41.0 * License: BSD-3-Clause ``` @@ -13279,7 +14431,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## golang.org/x/net * Name: golang.org/x/net -* Version: v0.56.0 +* Version: v0.61.0 * License: BSD-3-Clause ``` @@ -13316,7 +14468,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## golang.org/x/oauth2 * Name: golang.org/x/oauth2 -* Version: v0.36.0 +* Version: v0.37.0 * License: BSD-3-Clause ``` @@ -13353,7 +14505,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## golang.org/x/sync * Name: golang.org/x/sync -* Version: v0.22.0 +* Version: v0.24.0 * License: BSD-3-Clause ``` @@ -13390,7 +14542,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## golang.org/x/sys * Name: golang.org/x/sys -* Version: v0.47.0 +* Version: v0.49.0 * License: BSD-3-Clause ``` @@ -13427,7 +14579,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## golang.org/x/term * Name: golang.org/x/term -* Version: v0.45.0 +* Version: v0.47.0 * License: BSD-3-Clause ``` @@ -13464,7 +14616,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## golang.org/x/text * Name: golang.org/x/text -* Version: v0.40.0 +* Version: v0.43.0 * License: BSD-3-Clause ``` @@ -13501,7 +14653,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## golang.org/x/time/rate * Name: golang.org/x/time/rate -* Version: v0.15.0 +* Version: v0.16.0 * License: BSD-3-Clause ``` @@ -13538,7 +14690,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## golang.org/x/tools * Name: golang.org/x/tools -* Version: v0.47.0 +* Version: v0.51.0 * License: BSD-3-Clause ``` @@ -13787,7 +14939,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## google.golang.org/genproto/googleapis/api * Name: google.golang.org/genproto/googleapis/api -* Version: v0.0.0-20260526163538-3dc84a4a5aaa +* Version: v0.0.0-20260928230214-8a89bd6388cc * License: Apache-2.0 ``` @@ -13999,7 +15151,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## google.golang.org/genproto/googleapis/rpc * Name: google.golang.org/genproto/googleapis/rpc -* Version: v0.0.0-20260526163538-3dc84a4a5aaa +* Version: v0.0.0-20261005182115-fad411399dd8 * License: Apache-2.0 ``` @@ -14211,7 +15363,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## google.golang.org/grpc * Name: google.golang.org/grpc -* Version: v1.81.1 +* Version: v1.84.0 * License: Apache-2.0 ``` @@ -14423,7 +15575,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## google.golang.org/protobuf * Name: google.golang.org/protobuf -* Version: v1.36.12-0.20260120151049-f2248ac996af +* Version: v1.36.12 * License: BSD-3-Clause ``` @@ -14533,7 +15685,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## k8s.io/api * Name: k8s.io/api -* Version: v0.36.1 +* Version: v0.36.3 * License: Apache-2.0 ``` @@ -14957,7 +16109,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## k8s.io/apimachinery/pkg * Name: k8s.io/apimachinery/pkg -* Version: v0.36.1 +* Version: v0.36.3 * License: Apache-2.0 ``` @@ -15169,7 +16321,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## k8s.io/apimachinery/third_party/forked/golang * Name: k8s.io/apimachinery/third_party/forked/golang -* Version: v0.36.1 +* Version: v0.36.3 * License: BSD-3-Clause ``` @@ -15418,7 +16570,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## k8s.io/client-go * Name: k8s.io/client-go -* Version: v0.36.1 +* Version: v0.36.3 * License: Apache-2.0 ``` @@ -16958,7 +18110,7 @@ SOFTWARE. ## k8s.io/streaming/pkg * Name: k8s.io/streaming/pkg -* Version: v0.36.1 +* Version: v0.36.3 * License: Apache-2.0 ``` @@ -17170,7 +18322,7 @@ SOFTWARE. ## k8s.io/utils * Name: k8s.io/utils -* Version: v0.0.0-20260319190234-28399d86e0b5 +* Version: v0.0.0-20260707023825-cf1189d6abe3 * License: Apache-2.0 ``` @@ -17382,7 +18534,7 @@ SOFTWARE. ## k8s.io/utils/internal/third_party/forked/golang * Name: k8s.io/utils/internal/third_party/forked/golang -* Version: v0.0.0-20260319190234-28399d86e0b5 +* Version: v0.0.0-20260707023825-cf1189d6abe3 * License: BSD-3-Clause ``` @@ -17419,7 +18571,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## k8s.io/utils/third_party/forked/golang/btree * Name: k8s.io/utils/third_party/forked/golang/btree -* Version: v0.0.0-20260319190234-28399d86e0b5 +* Version: v0.0.0-20260707023825-cf1189d6abe3 * License: Apache-2.0 ``` @@ -19145,7 +20297,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## sigs.k8s.io/structured-merge-diff/v6 * Name: sigs.k8s.io/structured-merge-diff/v6 -* Version: v6.3.2 +* Version: v6.3.3 * License: Apache-2.0 ``` From 17d8207969888b7d993dbd4e5a51414c7a26af85 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 16 Oct 2026 10:25:55 -0500 Subject: [PATCH 5/6] Guard Galactic controller registration by cell configuration --- internal/cmd/cell/cell_test.go | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 internal/cmd/cell/cell_test.go diff --git a/internal/cmd/cell/cell_test.go b/internal/cmd/cell/cell_test.go new file mode 100644 index 00000000..33df1ddb --- /dev/null +++ b/internal/cmd/cell/cell_test.go @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package cell + +import ( + "slices" + "testing" + + "go.datum.net/network-services-operator/internal/config" +) + +func TestGalacticControllersRequireEnablement(t *testing.T) { + for _, tt := range []struct { + name string + enabled bool + }{ + {name: "disabled"}, + {name: "enabled", enabled: true}, + } { + t.Run(tt.name, func(t *testing.T) { + cfg := config.CellControllerManager{ + Galactic: config.GalacticCellConfig{Enabled: tt.enabled, AttachmentMode: "Hypervisor"}, + } + registrations := controllerRegistrations(nil, cfg, nil, nil) + var names []string + for _, registration := range registrations { + names = append(names, registration.name) + } + for _, name := range []string{ + "galactic-networkcontext", "galactic-subnet", "galactic-networkinterface", + } { + if got := slices.Contains(names, name); got != tt.enabled { + t.Errorf("controller %q registered = %t, want %t", name, got, tt.enabled) + } + } + }) + } +} From d5a204ccc12508468f0529e0e030a09609ee5442 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 16 Oct 2026 10:31:38 -0500 Subject: [PATCH 6/6] Resolve NSO lint failures after Galactic dependency update --- internal/cmd/cell/cell_test.go | 2 +- test/conformance/gatewayapi/roundtripper.go | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/cmd/cell/cell_test.go b/internal/cmd/cell/cell_test.go index 33df1ddb..5128657c 100644 --- a/internal/cmd/cell/cell_test.go +++ b/internal/cmd/cell/cell_test.go @@ -22,7 +22,7 @@ func TestGalacticControllersRequireEnablement(t *testing.T) { Galactic: config.GalacticCellConfig{Enabled: tt.enabled, AttachmentMode: "Hypervisor"}, } registrations := controllerRegistrations(nil, cfg, nil, nil) - var names []string + names := make([]string, 0, len(registrations)) for _, registration := range registrations { names = append(names, registration.name) } diff --git a/test/conformance/gatewayapi/roundtripper.go b/test/conformance/gatewayapi/roundtripper.go index f10c284c..cfa11747 100644 --- a/test/conformance/gatewayapi/roundtripper.go +++ b/test/conformance/gatewayapi/roundtripper.go @@ -13,7 +13,6 @@ import ( "net/http/httputil" "regexp" - "golang.org/x/net/http2" "sigs.k8s.io/gateway-api/conformance/utils/config" "sigs.k8s.io/gateway-api/conformance/utils/roundtripper" "sigs.k8s.io/gateway-api/conformance/utils/tlog" @@ -57,11 +56,11 @@ func (d *NodePortRoundTripper) h2cPriorKnowledgeTransport(request roundtripper.R return nil, errors.New("request has configured TLS but h2 prior knowledge is not encrypted") } - transport := &http2.Transport{ - AllowHTTP: true, - DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { - return d.DialContext(ctx, network, addr) - }, + protocols := new(http.Protocols) + protocols.SetUnencryptedHTTP2(true) + transport := &http.Transport{ + DialContext: d.DialContext, + Protocols: protocols, } return transport, nil