diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 047eb06..0c9f2db 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ on: jobs: lint: - name: Run on Ubuntu + name: Linting on Ubuntu runs-on: ubuntu-latest steps: - name: Clone the code diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index e74f126..0ce9473 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -8,7 +8,7 @@ on: jobs: test-e2e: - name: Run on Ubuntu + name: End-2-End on Ubuntu runs-on: ubuntu-latest steps: - name: Clone the code diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c1a5c4..1117ec9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ on: jobs: test: - name: Run on Ubuntu + name: Testing on Ubuntu runs-on: ubuntu-latest steps: - name: Clone the code diff --git a/api/v3/shared_types.go b/api/v3/shared_types.go index 5b8fa62..db94855 100644 --- a/api/v3/shared_types.go +++ b/api/v3/shared_types.go @@ -45,6 +45,7 @@ type WMSWFS interface { TypedName() string Options() Options HasPostgisData() bool + OwnerInfoRef() string // URL returns the configured service URL URL() smoothoperatormodel.URL diff --git a/api/v3/shared_validation.go b/api/v3/shared_validation.go index 397a22e..5ea0dbe 100644 --- a/api/v3/shared_validation.go +++ b/api/v3/shared_validation.go @@ -1,9 +1,13 @@ package v3 import ( + "context" "fmt" "slices" + smoothoperatorv1 "github.com/pdok/smooth-operator/api/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + sharedValidation "github.com/pdok/smooth-operator/pkg/validation" v1 "k8s.io/api/core/v1" @@ -11,7 +15,33 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) -func ValidateUpdate[W WMSWFS](newW, oldW W, validate func(W, *[]string, *field.ErrorList)) ([]string, error) { +func ValidateCreate[W WMSWFS](c client.Client, obj W, validate func(W, *[]string, *field.ErrorList)) ([]string, error) { + warnings := []string{} + allErrs := field.ErrorList{} + + err := sharedValidation.ValidateLabelsOnCreate(obj.GetLabels()) + if err != nil { + allErrs = append(allErrs, err) + } + + err = sharedValidation.ValidateIngressRouteURLsContainsBaseURL(obj.IngressRouteURLs(false), obj.URL(), nil) + if err != nil { + allErrs = append(allErrs, err) + } + + validate(obj, &warnings, &allErrs) + ValidateOwnerInfo(c, obj, &allErrs) + + if len(allErrs) == 0 { + return warnings, nil + } + + return warnings, apierrors.NewInvalid( + obj.GroupKind(), + obj.GetName(), allErrs) +} + +func ValidateUpdate[W WMSWFS](c client.Client, newW, oldW W, validate func(W, *[]string, *field.ErrorList)) ([]string, error) { warnings := []string{} allErrs := field.ErrorList{} @@ -47,6 +77,7 @@ func ValidateUpdate[W WMSWFS](newW, oldW W, validate func(W, *[]string, *field.E } validate(newW, &warnings, &allErrs) + ValidateOwnerInfo(c, newW, &allErrs) if len(allErrs) == 0 { return warnings, nil @@ -137,3 +168,43 @@ func ValidateInspire[O WMSWFS](obj O, allErrs *field.ErrorList) { } } + +func ValidateOwnerInfo[O WMSWFS](c client.Client, obj O, allErrs *field.ErrorList) { + ownerInfoRef := obj.OwnerInfoRef() + ownerInfo := &smoothoperatorv1.OwnerInfo{} + objectKey := client.ObjectKey{ + Namespace: obj.GetNamespace(), + Name: ownerInfoRef, + } + ctx := context.Background() + err := c.Get(ctx, objectKey, ownerInfo) + fieldPath := field.NewPath("spec").Child("service").Child("ownerInfoRef") + if err != nil { + *allErrs = append(*allErrs, field.NotFound(fieldPath, ownerInfoRef)) + return + } + + if ownerInfo.Spec.NamespaceTemplate == nil { + *allErrs = append(*allErrs, field.Required(fieldPath, "spec.namespaceTemplate missing in "+ownerInfo.Name)) + return + } + + if ((obj.Inspire() != nil && obj.Inspire().ServiceMetadataURL.CSW != nil) || + len(obj.DatasetMetadataIDs()) > 0) && + (ownerInfo.Spec.MetadataUrls == nil || ownerInfo.Spec.MetadataUrls.CSW == nil) { + *allErrs = append(*allErrs, field.Required(fieldPath, "spec.metadataUrls.csw missing in "+ownerInfo.Name)) + return + } + + switch obj.Type() { + case ServiceTypeWFS: + if ownerInfo.Spec.WFS == nil { + *allErrs = append(*allErrs, field.Required(fieldPath, "spec.WFS missing in "+ownerInfo.Name)) + } + case ServiceTypeWMS: + if ownerInfo.Spec.WMS == nil { + *allErrs = append(*allErrs, field.Required(fieldPath, "spec.WMS missing in "+ownerInfo.Name)) + } + } + +} diff --git a/api/v3/wfs_types.go b/api/v3/wfs_types.go index 276a882..858cbda 100644 --- a/api/v3/wfs_types.go +++ b/api/v3/wfs_types.go @@ -332,3 +332,7 @@ func (wfs *WFS) IngressRouteURLs(includeServiceURLWhenEmpty bool) smoothoperator return wfs.Spec.IngressRouteURLs } + +func (wfs *WFS) OwnerInfoRef() string { + return wfs.Spec.Service.OwnerInfoRef +} diff --git a/api/v3/wfs_validation.go b/api/v3/wfs_validation.go index 900e254..e5ef329 100644 --- a/api/v3/wfs_validation.go +++ b/api/v3/wfs_validation.go @@ -4,40 +4,19 @@ import ( "slices" "strings" + "sigs.k8s.io/controller-runtime/pkg/client" + sharedValidation "github.com/pdok/smooth-operator/pkg/validation" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" ) -func (wfs *WFS) ValidateCreate() ([]string, error) { - warnings := []string{} - allErrs := field.ErrorList{} - - err := sharedValidation.ValidateLabelsOnCreate(wfs.Labels) - if err != nil { - allErrs = append(allErrs, err) - } - - err = sharedValidation.ValidateIngressRouteURLsContainsBaseURL(wfs.Spec.IngressRouteURLs, wfs.URL(), nil) - if err != nil { - allErrs = append(allErrs, err) - } - - ValidateWFS(wfs, &warnings, &allErrs) - - if len(allErrs) == 0 { - return warnings, nil - } - - return warnings, apierrors.NewInvalid( - schema.GroupKind{Group: "pdok.nl", Kind: "WFS"}, - wfs.Name, allErrs) +func (wfs *WFS) ValidateCreate(c client.Client) ([]string, error) { + return ValidateCreate(c, wfs, ValidateWFS) } -func (wfs *WFS) ValidateUpdate(wfsOld *WFS) ([]string, error) { - return ValidateUpdate(wfs, wfsOld, ValidateWFS) +func (wfs *WFS) ValidateUpdate(c client.Client, wfsOld *WFS) ([]string, error) { + return ValidateUpdate(c, wfs, wfsOld, ValidateWFS) } func ValidateWFS(wfs *WFS, warnings *[]string, allErrs *field.ErrorList) { diff --git a/api/v3/wms_types.go b/api/v3/wms_types.go index 5ce8ac9..071ff35 100644 --- a/api/v3/wms_types.go +++ b/api/v3/wms_types.go @@ -701,3 +701,7 @@ func (wms *WMS) IngressRouteURLs(includeServiceURLWhenEmpty bool) smoothoperator return wms.Spec.IngressRouteURLs } + +func (wms *WMS) OwnerInfoRef() string { + return wms.Spec.Service.OwnerInfoRef +} diff --git a/api/v3/wms_validation.go b/api/v3/wms_validation.go index 23d98a1..bebd4b6 100644 --- a/api/v3/wms_validation.go +++ b/api/v3/wms_validation.go @@ -5,40 +5,19 @@ import ( "maps" "strings" + "sigs.k8s.io/controller-runtime/pkg/client" + sharedValidation "github.com/pdok/smooth-operator/pkg/validation" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/utils/strings/slices" ) -func (wms *WMS) ValidateCreate() ([]string, error) { - warnings := []string{} - allErrs := field.ErrorList{} - - err := sharedValidation.ValidateLabelsOnCreate(wms.Labels) - if err != nil { - allErrs = append(allErrs, err) - } - - err = sharedValidation.ValidateIngressRouteURLsContainsBaseURL(wms.Spec.IngressRouteURLs, wms.URL(), nil) - if err != nil { - allErrs = append(allErrs, err) - } - - ValidateWMS(wms, &warnings, &allErrs) - - if len(allErrs) == 0 { - return warnings, nil - } - - return warnings, apierrors.NewInvalid( - schema.GroupKind{Group: "pdok.nl", Kind: "WMS"}, - wms.Name, allErrs) +func (wms *WMS) ValidateCreate(c client.Client) ([]string, error) { + return ValidateCreate(c, wms, ValidateWMS) } -func (wms *WMS) ValidateUpdate(wmsOld *WMS) ([]string, error) { - return ValidateUpdate(wms, wmsOld, ValidateWMS) +func (wms *WMS) ValidateUpdate(c client.Client, wmsOld *WMS) ([]string, error) { + return ValidateUpdate(c, wms, wmsOld, ValidateWMS) } // TODO fix linting (cyclop,funlen) diff --git a/internal/controller/shared_controller_test.go b/internal/controller/shared_controller_test.go index 5459239..2d32671 100644 --- a/internal/controller/shared_controller_test.go +++ b/internal/controller/shared_controller_test.go @@ -239,16 +239,19 @@ func testMutates[R Reconciler, O pdoknlv3.WMSWFS](reconcilerFn func() R, resourc Expect(err).NotTo(HaveOccurred()) Expect(owner.Name).Should(Equal("owner")) + Expect(k8sClient.Create(ctx, &owner)).To(Succeed()) + var validationError error switch any(resource).(type) { case *pdoknlv3.WMS: wms := any(resource).(*pdoknlv3.WMS) - _, validationError = wms.ValidateCreate() + _, validationError = wms.ValidateCreate(k8sClient) case *pdoknlv3.WFS: wfs := any(resource).(*pdoknlv3.WFS) - _, validationError = wfs.ValidateCreate() + _, validationError = wfs.ValidateCreate(k8sClient) } Expect(validationError).NotTo(HaveOccurred()) + Expect(k8sClient.Delete(ctx, &owner)).To(Succeed()) }) configMapNames := types.HashedConfigMapNames{} diff --git a/internal/webhook/v3/shared_webhook.go b/internal/webhook/v3/shared_webhook.go index f91d64e..19db028 100644 --- a/internal/webhook/v3/shared_webhook.go +++ b/internal/webhook/v3/shared_webhook.go @@ -1,10 +1,16 @@ package v3 import ( + "context" "encoding/json" "errors" "os" + smoothoperatorv1 "github.com/pdok/smooth-operator/api/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + k8stypes "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + pdoknlv3 "github.com/pdok/mapserver-operator/api/v3" "sigs.k8s.io/yaml" ) @@ -13,6 +19,65 @@ const ( samplesPath = "test_data/" ) +func readOwnerInfo(ownerInfo *smoothoperatorv1.OwnerInfo) error { + data, err := os.ReadFile(samplesPath + "ownerinfo.yaml") + if err != nil { + return err + } + err = yaml.UnmarshalStrict(data, ownerInfo) + if err != nil { + return err + } + return err +} + +func createOwnerInfo(ctx context.Context, c client.Client, ownerInfo *smoothoperatorv1.OwnerInfo) error { + clusterOwner := &smoothoperatorv1.OwnerInfo{} + objectKeyOwner := k8stypes.NamespacedName{ + Namespace: ownerInfo.GetNamespace(), + Name: ownerInfo.GetName(), + } + + err := c.Get(ctx, objectKeyOwner, clusterOwner) + if client.IgnoreNotFound(err) != nil { + return err + } + if err != nil && apierrors.IsNotFound(err) { + resource := ownerInfo.DeepCopy() + err = c.Create(ctx, resource) + if err != nil { + return err + } + err = c.Get(ctx, objectKeyOwner, clusterOwner) + if err != nil { + return err + } + } + return nil +} + +func updateOwnerInfo(ctx context.Context, c client.Client, ownerInfo *smoothoperatorv1.OwnerInfo) error { + clusterOwner := &smoothoperatorv1.OwnerInfo{} + objectKeyOwner := k8stypes.NamespacedName{ + Namespace: ownerInfo.GetNamespace(), + Name: ownerInfo.GetName(), + } + + err := c.Get(ctx, objectKeyOwner, clusterOwner) + if err != nil { + return err + } + + ownerInfo.ResourceVersion = clusterOwner.ResourceVersion + + err = c.Update(ctx, ownerInfo) + if err != nil { + return err + } + + return nil +} + func getSampleFilename[W pdoknlv3.WMSWFS](webservice W) (string, error) { switch any(webservice).(type) { case *pdoknlv3.WFS: diff --git a/internal/webhook/v3/test_data/ownerinfo.yaml b/internal/webhook/v3/test_data/ownerinfo.yaml new file mode 100644 index 0000000..59eaaeb --- /dev/null +++ b/internal/webhook/v3/test_data/ownerinfo.yaml @@ -0,0 +1,33 @@ +apiVersion: pdok.nl/v1 +kind: OwnerInfo +metadata: + name: owner + namespace: default +spec: + metadataUrls: + csw: + hrefTemplate: "https://www.nationaalgeoregister.nl/geonetwork/srv/dut/csw?service=CSW&version=2.0.2&request=GetRecordById&outputschema=http://www.isotc211.org/2005/gmd&elementsetname=full&id={{identifier}}" + type: alternate + namespaceTemplate: "http://{{prefix}}.geonovum.nl" + wfs: + serviceprovider: + providername: PDOK + providersite: + type: simple + href: https://pdok.nl + wms: + contactinformation: + contactpersonprimary: + contactperson: KlantContactCenter PDOK + contactorganization: PDOK + contactposition: pointOfContact + contactaddress: + addresstype: + address: + city: Apeldoorn + stateorprovince: + postcode: + country: Netherlands + contactvoicetelephone: + contactfacsimiletelephone: + contactelectronicmailAddress: BeheerPDOK@kadaster.nl \ No newline at end of file diff --git a/internal/webhook/v3/test_data/v3_wfs.yaml b/internal/webhook/v3/test_data/v3_wfs.yaml index 45fc647..3a609a5 100644 --- a/internal/webhook/v3/test_data/v3_wfs.yaml +++ b/internal/webhook/v3/test_data/v3_wfs.yaml @@ -2,6 +2,7 @@ apiVersion: pdok.nl/v3 kind: WFS metadata: name: sample + namespace: default labels: sample: sample spec: diff --git a/internal/webhook/v3/test_data/v3_wms.yaml b/internal/webhook/v3/test_data/v3_wms.yaml index 99dfdc2..9a4d079 100644 --- a/internal/webhook/v3/test_data/v3_wms.yaml +++ b/internal/webhook/v3/test_data/v3_wms.yaml @@ -9,6 +9,7 @@ metadata: service-type: wms service-version: 1.0.0 name: sample-v3 + namespace: default spec: lifecycle: ttlInDays: 21 @@ -46,7 +47,7 @@ spec: keywords: - keyword1 - keyword2 - ownerInfoRef: + ownerInfoRef: owner fees: "" accessConstraints: "http://creativecommons.org/publicdomain/zero/1.0/deed.nl" maxSize: diff --git a/internal/webhook/v3/webhook_suite_test.go b/internal/webhook/v3/webhook_suite_test.go index 7e4722a..020ba2c 100644 --- a/internal/webhook/v3/webhook_suite_test.go +++ b/internal/webhook/v3/webhook_suite_test.go @@ -28,13 +28,19 @@ package v3 import ( "context" "crypto/tls" + "encoding/json" + "errors" "fmt" "net" "os" + "os/exec" "path/filepath" "testing" "time" + smoothoperatorv1 "github.com/pdok/smooth-operator/api/v1" + "golang.org/x/tools/go/packages" + pdoknlv2beta1 "github.com/pdok/mapserver-operator/api/v2beta1" "k8s.io/apimachinery/pkg/runtime" @@ -89,14 +95,19 @@ var _ = BeforeSuite(func() { err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) + err = smoothoperatorv1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme By("bootstrapping test environment") + ownerInfoCRDPath := must(getOwnerInfoCRDPath()) testEnv = &envtest.Environment{ Scheme: scheme, CRDDirectoryPaths: []string{ filepath.Join("..", "..", "config", "crd", "bases", "pdok.nl_wfs.yaml"), filepath.Join("..", "..", "config", "crd", "bases", "pdok.nl_wms.yaml"), + ownerInfoCRDPath, }, ErrorIfCRDPathMissing: false, CRDInstallOptions: envtest.CRDInstallOptions{ @@ -190,3 +201,31 @@ func getFirstFoundEnvTestBinaryDir() string { } return "" } + +func getOwnerInfoCRDPath() (string, error) { + smoothOperatorModule, err := getModule("github.com/pdok/smooth-operator") + if err != nil { + return "", err + } + if smoothOperatorModule.Dir == "" { + return "", errors.New("cannot find path for smooth-operator module") + } + return filepath.Join(smoothOperatorModule.Dir, "config", "crd", "bases", "pdok.nl_ownerinfo.yaml"), nil +} + +func getModule(name string) (module *packages.Module, err error) { + out, err := exec.Command("go", "list", "-json", "-m", name).Output() + if err != nil { + return + } + module = &packages.Module{} + err = json.Unmarshal(out, module) + return +} + +func must[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} diff --git a/internal/webhook/v3/wfs_webhook.go b/internal/webhook/v3/wfs_webhook.go index 01eb42f..fcdc061 100644 --- a/internal/webhook/v3/wfs_webhook.go +++ b/internal/webhook/v3/wfs_webhook.go @@ -22,13 +22,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -//nolint:dupl package v3 import ( "context" "fmt" + "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -44,7 +45,7 @@ var wfsLog = logf.Log.WithName("wfs-resource") // SetupWFSWebhookWithManager registers the webhook for WFS in the manager. func SetupWFSWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr).For(&pdoknlv3.WFS{}). - WithValidator(&WFSCustomValidator{}). + WithValidator(&WFSCustomValidator{mgr.GetClient()}). Complete() } @@ -59,7 +60,7 @@ func SetupWFSWebhookWithManager(mgr ctrl.Manager) error { // NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, // as this struct is used only for temporary operations and does not need to be deeply copied. type WFSCustomValidator struct { - // TODO(user): Add more fields as needed for validation + Client client.Client } var _ webhook.CustomValidator = &WFSCustomValidator{} @@ -72,7 +73,7 @@ func (v *WFSCustomValidator) ValidateCreate(_ context.Context, obj runtime.Objec } wfsLog.Info("Validation for WFS upon creation", "name", wfs.GetName()) - return wfs.ValidateCreate() + return wfs.ValidateCreate(v.Client) } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type WFS. @@ -87,7 +88,7 @@ func (v *WFSCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj ru } wfsLog.Info("Validation for WFS upon update", "name", wfs.GetName()) - return wfs.ValidateUpdate(wfsOld) + return wfs.ValidateUpdate(v.Client, wfsOld) } // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type WFS. diff --git a/internal/webhook/v3/wfs_webhook_test.go b/internal/webhook/v3/wfs_webhook_test.go index 95607b5..9b87b07 100644 --- a/internal/webhook/v3/wfs_webhook_test.go +++ b/internal/webhook/v3/wfs_webhook_test.go @@ -30,11 +30,11 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + pdoknlv3 "github.com/pdok/mapserver-operator/api/v3" + smoothoperatorv1 "github.com/pdok/smooth-operator/api/v1" smoothoperatormodel "github.com/pdok/smooth-operator/model" smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" corev1 "k8s.io/api/core/v1" - - pdoknlv3 "github.com/pdok/mapserver-operator/api/v3" ) var _ = Describe("WFS Webhook", func() { @@ -42,10 +42,11 @@ var _ = Describe("WFS Webhook", func() { obj *pdoknlv3.WFS oldObj *pdoknlv3.WFS validator WFSCustomValidator + ownerInfo *smoothoperatorv1.OwnerInfo ) BeforeEach(func() { - validator = WFSCustomValidator{} + validator = WFSCustomValidator{k8sClient} Expect(validator).NotTo(BeNil(), "Expected validator to be initialized") sample := &pdoknlv3.WFS{} @@ -57,10 +58,17 @@ var _ = Describe("WFS Webhook", func() { Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") + + ownerInfoSample := &smoothoperatorv1.OwnerInfo{} + Expect(readOwnerInfo(ownerInfoSample)).To(Succeed(), "Reading and parsing the Ownerinfo sample failed") + ownerInfo = ownerInfoSample.DeepCopy() + Expect(ownerInfo).NotTo(BeNil()) + Expect(createOwnerInfo(ctx, k8sClient, ownerInfo)).To(Succeed()) + }) AfterEach(func() { - + Expect(k8sClient.Delete(ctx, ownerInfo)).To(Succeed()) }) Context("When creating or updating WFS under Validating Webhook", func() { @@ -279,6 +287,44 @@ var _ = Describe("WFS Webhook", func() { Expect(err).To(HaveOccurred()) Expect(warnings).To(BeEmpty()) }) + + It("Should deny create if the OwnerInfoRef doesn't exist", func() { + obj.Spec.Service.OwnerInfoRef = "changed" + warnings, err := validator.ValidateCreate(ctx, obj) + Expect(err).To(HaveOccurred()) + Expect(warnings).To(BeEmpty()) + }) + + It("Should deny create if the OwnerInfoRef misses namespaceTemplate", func() { + ownerInfo.Spec.NamespaceTemplate = nil + Expect(updateOwnerInfo(ctx, k8sClient, ownerInfo)).To(Succeed()) + warnings, err := validator.ValidateCreate(ctx, obj) + Expect(err).To(HaveOccurred()) + Expect(warnings).To(BeEmpty()) + }) + + It("Should deny create if the OwnerInfoRef misses csw metadataTemplate", func() { + Expect(obj.Inspire().ServiceMetadataURL.CSW).ToNot(BeNil()) + ownerInfo.Spec.MetadataUrls.CSW = nil + Expect(updateOwnerInfo(ctx, k8sClient, ownerInfo)).To(Succeed()) + warnings, err := validator.ValidateCreate(ctx, obj) + Expect(err).To(HaveOccurred()) + Expect(warnings).To(BeEmpty()) + + ownerInfo.Spec.MetadataUrls = nil + Expect(updateOwnerInfo(ctx, k8sClient, ownerInfo)).To(Succeed()) + warnings, err = validator.ValidateCreate(ctx, obj) + Expect(err).To(HaveOccurred()) + Expect(warnings).To(BeEmpty()) + }) + + It("Should deny create if the OwnerInfoRef misses Wfs", func() { + ownerInfo.Spec.WFS = nil + Expect(updateOwnerInfo(ctx, k8sClient, ownerInfo)).To(Succeed()) + warnings, err := validator.ValidateCreate(ctx, obj) + Expect(err).To(HaveOccurred()) + Expect(warnings).To(BeEmpty()) + }) }) }) diff --git a/internal/webhook/v3/wms_webhook.go b/internal/webhook/v3/wms_webhook.go index b709ef3..ab41585 100644 --- a/internal/webhook/v3/wms_webhook.go +++ b/internal/webhook/v3/wms_webhook.go @@ -22,13 +22,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -//nolint:dupl package v3 import ( "context" "fmt" + "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -59,7 +60,7 @@ func SetupWMSWebhookWithManager(mgr ctrl.Manager) error { // NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, // as this struct is used only for temporary operations and does not need to be deeply copied. type WMSCustomValidator struct { - // TODO(user): Add more fields as needed for validation + Client client.Client } var _ webhook.CustomValidator = &WMSCustomValidator{} @@ -72,7 +73,7 @@ func (v *WMSCustomValidator) ValidateCreate(_ context.Context, obj runtime.Objec } wmsLog.Info("Validation for WMS upon creation", "name", wms.GetName()) - return wms.ValidateCreate() + return wms.ValidateCreate(v.Client) } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type WMS. @@ -87,7 +88,7 @@ func (v *WMSCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj ru } wmsLog.Info("Validation for WMS upon update", "name", wms.GetName()) - return wms.ValidateUpdate(wmsOld) + return wms.ValidateUpdate(v.Client, wmsOld) } // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type WMS. diff --git a/internal/webhook/v3/wms_webhook_test.go b/internal/webhook/v3/wms_webhook_test.go index 5293f8c..13bba8f 100644 --- a/internal/webhook/v3/wms_webhook_test.go +++ b/internal/webhook/v3/wms_webhook_test.go @@ -31,6 +31,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" pdoknlv3 "github.com/pdok/mapserver-operator/api/v3" + smoothoperatorv1 "github.com/pdok/smooth-operator/api/v1" smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" corev1 "k8s.io/api/core/v1" ) @@ -40,15 +41,15 @@ var _ = Describe("WMS Webhook", func() { obj *pdoknlv3.WMS oldObj *pdoknlv3.WMS validator WMSCustomValidator + ownerInfo *smoothoperatorv1.OwnerInfo ) BeforeEach(func() { - validator = WMSCustomValidator{} + validator = WMSCustomValidator{k8sClient} Expect(validator).NotTo(BeNil(), "Expected validator to be initialized") sample := &pdoknlv3.WMS{} - err := readSample(sample) - Expect(err).To(BeNil(), "Reading and parsing the WMS V3 sample failed") + Expect(readSample(sample)).To(Succeed(), "Reading and parsing the WMS V3 sample failed") obj = sample.DeepCopy() oldObj = sample.DeepCopy() @@ -56,10 +57,16 @@ var _ = Describe("WMS Webhook", func() { Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") + ownerInfoSample := &smoothoperatorv1.OwnerInfo{} + Expect(readOwnerInfo(ownerInfoSample)).To(Succeed(), "Reading and parsing the Ownerinfo sample failed") + ownerInfo = ownerInfoSample.DeepCopy() + Expect(ownerInfo).NotTo(BeNil()) + Expect(createOwnerInfo(ctx, k8sClient, ownerInfo)).To(Succeed()) + }) AfterEach(func() { - + Expect(k8sClient.Delete(ctx, ownerInfo)).To(Succeed()) }) Context("When creating or updating WMS under Conversion Webhook", func() {