@@ -20,10 +20,15 @@ import (
2020 "bytes"
2121 "context"
2222 "crypto/tls"
23+ "fmt"
2324 "os"
2425 "sync"
2526 "time"
2627
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"
2732 "sigs.k8s.io/controller-runtime/pkg/certwatcher/metrics"
2833 logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
2934)
@@ -40,6 +45,7 @@ type CertWatcher struct {
4045 sync.RWMutex
4146
4247 currentCert * tls.Certificate
48+ watcher * fsnotify.Watcher
4349 interval time.Duration
4450
4551 certPath string
@@ -53,13 +59,25 @@ type CertWatcher struct {
5359
5460// New returns a new CertWatcher watching the given certificate and key.
5561func New (certPath , keyPath string ) (* CertWatcher , error ) {
62+ var err error
63+
5664 cw := & CertWatcher {
5765 certPath : certPath ,
5866 keyPath : keyPath ,
5967 interval : defaultWatchInterval ,
6068 }
6169
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
6381}
6482
6583// WithWatchInterval sets the watch interval and returns the CertWatcher pointer
@@ -88,14 +106,35 @@ func (cw *CertWatcher) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate,
88106
89107// Start starts the watch on the certificate and key files.
90108func (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+
91130 ticker := time .NewTicker (cw .interval )
92131 defer ticker .Stop ()
93132
94- log .Info ("Starting certificate watcher" )
133+ log .Info ("Starting certificate poll+ watcher" , "interval" , cw . interval )
95134 for {
96135 select {
97136 case <- ctx .Done ():
98- return nil
137+ return cw . watcher . Close ()
99138 case <- ticker .C :
100139 if err := cw .ReadCertificate (); err != nil {
101140 log .Error (err , "failed read certificate" )
@@ -104,11 +143,26 @@ func (cw *CertWatcher) Start(ctx context.Context) error {
104143 }
105144}
106145
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.
111147func (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+ }
112166}
113167
114168// updateCachedCertificate checks if the new certificate differs from the cache,
@@ -166,3 +220,23 @@ func (cw *CertWatcher) ReadCertificate() error {
166220 }
167221 return nil
168222}
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