@@ -24,7 +24,10 @@ import (
24
24
"os"
25
25
"time"
26
26
27
+ "github.com/fsnotify/fsnotify"
27
28
"github.com/sirupsen/logrus"
29
+
30
+ "github.com/projectcalico/calico/lib/std/chanutil"
28
31
)
29
32
30
33
const ContentTypeMultilineJSON = "application/x-ndjson"
@@ -71,20 +74,96 @@ func newHTTPClient(caCert, clientKey, clientCert, serverName string) (*http.Clie
71
74
}
72
75
73
76
func newEmitterClient (url , caCert , clientKey , clientCert , serverName string ) (* emitterClient , error ) {
77
+ // Create an initial HTTP client, and a function to help encapsualte the reload logic.
74
78
client , err := newHTTPClient (caCert , clientKey , clientCert , serverName )
75
79
if err != nil {
76
80
return nil , err
77
81
}
78
- return & emitterClient {url : url , client : client }, nil
82
+
83
+ updChan := make (chan struct {})
84
+ getClient := func () (* http.Client , error ) {
85
+ select {
86
+ case _ , ok := <- updChan :
87
+ if ok {
88
+ // Only reload the client if the channel is still open. If the filewatcher
89
+ // has been closed, we'll just continue using the existing client as best-effort.
90
+ logrus .Info ("Reloading client after certificate change" )
91
+ client , err = newHTTPClient (caCert , clientKey , clientCert , serverName )
92
+ if err != nil {
93
+ return nil , fmt .Errorf ("error reloading CA cert: %s" , err )
94
+ }
95
+ }
96
+ default :
97
+ // No change, return the existing client.
98
+ }
99
+ return client , nil
100
+ }
101
+
102
+ if caCert != "" || clientKey != "" || clientCert != "" {
103
+ // Start a goroutine to watch for changes to the CA cert file and feed
104
+ // them into the update channel.
105
+ monitorFn , err := watchFiles (updChan , caCert , clientCert , clientKey )
106
+ if err != nil {
107
+ return nil , fmt .Errorf ("error setting up CA cert file watcher: %s" , err )
108
+ }
109
+ go monitorFn ()
110
+ }
111
+
112
+ return & emitterClient {
113
+ url : url ,
114
+ getClient : getClient ,
115
+ }, nil
116
+ }
117
+
118
+ func watchFiles (updChan chan struct {}, files ... string ) (func (), error ) {
119
+ fileWatcher , err := fsnotify .NewWatcher ()
120
+ if err != nil {
121
+ return nil , fmt .Errorf ("error creating file watcher: %s" , err )
122
+ }
123
+ for _ , file := range files {
124
+ if err := fileWatcher .Add (file ); err != nil {
125
+ logrus .WithError (err ).Warn ("Error watching file for changes" )
126
+ continue
127
+ }
128
+ logrus .WithField ("file" , file ).Debug ("Watching file for changes" )
129
+ }
130
+
131
+ return func () {
132
+ // If we exit this function, make sure to close the file watcher and update channel.
133
+ defer fileWatcher .Close ()
134
+ defer close (updChan )
135
+ defer logrus .Info ("File watcher closed" )
136
+ for {
137
+ select {
138
+ case event , ok := <- fileWatcher .Events :
139
+ if ! ok {
140
+ return
141
+ }
142
+ if event .Op & fsnotify .Write == fsnotify .Write {
143
+ logrus .WithField ("file" , event .Name ).Info ("File changed, triggering update" )
144
+ _ = chanutil .WriteNonBlocking (updChan , struct {}{})
145
+ }
146
+ case err , ok := <- fileWatcher .Errors :
147
+ if ! ok {
148
+ return
149
+ }
150
+ logrus .Errorf ("error watching CA cert file: %s" , err )
151
+ }
152
+ }
153
+ }, nil
79
154
}
80
155
81
156
type emitterClient struct {
82
- url string
83
- client * http.Client
157
+ url string
158
+ getClient func () ( * http.Client , error )
84
159
}
85
160
86
161
func (e * emitterClient ) Post (body io.Reader ) error {
87
- resp , err := e .client .Post (e .url , ContentTypeMultilineJSON , body )
162
+ client , err := e .getClient ()
163
+ if err != nil {
164
+ return err
165
+ }
166
+ resp , err := client .Post (e .url , ContentTypeMultilineJSON , body )
88
167
if err != nil {
89
168
return err
90
169
}
0 commit comments