blob: 5a6ca77e9f7e9d4766a7d9ad7183690e25b80191 [file] [log] [blame]
Jan Schära48bd3c2024-07-29 17:22:18 +02001package object
2
3// Taken and modified from the Kubernetes plugin of CoreDNS, under Apache 2.0.
4
5import (
6 "fmt"
7 "net/netip"
8 "regexp"
9 "slices"
10 "strings"
11
12 "github.com/miekg/dns"
13 api "k8s.io/api/core/v1"
14 meta "k8s.io/apimachinery/pkg/apis/meta/v1"
15 "k8s.io/apimachinery/pkg/runtime"
16)
17
18// Service is a stripped down api.Service with only the items we need.
19type Service struct {
20 Version string
21 Name string
22 Namespace string
23 // ClusterIPs contains IP addresses in binary format.
24 ClusterIPs []string
25 ExternalName string
26 Ports []Port
27 Headless bool
28
29 *Empty
30}
31
32var domainNameRegexp = regexp.MustCompile(`^([-a-z0-9]{1,63}\.)+$`)
33
34const ExternalNameInvalid = "."
35
36// ToService converts an api.Service to a *Service.
37func ToService(obj meta.Object) (meta.Object, error) {
38 svc, ok := obj.(*api.Service)
39 if !ok {
40 return nil, fmt.Errorf("unexpected object %v", obj)
41 }
42
43 s := &Service{
44 Version: svc.GetResourceVersion(),
45 Name: svc.GetName(),
46 Namespace: svc.GetNamespace(),
47 }
48
49 if svc.Spec.Type == api.ServiceTypeExternalName {
50 // Make the name fully qualified.
51 externalName := dns.Fqdn(svc.Spec.ExternalName)
52 // Check if the name is valid. Even names that pass Kubernetes validation
53 // can fail this check, because Kubernetes does not validate that labels
54 // must be at most 63 characters.
55 if !domainNameRegexp.MatchString(externalName) || len(externalName) > 254 {
56 externalName = ExternalNameInvalid
57 }
58 s.ExternalName = externalName
59 } else {
60 if svc.Spec.ClusterIP == api.ClusterIPNone {
61 s.Headless = true
62 } else {
63 s.ClusterIPs = make([]string, 0, len(svc.Spec.ClusterIPs))
64 for _, rawIP := range svc.Spec.ClusterIPs {
65 parsedIP, err := netip.ParseAddr(rawIP)
66 if err != nil || parsedIP.Zone() != "" {
67 continue
68 }
69 parsedIP = parsedIP.Unmap()
70 s.ClusterIPs = append(s.ClusterIPs, string(parsedIP.AsSlice()))
71 }
72
73 s.Ports = make([]Port, 0, len(svc.Spec.Ports))
74 for _, p := range svc.Spec.Ports {
75 if p.Port >= 1 && p.Port <= 0xffff && p.Name != "" {
76 ep := Port{
77 Port: uint16(p.Port),
78 Name: strings.ToLower(p.Name),
79 Protocol: strings.ToLower(string(p.Protocol)),
80 }
81 s.Ports = append(s.Ports, ep)
82 }
83 }
84 }
85 }
86
87 *svc = api.Service{}
88
89 return s, nil
90}
91
92var _ runtime.Object = &Service{}
93
94// DeepCopyObject implements the ObjectKind interface.
95func (s *Service) DeepCopyObject() runtime.Object {
96 s1 := &Service{
97 Version: s.Version,
98 Name: s.Name,
99 Namespace: s.Namespace,
100 ClusterIPs: make([]string, len(s.ClusterIPs)),
101 ExternalName: s.ExternalName,
102 Ports: make([]Port, len(s.Ports)),
103 Headless: s.Headless,
104 }
105 copy(s1.ClusterIPs, s.ClusterIPs)
106 copy(s1.Ports, s.Ports)
107 return s1
108}
109
110// GetNamespace implements the metav1.Object interface.
111func (s *Service) GetNamespace() string { return s.Namespace }
112
113// SetNamespace implements the metav1.Object interface.
114func (s *Service) SetNamespace(namespace string) {}
115
116// GetName implements the metav1.Object interface.
117func (s *Service) GetName() string { return s.Name }
118
119// SetName implements the metav1.Object interface.
120func (s *Service) SetName(name string) {}
121
122// GetResourceVersion implements the metav1.Object interface.
123func (s *Service) GetResourceVersion() string { return s.Version }
124
125// SetResourceVersion implements the metav1.Object interface.
126func (s *Service) SetResourceVersion(version string) {}
127
128// ServiceModified checks if the update to a service is something
129// that matters to us or if they are effectively equivalent.
130func ServiceModified(oldSvc, newSvc *Service) bool {
131 if oldSvc.ExternalName != newSvc.ExternalName {
132 return true
133 }
134 if oldSvc.Headless != newSvc.Headless {
135 return true
136 }
137 if !slices.Equal(oldSvc.ClusterIPs, newSvc.ClusterIPs) {
138 return true
139 }
140 if !slices.Equal(oldSvc.Ports, newSvc.Ports) {
141 return true
142 }
143 return false
144}