diff --git a/api/v1alpha1/serviceroute_admission_envtest_test.go b/api/v1alpha1/serviceroute_admission_envtest_test.go
new file mode 100644
index 0000000..2929fc2
--- /dev/null
+++ b/api/v1alpha1/serviceroute_admission_envtest_test.go
@@ -0,0 +1,128 @@
+// Copyright 2026 Datum Cloud, Inc.
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+package v1alpha1_test
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ corev1 "k8s.io/api/core/v1"
+ apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
+ "sigs.k8s.io/yaml"
+
+ networkv1alpha1 "go.datum.net/network/api/v1alpha1"
+)
+
+func TestTranslatedAPIAdmission(t *testing.T) {
+ if os.Getenv("KUBEBUILDER_ASSETS") == "" {
+ t.Skip("set KUBEBUILDER_ASSETS for isolated API admission tests")
+ }
+ apiRoot := os.Getenv("GALACTIC_TEST_NETWORK_API")
+ if apiRoot == "" {
+ apiRoot = "../.."
+ }
+ crds := make([]*apiextensionsv1.CustomResourceDefinition, 0, 2)
+ for _, name := range []string{
+ "network.datumapis.com_serviceendpoints.yaml",
+ "network.datumapis.com_serviceroutepolicies.yaml",
+ } {
+ data, err := os.ReadFile(filepath.Join(apiRoot, "config", "crd", name))
+ if err != nil {
+ t.Fatal(err)
+ }
+ crd := &apiextensionsv1.CustomResourceDefinition{}
+ if err := yaml.Unmarshal(data, crd); err != nil {
+ t.Fatal(err)
+ }
+ crds = append(crds, crd)
+ }
+ environment := &envtest.Environment{CRDs: crds}
+ cfg, err := environment.Start()
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := environment.Stop(); err != nil {
+ t.Errorf("stop API: %v", err)
+ }
+ })
+ scheme := runtime.NewScheme()
+ _ = corev1.AddToScheme(scheme)
+ _ = networkv1alpha1.AddToScheme(scheme)
+ c, err := client.New(cfg, client.Options{Scheme: scheme})
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.Background()
+ if err := c.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "psc-admission"}}); err != nil {
+ t.Fatal(err)
+ }
+ policy := func(name string) *networkv1alpha1.ServiceRoutePolicy {
+ return &networkv1alpha1.ServiceRoutePolicy{
+ ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "psc-admission"},
+ Spec: networkv1alpha1.ServiceRoutePolicySpec{
+ ServiceRef: networkv1alpha1.ServiceEndpointReference{Name: "dns"},
+ AttachmentSelector: metav1.LabelSelector{MatchLabels: map[string]string{"access": "dns"}},
+ Frontend: &networkv1alpha1.ServiceRouteFrontend{Address: "fd53::53"},
+ ConsumerVPCRef: &networkv1alpha1.ServiceRouteVPCReference{Name: "vpc", UID: "vpc-uid"},
+ Authorization: &networkv1alpha1.ServiceRouteAuthorization{
+ ValidUntil: metav1.NewTime(time.Now().Add(time.Minute)),
+ },
+ },
+ }
+ }
+ for _, tt := range []struct {
+ name string
+ mutate func(*networkv1alpha1.ServiceRoutePolicy)
+ wantError bool
+ }{
+ {"missing-pin", func(p *networkv1alpha1.ServiceRoutePolicy) { p.Spec.ConsumerVPCRef = nil }, true},
+ {"empty-uid", func(p *networkv1alpha1.ServiceRoutePolicy) { p.Spec.ConsumerVPCRef.UID = "" }, true},
+ {"missing-authorization", func(p *networkv1alpha1.ServiceRoutePolicy) { p.Spec.Authorization = nil }, true},
+ {"legacy-direct", func(p *networkv1alpha1.ServiceRoutePolicy) {
+ p.Spec.Frontend = nil
+ p.Spec.ConsumerVPCRef = nil
+ p.Spec.Authorization = nil
+ }, false},
+ {"translated", func(*networkv1alpha1.ServiceRoutePolicy) {}, false},
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ p := policy(tt.name)
+ tt.mutate(p)
+ err := c.Create(ctx, p)
+ if (err != nil) != tt.wantError {
+ t.Fatalf("create error=%v", err)
+ }
+ if tt.wantError && !apierrors.IsInvalid(err) {
+ t.Fatalf("expected schema rejection, got %v", err)
+ }
+ })
+ }
+ // A real API preserves competing node reports and prevents stale resource-
+ // version writes from replacing a newer authorization generation.
+ p := policy("fenced")
+ if err := c.Create(ctx, p); err != nil {
+ t.Fatal(err)
+ }
+ old := p.DeepCopy()
+ p.Spec.Authorization.ValidUntil = metav1.NewTime(time.Now().Add(90 * time.Second))
+ if err := c.Update(ctx, p); err != nil {
+ t.Fatal(err)
+ }
+ old.Status.Nodes = []networkv1alpha1.ServiceRouteNodeStatus{{
+ NodeName: "node-a", PolicyUID: string(old.UID), ObservedGeneration: old.Generation,
+ InputDigest: "old", Ready: true, ValidUntil: metav1.NewTime(time.Now().Add(time.Minute)),
+ }}
+ if err := c.Status().Update(ctx, old); !apierrors.IsConflict(err) {
+ t.Fatalf("stale status write error=%v", err)
+ }
+}
diff --git a/api/v1alpha1/serviceroute_types.go b/api/v1alpha1/serviceroute_types.go
index f5b7156..85451b5 100644
--- a/api/v1alpha1/serviceroute_types.go
+++ b/api/v1alpha1/serviceroute_types.go
@@ -126,11 +126,30 @@ type ServiceRoutePolicy struct {
}
// ServiceRoutePolicySpec defines service selection and attachment eligibility.
+// +kubebuilder:validation:XValidation:rule="!has(self.frontend) || has(self.consumerVPCRef)",message="frontend requires consumerVPCRef"
+// +kubebuilder:validation:XValidation:rule="!has(self.frontend) || has(self.authorization)",message="frontend requires authorization"
type ServiceRoutePolicySpec struct {
// ServiceRef identifies the ServiceEndpoint in this namespace.
// +kubebuilder:validation:Required
ServiceRef ServiceEndpointReference `json:"serviceRef"`
+ // ConsumerVPCRef pins authorization to the immutable lifetime of the VPC
+ // selected by AttachmentSelector. Galactic resolves the name through the
+ // Cloud API and requires both the live name and UID to match.
+ // +optional
+ ConsumerVPCRef *ServiceRouteVPCReference `json:"consumerVPCRef,omitempty"`
+
+ // Frontend is the address presented inside the consumer VPC. When set,
+ // Galactic translates it to the ServiceEndpoint address and restores it on
+ // replies.
+ // +optional
+ Frontend *ServiceRouteFrontend `json:"frontend,omitempty"`
+
+ // Authorization bounds translated access independently of application leases.
+ // Only trusted networking integration may renew this deadline.
+ // +optional
+ Authorization *ServiceRouteAuthorization `json:"authorization,omitempty"`
+
// AttachmentSelector selects eligible VPC attachments by authoritative
// labels. Labels that grant access to platform services must be assigned by
// the network control plane, not by tenants.
@@ -149,6 +168,32 @@ type ServiceRoutePolicySpec struct {
Region string `json:"region,omitempty"`
}
+// ServiceRouteAuthorization bounds one network authorization episode.
+type ServiceRouteAuthorization struct {
+ // ValidUntil is the absolute UTC deadline for requests and existing replies.
+ // Node controllers reject deadlines more than two minutes in the future.
+ ValidUntil metav1.Time `json:"validUntil"`
+}
+
+// ServiceRouteVPCReference identifies one immutable Cloud API VPC lifetime.
+type ServiceRouteVPCReference struct {
+ // Name is the VPC name in the policy namespace.
+ // +kubebuilder:validation:MinLength=1
+ Name string `json:"name"`
+
+ // UID is the Kubernetes UID of that VPC lifetime.
+ // +kubebuilder:validation:MinLength=1
+ UID string `json:"uid"`
+}
+
+// ServiceRouteFrontend declares the consumer-facing service address.
+type ServiceRouteFrontend struct {
+ // Address is translated to the ServiceEndpoint address for requests and is
+ // restored as the source address for replies.
+ // +kubebuilder:validation:XValidation:rule="isIP(self)",message="address must be an IP address"
+ Address string `json:"address"`
+}
+
// ServiceRouteProtocolPort identifies an allowed transport protocol and port.
type ServiceRouteProtocolPort struct {
// Protocol is the transport protocol.
@@ -170,8 +215,7 @@ type ServiceEndpointReference struct {
Name string `json:"name"`
}
-// ServiceRoutePolicyStatus contains durable policy conditions only. Per-
-// attachment selection and programming state belongs in metrics and logs.
+// ServiceRoutePolicyStatus contains policy conditions and fenced node reports.
type ServiceRoutePolicyStatus struct {
// ObservedGeneration is the .metadata.generation this status was computed from.
// +optional
@@ -183,6 +227,23 @@ type ServiceRoutePolicyStatus struct {
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
+
+ // Nodes contains short-lived acknowledgments written by trusted node controllers.
+ // Consumers require current UID, generation, input digest, and unexpired reports.
+ // +listType=map
+ // +listMapKey=nodeName
+ // +optional
+ Nodes []ServiceRouteNodeStatus `json:"nodes,omitempty"`
+}
+
+// ServiceRouteNodeStatus acknowledges one node's current programmed path.
+type ServiceRouteNodeStatus struct {
+ NodeName string `json:"nodeName"`
+ PolicyUID string `json:"policyUID"`
+ ObservedGeneration int64 `json:"observedGeneration"`
+ InputDigest string `json:"inputDigest"`
+ Ready bool `json:"ready"`
+ ValidUntil metav1.Time `json:"validUntil"`
}
const (
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 8833b84..7ac200c 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -2026,6 +2026,53 @@ func (in *ServiceEndpointSpec) DeepCopy() *ServiceEndpointSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServiceRouteAuthorization) DeepCopyInto(out *ServiceRouteAuthorization) {
+ *out = *in
+ in.ValidUntil.DeepCopyInto(&out.ValidUntil)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceRouteAuthorization.
+func (in *ServiceRouteAuthorization) DeepCopy() *ServiceRouteAuthorization {
+ if in == nil {
+ return nil
+ }
+ out := new(ServiceRouteAuthorization)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServiceRouteFrontend) DeepCopyInto(out *ServiceRouteFrontend) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceRouteFrontend.
+func (in *ServiceRouteFrontend) DeepCopy() *ServiceRouteFrontend {
+ if in == nil {
+ return nil
+ }
+ out := new(ServiceRouteFrontend)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServiceRouteNodeStatus) DeepCopyInto(out *ServiceRouteNodeStatus) {
+ *out = *in
+ in.ValidUntil.DeepCopyInto(&out.ValidUntil)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceRouteNodeStatus.
+func (in *ServiceRouteNodeStatus) DeepCopy() *ServiceRouteNodeStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(ServiceRouteNodeStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceRoutePolicy) DeepCopyInto(out *ServiceRoutePolicy) {
*out = *in
@@ -2089,6 +2136,21 @@ func (in *ServiceRoutePolicyList) DeepCopyObject() runtime.Object {
func (in *ServiceRoutePolicySpec) DeepCopyInto(out *ServiceRoutePolicySpec) {
*out = *in
out.ServiceRef = in.ServiceRef
+ if in.ConsumerVPCRef != nil {
+ in, out := &in.ConsumerVPCRef, &out.ConsumerVPCRef
+ *out = new(ServiceRouteVPCReference)
+ **out = **in
+ }
+ if in.Frontend != nil {
+ in, out := &in.Frontend, &out.Frontend
+ *out = new(ServiceRouteFrontend)
+ **out = **in
+ }
+ if in.Authorization != nil {
+ in, out := &in.Authorization, &out.Authorization
+ *out = new(ServiceRouteAuthorization)
+ (*in).DeepCopyInto(*out)
+ }
in.AttachmentSelector.DeepCopyInto(&out.AttachmentSelector)
if in.ProtocolPorts != nil {
in, out := &in.ProtocolPorts, &out.ProtocolPorts
@@ -2117,6 +2179,13 @@ func (in *ServiceRoutePolicyStatus) DeepCopyInto(out *ServiceRoutePolicyStatus)
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
+ if in.Nodes != nil {
+ in, out := &in.Nodes, &out.Nodes
+ *out = make([]ServiceRouteNodeStatus, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceRoutePolicyStatus.
@@ -2144,6 +2213,21 @@ func (in *ServiceRouteProtocolPort) DeepCopy() *ServiceRouteProtocolPort {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServiceRouteVPCReference) DeepCopyInto(out *ServiceRouteVPCReference) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceRouteVPCReference.
+func (in *ServiceRouteVPCReference) DeepCopy() *ServiceRouteVPCReference {
+ if in == nil {
+ return nil
+ }
+ out := new(ServiceRouteVPCReference)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceVIPBinding) DeepCopyInto(out *ServiceVIPBinding) {
*out = *in
diff --git a/config/crd/network.datumapis.com_serviceroutepolicies.yaml b/config/crd/network.datumapis.com_serviceroutepolicies.yaml
index 67d33c3..9ef3bdf 100644
--- a/config/crd/network.datumapis.com_serviceroutepolicies.yaml
+++ b/config/crd/network.datumapis.com_serviceroutepolicies.yaml
@@ -104,6 +104,55 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ authorization:
+ description: |-
+ Authorization bounds translated access independently of application leases.
+ Only trusted networking integration may renew this deadline.
+ properties:
+ validUntil:
+ description: |-
+ ValidUntil is the absolute UTC deadline for requests and existing replies.
+ Node controllers reject deadlines more than two minutes in the future.
+ format: date-time
+ type: string
+ required:
+ - validUntil
+ type: object
+ consumerVPCRef:
+ description: |-
+ ConsumerVPCRef pins authorization to the immutable lifetime of the VPC
+ selected by AttachmentSelector. Galactic resolves the name through the
+ Cloud API and requires both the live name and UID to match.
+ properties:
+ name:
+ description: Name is the VPC name in the policy namespace.
+ minLength: 1
+ type: string
+ uid:
+ description: UID is the Kubernetes UID of that VPC lifetime.
+ minLength: 1
+ type: string
+ required:
+ - name
+ - uid
+ type: object
+ frontend:
+ description: |-
+ Frontend is the address presented inside the consumer VPC. When set,
+ Galactic translates it to the ServiceEndpoint address and restores it on
+ replies.
+ properties:
+ address:
+ description: |-
+ Address is translated to the ServiceEndpoint address for requests and is
+ restored as the source address for replies.
+ type: string
+ x-kubernetes-validations:
+ - message: address must be an IP address
+ rule: isIP(self)
+ required:
+ - address
+ type: object
protocolPorts:
description: |-
ProtocolPorts limits the traffic that the route is intended to carry.
@@ -149,10 +198,14 @@ spec:
- attachmentSelector
- serviceRef
type: object
+ x-kubernetes-validations:
+ - message: frontend requires consumerVPCRef
+ rule: '!has(self.frontend) || has(self.consumerVPCRef)'
+ - message: frontend requires authorization
+ rule: '!has(self.frontend) || has(self.authorization)'
status:
- description: |-
- ServiceRoutePolicyStatus contains durable policy conditions only. Per-
- attachment selection and programming state belongs in metrics and logs.
+ description: ServiceRoutePolicyStatus contains policy conditions and fenced
+ node reports.
properties:
conditions:
description: Conditions contains durable acceptance and service-availability
@@ -215,6 +268,40 @@ spec:
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
+ nodes:
+ description: |-
+ Nodes contains short-lived acknowledgments written by trusted node controllers.
+ Consumers require current UID, generation, input digest, and unexpired reports.
+ items:
+ description: ServiceRouteNodeStatus acknowledges one node's current
+ programmed path.
+ properties:
+ inputDigest:
+ type: string
+ nodeName:
+ type: string
+ observedGeneration:
+ format: int64
+ type: integer
+ policyUID:
+ type: string
+ ready:
+ type: boolean
+ validUntil:
+ format: date-time
+ type: string
+ required:
+ - inputDigest
+ - nodeName
+ - observedGeneration
+ - policyUID
+ - ready
+ - validUntil
+ type: object
+ type: array
+ x-kubernetes-list-map-keys:
+ - nodeName
+ x-kubernetes-list-type: map
observedGeneration:
description: ObservedGeneration is the .metadata.generation this status
was computed from.
diff --git a/docs/api/bgp.md b/docs/api/bgp.md
index cc6c93f..92229e9 100644
--- a/docs/api/bgp.md
+++ b/docs/api/bgp.md
@@ -1506,6 +1506,59 @@ _Appears in:_
| `region` _string_ | Region limits endpoint selection to a region. An empty value means the
endpoint is not region-scoped. | | |
+#### ServiceRouteAuthorization
+
+
+
+ServiceRouteAuthorization bounds one network authorization episode.
+
+
+
+_Appears in:_
+- [ServiceRoutePolicySpec](#serviceroutepolicyspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `validUntil` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#time-v1-meta)_ | ValidUntil is the absolute UTC deadline for requests and existing replies.
Node controllers reject deadlines more than two minutes in the future. | | |
+
+
+#### ServiceRouteFrontend
+
+
+
+ServiceRouteFrontend declares the consumer-facing service address.
+
+
+
+_Appears in:_
+- [ServiceRoutePolicySpec](#serviceroutepolicyspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `address` _string_ | Address is translated to the ServiceEndpoint address for requests and is
restored as the source address for replies. | | |
+
+
+#### ServiceRouteNodeStatus
+
+
+
+ServiceRouteNodeStatus acknowledges one node's current programmed path.
+
+
+
+_Appears in:_
+- [ServiceRoutePolicyStatus](#serviceroutepolicystatus)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `nodeName` _string_ | | | |
+| `policyUID` _string_ | | | |
+| `observedGeneration` _integer_ | | | |
+| `inputDigest` _string_ | | | |
+| `ready` _boolean_ | | | |
+| `validUntil` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#time-v1-meta)_ | | | |
+
+
#### ServiceRoutePolicy
@@ -1543,6 +1596,9 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `serviceRef` _[ServiceEndpointReference](#serviceendpointreference)_ | ServiceRef identifies the ServiceEndpoint in this namespace. | | Required: \{\}
|
+| `consumerVPCRef` _[ServiceRouteVPCReference](#serviceroutevpcreference)_ | ConsumerVPCRef pins authorization to the immutable lifetime of the VPC
selected by AttachmentSelector. Galactic resolves the name through the
Cloud API and requires both the live name and UID to match. | | |
+| `frontend` _[ServiceRouteFrontend](#serviceroutefrontend)_ | Frontend is the address presented inside the consumer VPC. When set,
Galactic translates it to the ServiceEndpoint address and restores it on
replies. | | |
+| `authorization` _[ServiceRouteAuthorization](#servicerouteauthorization)_ | Authorization bounds translated access independently of application leases.
Only trusted networking integration may renew this deadline. | | |
| `attachmentSelector` _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#labelselector-v1-meta)_ | AttachmentSelector selects eligible VPC attachments by authoritative
labels. Labels that grant access to platform services must be assigned by
the network control plane, not by tenants. | | Required: \{\}
|
| `protocolPorts` _[ServiceRouteProtocolPort](#servicerouteprotocolport) array_ | ProtocolPorts limits the traffic that the route is intended to carry.
An empty list means the endpoint's declared port and protocol are used. | | |
| `region` _string_ | Region limits selected attachments to a region. An empty value means the
policy applies in every region where the endpoint is available. | | |
@@ -1552,8 +1608,7 @@ _Appears in:_
-ServiceRoutePolicyStatus contains durable policy conditions only. Per-
-attachment selection and programming state belongs in metrics and logs.
+ServiceRoutePolicyStatus contains policy conditions and fenced node reports.
@@ -1564,6 +1619,7 @@ _Appears in:_
| --- | --- | --- | --- |
| `observedGeneration` _integer_ | ObservedGeneration is the .metadata.generation this status was computed from. | | |
| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#condition-v1-meta) array_ | Conditions contains durable acceptance and service-availability state. | | |
+| `nodes` _[ServiceRouteNodeStatus](#serviceroutenodestatus) array_ | Nodes contains short-lived acknowledgments written by trusted node controllers.
Consumers require current UID, generation, input digest, and unexpired reports. | | |
#### ServiceRouteProtocolPort
@@ -1582,6 +1638,23 @@ _Appears in:_
| `port` _integer_ | Port is the transport port. | | Maximum: 65535
Minimum: 1
Required: \{\}
|
+#### ServiceRouteVPCReference
+
+
+
+ServiceRouteVPCReference identifies one immutable Cloud API VPC lifetime.
+
+
+
+_Appears in:_
+- [ServiceRoutePolicySpec](#serviceroutepolicyspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `name` _string_ | Name is the VPC name in the policy namespace. | | MinLength: 1
|
+| `uid` _string_ | UID is the Kubernetes UID of that VPC lifetime. | | MinLength: 1
|
+
+
#### ServiceVIPBinding
diff --git a/docs/api/gateway.md b/docs/api/gateway.md
index 179641d..4622339 100644
--- a/docs/api/gateway.md
+++ b/docs/api/gateway.md
@@ -566,6 +566,59 @@ _Appears in:_
| `region` _string_ | Region limits endpoint selection to a region. An empty value means the
endpoint is not region-scoped. | | |
+#### ServiceRouteAuthorization
+
+
+
+ServiceRouteAuthorization bounds one network authorization episode.
+
+
+
+_Appears in:_
+- [ServiceRoutePolicySpec](#serviceroutepolicyspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `validUntil` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#time-v1-meta)_ | ValidUntil is the absolute UTC deadline for requests and existing replies.
Node controllers reject deadlines more than two minutes in the future. | | |
+
+
+#### ServiceRouteFrontend
+
+
+
+ServiceRouteFrontend declares the consumer-facing service address.
+
+
+
+_Appears in:_
+- [ServiceRoutePolicySpec](#serviceroutepolicyspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `address` _string_ | Address is translated to the ServiceEndpoint address for requests and is
restored as the source address for replies. | | |
+
+
+#### ServiceRouteNodeStatus
+
+
+
+ServiceRouteNodeStatus acknowledges one node's current programmed path.
+
+
+
+_Appears in:_
+- [ServiceRoutePolicyStatus](#serviceroutepolicystatus)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `nodeName` _string_ | | | |
+| `policyUID` _string_ | | | |
+| `observedGeneration` _integer_ | | | |
+| `inputDigest` _string_ | | | |
+| `ready` _boolean_ | | | |
+| `validUntil` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#time-v1-meta)_ | | | |
+
+
#### ServiceRoutePolicy
@@ -603,6 +656,9 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `serviceRef` _[ServiceEndpointReference](#serviceendpointreference)_ | ServiceRef identifies the ServiceEndpoint in this namespace. | | Required: \{\}
|
+| `consumerVPCRef` _[ServiceRouteVPCReference](#serviceroutevpcreference)_ | ConsumerVPCRef pins authorization to the immutable lifetime of the VPC
selected by AttachmentSelector. Galactic resolves the name through the
Cloud API and requires both the live name and UID to match. | | |
+| `frontend` _[ServiceRouteFrontend](#serviceroutefrontend)_ | Frontend is the address presented inside the consumer VPC. When set,
Galactic translates it to the ServiceEndpoint address and restores it on
replies. | | |
+| `authorization` _[ServiceRouteAuthorization](#servicerouteauthorization)_ | Authorization bounds translated access independently of application leases.
Only trusted networking integration may renew this deadline. | | |
| `attachmentSelector` _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#labelselector-v1-meta)_ | AttachmentSelector selects eligible VPC attachments by authoritative
labels. Labels that grant access to platform services must be assigned by
the network control plane, not by tenants. | | Required: \{\}
|
| `protocolPorts` _[ServiceRouteProtocolPort](#servicerouteprotocolport) array_ | ProtocolPorts limits the traffic that the route is intended to carry.
An empty list means the endpoint's declared port and protocol are used. | | |
| `region` _string_ | Region limits selected attachments to a region. An empty value means the
policy applies in every region where the endpoint is available. | | |
@@ -612,8 +668,7 @@ _Appears in:_
-ServiceRoutePolicyStatus contains durable policy conditions only. Per-
-attachment selection and programming state belongs in metrics and logs.
+ServiceRoutePolicyStatus contains policy conditions and fenced node reports.
@@ -624,6 +679,7 @@ _Appears in:_
| --- | --- | --- | --- |
| `observedGeneration` _integer_ | ObservedGeneration is the .metadata.generation this status was computed from. | | |
| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#condition-v1-meta) array_ | Conditions contains durable acceptance and service-availability state. | | |
+| `nodes` _[ServiceRouteNodeStatus](#serviceroutenodestatus) array_ | Nodes contains short-lived acknowledgments written by trusted node controllers.
Consumers require current UID, generation, input digest, and unexpired reports. | | |
#### ServiceRouteProtocolPort
@@ -643,6 +699,23 @@ _Appears in:_
| `port` _integer_ | Port is the transport port. | | Maximum: 65535
Minimum: 1
Required: \{\}
|
+#### ServiceRouteVPCReference
+
+
+
+ServiceRouteVPCReference identifies one immutable Cloud API VPC lifetime.
+
+
+
+_Appears in:_
+- [ServiceRoutePolicySpec](#serviceroutepolicyspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `name` _string_ | Name is the VPC name in the policy namespace. | | MinLength: 1
|
+| `uid` _string_ | UID is the Kubernetes UID of that VPC lifetime. | | MinLength: 1
|
+
+
#### ServiceVIPBinding
diff --git a/go.mod b/go.mod
index 87a8f53..a4a55b2 100644
--- a/go.mod
+++ b/go.mod
@@ -3,29 +3,51 @@ module go.datum.net/network
go 1.26
require (
+ k8s.io/api v0.33.0
+ k8s.io/apiextensions-apiserver v0.33.0
k8s.io/apimachinery v0.33.0
sigs.k8s.io/controller-runtime v0.21.0
+ sigs.k8s.io/yaml v1.4.0
)
require (
+ github.com/blang/semver/v4 v4.0.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/emicklei/go-restful/v3 v3.11.0 // indirect
+ github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
+ github.com/go-openapi/jsonpointer v0.21.0 // indirect
+ github.com/go-openapi/jsonreference v0.20.2 // indirect
+ github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/google/gnostic-models v0.6.9 // indirect
+ github.com/google/go-cmp v0.7.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/kr/pretty v0.3.1 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.51.0 // indirect
+ golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sys v0.42.0 // indirect
+ golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
- gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+ golang.org/x/time v0.9.0 // indirect
+ google.golang.org/protobuf v1.36.5 // indirect
+ gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/client-go v0.33.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
+ k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
- sigs.k8s.io/yaml v1.4.0 // indirect
)
diff --git a/go.sum b/go.sum
index 645608b..d1f305b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,21 +1,49 @@
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
+github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
+github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
+github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
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/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
+github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
+github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
+github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
+github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
+github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
+github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -27,31 +55,54 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
+github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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=
@@ -63,6 +114,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
+golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
+golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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=
@@ -71,10 +124,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
+golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
+golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
+golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -85,19 +142,32 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
+gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
+google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
+gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
+k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs=
+k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
+k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98=
+k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
+k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=