Skip to content

Commit 9a73ff4

Browse files
committed
Update tests and docs for StandaloneServer
1 parent 12c19f7 commit 9a73ff4

File tree

4 files changed

+61
-96
lines changed

4 files changed

+61
-96
lines changed

pkg/webhook/admission/webhook.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ func (f HandlerFunc) Handle(ctx context.Context, req Request) Response {
113113
}
114114

115115
// Webhook represents each individual webhook.
116+
//
117+
// It must be registered with a webhook.Server or
118+
// populated by StandaloneWebhook to be ran on an arbitrary HTTP server.
116119
type Webhook struct {
117120
// Handler actually processes an admission request returning whether it was allowed or denied,
118121
// and potentially patches to apply to the handler.
@@ -221,8 +224,11 @@ type StandaloneOptions struct {
221224
Path string
222225
}
223226

224-
// StandaloneWebhook transforms a Webhook that needs to be registered
225-
// on a webhook.Server into one that can be ran on any arbitrary mux.
227+
// StandaloneWebhook prepares a webhook for use without a webhook.Server,
228+
// passing in the information normally populated by webhook.Server
229+
// and instrumenting the webhook with metrics.
230+
//
231+
// Use this to attach your webhook to an arbitrary HTTP server or mux.
226232
func StandaloneWebhook(hook *Webhook, opts StandaloneOptions) (http.Handler, error) {
227233
if opts.Scheme == nil {
228234
opts.Scheme = scheme.Scheme

pkg/webhook/server.go

Lines changed: 23 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ import (
3030
"sync"
3131

3232
"k8s.io/apimachinery/pkg/runtime"
33-
"k8s.io/client-go/kubernetes/scheme"
33+
kscheme "k8s.io/client-go/kubernetes/scheme"
3434
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
3535
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
36-
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
36+
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/metrics"
3737
)
3838

3939
// DefaultPort is the default port that the webhook server serves.
@@ -105,67 +105,6 @@ func (s *Server) setDefaults() {
105105
}
106106
}
107107

108-
// Options are the subset of fields on the controller that can be
109-
// configured when running an unmanaged webhook server (i.e. webhook.NewUnmanaged())
110-
type Options struct {
111-
// Host is the address that the server will listen on.
112-
// Defaults to "" - all addresses.
113-
Host string
114-
115-
// Port is the port number that the server will serve.
116-
// It will be defaulted to 9443 if unspecified.
117-
Port int
118-
119-
// CertDir is the directory that contains the server key and certificate. The
120-
// server key and certificate.
121-
CertDir string
122-
123-
// CertName is the server certificate name. Defaults to tls.crt.
124-
CertName string
125-
126-
// KeyName is the server key name. Defaults to tls.key.
127-
KeyName string
128-
129-
// ClientCAName is the CA certificate name which server used to verify remote(client)'s certificate.
130-
// Defaults to "", which means server does not verify client's certificate.
131-
ClientCAName string
132-
133-
// WebhookMux is the multiplexer that handles different webhooks.
134-
WebhookMux *http.ServeMux
135-
136-
// Scheme is the scheme used to resolve runtime.Objects to GroupVersionKinds / Resources
137-
// Defaults to the kubernetes/client-go scheme.Scheme, but it's almost always better
138-
// idea to pass your own scheme in. See the documentation in pkg/scheme for more information.
139-
Scheme *runtime.Scheme
140-
}
141-
142-
// NewUnmanaged provides a webhook server that can be ran without
143-
// a controller manager.
144-
func NewUnmanaged(options Options) (*Server, error) {
145-
server := &Server{
146-
Host: options.Host,
147-
Port: options.Port,
148-
CertDir: options.CertDir,
149-
CertName: options.CertName,
150-
KeyName: options.KeyName,
151-
WebhookMux: options.WebhookMux,
152-
}
153-
server.setDefaults()
154-
// Use the Kubernetes client-go scheme if none is specified
155-
if options.Scheme == nil {
156-
options.Scheme = scheme.Scheme
157-
}
158-
159-
// TODO: can we do this without dep injection?
160-
server.InjectFunc(func(i interface{}) error {
161-
if _, err := inject.SchemeInto(options.Scheme, i); err != nil {
162-
return err
163-
}
164-
return nil
165-
})
166-
return server, nil
167-
}
168-
169108
// NeedLeaderElection implements the LeaderElectionRunnable interface, which indicates
170109
// the webhook server doesn't need leader election.
171110
func (*Server) NeedLeaderElection() bool {
@@ -185,7 +124,7 @@ func (s *Server) Register(path string, hook http.Handler) {
185124
}
186125
// TODO(directxman12): call setfields if we've already started the server
187126
s.webhooks[path] = hook
188-
s.WebhookMux.Handle(path, admission.InstrumentedHook(path, hook))
127+
s.WebhookMux.Handle(path, metrics.InstrumentedHook(path, hook))
189128

190129
regLog := log.WithValues("path", path)
191130
regLog.Info("registering webhook")
@@ -210,6 +149,26 @@ func (s *Server) Register(path string, hook http.Handler) {
210149
}
211150
}
212151

152+
// StartStandalone runs a webhook server without
153+
// a controller manager.
154+
func (s *Server) StartStandalone(ctx context.Context, scheme *runtime.Scheme) error {
155+
// Use the Kubernetes client-go scheme if none is specified
156+
if scheme == nil {
157+
scheme = kscheme.Scheme
158+
}
159+
160+
if err := s.InjectFunc(func(i interface{}) error {
161+
if _, err := inject.SchemeInto(scheme, i); err != nil {
162+
return err
163+
}
164+
return nil
165+
}); err != nil {
166+
return err
167+
}
168+
169+
return s.Start(ctx)
170+
}
171+
213172
// Start runs the server.
214173
// It will install the webhook related resources depend on the server configuration.
215174
func (s *Server) Start(ctx context.Context) error {

pkg/webhook/server_test.go

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
. "github.com/onsi/ginkgo"
2727
. "github.com/onsi/gomega"
28+
"k8s.io/client-go/kubernetes/scheme"
2829
"k8s.io/client-go/rest"
2930
"sigs.k8s.io/controller-runtime/pkg/envtest"
3031
"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -68,12 +69,12 @@ var _ = Describe("Webhook Server", func() {
6869
Expect(servingOpts.Cleanup()).To(Succeed())
6970
})
7071

71-
startServer := func() (done <-chan struct{}) {
72+
genericStartServer := func(f func(ctx context.Context)) (done <-chan struct{}) {
7273
doneCh := make(chan struct{})
7374
go func() {
7475
defer GinkgoRecover()
7576
defer close(doneCh)
76-
Expect(server.Start(ctx)).To(Succeed())
77+
f(ctx)
7778
}()
7879
// wait till we can ping the server to start the test
7980
Eventually(func() error {
@@ -93,6 +94,12 @@ var _ = Describe("Webhook Server", func() {
9394
return doneCh
9495
}
9596

97+
startServer := func() (done <-chan struct{}) {
98+
return genericStartServer(func(ctx context.Context) {
99+
Expect(server.Start(ctx)).To(Succeed())
100+
})
101+
}
102+
96103
// TODO(directxman12): figure out a good way to test all the serving setup
97104
// with httptest.Server to get all the niceness from that.
98105

@@ -175,32 +182,26 @@ var _ = Describe("Webhook Server", func() {
175182
})
176183
})
177184

178-
Context("when using an unmanaged webhook server", func() {
179-
It("should serve a webhook on the requested path", func() {
180-
opts := webhook.Options{
181-
Host: servingOpts.LocalServingHost,
182-
Port: servingOpts.LocalServingPort,
183-
CertDir: servingOpts.LocalServingCertDir,
184-
}
185-
var err error
186-
// overwrite the server so that startServer() starts it
187-
server, err = webhook.NewUnmanaged(opts)
185+
It("should serve be able to serve in unmanaged mode", func() {
186+
server = &webhook.Server{
187+
Host: servingOpts.LocalServingHost,
188+
Port: servingOpts.LocalServingPort,
189+
CertDir: servingOpts.LocalServingCertDir,
190+
}
191+
server.Register("/somepath", &testHandler{})
192+
doneCh := genericStartServer(func(ctx context.Context) {
193+
Expect(server.StartStandalone(ctx, scheme.Scheme))
194+
})
188195

196+
Eventually(func() ([]byte, error) {
197+
resp, err := client.Get(fmt.Sprintf("https://%s/somepath", testHostPort))
189198
Expect(err).NotTo(HaveOccurred())
190-
server.Register("/somepath", &testHandler{})
191-
doneCh := startServer()
192-
193-
Eventually(func() ([]byte, error) {
194-
resp, err := client.Get(fmt.Sprintf("https://%s/somepath", testHostPort))
195-
Expect(err).NotTo(HaveOccurred())
196-
defer resp.Body.Close()
197-
return ioutil.ReadAll(resp.Body)
198-
}).Should(Equal([]byte("gadzooks!")))
199-
200-
ctxCancel()
201-
Eventually(doneCh, "4s").Should(BeClosed())
202-
})
199+
defer resp.Body.Close()
200+
return ioutil.ReadAll(resp.Body)
201+
}).Should(Equal([]byte("gadzooks!")))
203202

203+
ctxCancel()
204+
Eventually(doneCh, "4s").Should(BeClosed())
204205
})
205206
})
206207

pkg/webhook/webhook_integration_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
corev1 "k8s.io/api/core/v1"
1717
"k8s.io/apimachinery/pkg/api/errors"
1818
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
"k8s.io/client-go/kubernetes/scheme"
1920
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
2021
"sigs.k8s.io/controller-runtime/pkg/client"
2122
"sigs.k8s.io/controller-runtime/pkg/manager"
@@ -86,22 +87,20 @@ var _ = Describe("Webhook", func() {
8687
})
8788
Context("when running a webhook server without a manager", func() {
8889
It("should reject create request for webhook that rejects all requests", func(done Done) {
89-
opts := webhook.Options{
90+
server := webhook.Server{
9091
Port: testenv.WebhookInstallOptions.LocalServingPort,
9192
Host: testenv.WebhookInstallOptions.LocalServingHost,
9293
CertDir: testenv.WebhookInstallOptions.LocalServingCertDir,
9394
}
94-
server, err := webhook.NewUnmanaged(opts)
95-
Expect(err).NotTo(HaveOccurred())
9695
server.Register("/failing", &webhook.Admission{Handler: &rejectingValidator{}})
9796

9897
ctx, cancel := context.WithCancel(context.Background())
9998
go func() {
100-
_ = server.Start(ctx)
99+
_ = server.StartStandalone(ctx, scheme.Scheme)
101100
}()
102101

103102
Eventually(func() bool {
104-
err = c.Create(context.TODO(), obj)
103+
err := c.Create(context.TODO(), obj)
105104
return errors.ReasonForError(err) == metav1.StatusReason("Always denied")
106105
}, 1*time.Second).Should(BeTrue())
107106

0 commit comments

Comments
 (0)