-
Notifications
You must be signed in to change notification settings - Fork 1
/
kubernetes.go
171 lines (134 loc) · 5.25 KB
/
kubernetes.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package main
import (
"context"
"fmt"
"log"
"strings"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
"k8s.io/client-go/util/retry"
)
// AnnotationParser parses annotations to find translation metadata
type AnnotationParser interface {
// ParseDomains retrieves the list of translation domains
ParseDomains(annotations map[string]string) []string
// ParseDomainHashes will retrieve a map of translation domains/hashes
ParseDomainHashes(annotations map[string]string) map[string]string
}
// AnnotationWriter can write a set of changes to the pod template annotations
type AnnotationWriter interface {
// WriteChanges updates the pod template annotations with new values from changeset
WriteChanges(podSpec *corev1.PodTemplateSpec, changeset map[string]string)
}
// AnnotationParserWriter parses and updates annotations
type AnnotationParserWriter struct {
prefix string
}
// ParseDomains implements the AnnotationParser.ParseDomain method
func (a AnnotationParserWriter) ParseDomains(annotations map[string]string) []string {
var domains []string
// Parse the deployment's annotations to find the translation domains
for key, domain := range annotations {
if key == a.prefix+"/domains" {
domains = strings.Split(domain, ",")
}
}
return domains
}
// ParseDomainHashes implements the AnnotationParser.ParseDomainHashes method
func (a AnnotationParserWriter) ParseDomainHashes(annotations map[string]string) map[string]string {
// Look for current version of the translations in pod template annotations
hashes := make(map[string]string)
for k, v := range annotations {
// Filter annotations prefixed by our annotation domain
if strings.HasPrefix(k, a.prefix+"/") {
// Extract the translation domain and hash from the annotation
hashes[strings.Replace(k, a.prefix+"/", "", 1)] = v
}
}
return hashes
}
// WriteChanges implements the AnnotationWriter.WriteChanges method
func (a AnnotationParserWriter) WriteChanges(podSpec *corev1.PodTemplateSpec, changeset map[string]string) {
// create an empty map if there is no existing annotations
if podSpec.Annotations == nil {
podSpec.Annotations = make(map[string]string)
}
// add or erase deployment's annotations from changeset
for k, v := range changeset {
podSpec.Annotations[a.prefix+"/"+k] = v
}
}
// Repository is a kubernetes repository
type Repository struct {
client clientappsv1.AppsV1Interface
selector metav1.ListOptions
}
// NewRepository creates an new Repository instance
func NewRepository(clientset *kubernetes.Clientset, selectorLabel string) *Repository {
return &Repository{
clientset.AppsV1(),
metav1.ListOptions{
LabelSelector: selectorLabel,
},
}
}
// FindDeployments gets the list of eligible Deployment resources
func (r Repository) FindDeployments(ns string) []appsv1.Deployment {
client := r.client.Deployments(ns)
list, err := client.List(context.TODO(), r.selector)
if err != nil {
log.Printf("No deployments found in namespace %s: %+v\n", ns, err)
return []appsv1.Deployment{}
}
return list.Items
}
// UpdateDeployments applies updates of Deployment resources against the kubernetes API
func (r Repository) UpdateDeployments(ns string, deployments []appsv1.Deployment) {
client := r.client.Deployments(ns)
for _, deploy := range deployments {
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
_, err := client.Update(context.TODO(), &deploy, metav1.UpdateOptions{})
return err
})
if retryErr != nil {
log.Printf("Update failed: %+v\n", retryErr)
continue
}
fmt.Printf("Updated deployment %s\n", deploy.Name)
}
}
// ResourceHandler is a handler function with all translation sync logic for generic objects
type ResourceHandler func(_ context.Context, obj *metav1.Object)
// Appsv1ResourceHandler returns the handler function with all translation sync logic
// The handler function will take any resource from the apps/v1 API
func Appsv1ResourceHandler(annotationPrefix string, hashes *Hashes) ResourceHandler {
parser := &AnnotationParserWriter{annotationPrefix}
return func(_ context.Context, obj *metav1.Object) {
switch v := (*obj).(type) {
case *appsv1.DaemonSet:
ds := (*obj).(*appsv1.DaemonSet)
handleResource(parser, hashes, &ds.ObjectMeta, &ds.Spec.Template)
case *appsv1.Deployment:
deploy := (*obj).(*appsv1.Deployment)
handleResource(parser, hashes, &deploy.ObjectMeta, &deploy.Spec.Template)
case *appsv1.StatefulSet:
sts := (*obj).(*appsv1.StatefulSet)
handleResource(parser, hashes, &sts.ObjectMeta, &sts.Spec.Template)
default:
log.Printf("Warning: Resource of type %T is not supported\n", v)
}
}
}
func handleResource(parser *AnnotationParserWriter, hashes *Hashes, meta *metav1.ObjectMeta, podSpec *corev1.PodTemplateSpec) {
domains := parser.ParseDomains(meta.Annotations)
log.Printf("Resource %s subscribed to translations domains: %+v\n", meta.Name, domains)
// Look for current version of the translations in pod template annotations
currentHashes := parser.ParseDomainHashes(podSpec.Annotations)
changeset := computeChangeset(domains, currentHashes, *hashes)
log.Printf("Changeset: %+v\n", changeset)
parser.WriteChanges(podSpec, changeset)
}