Skip to content

Commit 98f08f0

Browse files
authored
Validate image names in the web-hook (#619)
1 parent 96cfb4d commit 98f08f0

File tree

5 files changed

+317
-9
lines changed

5 files changed

+317
-9
lines changed

api/v1/coherence_webhook.go

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ package v1
88

99
import (
1010
"fmt"
11+
"github.com/distribution/reference"
1112
"github.com/go-test/deep"
1213
"github.com/oracle/coherence-operator/pkg/operator"
14+
"github.com/pkg/errors"
1315
appsv1 "k8s.io/api/apps/v1"
1416
apiequality "k8s.io/apimachinery/pkg/api/equality"
1517
"k8s.io/apimachinery/pkg/runtime"
@@ -132,16 +134,19 @@ var commonWebHook = CommonWebHook{}
132134
// The optional warnings will be added to the response as warning messages.
133135
// Return an error if the object is invalid.
134136
func (in *Coherence) ValidateCreate() (admission.Warnings, error) {
135-
var err error
136137
var warnings admission.Warnings
137138

138139
webhookLogger.Info("validate create", "name", in.Name)
139-
err = commonWebHook.validateReplicas(in)
140-
if err != nil {
140+
if err := commonWebHook.validateReplicas(in); err != nil {
141+
return warnings, err
142+
}
143+
if err := commonWebHook.validateImages(in); err != nil {
141144
return warnings, err
142145
}
143-
err = commonWebHook.validateNodePorts(in)
144-
return warnings, err
146+
if err := commonWebHook.validateNodePorts(in); err != nil {
147+
return warnings, err
148+
}
149+
return warnings, nil
145150
}
146151

147152
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
@@ -154,6 +159,9 @@ func (in *Coherence) ValidateUpdate(previous runtime.Object) (admission.Warnings
154159
if err := commonWebHook.validateReplicas(in); err != nil {
155160
return warnings, err
156161
}
162+
if err := commonWebHook.validateImages(in); err != nil {
163+
return warnings, err
164+
}
157165
prev := previous.(*Coherence)
158166

159167
if err := commonWebHook.validatePersistence(in, prev); err != nil {
@@ -211,6 +219,41 @@ func (in *Coherence) validateVolumeClaimTemplates(previous *Coherence) error {
211219
type CommonWebHook struct {
212220
}
213221

222+
// validateImages validates image names
223+
func (in *CommonWebHook) validateImages(c CoherenceResource) error {
224+
var err error
225+
spec := c.GetSpec()
226+
if spec != nil {
227+
img := spec.GetCoherenceImage()
228+
if img != nil {
229+
_, err = reference.Parse(*img)
230+
if err != nil {
231+
return errors.Errorf("invalid spec.image field, %s", err.Error())
232+
}
233+
}
234+
img = spec.GetCoherenceOperatorImage()
235+
if img != nil {
236+
_, err = reference.Parse(*img)
237+
if err != nil {
238+
return errors.Errorf("invalid spec.coherenceUtils.image field, %s", err.Error())
239+
}
240+
}
241+
for _, c := range spec.InitContainers {
242+
_, err = reference.Parse(c.Image)
243+
if err != nil {
244+
return errors.Errorf("invalid image name in init-container %s, %s", c.Name, err.Error())
245+
}
246+
}
247+
for _, c := range spec.SideCars {
248+
_, err = reference.Parse(c.Image)
249+
if err != nil {
250+
return errors.Errorf("invalid image name in side-car container %s, %s", c.Name, err.Error())
251+
}
252+
}
253+
}
254+
return err
255+
}
256+
214257
// validateReplicas validates that spec.replicas >= 0
215258
func (in *CommonWebHook) validateReplicas(c CoherenceResource) error {
216259
replicas := c.GetReplicas()
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates.
3+
* Licensed under the Universal Permissive License v 1.0 as shown at
4+
* http://oss.oracle.com/licenses/upl.
5+
*/
6+
7+
package v1_test
8+
9+
import (
10+
. "github.com/onsi/gomega"
11+
coh "github.com/oracle/coherence-operator/api/v1"
12+
corev1 "k8s.io/api/core/v1"
13+
"testing"
14+
)
15+
16+
// Tests for image name validation
17+
18+
func TestCoherenceWithNoImageNames(t *testing.T) {
19+
g := NewGomegaWithT(t)
20+
21+
c := coh.Coherence{}
22+
_, err := c.ValidateCreate()
23+
g.Expect(err).NotTo(HaveOccurred())
24+
}
25+
26+
func TestCoherenceCreateWithValidImageName(t *testing.T) {
27+
g := NewGomegaWithT(t)
28+
29+
c := coh.Coherence{
30+
Spec: coh.CoherenceStatefulSetResourceSpec{
31+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
32+
Image: stringPtr("test/coherence:1.0"),
33+
},
34+
},
35+
}
36+
_, err := c.ValidateCreate()
37+
g.Expect(err).NotTo(HaveOccurred())
38+
}
39+
40+
func TestCoherenceCreateWithInvalidImageName(t *testing.T) {
41+
g := NewGomegaWithT(t)
42+
43+
c := coh.Coherence{
44+
Spec: coh.CoherenceStatefulSetResourceSpec{
45+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
46+
Image: stringPtr("test/bad image name:1.0"),
47+
},
48+
},
49+
}
50+
_, err := c.ValidateCreate()
51+
g.Expect(err).To(HaveOccurred())
52+
}
53+
54+
func TestCoherenceCreateWithImageNameWithTrailingSpace(t *testing.T) {
55+
g := NewGomegaWithT(t)
56+
57+
c := coh.Coherence{
58+
Spec: coh.CoherenceStatefulSetResourceSpec{
59+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
60+
Image: stringPtr("test/coherence:1.0 "),
61+
},
62+
},
63+
}
64+
_, err := c.ValidateCreate()
65+
g.Expect(err).To(HaveOccurred())
66+
}
67+
68+
func TestCoherenceCreateWithValidOperatorImageName(t *testing.T) {
69+
g := NewGomegaWithT(t)
70+
71+
c := coh.Coherence{
72+
Spec: coh.CoherenceStatefulSetResourceSpec{
73+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
74+
CoherenceUtils: &coh.ImageSpec{
75+
Image: stringPtr("test/coherence:1.0"),
76+
},
77+
},
78+
},
79+
}
80+
_, err := c.ValidateCreate()
81+
g.Expect(err).NotTo(HaveOccurred())
82+
}
83+
84+
func TestCoherenceCreateWithInvalidOperatorImageName(t *testing.T) {
85+
g := NewGomegaWithT(t)
86+
87+
c := coh.Coherence{
88+
Spec: coh.CoherenceStatefulSetResourceSpec{
89+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
90+
CoherenceUtils: &coh.ImageSpec{
91+
Image: stringPtr("test/bad image name:1.0"),
92+
},
93+
},
94+
},
95+
}
96+
_, err := c.ValidateCreate()
97+
g.Expect(err).To(HaveOccurred())
98+
}
99+
100+
func TestCoherenceUpdateWithInvalidImageName(t *testing.T) {
101+
g := NewGomegaWithT(t)
102+
103+
c := coh.Coherence{
104+
Spec: coh.CoherenceStatefulSetResourceSpec{
105+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
106+
Image: stringPtr("test/bad image name:1.0"),
107+
},
108+
},
109+
}
110+
_, err := c.ValidateUpdate(&c)
111+
g.Expect(err).To(HaveOccurred())
112+
}
113+
114+
func TestCoherenceCreateWithValidInitContainerImageName(t *testing.T) {
115+
g := NewGomegaWithT(t)
116+
117+
c := coh.Coherence{
118+
Spec: coh.CoherenceStatefulSetResourceSpec{
119+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
120+
InitContainers: []corev1.Container{
121+
{
122+
Name: "side-one",
123+
Image: "test/coherence:1.0",
124+
},
125+
},
126+
},
127+
},
128+
}
129+
_, err := c.ValidateCreate()
130+
g.Expect(err).NotTo(HaveOccurred())
131+
}
132+
133+
func TestCoherenceCreateWithInvalidInitContainerImageName(t *testing.T) {
134+
g := NewGomegaWithT(t)
135+
136+
c := coh.Coherence{
137+
Spec: coh.CoherenceStatefulSetResourceSpec{
138+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
139+
InitContainers: []corev1.Container{
140+
{
141+
Name: "side-one",
142+
Image: "test/bad image name:1.0",
143+
},
144+
},
145+
},
146+
},
147+
}
148+
_, err := c.ValidateCreate()
149+
g.Expect(err).To(HaveOccurred())
150+
}
151+
152+
func TestCoherenceCreateWithValidSidecarImageName(t *testing.T) {
153+
g := NewGomegaWithT(t)
154+
155+
c := coh.Coherence{
156+
Spec: coh.CoherenceStatefulSetResourceSpec{
157+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
158+
SideCars: []corev1.Container{
159+
{
160+
Name: "side-one",
161+
Image: "test/coherence:1.0",
162+
},
163+
},
164+
},
165+
},
166+
}
167+
_, err := c.ValidateCreate()
168+
g.Expect(err).NotTo(HaveOccurred())
169+
}
170+
171+
func TestCoherenceCreateWithInvalidSidecarImageName(t *testing.T) {
172+
g := NewGomegaWithT(t)
173+
174+
c := coh.Coherence{
175+
Spec: coh.CoherenceStatefulSetResourceSpec{
176+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
177+
SideCars: []corev1.Container{
178+
{
179+
Name: "side-one",
180+
Image: "test/bad image name:1.0",
181+
},
182+
},
183+
},
184+
},
185+
}
186+
_, err := c.ValidateCreate()
187+
g.Expect(err).To(HaveOccurred())
188+
}
189+
190+
func TestJobWithNoImageNames(t *testing.T) {
191+
g := NewGomegaWithT(t)
192+
193+
c := coh.CoherenceJob{}
194+
_, err := c.ValidateCreate()
195+
g.Expect(err).NotTo(HaveOccurred())
196+
}
197+
198+
func TestJobCreateWithInvalidImageName(t *testing.T) {
199+
g := NewGomegaWithT(t)
200+
201+
c := coh.CoherenceJob{
202+
Spec: coh.CoherenceJobResourceSpec{
203+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
204+
Image: stringPtr("test/bad image name:1.0"),
205+
},
206+
},
207+
}
208+
_, err := c.ValidateCreate()
209+
g.Expect(err).To(HaveOccurred())
210+
}
211+
212+
func TestJobCreateWithValidImageDigest(t *testing.T) {
213+
g := NewGomegaWithT(t)
214+
215+
c := coh.CoherenceJob{
216+
Spec: coh.CoherenceJobResourceSpec{
217+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
218+
Image: stringPtr("ghcr.io@sha256:f8a592ee6d31c02feea037c269a87564ae666f91480d1d6be24ff9dd1675c7d0"),
219+
},
220+
},
221+
}
222+
_, err := c.ValidateCreate()
223+
g.Expect(err).NotTo(HaveOccurred())
224+
}
225+
226+
func TestJobCreateWithInvalidImageDigest(t *testing.T) {
227+
g := NewGomegaWithT(t)
228+
229+
c := coh.CoherenceJob{
230+
Spec: coh.CoherenceJobResourceSpec{
231+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
232+
Image: stringPtr("test@sha256:1234"),
233+
},
234+
},
235+
}
236+
_, err := c.ValidateCreate()
237+
g.Expect(err).To(HaveOccurred())
238+
}
239+
240+
func TestJobUpdateWithInvalidImageName(t *testing.T) {
241+
g := NewGomegaWithT(t)
242+
243+
c := coh.CoherenceJob{
244+
Spec: coh.CoherenceJobResourceSpec{
245+
CoherenceResourceSpec: coh.CoherenceResourceSpec{
246+
Image: stringPtr("test/bad image name:1.0"),
247+
},
248+
},
249+
}
250+
_, err := c.ValidateUpdate(&c)
251+
g.Expect(err).To(HaveOccurred())
252+
}

api/v1/coherence_webhook_job.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,16 @@ func (in *CoherenceJob) ValidateCreate() (admission.Warnings, error) {
8585
var warnings admission.Warnings
8686

8787
webhookLogger.Info("validate create", "name", in.Name)
88-
err = commonWebHook.validateReplicas(in)
89-
if err != nil {
88+
if err = commonWebHook.validateReplicas(in); err != nil {
9089
return warnings, err
9190
}
92-
err = commonWebHook.validateNodePorts(in)
93-
return warnings, err
91+
if err = commonWebHook.validateImages(in); err != nil {
92+
return warnings, err
93+
}
94+
if err = commonWebHook.validateNodePorts(in); err != nil {
95+
return warnings, err
96+
}
97+
return warnings, nil
9498
}
9599

96100
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
@@ -101,6 +105,9 @@ func (in *CoherenceJob) ValidateUpdate(previous runtime.Object) (admission.Warni
101105
if err := commonWebHook.validateReplicas(in); err != nil {
102106
return warnings, err
103107
}
108+
if err := commonWebHook.validateImages(in); err != nil {
109+
return warnings, err
110+
}
104111
prev := previous.(*CoherenceJob)
105112

106113
if err := commonWebHook.validatePersistence(in, prev); err != nil {

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.20
44

55
require (
66
github.com/davecgh/go-spew v1.1.1
7+
github.com/distribution/reference v0.5.0
78
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
89
github.com/go-logr/logr v1.2.4
910
github.com/go-test/deep v1.1.0
@@ -56,6 +57,7 @@ require (
5657
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
5758
github.com/modern-go/reflect2 v1.0.2 // indirect
5859
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
60+
github.com/opencontainers/go-digest v1.0.0 // indirect
5961
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
6062
github.com/prometheus/client_golang v1.16.0 // indirect
6163
github.com/prometheus/client_model v0.4.0 // indirect

0 commit comments

Comments
 (0)