Skip to content

Commit

Permalink
Validate image names in the web-hook (#619)
Browse files Browse the repository at this point in the history
  • Loading branch information
thegridman committed Sep 15, 2023
1 parent 96cfb4d commit 98f08f0
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 9 deletions.
53 changes: 48 additions & 5 deletions api/v1/coherence_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ package v1

import (
"fmt"
"github.com/distribution/reference"
"github.com/go-test/deep"
"github.com/oracle/coherence-operator/pkg/operator"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -132,16 +134,19 @@ var commonWebHook = CommonWebHook{}
// The optional warnings will be added to the response as warning messages.
// Return an error if the object is invalid.
func (in *Coherence) ValidateCreate() (admission.Warnings, error) {
var err error
var warnings admission.Warnings

webhookLogger.Info("validate create", "name", in.Name)
err = commonWebHook.validateReplicas(in)
if err != nil {
if err := commonWebHook.validateReplicas(in); err != nil {
return warnings, err
}
if err := commonWebHook.validateImages(in); err != nil {
return warnings, err
}
err = commonWebHook.validateNodePorts(in)
return warnings, err
if err := commonWebHook.validateNodePorts(in); err != nil {
return warnings, err
}
return warnings, nil
}

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

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

// validateImages validates image names
func (in *CommonWebHook) validateImages(c CoherenceResource) error {
var err error
spec := c.GetSpec()
if spec != nil {
img := spec.GetCoherenceImage()
if img != nil {
_, err = reference.Parse(*img)
if err != nil {
return errors.Errorf("invalid spec.image field, %s", err.Error())
}
}
img = spec.GetCoherenceOperatorImage()
if img != nil {
_, err = reference.Parse(*img)
if err != nil {
return errors.Errorf("invalid spec.coherenceUtils.image field, %s", err.Error())
}
}
for _, c := range spec.InitContainers {
_, err = reference.Parse(c.Image)
if err != nil {
return errors.Errorf("invalid image name in init-container %s, %s", c.Name, err.Error())
}
}
for _, c := range spec.SideCars {
_, err = reference.Parse(c.Image)
if err != nil {
return errors.Errorf("invalid image name in side-car container %s, %s", c.Name, err.Error())
}
}
}
return err
}

// validateReplicas validates that spec.replicas >= 0
func (in *CommonWebHook) validateReplicas(c CoherenceResource) error {
replicas := c.GetReplicas()
Expand Down
252 changes: 252 additions & 0 deletions api/v1/coherence_webhook_image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/

package v1_test

import (
. "github.com/onsi/gomega"
coh "github.com/oracle/coherence-operator/api/v1"
corev1 "k8s.io/api/core/v1"
"testing"
)

// Tests for image name validation

func TestCoherenceWithNoImageNames(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{}
_, err := c.ValidateCreate()
g.Expect(err).NotTo(HaveOccurred())
}

func TestCoherenceCreateWithValidImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
Image: stringPtr("test/coherence:1.0"),
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).NotTo(HaveOccurred())
}

func TestCoherenceCreateWithInvalidImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
Image: stringPtr("test/bad image name:1.0"),
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).To(HaveOccurred())
}

func TestCoherenceCreateWithImageNameWithTrailingSpace(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
Image: stringPtr("test/coherence:1.0 "),
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).To(HaveOccurred())
}

func TestCoherenceCreateWithValidOperatorImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
CoherenceUtils: &coh.ImageSpec{
Image: stringPtr("test/coherence:1.0"),
},
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).NotTo(HaveOccurred())
}

func TestCoherenceCreateWithInvalidOperatorImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
CoherenceUtils: &coh.ImageSpec{
Image: stringPtr("test/bad image name:1.0"),
},
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).To(HaveOccurred())
}

func TestCoherenceUpdateWithInvalidImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
Image: stringPtr("test/bad image name:1.0"),
},
},
}
_, err := c.ValidateUpdate(&c)
g.Expect(err).To(HaveOccurred())
}

func TestCoherenceCreateWithValidInitContainerImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
InitContainers: []corev1.Container{
{
Name: "side-one",
Image: "test/coherence:1.0",
},
},
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).NotTo(HaveOccurred())
}

func TestCoherenceCreateWithInvalidInitContainerImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
InitContainers: []corev1.Container{
{
Name: "side-one",
Image: "test/bad image name:1.0",
},
},
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).To(HaveOccurred())
}

func TestCoherenceCreateWithValidSidecarImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
SideCars: []corev1.Container{
{
Name: "side-one",
Image: "test/coherence:1.0",
},
},
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).NotTo(HaveOccurred())
}

func TestCoherenceCreateWithInvalidSidecarImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.Coherence{
Spec: coh.CoherenceStatefulSetResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
SideCars: []corev1.Container{
{
Name: "side-one",
Image: "test/bad image name:1.0",
},
},
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).To(HaveOccurred())
}

func TestJobWithNoImageNames(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.CoherenceJob{}
_, err := c.ValidateCreate()
g.Expect(err).NotTo(HaveOccurred())
}

func TestJobCreateWithInvalidImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.CoherenceJob{
Spec: coh.CoherenceJobResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
Image: stringPtr("test/bad image name:1.0"),
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).To(HaveOccurred())
}

func TestJobCreateWithValidImageDigest(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.CoherenceJob{
Spec: coh.CoherenceJobResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
Image: stringPtr("ghcr.io@sha256:f8a592ee6d31c02feea037c269a87564ae666f91480d1d6be24ff9dd1675c7d0"),
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).NotTo(HaveOccurred())
}

func TestJobCreateWithInvalidImageDigest(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.CoherenceJob{
Spec: coh.CoherenceJobResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
Image: stringPtr("test@sha256:1234"),
},
},
}
_, err := c.ValidateCreate()
g.Expect(err).To(HaveOccurred())
}

func TestJobUpdateWithInvalidImageName(t *testing.T) {
g := NewGomegaWithT(t)

c := coh.CoherenceJob{
Spec: coh.CoherenceJobResourceSpec{
CoherenceResourceSpec: coh.CoherenceResourceSpec{
Image: stringPtr("test/bad image name:1.0"),
},
},
}
_, err := c.ValidateUpdate(&c)
g.Expect(err).To(HaveOccurred())
}
15 changes: 11 additions & 4 deletions api/v1/coherence_webhook_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,16 @@ func (in *CoherenceJob) ValidateCreate() (admission.Warnings, error) {
var warnings admission.Warnings

webhookLogger.Info("validate create", "name", in.Name)
err = commonWebHook.validateReplicas(in)
if err != nil {
if err = commonWebHook.validateReplicas(in); err != nil {
return warnings, err
}
err = commonWebHook.validateNodePorts(in)
return warnings, err
if err = commonWebHook.validateImages(in); err != nil {
return warnings, err
}
if err = commonWebHook.validateNodePorts(in); err != nil {
return warnings, err
}
return warnings, nil
}

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

if err := commonWebHook.validatePersistence(in, prev); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.20

require (
github.com/davecgh/go-spew v1.1.1
github.com/distribution/reference v0.5.0
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
github.com/go-logr/logr v1.2.4
github.com/go-test/deep v1.1.0
Expand Down Expand Up @@ -56,6 +57,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
Expand Down
Loading

0 comments on commit 98f08f0

Please sign in to comment.