Skip to content
This repository was archived by the owner on May 24, 2025. It is now read-only.

Commit 68e073c

Browse files
authored
Merge pull request #8 from sers-dev/#5TriggerReloadOnSecretsEvent
#5 added another watcher for secrets...
2 parents b3a1b4c + 03d58e4 commit 68e073c

File tree

6 files changed

+148
-63
lines changed

6 files changed

+148
-63
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
go.sum
33
main
44
vendor
5+
.env

README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
11
# Dovecot Director Controller
22

3-
Dovecot director is used to keep a temporary user -> mail server (= dovecot server) mapping, as described in <a href="https://wiki.dovecot.org/Director">dovecot docs</a>.
4-
If a dovecot director and dovecot server are used in a kubernetes cluster, mappings are not being updated in case a dovecot container restarts for example.
5-
To update the new pod ip and therefore to correct the mapping a manual execution of the command "doveadm reload" needs to be done on the dovecot director server.
6-
Since no one wants to waste manual effort on responses to ordinary container events this tool intends to automatically execute said command on the dovecot director shell whenever a dovecot container/pod becomes ready.
3+
Dovecot director is used to keep a temporary user -> mail server (= dovecot server) mapping, as described in
4+
[dovecot docs](https://wiki.dovecot.org/Director).
5+
6+
If a dovecot director and dovecot server are used in a kubernetes cluster, mappings are not being updated in case a
7+
dovecot container restarts for example since dovecot is not made to be used with private IPs as it is in k8s setup.
8+
To update the new pod ip and therefore to correct the mapping, a manual execution of the command `doveadm reload` needs
9+
to be done on the dovecot director server.
10+
11+
Since no one wants to waste manual effort on responses to ordinary container events this tool intends to automatically
12+
execute said command on the dovecot director pod container shell whenever a dovecot container/pod becomes ready
13+
or when the tls secret is changed or updated.
714

815

916
### Usage
1017

1118
Runs inside and outside of a kubernetes cluster.
19+
1220
If you don't run it inside a k8s cluster it tries to load the kubeconfig in the executing users homedir.
1321
If it does not exist you need to specify the absolute path with command flag "-c".
1422

1523
Environment variables needed for successful execution:
16-
* `DOVECOT_NAMESPACE`(string): Namespace name which must contain both dovecot director and dovecot pods
17-
* `DOVECOT_LABELS`(string): All labels given to dovecot for conclusive identification of dovecot pods, same format as in `DOVECOT_DIRECTOR_LABELS`
24+
* `DOVECOT_NAMESPACE`(string): Name of namespace that must contain both dovecot and dovecot director pods
1825
* `DOVECOT_DIRECTOR_LABELS`(string): All labels given to dovecot director for conclusive identification of dovecot director pods in the following format: `<LABEL1>=<VALUE1>,<LABEL2>=<VALUE2>`
19-
* `DOVECOT_DIRECTOR_CONTAINER_NAME` (string) (optional): Container Name of dovecot-director in Pod. Defaults to first Container in Pod if not set.
26+
* `DOVECOT_LABELS`(string): All labels given to dovecot for conclusive identification of dovecot pods, same format as in `DOVECOT_DIRECTOR_LABELS`
27+
28+
Optional environment parameters:
29+
* `DOVECOT_DIRECTOR_CONTAINER_NAME` (string) : Container Name of dovecot-director in Pod. Defaults to first Container in Pod if not set.
30+
* `SYNC_FREQUENCY_DURATION` (int, seconds, default: 70): This parameter is based on kubelets parameter `--sync-frequency duration` which is
31+
by default set to 60s, so if you change the value of kubelets parameter you should use the same value plus a few seconds
32+
to trigger at `doveadm reload` after adding/changing tls secrets successfully
2033

2134
### Used Library
2235
https://github.com/kubernetes/client-go

cmd/main.go

Lines changed: 82 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ import (
1616
"k8s.io/client-go/util/homedir"
1717
"os"
1818
"path/filepath"
19+
"strconv"
1920
"time"
2021
)
2122

2223
// variables: namespace, labels
2324
var dovecotLabels string
2425
var dovecotDirectorLabels string
2526
var dovecotDirectorContainerName string
26-
27+
var syncFrequencyDuration int
2728

2829
var namespace string
2930
var kubeconf *rest.Config
@@ -41,14 +42,22 @@ func main() {
4142
}
4243
dovecotDirectorLabels = os.Getenv("DOVECOT_DIRECTOR_LABELS")
4344
dovecotDirectorContainerName = os.Getenv("DOVECOT_DIRECTOR_CONTAINER_NAME")
44-
4545
dovecotLabels = os.Getenv("DOVECOT_LABELS")
4646
namespace = os.Getenv("DOVECOT_NAMESPACE")
47+
syncFrequencyDurationEnv := os.Getenv("SYNC_FREQUENCY_DURATION")
48+
49+
syncFrequencyDuration = 70
50+
if syncFrequencyDurationEnv != "" {
51+
syncFrequencyDuration, err = strconv.Atoi(syncFrequencyDurationEnv)
52+
if err != nil {
53+
syncFrequencyDuration = 70
54+
}
55+
}
4756

4857
dovecotPods := GetPodsByLabel(clientset, namespace, dovecotLabels)
4958
initialDovecotPodCount = len(dovecotPods.Items)
5059

51-
StartWatcher(clientset, namespace)
60+
StartWatchers(clientset, namespace)
5261
}
5362

5463
func GetPodsByLabel(clientset *kubernetes.Clientset, namespace string, labels string) *v1.PodList {
@@ -73,11 +82,11 @@ func ExecuteCommand(command string, podname string, namespace string, clientset
7382
// THE FOLLOWING EXPECTS THE POD TO HAVE ONLY ONE CONTAINER IN WHICH THE COMMAND IS GOING TO BE EXECUTED
7483
option := &v1.PodExecOptions{
7584
Container: dovecotDirectorContainerName,
76-
Command: cmd,
77-
Stdin: false,
78-
Stdout: true,
79-
Stderr: true,
80-
TTY: true,
85+
Command: cmd,
86+
Stdin: false,
87+
Stdout: true,
88+
Stderr: true,
89+
TTY: true,
8190
}
8291

8392
req.VersionedParams(
@@ -116,69 +125,97 @@ func handleEvent(pod *v1.Pod, clientset *kubernetes.Clientset) {
116125
containerStatusSlice := pod.Status.ContainerStatuses
117126

118127
for _, containerStatus := range containerStatusSlice {
119-
120128
if containerStatus.Ready {
121-
podlist := GetPodsByLabel(clientset, namespace, dovecotDirectorLabels)
122-
123-
for _, dovecotDirectorPod := range podlist.Items {
124-
time := time.Now()
125-
logLevel := "info"
126-
logMessage := "success"
127-
formattedTime := time.Format("2006-01-02 15:04:05 MST")
128-
129-
err := ExecuteCommand(
130-
"doveadm reload",
131-
dovecotDirectorPod.ObjectMeta.Name,
132-
namespace,
133-
clientset)
134-
135-
if err != nil {
136-
logLevel = "error"
137-
logMessage = err.Error()
138-
}
139-
140-
log := fmt.Sprintf("{ \"level\": \"%s\", \"timestamp\": \"%s\", \"pod\": \"%s\", \"command\": \"doveadm reload\", \"message\": \"%s\" }", logLevel, formattedTime, dovecotDirectorPod.ObjectMeta.Name, logMessage)
141-
fmt.Println(log)
142-
}
129+
ExecuteDoveAdm(clientset, dovecotDirectorLabels, 0)
143130
}
144131
}
145132
}
146-
147133
}
148134

149-
func StartWatcher(clientset *kubernetes.Clientset, namespace string) () {
150-
optionsModifierFunc := func(options *metav1.ListOptions) {
151-
options.LabelSelector = dovecotLabels
135+
func ExecuteDoveAdm(clientset *kubernetes.Clientset, dovecotDirectorLabels string, sleeptime int) {
136+
if sleeptime != 0 {
137+
time.Sleep(time.Second * time.Duration(int64(sleeptime)))
152138
}
153-
watchlist := cache.NewFilteredListWatchFromClient(
139+
podlist := GetPodsByLabel(clientset, namespace, dovecotDirectorLabels)
140+
141+
for _, dovecotDirectorPod := range podlist.Items {
142+
curTime := time.Now()
143+
logLevel := "info"
144+
logMessage := "success"
145+
formattedTime := curTime.Format("2006-01-02 15:04:05 MST")
146+
147+
err := ExecuteCommand(
148+
"doveadm reload",
149+
dovecotDirectorPod.ObjectMeta.Name,
150+
namespace,
151+
clientset)
152+
153+
if err != nil {
154+
logLevel = "error"
155+
logMessage = err.Error()
156+
}
157+
158+
log := fmt.Sprintf("{ \"level\": \"%s\", \"timestamp\": \"%s\", \"pod\": \"%s\", \"command\": \"doveadm reload\", \"message\": \"%s\" }", logLevel, formattedTime, dovecotDirectorPod.ObjectMeta.Name, logMessage)
159+
fmt.Println(log)
160+
}
161+
}
162+
163+
func StartWatchers(clientset *kubernetes.Clientset, namespace string) {
164+
watchlistSecrets := cache.NewFilteredListWatchFromClient(
165+
clientset.CoreV1().RESTClient(),
166+
"secrets",
167+
namespace,
168+
func(options *metav1.ListOptions) {},
169+
)
170+
_, controllerSecrets := cache.NewInformer(
171+
watchlistSecrets,
172+
&v1.Secret{},
173+
time.Second*0,
174+
cache.ResourceEventHandlerFuncs{
175+
AddFunc: func(obj interface{}) {
176+
secret := obj.(*v1.Secret)
177+
if secret.Type == "kubernetes.io/tls" {
178+
go ExecuteDoveAdm(clientset, dovecotDirectorLabels, syncFrequencyDuration)
179+
}
180+
},
181+
UpdateFunc: func(oldObj, newObj interface{}) {
182+
secret := newObj.(*v1.Secret)
183+
if secret.Type == "kubernetes.io/tls" {
184+
go ExecuteDoveAdm(clientset, dovecotDirectorLabels, syncFrequencyDuration)
185+
}
186+
},
187+
},
188+
)
189+
190+
watchlistPods := cache.NewFilteredListWatchFromClient(
154191
clientset.CoreV1().RESTClient(),
155192
"pods",
156193
namespace,
157-
optionsModifierFunc)
194+
func(options *metav1.ListOptions) { options.LabelSelector = dovecotLabels },
195+
)
158196

159-
_, controller := cache.NewInformer(
160-
watchlist,
197+
_, controllerPods := cache.NewInformer(
198+
watchlistPods,
161199
&v1.Pod{},
162200
time.Second*0,
163201
cache.ResourceEventHandlerFuncs{
164202
AddFunc: func(obj interface{}) {
165-
pod := obj.(*v1.Pod)
166-
handleEvent(pod, clientset)
203+
handleEvent(obj.(*v1.Pod), clientset)
167204
},
168205
UpdateFunc: func(oldObj, newObj interface{}) {
169-
pod := newObj.(*v1.Pod)
170-
handleEvent(pod, clientset)
206+
handleEvent(newObj.(*v1.Pod), clientset)
171207
},
172208
},
173209
)
174210

175-
go controller.Run(make(chan struct{}))
211+
go controllerPods.Run(make(chan struct{}))
212+
go controllerSecrets.Run(make(chan struct{}))
213+
176214
for {
177215
time.Sleep(time.Second)
178216
}
179217
}
180218

181-
182219
func InClusterAuth() (*kubernetes.Clientset, error) {
183220
var err error
184221
kubeconf, err = rest.InClusterConfig()

docker/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.15.3-alpine3.12 as builder
1+
FROM golang:1.19.2-alpine as builder
22

33
WORKDIR /workdir/go
44

docker/docker-compose.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ services:
1212
- DOVECOT_LABELS=app.kubernetes.io/instance=test,app.kubernetes.io/name=dovecot
1313
- DOVECOT_DIRECTOR_LABELS=app.kubernetes.io/instance=test,app.kubernetes.io/name=dovecot-director
1414
- DOVECOT_DIRECTOR_CONTAINER_NAME=dovecot-director
15+
- SYNC_FREQUENCY_DURATION=80

go.mod

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,48 @@
11
module cmd/main.go
22

3-
go 1.15
3+
go 1.19
44

55
require (
6-
github.com/golang/protobuf v1.4.3 // indirect
7-
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect
8-
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 // indirect
9-
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
10-
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
11-
k8s.io/api v0.19.2
12-
k8s.io/apimachinery v0.19.2
13-
k8s.io/client-go v0.19.2
14-
k8s.io/utils v0.0.0-20201015054608-420da100c033 // indirect
6+
github.com/PuerkitoBio/purell v1.1.1 // indirect
7+
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
8+
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
10+
github.com/go-logr/logr v1.2.3 // indirect
11+
github.com/go-openapi/jsonpointer v0.19.5 // indirect
12+
github.com/go-openapi/jsonreference v0.19.5 // indirect
13+
github.com/go-openapi/swag v0.19.14 // indirect
14+
github.com/gogo/protobuf v1.3.2 // indirect
15+
github.com/golang/protobuf v1.5.2 // indirect
16+
github.com/google/gnostic v0.5.7-v3refs // indirect
17+
github.com/google/go-cmp v0.5.8 // indirect
18+
github.com/google/gofuzz v1.1.0 // indirect
19+
github.com/imdario/mergo v0.3.6 // indirect
20+
github.com/josharian/intern v1.0.0 // indirect
21+
github.com/json-iterator/go v1.1.12 // indirect
22+
github.com/mailru/easyjson v0.7.6 // indirect
23+
github.com/moby/spdystream v0.2.0 // indirect
24+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
25+
github.com/modern-go/reflect2 v1.0.2 // indirect
26+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
27+
github.com/spf13/pflag v1.0.5 // indirect
28+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
29+
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
30+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
31+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
32+
golang.org/x/text v0.3.7 // indirect
33+
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
34+
google.golang.org/appengine v1.6.7 // indirect
35+
google.golang.org/protobuf v1.28.0 // indirect
36+
gopkg.in/inf.v0 v0.9.1 // indirect
37+
gopkg.in/yaml.v2 v2.4.0 // indirect
38+
gopkg.in/yaml.v3 v3.0.1 // indirect
39+
k8s.io/api v0.25.3 // indirect
40+
k8s.io/apimachinery v0.25.3 // indirect
41+
k8s.io/client-go v0.25.3 // indirect
42+
k8s.io/klog/v2 v2.70.1 // indirect
43+
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
44+
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
45+
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
46+
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
47+
sigs.k8s.io/yaml v1.2.0 // indirect
1548
)

0 commit comments

Comments
 (0)