forked from coderanger/controller-runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.go
292 lines (250 loc) · 9.21 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package webhook
import (
"context"
"fmt"
"io"
"net/http"
"path"
"sync"
"time"
"k8s.io/apimachinery/pkg/runtime"
apitypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
atypes "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert"
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer"
"sigs.k8s.io/controller-runtime/pkg/webhook/types"
)
// ServerOptions are options for configuring an admission webhook server.
type ServerOptions struct {
// Port is the port number that the server will serve.
// It will be defaulted to 443 if unspecified.
Port int32
// CertDir is the directory that contains the server key and certificate.
// If using FSCertWriter in Provisioner, the server itself will provision the certificate and
// store it in this directory.
// If using SecretCertWriter in Provisioner, the server will provision the certificate in a secret,
// the user is responsible to mount the secret to the this location for the server to consume.
CertDir string
// Client is a client defined in controller-runtime instead of a client-go client.
// It knows how to talk to a kubernetes cluster.
// Client will be injected by the manager if not set.
Client client.Client
// DisableWebhookConfigInstaller controls if the server will automatically create webhook related objects
// during bootstrapping. e.g. webhookConfiguration, service and secret.
// If false, the server will install the webhook config objects. It is defaulted to false.
DisableWebhookConfigInstaller *bool
// BootstrapOptions contains the options for bootstrapping the admission server.
*BootstrapOptions
}
// BootstrapOptions are options for bootstrapping an admission webhook server.
type BootstrapOptions struct {
// MutatingWebhookConfigName is the name that used for creating the MutatingWebhookConfiguration object.
MutatingWebhookConfigName string
// ValidatingWebhookConfigName is the name that used for creating the ValidatingWebhookConfiguration object.
ValidatingWebhookConfigName string
// Secret is the location for storing the certificate for the admission server.
// The server should have permission to create a secret in the namespace.
// This is optional. If unspecified, it will write to the filesystem.
// It the secret already exists and is different from the desired, it will be replaced.
Secret *apitypes.NamespacedName
// Deprecated: Writer will not be used anywhere.
Writer io.Writer
// Service is k8s service fronting the webhook server pod(s).
// This field is optional. But one and only one of Service and Host need to be set.
// This maps to field .webhooks.getClientConfig.service
// https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L260
Service *Service
// Host is the host name of .webhooks.clientConfig.url
// https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L250
// This field is optional. But one and only one of Service and Host need to be set.
// If neither Service nor Host is unspecified, Host will be defaulted to "localhost".
Host *string
// certProvisioner is constructed using certGenerator and certWriter
certProvisioner *cert.Provisioner // nolint: structcheck
// err will be non-nil if there is an error occur during initialization.
err error // nolint: structcheck
}
// Service contains information for creating a service
type Service struct {
// Name of the service
Name string
// Namespace of the service
Namespace string
// Selectors is the selector of the service.
// This must select the pods that runs this webhook server.
Selectors map[string]string
}
// Server is an admission webhook server that can serve traffic and
// generates related k8s resources for deploying.
type Server struct {
// Name is the name of server
Name string
// ServerOptions contains options for configuring the admission server.
ServerOptions
sMux *http.ServeMux
// registry maps a path to a http.Handler.
registry map[string]Webhook
// mutatingWebhookConfiguration and validatingWebhookConfiguration are populated during server bootstrapping.
// They can be nil, if there is no webhook registered under it.
webhookConfigurations []runtime.Object
// manager is the manager that this webhook server will be registered.
manager manager.Manager
once sync.Once
}
// Webhook defines the basics that a webhook should support.
type Webhook interface {
// GetName returns the name of the webhook.
GetName() string
// GetPath returns the path that the webhook registered.
GetPath() string
// GetType returns the Type of the webhook.
// e.g. mutating or validating
GetType() types.WebhookType
// Handler returns a http.Handler for the webhook.
Handler() http.Handler
// Validate validates if the webhook itself is valid.
// If invalid, a non-nil error will be returned.
Validate() error
}
// NewServer creates a new admission webhook server.
func NewServer(name string, mgr manager.Manager, options ServerOptions) (*Server, error) {
as := &Server{
Name: name,
sMux: http.NewServeMux(),
registry: map[string]Webhook{},
ServerOptions: options,
manager: mgr,
}
return as, nil
}
// Register validates and registers webhook(s) in the server
func (s *Server) Register(webhooks ...Webhook) error {
for i, webhook := range webhooks {
// validate the webhook before registering it.
err := webhook.Validate()
if err != nil {
return err
}
_, found := s.registry[webhook.GetPath()]
if found {
return fmt.Errorf("can't register duplicate path: %v", webhook.GetPath())
}
s.registry[webhook.GetPath()] = webhooks[i]
s.sMux.Handle(webhook.GetPath(), webhook.Handler())
}
// Lazily add Server to manager.
// Because the all webhook handlers to be in place, so we can inject the things they need.
return s.manager.Add(s)
}
// Handle registers a http.Handler for the given pattern.
func (s *Server) Handle(pattern string, handler http.Handler) {
s.sMux.Handle(pattern, handler)
}
var _ manager.Runnable = &Server{}
// Start runs the server.
// It will install the webhook related resources depend on the server configuration.
func (s *Server) Start(stop <-chan struct{}) error {
s.once.Do(s.setDefault)
if s.err != nil {
return s.err
}
if s.DisableWebhookConfigInstaller != nil && !*s.DisableWebhookConfigInstaller {
log.Info("installing webhook configuration in cluster")
err := s.InstallWebhookManifests()
if err != nil {
return err
}
} else {
log.Info("webhook installer is disabled")
}
return s.run(stop)
}
func (s *Server) run(stop <-chan struct{}) error {
srv := &http.Server{
Addr: fmt.Sprintf(":%v", s.Port),
Handler: s.sMux,
}
errCh := make(chan error)
serveFn := func() {
errCh <- srv.ListenAndServeTLS(path.Join(s.CertDir, writer.ServerCertName), path.Join(s.CertDir, writer.ServerKeyName))
}
go serveFn()
for {
// TODO(mengqiy): add jitter to the timer
// Could use https://godoc.org/k8s.io/apimachinery/pkg/util/wait#Jitter
timer := time.Tick(6 * 30 * 24 * time.Hour)
select {
case <-timer:
changed, err := s.RefreshCert()
if err != nil {
log.Error(err, "encountering error when refreshing the certificate")
return err
}
if !changed {
continue
}
log.Info("server is shutting down to reload the certificates.")
err = srv.Shutdown(context.Background())
if err != nil {
log.Error(err, "encountering error when shutting down")
return err
}
go serveFn()
case <-stop:
return nil
case e := <-errCh:
return e
}
}
}
// RefreshCert refreshes the certificate using Server's Provisioner if the certificate is expiring.
func (s *Server) RefreshCert() (bool, error) {
cc, err := s.getClientConfig()
if err != nil {
return false, err
}
changed, err := s.certProvisioner.Provision(cert.Options{
ClientConfig: cc,
Objects: s.webhookConfigurations,
})
if err != nil {
return false, err
}
return changed, batchCreateOrReplace(s.Client, s.webhookConfigurations...)
}
var _ inject.Client = &Server{}
// InjectClient injects the client into the server
func (s *Server) InjectClient(c client.Client) error {
s.Client = c
for _, wh := range s.registry {
if _, err := inject.ClientInto(c, wh.Handler()); err != nil {
return err
}
}
return nil
}
var _ inject.Decoder = &Server{}
// InjectDecoder injects the client into the server
func (s *Server) InjectDecoder(d atypes.Decoder) error {
for _, wh := range s.registry {
if _, err := inject.DecoderInto(d, wh.Handler()); err != nil {
return err
}
}
return nil
}