-
Notifications
You must be signed in to change notification settings - Fork 73
/
webhook.go
121 lines (100 loc) · 3.33 KB
/
webhook.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
package validating
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/slok/kubewebhook/v2/pkg/log"
"github.com/slok/kubewebhook/v2/pkg/model"
"github.com/slok/kubewebhook/v2/pkg/webhook"
"github.com/slok/kubewebhook/v2/pkg/webhook/internal/helpers"
)
// WebhookConfig is the Validating webhook configuration.
type WebhookConfig struct {
// ID is the id of the webhook.
ID string
// Object is the object of the webhook, to use multiple types on the same webhook or
// type inference, don't set this field (will be `nil`).
Obj metav1.Object
// Validator is the webhook validator.
Validator Validator
// Logger is the app logger.
Logger log.Logger
}
func (c *WebhookConfig) defaults() error {
if c.ID == "" {
return fmt.Errorf("id is required")
}
if c.Validator == nil {
return fmt.Errorf("validator is required")
}
if c.Logger == nil {
c.Logger = log.Noop
}
c.Logger = c.Logger.WithValues(log.Kv{"webhook-id": c.ID, "webhook-type": "validating"})
return nil
}
// NewWebhook is a validating webhook and will return a webhook ready for a type of resource
// it will validate the received resources.
func NewWebhook(cfg WebhookConfig) (webhook.Webhook, error) {
if err := cfg.defaults(); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
// If we don't have the type of the object create a dynamic object creator that will
// infer the type.
var oc helpers.ObjectCreator
if cfg.Obj != nil {
oc = helpers.NewStaticObjectCreator(cfg.Obj)
} else {
oc = helpers.NewDynamicObjectCreator()
}
// Create our webhook and wrap for instrumentation (metrics and tracing).
return &validatingWebhook{
id: cfg.ID,
objectCreator: oc,
validator: cfg.Validator,
cfg: cfg,
logger: cfg.Logger,
}, nil
}
type validatingWebhook struct {
id string
objectCreator helpers.ObjectCreator
validator Validator
cfg WebhookConfig
logger log.Logger
}
func (w validatingWebhook) ID() string { return w.id }
func (w validatingWebhook) Kind() model.WebhookKind { return model.WebhookKindValidating }
func (w validatingWebhook) Review(ctx context.Context, ar model.AdmissionReview) (model.AdmissionResponse, error) {
// Delete operations don't have body because should be gone on the deletion, instead they have the body
// of the object we want to delete as an old object.
raw := ar.NewObjectRaw
if ar.Operation == model.OperationDelete {
raw = ar.OldObjectRaw
}
// Create a new object from the raw type.
runtimeObj, err := w.objectCreator.NewObject(raw)
if err != nil {
return nil, fmt.Errorf("could not create object from raw: %w", err)
}
validatingObj, ok := runtimeObj.(metav1.Object)
// Get the object.
if !ok {
return nil, fmt.Errorf("impossible to type assert the deep copy to metav1.Object")
}
res, err := w.validator.Validate(ctx, &ar, validatingObj)
if err != nil {
return nil, fmt.Errorf("validator error: %w", err)
}
if res == nil {
return nil, fmt.Errorf("result is required, validator result is nil")
}
w.logger.WithCtxValues(ctx).WithValues(log.Kv{"valid": res.Valid}).Debugf("Webhook validating review finished with '%t' result", res.Valid)
// Forge response.
return &model.ValidatingAdmissionResponse{
ID: ar.ID,
Allowed: res.Valid,
Message: res.Message,
Warnings: res.Warnings,
}, nil
}