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

Commit 071fb5e

Browse files
committed
initial commit
1 parent 1d5ab92 commit 071fb5e

File tree

6 files changed

+245
-0
lines changed

6 files changed

+245
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea
2+
go.sum
3+
main

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Dovecot Director Controller
2+
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.
7+
8+
9+
### Usage
10+
11+
Runs inside and outside of a kubernetes cluster.
12+
If you don't run it inside a k8s cluster it tries to load the kubeconfig in the executing users homedir.
13+
If it does not exist you need to specify the absolute path with command flag "-c".
14+
15+
Environment variables needed for successful execution:
16+
* `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>`
17+
* `DOVECOT_LABELS`(string): All labels given to dovecot for conclusive identification of dovecot pods, same format as in `DOVECOT_DIRECTOR_LABELS`
18+
* `DOVECOT_NAMESPACE`(string): Namespace name which must contain both dovecot director and dovecot pods
19+
20+
### Used Library
21+
https://github.com/kubernetes/client-go

cmd/main.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"flag"
7+
"fmt"
8+
v1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/client-go/kubernetes"
11+
"k8s.io/client-go/kubernetes/scheme"
12+
"k8s.io/client-go/rest"
13+
"k8s.io/client-go/tools/cache"
14+
"k8s.io/client-go/tools/clientcmd"
15+
"k8s.io/client-go/tools/remotecommand"
16+
"k8s.io/client-go/util/homedir"
17+
"os"
18+
"path/filepath"
19+
"time"
20+
)
21+
22+
// variables: namespace, labels
23+
var dovecotLabels string
24+
var dovecotDirectorLabels string
25+
26+
var namespace string
27+
var kubeconf *rest.Config
28+
29+
func main() {
30+
clientset, err := InClusterAuth()
31+
32+
if clientset == nil {
33+
clientset, err = OutOfClusterAuth()
34+
}
35+
if err != nil {
36+
panic(err.Error())
37+
}
38+
dovecotDirectorLabels = os.Getenv("DOVECOT_DIRECTOR_LABELS")
39+
dovecotLabels = os.Getenv("DOVECOT_LABELS")
40+
namespace = os.Getenv("DOVECOT_NAMESPACE")
41+
42+
StartWatcher(clientset, namespace)
43+
}
44+
45+
func GetPodsByLabel(clientset *kubernetes.Clientset, namespace string, labels string) *v1.PodList {
46+
listOptions := metav1.ListOptions{
47+
LabelSelector: labels,
48+
}
49+
pods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), listOptions)
50+
if err != nil {
51+
panic(err.Error())
52+
}
53+
54+
return pods
55+
}
56+
57+
func ExecuteCommand(command string, podname string, namespace string, clientset *kubernetes.Clientset) error {
58+
cmd := []string{
59+
"sh",
60+
"-c",
61+
command,
62+
}
63+
req := clientset.CoreV1().RESTClient().Post().Resource("pods").Name(podname).Namespace(namespace).SubResource("exec")
64+
// THE FOLLOWING EXPECTS THE POD TO HAVE ONLY ONE CONTAINER IN WHICH THE COMMAND IS GOING TO BE EXECUTED
65+
option := &v1.PodExecOptions{
66+
Command: cmd,
67+
Stdin: false,
68+
Stdout: true,
69+
Stderr: true,
70+
TTY: true,
71+
}
72+
73+
req.VersionedParams(
74+
option,
75+
scheme.ParameterCodec,
76+
)
77+
78+
exec, err := remotecommand.NewSPDYExecutor(kubeconf, "POST", req.URL())
79+
if err != nil {
80+
return err
81+
}
82+
var stdout, stderr bytes.Buffer
83+
84+
err = exec.Stream(remotecommand.StreamOptions{
85+
Stdin: nil,
86+
Stdout: &stdout,
87+
Stderr: &stderr,
88+
Tty: true,
89+
})
90+
if err != nil {
91+
return err
92+
}
93+
94+
return nil
95+
}
96+
97+
func handleEvent(pod *v1.Pod, clientset *kubernetes.Clientset) {
98+
switch pod.Status.Phase {
99+
case v1.PodFailed, v1.PodSucceeded:
100+
case v1.PodRunning:
101+
containerStatusSlice := pod.Status.ContainerStatuses
102+
103+
for _, containerStatus := range containerStatusSlice {
104+
105+
if containerStatus.Ready {
106+
podlist := GetPodsByLabel(clientset, namespace, dovecotDirectorLabels)
107+
108+
for _, dovecotPod := range podlist.Items {
109+
err := ExecuteCommand(
110+
"doveadm reload",
111+
dovecotPod.ObjectMeta.Name,
112+
namespace,
113+
clientset)
114+
if err != nil {
115+
fmt.Println(err.Error())
116+
}
117+
}
118+
}
119+
}
120+
}
121+
122+
}
123+
124+
func StartWatcher(clientset *kubernetes.Clientset, namespace string) () {
125+
optionsModifierFunc := func(options *metav1.ListOptions) {
126+
options.LabelSelector = dovecotLabels
127+
}
128+
watchlist := cache.NewFilteredListWatchFromClient(
129+
clientset.CoreV1().RESTClient(),
130+
"pods",
131+
namespace,
132+
optionsModifierFunc)
133+
134+
_, controller := cache.NewInformer(
135+
watchlist,
136+
&v1.Pod{},
137+
time.Second*0,
138+
cache.ResourceEventHandlerFuncs{
139+
AddFunc: func(obj interface{}) {
140+
pod := obj.(*v1.Pod)
141+
handleEvent(pod, clientset)
142+
},
143+
UpdateFunc: func(oldObj, newObj interface{}) {
144+
pod := newObj.(*v1.Pod)
145+
handleEvent(pod, clientset)
146+
},
147+
},
148+
)
149+
150+
go controller.Run(make(chan struct{}))
151+
for {
152+
time.Sleep(time.Second)
153+
}
154+
}
155+
156+
157+
func InClusterAuth() (*kubernetes.Clientset, error) {
158+
var err error
159+
kubeconf, err = rest.InClusterConfig()
160+
161+
if err != nil {
162+
return nil, nil
163+
}
164+
165+
clientset, err := kubernetes.NewForConfig(kubeconf)
166+
if err != nil {
167+
panic(err.Error())
168+
}
169+
170+
return clientset, nil
171+
}
172+
173+
func OutOfClusterAuth() (*kubernetes.Clientset, error) {
174+
var kubeconfig *string
175+
if home := homedir.HomeDir(); home != "" {
176+
kubeconfig = flag.String("c", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
177+
} else {
178+
kubeconfig = flag.String("c", "", "absolute path to the kubeconfig file")
179+
}
180+
flag.Parse()
181+
182+
var err error
183+
kubeconf, err = clientcmd.BuildConfigFromFlags("", *kubeconfig)
184+
if err != nil {
185+
panic(err.Error())
186+
}
187+
188+
clientset, err := kubernetes.NewForConfig(kubeconf)
189+
if err != nil {
190+
panic(err.Error())
191+
}
192+
193+
return clientset, nil
194+
}

docker/Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM golang:1.15.3-alpine3.12
2+
3+
WORKDIR /workdir/go
4+
5+
ADD cmd/main.go go.mod ./
6+
7+
RUN go get -d -v ./... && go install -v ./... && go build main.go
8+
9+
CMD ["main"]

docker/build.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
docker build --tag dovecot-director-controller:latest -f $(dirname "$0")/Dockerfile $(dirname "$0")/..

go.mod

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module cmd/main.go
2+
3+
go 1.15
4+
5+
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
15+
)

0 commit comments

Comments
 (0)