@@ -20,10 +20,15 @@ import (
20
20
"bytes"
21
21
"context"
22
22
"crypto/tls"
23
+ "fmt"
23
24
"os"
24
25
"sync"
25
26
"time"
26
27
28
+ "github.com/fsnotify/fsnotify"
29
+ kerrors "k8s.io/apimachinery/pkg/util/errors"
30
+ "k8s.io/apimachinery/pkg/util/sets"
31
+ "k8s.io/apimachinery/pkg/util/wait"
27
32
"sigs.k8s.io/controller-runtime/pkg/certwatcher/metrics"
28
33
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
29
34
)
@@ -40,6 +45,7 @@ type CertWatcher struct {
40
45
sync.RWMutex
41
46
42
47
currentCert * tls.Certificate
48
+ watcher * fsnotify.Watcher
43
49
interval time.Duration
44
50
45
51
certPath string
@@ -53,13 +59,25 @@ type CertWatcher struct {
53
59
54
60
// New returns a new CertWatcher watching the given certificate and key.
55
61
func New (certPath , keyPath string ) (* CertWatcher , error ) {
62
+ var err error
63
+
56
64
cw := & CertWatcher {
57
65
certPath : certPath ,
58
66
keyPath : keyPath ,
59
67
interval : defaultWatchInterval ,
60
68
}
61
69
62
- return cw , cw .ReadCertificate ()
70
+ // Initial read of certificate and key.
71
+ if err := cw .ReadCertificate (); err != nil {
72
+ return nil , err
73
+ }
74
+
75
+ cw .watcher , err = fsnotify .NewWatcher ()
76
+ if err != nil {
77
+ return nil , err
78
+ }
79
+
80
+ return cw , nil
63
81
}
64
82
65
83
// WithWatchInterval sets the watch interval and returns the CertWatcher pointer
@@ -88,14 +106,35 @@ func (cw *CertWatcher) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate,
88
106
89
107
// Start starts the watch on the certificate and key files.
90
108
func (cw * CertWatcher ) Start (ctx context.Context ) error {
109
+ files := sets .New (cw .certPath , cw .keyPath )
110
+
111
+ {
112
+ var watchErr error
113
+ if err := wait .PollUntilContextTimeout (ctx , 1 * time .Second , 10 * time .Second , true , func (ctx context.Context ) (done bool , err error ) {
114
+ for _ , f := range files .UnsortedList () {
115
+ if err := cw .watcher .Add (f ); err != nil {
116
+ watchErr = err
117
+ return false , nil //nolint:nilerr // We want to keep trying.
118
+ }
119
+ // We've added the watch, remove it from the set.
120
+ files .Delete (f )
121
+ }
122
+ return true , nil
123
+ }); err != nil {
124
+ return fmt .Errorf ("failed to add watches: %w" , kerrors .NewAggregate ([]error {err , watchErr }))
125
+ }
126
+ }
127
+
128
+ go cw .Watch ()
129
+
91
130
ticker := time .NewTicker (cw .interval )
92
131
defer ticker .Stop ()
93
132
94
- log .Info ("Starting certificate watcher" )
133
+ log .Info ("Starting certificate poll+ watcher" , "interval" , cw . interval )
95
134
for {
96
135
select {
97
136
case <- ctx .Done ():
98
- return nil
137
+ return cw . watcher . Close ()
99
138
case <- ticker .C :
100
139
if err := cw .ReadCertificate (); err != nil {
101
140
log .Error (err , "failed read certificate" )
@@ -104,11 +143,26 @@ func (cw *CertWatcher) Start(ctx context.Context) error {
104
143
}
105
144
}
106
145
107
- // Watch used to read events from the watcher's channel and reacts to changes,
108
- // it has currently no function and it's left here for backward compatibility until a future release.
109
- //
110
- // Deprecated: fsnotify has been removed and Start() is now polling instead.
146
+ // Watch reads events from the watcher's channel and reacts to changes.
111
147
func (cw * CertWatcher ) Watch () {
148
+ for {
149
+ select {
150
+ case event , ok := <- cw .watcher .Events :
151
+ // Channel is closed.
152
+ if ! ok {
153
+ return
154
+ }
155
+
156
+ cw .handleEvent (event )
157
+ case err , ok := <- cw .watcher .Errors :
158
+ // Channel is closed.
159
+ if ! ok {
160
+ return
161
+ }
162
+
163
+ log .Error (err , "certificate watch error" )
164
+ }
165
+ }
112
166
}
113
167
114
168
// updateCachedCertificate checks if the new certificate differs from the cache,
@@ -166,3 +220,23 @@ func (cw *CertWatcher) ReadCertificate() error {
166
220
}
167
221
return nil
168
222
}
223
+
224
+ func (cw * CertWatcher ) handleEvent (event fsnotify.Event ) {
225
+ // Only care about events which may modify the contents of the file.
226
+ switch {
227
+ case event .Op .Has (fsnotify .Write ):
228
+ case event .Op .Has (fsnotify .Create ):
229
+ case event .Op .Has (fsnotify .Chmod ), event .Op .Has (fsnotify .Remove ):
230
+ // If the file was removed or renamed, re-add the watch to the previous name
231
+ if err := cw .watcher .Add (event .Name ); err != nil {
232
+ log .Error (err , "error re-watching file" )
233
+ }
234
+ default :
235
+ return
236
+ }
237
+
238
+ log .V (1 ).Info ("certificate event" , "event" , event )
239
+ if err := cw .ReadCertificate (); err != nil {
240
+ log .Error (err , "error re-reading certificate" )
241
+ }
242
+ }
0 commit comments