Skip to content

Commit 14ea63d

Browse files
committed
fixup! feat: Add preflight checks framework
Use CheckerFactory. This allows the preflight framework to construct a new Checker for each cluster, and that allows the Checker to store state specific to that cluster.
1 parent d766f3a commit 14ea63d

File tree

3 files changed

+90
-63
lines changed

3 files changed

+90
-63
lines changed

cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func main() {
222222

223223
mgr.GetWebhookServer().Register("/preflight-v1beta1-cluster", &webhook.Admission{
224224
Handler: preflight.New(mgr.GetClient(), admission.NewDecoder(mgr.GetScheme()),
225-
[]preflight.Checker{
225+
[]preflight.CheckerFactory{
226226
// Add your preflight checkers here.
227227
}...,
228228
),

pkg/webhook/preflight/preflight.go

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,43 +15,41 @@ import (
1515
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
1616
)
1717

18-
type Cause struct {
19-
Message string
20-
Field string
21-
}
22-
23-
type CheckResult struct {
24-
Name string
25-
26-
Allowed bool
27-
Error bool
18+
type (
19+
// CheckerFactory returns a Checker for a given cluster.
20+
CheckerFactory func(client ctrlclient.Client, cluster *clusterv1.Cluster) Checker
21+
Checker interface {
22+
// Init returns the checks that should run for the cluster.
23+
Init(ctx context.Context) []Check
24+
}
2825

29-
Causes []Cause
30-
Warnings []string
31-
}
26+
Check = func(ctx context.Context) CheckResult
27+
CheckResult struct {
28+
Name string
3229

33-
type Check = func(ctx context.Context) CheckResult
30+
Allowed bool
31+
Error bool
3432

35-
type Checker interface {
36-
// Init decides which of its checks should run for the cluster. It then initializes the checks
37-
// with common dependencies, such as an infrastructure client. Finally, it returns the initialized checks,
38-
// ready to be run.
39-
//
40-
// Init can store the client and cluster, but not the context, because each check will accept its own context.
41-
Init(ctx context.Context, client ctrlclient.Client, cluster *clusterv1.Cluster) []Check
42-
}
33+
Causes []Cause
34+
Warnings []string
35+
}
36+
Cause struct {
37+
Message string
38+
Field string
39+
}
40+
)
4341

4442
type WebhookHandler struct {
45-
client ctrlclient.Client
46-
decoder admission.Decoder
47-
checkers []Checker
43+
client ctrlclient.Client
44+
decoder admission.Decoder
45+
factories []CheckerFactory
4846
}
4947

50-
func New(client ctrlclient.Client, decoder admission.Decoder, checkers ...Checker) *WebhookHandler {
48+
func New(client ctrlclient.Client, decoder admission.Decoder, factories ...CheckerFactory) *WebhookHandler {
5149
h := &WebhookHandler{
52-
client: client,
53-
decoder: decoder,
54-
checkers: checkers,
50+
client: client,
51+
decoder: decoder,
52+
factories: factories,
5553
}
5654
return h
5755
}
@@ -72,7 +70,7 @@ func (h *WebhookHandler) Handle(ctx context.Context, req admission.Request) admi
7270
return admission.Allowed("")
7371
}
7472

75-
resultsOrderedByCheckerAndCheck := run(ctx, h.client, cluster, h.checkers)
73+
resultsOrderedByCheckerAndCheck := run(ctx, h.client, cluster, h.factories)
7674

7775
// Summarize the results.
7876
resp := admission.Response{
@@ -124,15 +122,17 @@ func (h *WebhookHandler) Handle(ctx context.Context, req admission.Request) admi
124122
func run(ctx context.Context,
125123
client ctrlclient.Client,
126124
cluster *clusterv1.Cluster,
127-
checkers []Checker,
125+
factories []CheckerFactory,
128126
) [][]CheckResult {
129-
resultsOrderedByCheckerAndCheck := make([][]CheckResult, len(checkers))
127+
resultsOrderedByCheckerAndCheck := make([][]CheckResult, len(factories))
130128

131129
checkersWG := sync.WaitGroup{}
132-
for i, checker := range checkers {
130+
for i, factory := range factories {
133131
checkersWG.Add(1)
134-
go func(ctx context.Context, client ctrlclient.Client, cluster *clusterv1.Cluster, checker Checker, i int) {
135-
checks := checker.Init(ctx, client, cluster)
132+
go func(ctx context.Context, client ctrlclient.Client, cluster *clusterv1.Cluster, factory CheckerFactory, i int) {
133+
checker := factory(client, cluster)
134+
135+
checks := checker.Init(ctx)
136136
resultsOrderedByCheck := make([]CheckResult, len(checks))
137137

138138
checksWG := sync.WaitGroup{}
@@ -148,7 +148,13 @@ func run(ctx context.Context,
148148
resultsOrderedByCheckerAndCheck[i] = resultsOrderedByCheck
149149

150150
checkersWG.Done()
151-
}(ctx, client, cluster, checker, i)
151+
}(
152+
ctx,
153+
client,
154+
cluster,
155+
factory,
156+
i,
157+
)
152158
}
153159
checkersWG.Wait()
154160

pkg/webhook/preflight/preflight_test.go

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@ import (
2222
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
2323
)
2424

25+
func mockCheckerFactory(checker Checker) CheckerFactory {
26+
return func(_ ctrlclient.Client, _ *clusterv1.Cluster) Checker {
27+
return checker
28+
}
29+
}
30+
2531
type mockChecker struct {
26-
name string
2732
checks []Check
2833
}
2934

30-
func (m *mockChecker) Init(_ context.Context, _ ctrlclient.Client, _ *clusterv1.Cluster) []Check {
35+
func (m *mockChecker) Init(_ context.Context) []Check {
3136
return m.checks
3237
}
3338

@@ -349,7 +354,11 @@ func TestHandle(t *testing.T) {
349354
decoder = tt.decoder
350355
}
351356

352-
handler := New(fake.NewClientBuilder().Build(), decoder, tt.checkers...)
357+
factories := make([]CheckerFactory, len(tt.checkers))
358+
for i, checker := range tt.checkers {
359+
factories[i] = mockCheckerFactory(checker)
360+
}
361+
handler := New(fake.NewClientBuilder().Build(), decoder, factories...)
353362

354363
ctx := context.TODO()
355364

@@ -473,7 +482,7 @@ func TestHandleCancelledContext(t *testing.T) {
473482
},
474483
}
475484

476-
handler := New(fake.NewClientBuilder().Build(), decoder, checker)
485+
handler := New(fake.NewClientBuilder().Build(), decoder, mockCheckerFactory(checker))
477486

478487
ctx := context.Background()
479488
ctx, cancel := context.WithCancel(ctx)
@@ -523,14 +532,14 @@ func TestRun_NoCheckers(t *testing.T) {
523532
func TestRun_SingleCheckerSingleCheck(t *testing.T) {
524533
ctx := context.Background()
525534
checker := &mockChecker{
526-
name: "checker1",
527535
checks: []Check{
528536
func(ctx context.Context) CheckResult {
529537
return CheckResult{Name: "check1", Allowed: true}
530538
},
531539
},
532540
}
533-
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []Checker{checker})
541+
factory := mockCheckerFactory(checker)
542+
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []CheckerFactory{factory})
534543
if len(resultsOrderedByCheckerAndCheck) != 1 {
535544
t.Fatalf("expected results for 1 checker, got %d", len(resultsOrderedByCheckerAndCheck))
536545
}
@@ -546,7 +555,6 @@ func TestRun_SingleCheckerSingleCheck(t *testing.T) {
546555
func TestRun_MultipleCheckersMultipleChecks(t *testing.T) {
547556
ctx := context.Background()
548557
checker1 := &mockChecker{
549-
name: "checker1",
550558
checks: []Check{
551559
func(ctx context.Context) CheckResult {
552560
return CheckResult{Name: "c1-check1", Allowed: true}
@@ -557,14 +565,17 @@ func TestRun_MultipleCheckersMultipleChecks(t *testing.T) {
557565
},
558566
}
559567
checker2 := &mockChecker{
560-
name: "checker2",
561568
checks: []Check{
562569
func(ctx context.Context) CheckResult {
563570
return CheckResult{Name: "c2-check1", Error: true}
564571
},
565572
},
566573
}
567-
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []Checker{checker1, checker2})
574+
factories := []CheckerFactory{
575+
mockCheckerFactory(checker1),
576+
mockCheckerFactory(checker2),
577+
}
578+
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, factories)
568579
if len(resultsOrderedByCheckerAndCheck) != 2 {
569580
t.Fatalf("expected results for 2 checkers, got %d", len(resultsOrderedByCheckerAndCheck))
570581
}
@@ -586,7 +597,6 @@ func TestRun_ChecksRunInParallel(t *testing.T) {
586597
var mu sync.Mutex
587598
order := []string{}
588599
checker := &mockChecker{
589-
name: "checker",
590600
checks: []Check{
591601
func(ctx context.Context) CheckResult {
592602
time.Sleep(50 * time.Millisecond)
@@ -603,7 +613,8 @@ func TestRun_ChecksRunInParallel(t *testing.T) {
603613
},
604614
},
605615
}
606-
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []Checker{checker})
616+
factory := mockCheckerFactory(checker)
617+
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []CheckerFactory{factory})
607618

608619
results := resultsOrderedByCheckerAndCheck[0]
609620
if len(results) != 2 {
@@ -620,7 +631,6 @@ func TestRun_CheckersRunInParallel(t *testing.T) {
620631
var mu sync.Mutex
621632
order := []string{}
622633
checker1 := &mockChecker{
623-
name: "checker1",
624634
checks: []Check{
625635
func(ctx context.Context) CheckResult {
626636
time.Sleep(50 * time.Millisecond)
@@ -632,7 +642,6 @@ func TestRun_CheckersRunInParallel(t *testing.T) {
632642
},
633643
}
634644
checker2 := &mockChecker{
635-
name: "checker2",
636645
checks: []Check{
637646
func(ctx context.Context) CheckResult {
638647
mu.Lock()
@@ -642,7 +651,11 @@ func TestRun_CheckersRunInParallel(t *testing.T) {
642651
},
643652
},
644653
}
645-
results := run(ctx, nil, nil, []Checker{checker1, checker2})
654+
factories := []CheckerFactory{
655+
mockCheckerFactory(checker1),
656+
mockCheckerFactory(checker2),
657+
}
658+
results := run(ctx, nil, nil, factories)
646659
if len(results) != 2 {
647660
t.Fatalf("expected 2 results, got %d", len(results))
648661
}
@@ -660,7 +673,6 @@ func TestRun_ContextCancellation(t *testing.T) {
660673
completed := make(chan struct{})
661674

662675
checker := &mockChecker{
663-
name: "checker",
664676
checks: []Check{
665677
func(ctx context.Context) CheckResult {
666678
close(started)
@@ -680,7 +692,8 @@ func TestRun_ContextCancellation(t *testing.T) {
680692
cancel()
681693
}()
682694

683-
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []Checker{checker})
695+
factory := mockCheckerFactory(checker)
696+
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []CheckerFactory{factory})
684697
if len(resultsOrderedByCheckerAndCheck) != 1 {
685698
t.Fatalf("expected results for 1 checker, got %d", len(resultsOrderedByCheckerAndCheck))
686699
}
@@ -705,7 +718,6 @@ func TestRun_OrderOfResults(t *testing.T) {
705718
ctx := context.Background()
706719

707720
checker1 := &mockChecker{
708-
name: "checker1",
709721
checks: []Check{
710722
func(ctx context.Context) CheckResult {
711723
time.Sleep(30 * time.Millisecond)
@@ -719,7 +731,6 @@ func TestRun_OrderOfResults(t *testing.T) {
719731
}
720732

721733
checker2 := &mockChecker{
722-
name: "checker2",
723734
checks: []Check{
724735
func(ctx context.Context) CheckResult {
725736
return CheckResult{Name: "c2-check1"}
@@ -731,7 +742,11 @@ func TestRun_OrderOfResults(t *testing.T) {
731742
},
732743
}
733744

734-
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []Checker{checker1, checker2})
745+
factories := []CheckerFactory{
746+
mockCheckerFactory(checker1),
747+
mockCheckerFactory(checker2),
748+
}
749+
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, factories)
735750
if len(resultsOrderedByCheckerAndCheck) != 2 {
736751
t.Fatalf("expected results for 2 checkers, got %d", len(resultsOrderedByCheckerAndCheck))
737752
}
@@ -772,13 +787,17 @@ func TestRun_LargeNumberOfCheckersAndChecks(t *testing.T) {
772787
}
773788
}
774789
checkers[i] = &mockChecker{
775-
name: fmt.Sprintf("checker%d", i),
776790
checks: checks,
777791
}
778792
}
779793

794+
factories := make([]CheckerFactory, checkerCount)
795+
for i, checker := range checkers {
796+
factories[i] = mockCheckerFactory(checker)
797+
}
798+
780799
start := time.Now()
781-
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, checkers)
800+
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, factories)
782801
duration := time.Since(start)
783802

784803
resultTotal := 0
@@ -812,7 +831,6 @@ func TestRun_ErrorHandlingInChecks(t *testing.T) {
812831
}
813832

814833
checker := &mockChecker{
815-
name: "error-checker",
816834
checks: []Check{
817835
safeCheck,
818836
func(ctx context.Context) CheckResult {
@@ -821,7 +839,8 @@ func TestRun_ErrorHandlingInChecks(t *testing.T) {
821839
},
822840
}
823841

824-
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []Checker{checker})
842+
factory := mockCheckerFactory(checker)
843+
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []CheckerFactory{factory})
825844

826845
if len(resultsOrderedByCheckerAndCheck) != 1 {
827846
t.Fatalf("expected results for 1 checker, got %d", len(resultsOrderedByCheckerAndCheck))
@@ -853,21 +872,23 @@ func TestRun_ZeroChecksFromChecker(t *testing.T) {
853872

854873
// Checker that returns no checks
855874
emptyChecker := &mockChecker{
856-
name: "empty-checker",
857875
checks: []Check{},
858876
}
859877

860878
// Checker that returns some checks
861879
normalChecker := &mockChecker{
862-
name: "normal-checker",
863880
checks: []Check{
864881
func(ctx context.Context) CheckResult {
865882
return CheckResult{Name: "check1", Allowed: true}
866883
},
867884
},
868885
}
869886

870-
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, []Checker{emptyChecker, normalChecker})
887+
factories := []CheckerFactory{
888+
mockCheckerFactory(emptyChecker),
889+
mockCheckerFactory(normalChecker),
890+
}
891+
resultsOrderedByCheckerAndCheck := run(ctx, nil, nil, factories)
871892

872893
if len(resultsOrderedByCheckerAndCheck) != 2 {
873894
t.Fatalf("expected results for 2 checkers, got %d", len(resultsOrderedByCheckerAndCheck))

0 commit comments

Comments
 (0)