Skip to content

Commit 7435716

Browse files
syscod3claude
andcommitted
feat(webhook): ImpVMTemplate validator
Implements admission.Validator[*ImpVMTemplate]. ValidateCreate and ValidateUpdate reject templates where spec.classRef.name is empty — a case the CRD required marker cannot catch because ClassRef is an inline (non-pointer) struct. ValidateDelete is a no-op. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent efa10a8 commit 7435716

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
Copyright 2026.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
"context"
21+
22+
"k8s.io/apimachinery/pkg/util/validation/field"
23+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
24+
25+
impdevv1alpha1 "github.com/syscode-labs/imp/api/v1alpha1"
26+
)
27+
28+
// ImpVMTemplateWebhook validates ImpVMTemplate objects.
29+
type ImpVMTemplateWebhook struct{}
30+
31+
var _ admission.Validator[*impdevv1alpha1.ImpVMTemplate] = &ImpVMTemplateWebhook{}
32+
33+
// ValidateCreate implements admission.Validator[*impdevv1alpha1.ImpVMTemplate].
34+
func (w *ImpVMTemplateWebhook) ValidateCreate(_ context.Context, tmpl *impdevv1alpha1.ImpVMTemplate) (admission.Warnings, error) {
35+
return nil, validateImpVMTemplate(tmpl).ToAggregate()
36+
}
37+
38+
// ValidateUpdate implements admission.Validator[*impdevv1alpha1.ImpVMTemplate].
39+
func (w *ImpVMTemplateWebhook) ValidateUpdate(_ context.Context, _, newTmpl *impdevv1alpha1.ImpVMTemplate) (admission.Warnings, error) {
40+
return nil, validateImpVMTemplate(newTmpl).ToAggregate()
41+
}
42+
43+
// ValidateDelete implements admission.Validator[*impdevv1alpha1.ImpVMTemplate].
44+
func (w *ImpVMTemplateWebhook) ValidateDelete(_ context.Context, _ *impdevv1alpha1.ImpVMTemplate) (admission.Warnings, error) {
45+
return nil, nil
46+
}
47+
48+
// validateImpVMTemplate checks the spec invariants shared by create and update.
49+
func validateImpVMTemplate(tmpl *impdevv1alpha1.ImpVMTemplate) field.ErrorList {
50+
var errs field.ErrorList
51+
52+
if tmpl.Spec.ClassRef.Name == "" {
53+
errs = append(errs, field.Required(
54+
field.NewPath("spec", "classRef", "name"),
55+
"classRef.name is required",
56+
))
57+
}
58+
59+
return errs
60+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
Copyright 2026.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
"context"
21+
"testing"
22+
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
25+
impdevv1alpha1 "github.com/syscode-labs/imp/api/v1alpha1"
26+
)
27+
28+
// newTemplate builds a minimal ImpVMTemplate for use in tests.
29+
func newTemplate(classRefName string) *impdevv1alpha1.ImpVMTemplate {
30+
return &impdevv1alpha1.ImpVMTemplate{
31+
ObjectMeta: metav1.ObjectMeta{
32+
Name: "test-template",
33+
Namespace: "default",
34+
},
35+
Spec: impdevv1alpha1.ImpVMTemplateSpec{
36+
ClassRef: impdevv1alpha1.ClusterObjectRef{Name: classRefName},
37+
},
38+
}
39+
}
40+
41+
func TestImpVMTemplateWebhook_ValidateCreate_Valid(t *testing.T) {
42+
wh := &ImpVMTemplateWebhook{}
43+
tmpl := newTemplate("small")
44+
45+
_, err := wh.ValidateCreate(context.Background(), tmpl)
46+
if err != nil {
47+
t.Errorf("expected no error for valid template, got: %v", err)
48+
}
49+
}
50+
51+
func TestImpVMTemplateWebhook_ValidateCreate_EmptyClassName(t *testing.T) {
52+
wh := &ImpVMTemplateWebhook{}
53+
tmpl := newTemplate("")
54+
55+
_, err := wh.ValidateCreate(context.Background(), tmpl)
56+
if err == nil {
57+
t.Fatal("expected error when classRef.name is empty, got nil")
58+
}
59+
}
60+
61+
func TestImpVMTemplateWebhook_ValidateUpdate_Valid(t *testing.T) {
62+
wh := &ImpVMTemplateWebhook{}
63+
oldTmpl := newTemplate("small")
64+
newTmpl := newTemplate("large")
65+
66+
_, err := wh.ValidateUpdate(context.Background(), oldTmpl, newTmpl)
67+
if err != nil {
68+
t.Errorf("expected no error for valid update, got: %v", err)
69+
}
70+
}
71+
72+
func TestImpVMTemplateWebhook_ValidateDelete(t *testing.T) {
73+
wh := &ImpVMTemplateWebhook{}
74+
tmpl := newTemplate("small")
75+
76+
_, err := wh.ValidateDelete(context.Background(), tmpl)
77+
if err != nil {
78+
t.Errorf("expected no error on delete, got: %v", err)
79+
}
80+
}

0 commit comments

Comments
 (0)