Skip to content

Commit 264c398

Browse files
committed
add default labels
1 parent d97f091 commit 264c398

2 files changed

Lines changed: 127 additions & 1 deletion

File tree

serverscom/loadbalancers.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const (
1919
loadBalancerLocationIdAnnotation = "servers.com/load-balancer-location-id"
2020
loadBalancerProxyProtocolAnnotation = "servers.com/proxy-protocol"
2121
loadBalancerClusterAnnotation = "servers.com/cluster-id"
22+
23+
loadBalancerServiceUUIDLabel = "k8s.servers.com/service-id"
24+
loadBalancerClusterNameLabel = "k8s.servers.com/cluster-name"
2225
)
2326

2427
type loadBalancers struct {
@@ -71,6 +74,11 @@ func (l *loadBalancers) EnsureLoadBalancer(ctx context.Context, clusterName stri
7174
// empty value for cluster id still returns nil
7275
lbClusterID := l.extractLBClusterID(service)
7376

77+
defaultLabels := map[string]string{
78+
loadBalancerServiceUUIDLabel: string(service.UID),
79+
loadBalancerClusterNameLabel: sanitizeLabelValue(clusterName),
80+
}
81+
7482
if loadBalancer == nil {
7583
locationID, err := l.extractLocationID(service)
7684
if err != nil {
@@ -83,6 +91,7 @@ func (l *loadBalancers) EnsureLoadBalancer(ctx context.Context, clusterName stri
8391
input.LocationID = locationID
8492
input.Name = l.GetLoadBalancerName(ctx, clusterName, service)
8593
input.ClusterID = lbClusterID
94+
input.Labels = defaultLabels
8695

8796
loadBalancer, err = l.client.LoadBalancers.CreateL4LoadBalancer(ctx, input)
8897
if err != nil {
@@ -95,6 +104,7 @@ func (l *loadBalancers) EnsureLoadBalancer(ctx context.Context, clusterName stri
95104
} else {
96105
name := l.GetLoadBalancerName(ctx, clusterName, service)
97106

107+
mergedLabels := mergeDefaultLabels(loadBalancer.Labels, defaultLabels)
98108
input := cli.L4LoadBalancerUpdateInput{}
99109
input.VHostZones = vhostZones
100110
input.UpstreamZones = upstreamZones
@@ -104,6 +114,7 @@ func (l *loadBalancers) EnsureLoadBalancer(ctx context.Context, clusterName stri
104114
input.SharedCluster = new(bool)
105115
*input.SharedCluster = true
106116
}
117+
input.Labels = mergedLabels
107118

108119
loadBalancer, err = l.client.LoadBalancers.UpdateL4LoadBalancer(ctx, loadBalancer.ID, input)
109120
if err != nil {
@@ -128,13 +139,19 @@ func (l *loadBalancers) UpdateLoadBalancer(ctx context.Context, clusterName stri
128139
if err != nil {
129140
return err
130141
}
142+
defaultLabels := map[string]string{
143+
loadBalancerServiceUUIDLabel: string(service.UID),
144+
loadBalancerClusterNameLabel: sanitizeLabelValue(clusterName),
145+
}
146+
mergedLabels := mergeDefaultLabels(loadBalancer.Labels, defaultLabels)
131147

132148
name := l.GetLoadBalancerName(ctx, clusterName, service)
133149

134150
input := cli.L4LoadBalancerUpdateInput{}
135151
input.VHostZones = vhostZones
136152
input.UpstreamZones = upstreamZones
137153
input.Name = &name
154+
input.Labels = mergedLabels
138155

139156
_, err = l.client.LoadBalancers.UpdateL4LoadBalancer(ctx, loadBalancer.ID, input)
140157
if err != nil {

serverscom/utils.go

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package serverscom
22

33
import (
44
"fmt"
5-
"k8s.io/klog/v2"
65
"regexp"
6+
"sort"
77
"strings"
88

9+
"k8s.io/klog/v2"
10+
11+
"maps"
12+
913
cli "github.com/serverscom/serverscom-go-client/pkg"
1014
v1 "k8s.io/api/core/v1"
1115
)
@@ -134,3 +138,108 @@ func anyMatch(str string, matches ...*string) bool {
134138

135139
return false
136140
}
141+
142+
// sanitizeLabelValue sanitazes label value according to:
143+
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set
144+
//
145+
// rules:
146+
// must be 63 characters or less (can be empty),
147+
// unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]),
148+
// could contain dashes (-), underscores (_), dots (.), and alphanumerics between.
149+
//
150+
// returns empty string if no valid chars found
151+
func sanitizeLabelValue(value string) string {
152+
if len(value) == 0 {
153+
return value
154+
}
155+
156+
runes := []rune(value)
157+
158+
// replace any invalid char to '-'
159+
for i, r := range runes {
160+
if !isValidLabelChar(r) {
161+
runes[i] = '-'
162+
}
163+
}
164+
165+
start := 0
166+
for start < len(runes) && !isAlphaNumeric(runes[start]) {
167+
start++
168+
}
169+
170+
if start == len(runes) {
171+
return ""
172+
}
173+
174+
end := len(runes) - 1
175+
for end >= 0 && !isAlphaNumeric(runes[end]) {
176+
end--
177+
}
178+
179+
runes = runes[start : end+1]
180+
181+
if len(runes) > 63 {
182+
runes = runes[:63]
183+
184+
// check that after truncate we still have valid chars in the end
185+
if !isAlphaNumeric(runes[len(runes)-1]) {
186+
lastValid := len(runes) - 1
187+
for lastValid >= 0 && !isAlphaNumeric(runes[lastValid]) {
188+
lastValid--
189+
}
190+
191+
if lastValid >= 0 {
192+
runes = runes[:lastValid+1]
193+
} else {
194+
return ""
195+
}
196+
}
197+
}
198+
199+
return string(runes)
200+
}
201+
202+
func isValidLabelChar(r rune) bool {
203+
return isAlphaNumeric(r) || r == '-' || r == '_' || r == '.'
204+
}
205+
206+
func isAlphaNumeric(r rune) bool {
207+
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')
208+
}
209+
210+
// mergeDefaultLabels merge existing labels with default ones.
211+
// To ensure that we can add default labels, existing labels will sorted by keys and truncated to 64 - count of default labels.
212+
// 64 - max supported labels for resource.
213+
func mergeDefaultLabels(existing, defaultLabels map[string]string) map[string]string {
214+
if defaultLabels == nil {
215+
return existing
216+
}
217+
if existing == nil {
218+
return defaultLabels
219+
}
220+
// max labels - count of default labels
221+
truncateTo := 64 - len(defaultLabels)
222+
223+
for k := range defaultLabels {
224+
delete(existing, k)
225+
}
226+
227+
if len(existing) > truncateTo {
228+
keys := make([]string, 0, len(existing))
229+
for k := range existing {
230+
keys = append(keys, k)
231+
}
232+
sort.Strings(keys)
233+
234+
truncated := make(map[string]string, 64)
235+
for i := range truncateTo {
236+
k := keys[i]
237+
truncated[k] = existing[k]
238+
}
239+
existing = truncated
240+
}
241+
242+
maps.Copy(existing, defaultLabels)
243+
244+
return existing
245+
}

0 commit comments

Comments
 (0)