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