1818package utils
1919
2020import (
21+ "fmt"
22+ "net"
23+
2124 corev1 "k8s.io/api/core/v1"
2225 discoveryv1 "k8s.io/api/discovery/v1"
2326 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2427 "k8s.io/utils/ptr"
2528)
2629
27- // ConvertEndpointsToEndpointSlice converts a Kubernetes Endpoints object to an EndpointSlice object.
30+ // ConvertEndpointsToEndpointSlice converts a Kubernetes Endpoints object to one
31+ // or more EndpointSlice objects, supporting IPv4/IPv6 dual stack.
2832// This function is used to provide backward compatibility for Kubernetes 1.18 clusters that don't
2933// have EndpointSlice support but still use the older Endpoints API.
3034//
3135// The conversion follows these rules:
32- // - Each Endpoints subset becomes a separate EndpointSlice
33- // - Endpoint addresses are mapped to EndpointSlice endpoints
36+ // - Each Endpoints subset is split into separate IPv4 and IPv6 EndpointSlices
37+ // - Uses net.ParseIP for reliable address family detection instead of string matching
38+ // - IPv4 and IPv6 endpoints from the same subset are separated into different slices
39+ // - Naming convention: <svc-name>-<subset-index>-v4 / -v6
3440// - Port information is preserved
3541// - Ready state is mapped from Endpoints addresses vs notReadyAddresses
3642//
@@ -41,127 +47,104 @@ func ConvertEndpointsToEndpointSlice(ep *corev1.Endpoints) []discoveryv1.Endpoin
4147 return nil
4248 }
4349
44- //nolint:prealloc
4550 var endpointSlices []discoveryv1.EndpointSlice
4651
4752 // If there are no subsets, create an empty EndpointSlice
4853 if len (ep .Subsets ) == 0 {
49- endpointSlice := discoveryv1.EndpointSlice {
54+ endpointSlices = append ( endpointSlices , discoveryv1.EndpointSlice {
5055 ObjectMeta : metav1.ObjectMeta {
51- Name : ep .Name ,
52- Namespace : ep .Namespace ,
53- Labels : map [string ]string {
54- discoveryv1 .LabelServiceName : ep .Name ,
55- },
56+ Name : ep .Name + "-v4" , // Default to v4
57+ Namespace : ep .Namespace ,
58+ Labels : map [string ]string {discoveryv1 .LabelServiceName : ep .Name },
59+ OwnerReferences : ep .OwnerReferences ,
5660 },
57- AddressType : discoveryv1 .AddressTypeIPv4 , // Default to IPv4
61+ AddressType : discoveryv1 .AddressTypeIPv4 ,
5862 Ports : []discoveryv1.EndpointPort {},
5963 Endpoints : []discoveryv1.Endpoint {},
60- }
61- endpointSlices = append (endpointSlices , endpointSlice )
64+ })
6265 return endpointSlices
6366 }
6467
65- // Convert each subset to an EndpointSlice
6668 for i , subset := range ep .Subsets {
6769 // Create ports array
6870 var ports []discoveryv1.EndpointPort
69- for _ , port := range subset .Ports {
70- endpointPort := discoveryv1.EndpointPort {
71- Name : & port .Name ,
72- Port : & port .Port ,
73- Protocol : & port .Protocol ,
71+ for _ , p := range subset .Ports {
72+ epPort := discoveryv1.EndpointPort {
73+ Port : & p .Port ,
74+ Protocol : & p .Protocol ,
7475 }
75- ports = append (ports , endpointPort )
76+ if p .Name != "" {
77+ epPort .Name = & p .Name
78+ }
79+ ports = append (ports , epPort )
7680 }
7781
78- // Create endpoints array from addresses (ready endpoints)
79- var endpoints []discoveryv1.Endpoint
80- for _ , addr := range subset .Addresses {
81- endpoint := discoveryv1.Endpoint {
82+ // Separate IPv4 and IPv6 addresses
83+ var (
84+ ipv4Endpoints []discoveryv1.Endpoint
85+ ipv6Endpoints []discoveryv1.Endpoint
86+ )
87+ buildEndpoint := func (addr corev1.EndpointAddress , ready bool ) discoveryv1.Endpoint {
88+ e := discoveryv1.Endpoint {
8289 Addresses : []string {addr .IP },
8390 Conditions : discoveryv1.EndpointConditions {
84- Ready : ptr .To (true ), // Addresses in Endpoints.Addresses are ready
91+ Ready : ptr .To (ready ),
8592 },
8693 }
87-
88- // Add target ref if available
8994 if addr .TargetRef != nil {
90- endpoint .TargetRef = addr .TargetRef
95+ e .TargetRef = addr .TargetRef
9196 }
92-
93- // Add hostname if available
9497 if addr .Hostname != "" {
95- endpoint .Hostname = & addr .Hostname
98+ e .Hostname = & addr .Hostname
9699 }
97-
98- endpoints = append (endpoints , endpoint )
100+ return e
99101 }
100102
101- // Add not ready addresses
102- for _ , addr := range subset .NotReadyAddresses {
103- endpoint := discoveryv1.Endpoint {
104- Addresses : []string {addr .IP },
105- Conditions : discoveryv1.EndpointConditions {
106- Ready : ptr .To (false ), // NotReadyAddresses are not ready
107- },
108- }
109-
110- // Add target ref if available
111- if addr .TargetRef != nil {
112- endpoint .TargetRef = addr .TargetRef
103+ // Process ready addresses
104+ for _ , a := range subset .Addresses {
105+ if isIPv6 (a .IP ) {
106+ ipv6Endpoints = append (ipv6Endpoints , buildEndpoint (a , true ))
107+ } else {
108+ ipv4Endpoints = append (ipv4Endpoints , buildEndpoint (a , true ))
113109 }
114-
115- // Add hostname if available
116- if addr .Hostname != "" {
117- endpoint .Hostname = & addr .Hostname
110+ }
111+ // Process not ready addresses
112+ for _ , a := range subset .NotReadyAddresses {
113+ if isIPv6 (a .IP ) {
114+ ipv6Endpoints = append (ipv6Endpoints , buildEndpoint (a , false ))
115+ } else {
116+ ipv4Endpoints = append (ipv4Endpoints , buildEndpoint (a , false ))
118117 }
119-
120- endpoints = append (endpoints , endpoint )
121118 }
122119
123- // Determine address type based on first endpoint
124- addressType := discoveryv1 .AddressTypeIPv4
125- if len (endpoints ) > 0 && len (endpoints [0 ].Addresses ) > 0 {
126- // Simple IPv6 detection - if address contains colons, assume IPv6
127- if containsColon (endpoints [0 ].Addresses [0 ]) {
128- addressType = discoveryv1 .AddressTypeIPv6
120+ // Create EndpointSlices for each address type
121+ makeSlice := func (suffix string , addrType discoveryv1.AddressType , eps []discoveryv1.Endpoint ) discoveryv1.EndpointSlice {
122+ return discoveryv1.EndpointSlice {
123+ ObjectMeta : metav1.ObjectMeta {
124+ Name : fmt .Sprintf ("%s-%d-%s" , ep .Name , i , suffix ),
125+ Namespace : ep .Namespace ,
126+ Labels : map [string ]string {discoveryv1 .LabelServiceName : ep .Name },
127+ OwnerReferences : ep .OwnerReferences ,
128+ },
129+ AddressType : addrType ,
130+ Ports : ports ,
131+ Endpoints : eps ,
129132 }
130133 }
131134
132- // Create EndpointSlice name with suffix if multiple subsets
133- name := ep .Name
134- if len (ep .Subsets ) > 1 {
135- name = ep .Name + "-" + string (rune ('a' + i ))
135+ if len (ipv4Endpoints ) > 0 {
136+ endpointSlices = append (endpointSlices , makeSlice ("v4" , discoveryv1 .AddressTypeIPv4 , ipv4Endpoints ))
136137 }
137-
138- endpointSlice := discoveryv1.EndpointSlice {
139- ObjectMeta : metav1.ObjectMeta {
140- Name : name ,
141- Namespace : ep .Namespace ,
142- Labels : map [string ]string {
143- discoveryv1 .LabelServiceName : ep .Name ,
144- },
145- // Copy owner references if they exist
146- OwnerReferences : ep .OwnerReferences ,
147- },
148- AddressType : addressType ,
149- Ports : ports ,
150- Endpoints : endpoints ,
138+ if len (ipv6Endpoints ) > 0 {
139+ endpointSlices = append (endpointSlices , makeSlice ("v6" , discoveryv1 .AddressTypeIPv6 , ipv6Endpoints ))
151140 }
152-
153- endpointSlices = append (endpointSlices , endpointSlice )
154141 }
155142
156143 return endpointSlices
157144}
158145
159- // containsColon is a simple helper to detect IPv6 addresses
160- func containsColon (addr string ) bool {
161- for _ , char := range addr {
162- if char == ':' {
163- return true
164- }
165- }
166- return false
146+ // isIPv6 uses net.ParseIP to determine if an IP address is IPv6
147+ func isIPv6 (ip string ) bool {
148+ parsed := net .ParseIP (ip )
149+ return parsed != nil && parsed .To4 () == nil
167150}
0 commit comments