Skip to content

Commit 6bf1f53

Browse files
committed
refactor: code quality improvements and bug fixes
High priority fixes: - Add type assertion panic protection in informer handlers - Add webhook path duplicate validation Code deduplication: - Extract buildCABundlePatch helper function - Remove redundant HookType definition in server package - Unify errCh close behavior in HTTP servers New features: - Add CertSyncInterval config option (default: 1m) Code simplification: - Simplify determineWebhookRefs using map for seen types
1 parent 21a9532 commit 6bf1f53

File tree

7 files changed

+72
-50
lines changed

7 files changed

+72
-50
lines changed

internal/cabundle/syncer.go

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,21 @@ func (s *Syncer) Start(ctx context.Context) error {
6969

7070
_, err := cmInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
7171
AddFunc: func(obj interface{}) {
72-
cm := obj.(*corev1.ConfigMap)
72+
cm, ok := obj.(*corev1.ConfigMap)
73+
if !ok {
74+
klog.Warningf("unexpected object type in AddFunc: %T", obj)
75+
return
76+
}
7377
if cm.Name == s.caBundleConfigMapName {
7478
s.onConfigMapUpdate(ctx, cm)
7579
}
7680
},
7781
UpdateFunc: func(oldObj, newObj interface{}) {
78-
cm := newObj.(*corev1.ConfigMap)
82+
cm, ok := newObj.(*corev1.ConfigMap)
83+
if !ok {
84+
klog.Warningf("unexpected object type in UpdateFunc: %T", newObj)
85+
return
86+
}
7987
if cm.Name == s.caBundleConfigMapName {
8088
s.onConfigMapUpdate(ctx, cm)
8189
}
@@ -141,9 +149,21 @@ func (s *Syncer) patchWebhook(ctx context.Context, ref WebhookRef, caBundle []by
141149
}
142150
}
143151

152+
// buildCABundlePatch builds a JSON patch for updating caBundle on all webhooks.
153+
func buildCABundlePatch(webhookCount int, caBundle []byte) ([]byte, error) {
154+
var patches []map[string]interface{}
155+
for i := 0; i < webhookCount; i++ {
156+
patches = append(patches, map[string]interface{}{
157+
"op": "replace",
158+
"path": fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i),
159+
"value": caBundle,
160+
})
161+
}
162+
return json.Marshal(patches)
163+
}
164+
144165
// patchValidatingWebhook patches a ValidatingWebhookConfiguration.
145166
func (s *Syncer) patchValidatingWebhook(ctx context.Context, name string, caBundle []byte) error {
146-
// Get current configuration to determine how many webhooks need patching
147167
current, err := s.client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, name, metav1.GetOptions{})
148168
if err != nil {
149169
if errors.IsNotFound(err) {
@@ -153,17 +173,7 @@ func (s *Syncer) patchValidatingWebhook(ctx context.Context, name string, caBund
153173
return err
154174
}
155175

156-
// Build patch for all webhooks
157-
var patches []map[string]interface{}
158-
for i := range current.Webhooks {
159-
patches = append(patches, map[string]interface{}{
160-
"op": "replace",
161-
"path": fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i),
162-
"value": caBundle,
163-
})
164-
}
165-
166-
patchBytes, err := json.Marshal(patches)
176+
patchBytes, err := buildCABundlePatch(len(current.Webhooks), caBundle)
167177
if err != nil {
168178
return fmt.Errorf("failed to marshal patch: %w", err)
169179
}
@@ -175,7 +185,6 @@ func (s *Syncer) patchValidatingWebhook(ctx context.Context, name string, caBund
175185

176186
// patchMutatingWebhook patches a MutatingWebhookConfiguration.
177187
func (s *Syncer) patchMutatingWebhook(ctx context.Context, name string, caBundle []byte) error {
178-
// Get current configuration to determine how many webhooks need patching
179188
current, err := s.client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, name, metav1.GetOptions{})
180189
if err != nil {
181190
if errors.IsNotFound(err) {
@@ -185,17 +194,7 @@ func (s *Syncer) patchMutatingWebhook(ctx context.Context, name string, caBundle
185194
return err
186195
}
187196

188-
// Build patch for all webhooks
189-
var patches []map[string]interface{}
190-
for i := range current.Webhooks {
191-
patches = append(patches, map[string]interface{}{
192-
"op": "replace",
193-
"path": fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i),
194-
"value": caBundle,
195-
})
196-
}
197-
198-
patchBytes, err := json.Marshal(patches)
197+
patchBytes, err := buildCABundlePatch(len(current.Webhooks), caBundle)
199198
if err != nil {
200199
return fmt.Errorf("failed to marshal patch: %w", err)
201200
}

internal/certmanager/manager.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ type Config struct {
4949

5050
// CertRefresh is the refresh interval for the server certificate.
5151
CertRefresh time.Duration
52+
53+
// SyncInterval is the interval between certificate sync checks.
54+
SyncInterval time.Duration
5255
}
5356

5457
// Manager handles certificate rotation using openshift/library-go.
@@ -101,7 +104,11 @@ func (m *Manager) Start(ctx context.Context) error {
101104
m.configMapLister = m.informers.InformersFor(m.config.Namespace).Core().V1().ConfigMaps().Lister()
102105

103106
// Start the sync loop
104-
ticker := time.NewTicker(time.Minute)
107+
syncInterval := m.config.SyncInterval
108+
if syncInterval <= 0 {
109+
syncInterval = time.Minute
110+
}
111+
ticker := time.NewTicker(syncInterval)
105112
defer ticker.Stop()
106113

107114
// Run immediately on start

internal/certprovider/provider.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,21 @@ func (p *Provider) Start(ctx context.Context) error {
5555

5656
_, err := secretInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
5757
AddFunc: func(obj interface{}) {
58-
secret := obj.(*corev1.Secret)
58+
secret, ok := obj.(*corev1.Secret)
59+
if !ok {
60+
klog.Warningf("unexpected object type in AddFunc: %T", obj)
61+
return
62+
}
5963
if secret.Name == p.name {
6064
p.onSecretUpdate(secret)
6165
}
6266
},
6367
UpdateFunc: func(oldObj, newObj interface{}) {
64-
secret := newObj.(*corev1.Secret)
68+
secret, ok := newObj.(*corev1.Secret)
69+
if !ok {
70+
klog.Warningf("unexpected object type in UpdateFunc: %T", newObj)
71+
return
72+
}
6573
if secret.Name == p.name {
6674
p.onSecretUpdate(secret)
6775
}

internal/metrics/server.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ func (s *Server) Start(ctx context.Context) error {
5858
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
5959
errCh <- err
6060
}
61-
close(errCh)
6261
}()
6362

6463
select {

internal/server/server.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ import (
1414
"github.com/jimyag/auto-cert-webhook/internal/certprovider"
1515
)
1616

17-
// HookType defines the type of admission webhook.
18-
type HookType string
19-
2017
// AdmitFunc is the function signature for handling admission requests.
2118
// This is defined here to match the public API type signature.
2219
type AdmitFunc = func(ar admissionv1.AdmissionReview) *admissionv1.AdmissionResponse
@@ -54,7 +51,7 @@ func New(certProvider *certprovider.Provider, config Config) *Server {
5451
}
5552

5653
// RegisterHook registers a webhook handler at the given path.
57-
func (s *Server) RegisterHook(path string, hookType HookType, admit AdmitFunc) {
54+
func (s *Server) RegisterHook(path string, hookType string, admit AdmitFunc) {
5855
s.mux.Handle(path, newAdmissionHandler(admit))
5956
klog.V(2).Infof("Registered %s webhook at %s", hookType, path)
6057
}

run.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,18 @@ func RunWithContext(ctx context.Context, admission Admission) error {
6464
}
6565

6666
// Validate hooks
67+
seenPaths := make(map[string]int)
6768
for i, hook := range hooks {
6869
if hook.Path == "" {
6970
return fmt.Errorf("hook[%d]: path is required", i)
7071
}
7172
if hook.Path[0] != '/' {
7273
return fmt.Errorf("hook[%d]: path must start with '/'", i)
7374
}
75+
if prev, exists := seenPaths[hook.Path]; exists {
76+
return fmt.Errorf("hook[%d]: path %q already defined by hook[%d]", i, hook.Path, prev)
77+
}
78+
seenPaths[hook.Path] = i
7479
if hook.Admit == nil {
7580
return fmt.Errorf("hook[%d]: admit function is required", i)
7681
}
@@ -125,7 +130,7 @@ func RunWithContext(ctx context.Context, admission Admission) error {
125130

126131
// Register webhook handlers
127132
for _, hook := range hooks {
128-
srv.RegisterHook(hook.Path, server.HookType(hook.Type), hook.Admit)
133+
srv.RegisterHook(hook.Path, string(hook.Type), hook.Admit)
129134
klog.Infof("Registered %s webhook at path %s", hook.Type, hook.Path)
130135
}
131136

@@ -163,6 +168,7 @@ func RunWithContext(ctx context.Context, admission Admission) error {
163168
CARefresh: cfg.CARefresh,
164169
CertValidity: cfg.CertValidity,
165170
CertRefresh: cfg.CertRefresh,
171+
SyncInterval: cfg.CertSyncInterval,
166172
})
167173

168174
caBundleSyncer := cabundle.NewSyncer(client, cfg.Namespace, cfg.CABundleConfigMapName, webhookRefs)
@@ -352,25 +358,27 @@ func validateCertDurations(cfg *Config) error {
352358
// determineWebhookRefs determines webhook references for CA bundle syncing.
353359
func determineWebhookRefs(name string, hooks []Hook) []cabundle.WebhookRef {
354360
var refs []cabundle.WebhookRef
355-
356-
hasMutating := false
357-
hasValidating := false
361+
seen := make(map[HookType]bool)
358362

359363
for _, hook := range hooks {
360-
if hook.Type == Mutating && !hasMutating {
361-
refs = append(refs, cabundle.WebhookRef{
362-
Name: name,
363-
Type: cabundle.MutatingWebhook,
364-
})
365-
hasMutating = true
364+
if seen[hook.Type] {
365+
continue
366366
}
367-
if hook.Type == Validating && !hasValidating {
368-
refs = append(refs, cabundle.WebhookRef{
369-
Name: name,
370-
Type: cabundle.ValidatingWebhook,
371-
})
372-
hasValidating = true
367+
seen[hook.Type] = true
368+
369+
var webhookType cabundle.WebhookType
370+
switch hook.Type {
371+
case Mutating:
372+
webhookType = cabundle.MutatingWebhook
373+
case Validating:
374+
webhookType = cabundle.ValidatingWebhook
375+
default:
376+
continue
373377
}
378+
refs = append(refs, cabundle.WebhookRef{
379+
Name: name,
380+
Type: webhookType,
381+
})
374382
}
375383

376384
return refs

types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ type Config struct {
113113
// Env: ACW_CERT_REFRESH (e.g., "12h")
114114
CertRefresh time.Duration `envconfig:"CERT_REFRESH" default:"12h"`
115115

116+
// CertSyncInterval is the interval between certificate sync checks.
117+
// Env: ACW_CERT_SYNC_INTERVAL (e.g., "1m")
118+
CertSyncInterval time.Duration `envconfig:"CERT_SYNC_INTERVAL" default:"1m"`
119+
116120
// LeaderElection enables leader election for certificate rotation.
117121
// Env: ACW_LEADER_ELECTION
118122
LeaderElection *bool `envconfig:"LEADER_ELECTION"`

0 commit comments

Comments
 (0)