diff --git a/api/v2beta1/shared_conversion.go b/api/v2beta1/shared_conversion.go index 3852323..3afe9a1 100644 --- a/api/v2beta1/shared_conversion.go +++ b/api/v2beta1/shared_conversion.go @@ -5,43 +5,34 @@ import ( pdoknlv3 "github.com/pdok/mapserver-operator/api/v3" shared_model "github.com/pdok/smooth-operator/model" + smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" ) -func Pointer[T interface{}](val T) *T { - return &val -} - -func PointerVal[T interface{}](val *T, def T) T { - if val == nil { - return def - } - - return *val -} +func ConvertOptionsV2ToV3(src WMSWFSOptions) pdoknlv3.Options { + defaults := pdoknlv3.GetDefaultOptions() -func ConvertOptionsV2ToV3(src WMSWFSOptions) *pdoknlv3.Options { - return &pdoknlv3.Options{ + return pdoknlv3.Options{ AutomaticCasing: src.AutomaticCasing, IncludeIngress: src.IncludeIngress, - PrefetchData: src.PrefetchData, - ValidateRequests: src.ValidateRequests, - RewriteGroupToDataLayers: src.RewriteGroupToDataLayers, - DisableWebserviceProxy: src.DisableWebserviceProxy, - ValidateChildStyleNameEqual: src.ValidateChildStyleNameEqual, + PrefetchData: smoothoperatorutils.PointerVal(src.PrefetchData, defaults.PrefetchData), + ValidateRequests: smoothoperatorutils.PointerVal(src.ValidateRequests, defaults.ValidateRequests), + RewriteGroupToDataLayers: smoothoperatorutils.PointerVal(src.RewriteGroupToDataLayers, defaults.RewriteGroupToDataLayers), + DisableWebserviceProxy: smoothoperatorutils.PointerVal(src.DisableWebserviceProxy, defaults.DisableWebserviceProxy), + ValidateChildStyleNameEqual: smoothoperatorutils.PointerVal(src.ValidateChildStyleNameEqual, defaults.ValidateChildStyleNameEqual), } } -func ConvertOptionsV3ToV2(src *pdoknlv3.Options) WMSWFSOptions { +func ConvertOptionsV3ToV2(src pdoknlv3.Options) WMSWFSOptions { return WMSWFSOptions{ AutomaticCasing: src.AutomaticCasing, - PrefetchData: src.PrefetchData, IncludeIngress: src.IncludeIngress, - ValidateRequests: src.ValidateRequests, - RewriteGroupToDataLayers: src.RewriteGroupToDataLayers, - DisableWebserviceProxy: src.DisableWebserviceProxy, - ValidateChildStyleNameEqual: src.ValidateChildStyleNameEqual, + PrefetchData: &src.PrefetchData, + ValidateRequests: &src.ValidateRequests, + RewriteGroupToDataLayers: &src.RewriteGroupToDataLayers, + DisableWebserviceProxy: &src.DisableWebserviceProxy, + ValidateChildStyleNameEqual: &src.ValidateChildStyleNameEqual, } } @@ -49,7 +40,7 @@ func ConvertAutoscaling(src Autoscaling) *pdoknlv3.HorizontalPodAutoscalerPatch var minReplicas *int32 if src.MinReplicas != nil { //nolint:gosec - minReplicas = Pointer(int32(*src.MinReplicas)) + minReplicas = smoothoperatorutils.Pointer(int32(*src.MinReplicas)) } var maxReplicas int32 @@ -65,7 +56,7 @@ func ConvertAutoscaling(src Autoscaling) *pdoknlv3.HorizontalPodAutoscalerPatch Name: corev1.ResourceCPU, Target: autoscalingv2.MetricTarget{ Type: autoscalingv2.UtilizationMetricType, - AverageUtilization: Pointer(int32(*src.AverageCPUUtilization)), + AverageUtilization: smoothoperatorutils.Pointer(int32(*src.AverageCPUUtilization)), }, }, }) @@ -164,7 +155,7 @@ func ConvertV2DataToV3(v2 Data) pdoknlv3.Data { BlobKey: v2.Tif.BlobKey, Resample: v2.Tif.Resample, Offsite: v2.Tif.Offsite, - GetFeatureInfoIncludesClass: v2.Tif.GetFeatureInfoIncludesClass, + GetFeatureInfoIncludesClass: smoothoperatorutils.PointerVal(v2.Tif.GetFeatureInfoIncludesClass, false), } } @@ -200,7 +191,7 @@ func ConvertV3DataToV2(v3 pdoknlv3.Data) Data { BlobKey: v3.TIF.BlobKey, Offsite: v3.TIF.Offsite, Resample: v3.TIF.Resample, - GetFeatureInfoIncludesClass: v3.TIF.GetFeatureInfoIncludesClass, + GetFeatureInfoIncludesClass: &v3.TIF.GetFeatureInfoIncludesClass, } } @@ -212,7 +203,7 @@ func NewV2KubernetesObject(lifecycle *shared_model.Lifecycle, podSpecPatch *core if lifecycle != nil && lifecycle.TTLInDays != nil { kub.Lifecycle = &Lifecycle{ - TTLInDays: Pointer(int(*lifecycle.TTLInDays)), + TTLInDays: smoothoperatorutils.Pointer(int(*lifecycle.TTLInDays)), } } @@ -223,15 +214,15 @@ func NewV2KubernetesObject(lifecycle *shared_model.Lifecycle, podSpecPatch *core if scalingSpec != nil { kub.Autoscaling = &Autoscaling{ - MaxReplicas: Pointer(int(scalingSpec.MaxReplicas)), + MaxReplicas: smoothoperatorutils.Pointer(int(scalingSpec.MaxReplicas)), } if scalingSpec.MinReplicas != nil { - kub.Autoscaling.MinReplicas = Pointer(int(*scalingSpec.MinReplicas)) + kub.Autoscaling.MinReplicas = smoothoperatorutils.Pointer(int(*scalingSpec.MinReplicas)) } if scalingSpec.Metrics != nil { - kub.Autoscaling.AverageCPUUtilization = Pointer( + kub.Autoscaling.AverageCPUUtilization = smoothoperatorutils.Pointer( int(*scalingSpec.Metrics[0].Resource.Target.AverageUtilization), ) } diff --git a/api/v2beta1/shared_types.go b/api/v2beta1/shared_types.go index 3a5a9e6..9ea921f 100644 --- a/api/v2beta1/shared_types.go +++ b/api/v2beta1/shared_types.go @@ -90,11 +90,15 @@ type Lifecycle struct { // WMSWFSOptions is the struct with options available in the operator type WMSWFSOptions struct { - IncludeIngress bool `json:"includeIngress"` - AutomaticCasing bool `json:"automaticCasing"` - ValidateRequests *bool `json:"validateRequests,omitempty"` - RewriteGroupToDataLayers *bool `json:"rewriteGroupToDataLayers,omitempty"` - DisableWebserviceProxy *bool `json:"disableWebserviceProxy,omitempty"` + // +kubebuilder:default:=true + IncludeIngress bool `json:"includeIngress"` + // +kubebuilder:default:=true + AutomaticCasing bool `json:"automaticCasing"` + // +kubebuilder:default:=true + ValidateRequests *bool `json:"validateRequests,omitempty"` + RewriteGroupToDataLayers *bool `json:"rewriteGroupToDataLayers,omitempty"` + DisableWebserviceProxy *bool `json:"disableWebserviceProxy,omitempty"` + // +kubebuilder:default:=true PrefetchData *bool `json:"prefetchData,omitempty"` ValidateChildStyleNameEqual *bool `json:"validateChildStyleNameEqual,omitempty"` } diff --git a/api/v2beta1/wfs_conversion.go b/api/v2beta1/wfs_conversion.go index d895fb9..8111134 100644 --- a/api/v2beta1/wfs_conversion.go +++ b/api/v2beta1/wfs_conversion.go @@ -25,6 +25,7 @@ SOFTWARE. package v2beta1 import ( + smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" "log" sharedModel "github.com/pdok/smooth-operator/model" @@ -49,7 +50,7 @@ func (src *WFS) ToV3(dst *pdoknlv3.WFS) error { // Set LifeCycle if defined if src.Spec.Kubernetes.Lifecycle != nil && src.Spec.Kubernetes.Lifecycle.TTLInDays != nil { dst.Spec.Lifecycle = &sharedModel.Lifecycle{ - TTLInDays: Pointer(int32(*src.Spec.Kubernetes.Lifecycle.TTLInDays)), + TTLInDays: smoothoperatorutils.Pointer(int32(*src.Spec.Kubernetes.Lifecycle.TTLInDays)), } } @@ -62,7 +63,7 @@ func (src *WFS) ToV3(dst *pdoknlv3.WFS) error { dst.Spec.PodSpecPatch = ConvertResources(*src.Spec.Kubernetes.Resources) } - dst.Spec.Options = *ConvertOptionsV2ToV3(src.Spec.Options) + dst.Spec.Options = ConvertOptionsV2ToV3(src.Spec.Options) service := pdoknlv3.WFSService{ // TODO what is prefix, Geonovum subdomain? @@ -162,7 +163,7 @@ func (dst *WFS) ConvertFrom(srcRaw conversion.Hub) error { dst.Spec.Kubernetes = NewV2KubernetesObject(src.Spec.Lifecycle, src.Spec.PodSpecPatch, src.Spec.HorizontalPodAutoscalerPatch) - dst.Spec.Options = ConvertOptionsV3ToV2(&src.Spec.Options) + dst.Spec.Options = ConvertOptionsV3ToV2(src.Spec.Options) service := WFSService{ Title: src.Spec.Service.Title, @@ -178,9 +179,9 @@ func (dst *WFS) ConvertFrom(srcRaw conversion.Hub) error { } if src.Spec.Service.Bbox != nil { - service.Extent = Pointer(src.Spec.Service.Bbox.DefaultCRS.ToExtent()) + service.Extent = smoothoperatorutils.Pointer(src.Spec.Service.Bbox.DefaultCRS.ToExtent()) } else { - service.Extent = Pointer("-25000 250000 280000 860000") + service.Extent = smoothoperatorutils.Pointer("-25000 250000 280000 860000") } if src.Spec.Service.Mapfile != nil { @@ -213,7 +214,7 @@ func (dst *WFS) ConvertFrom(srcRaw conversion.Hub) error { } if featureType.Bbox != nil { - featureTypeV2.Extent = Pointer(featureType.Bbox.DefaultCRS.ToExtent()) + featureTypeV2.Extent = smoothoperatorutils.Pointer(featureType.Bbox.DefaultCRS.ToExtent()) } service.FeatureTypes = append(service.FeatureTypes, featureTypeV2) diff --git a/api/v2beta1/wms_conversion.go b/api/v2beta1/wms_conversion.go index af4f4bf..c57b28b 100644 --- a/api/v2beta1/wms_conversion.go +++ b/api/v2beta1/wms_conversion.go @@ -59,7 +59,7 @@ func (src *WMS) ToV3(target *pdoknlv3.WMS) { // Set LifeCycle if defined if src.Spec.Kubernetes.Lifecycle != nil && src.Spec.Kubernetes.Lifecycle.TTLInDays != nil { dst.Spec.Lifecycle = &sharedModel.Lifecycle{ - TTLInDays: Pointer(int32(*src.Spec.Kubernetes.Lifecycle.TTLInDays)), + TTLInDays: smoothoperatorutils.Pointer(int32(*src.Spec.Kubernetes.Lifecycle.TTLInDays)), } } @@ -72,7 +72,7 @@ func (src *WMS) ToV3(target *pdoknlv3.WMS) { dst.Spec.PodSpecPatch = ConvertResources(*src.Spec.Kubernetes.Resources) } - dst.Spec.Options = *ConvertOptionsV2ToV3(src.Spec.Options) + dst.Spec.Options = ConvertOptionsV2ToV3(src.Spec.Options) service := pdoknlv3.WMSService{ URL: CreateBaseURL("https://service.pdok.nl", "wms", src.Spec.General), @@ -80,8 +80,7 @@ func (src *WMS) ToV3(target *pdoknlv3.WMS) { Title: src.Spec.Service.Title, Abstract: src.Spec.Service.Abstract, Keywords: src.Spec.Service.Keywords, - Fees: nil, - AccessConstraints: src.Spec.Service.AccessConstraints, + AccessConstraints: smoothoperatorutils.PointerVal(src.Spec.Service.AccessConstraints, "https://creativecommons.org/publicdomain/zero/1.0/deed.nl"), MaxSize: nil, Resolution: nil, DefResolution: nil, @@ -91,15 +90,15 @@ func (src *WMS) ToV3(target *pdoknlv3.WMS) { } if src.Spec.Service.Maxsize != nil { - service.MaxSize = Pointer(int32(*src.Spec.Service.Maxsize)) + service.MaxSize = smoothoperatorutils.Pointer(int32(*src.Spec.Service.Maxsize)) } if src.Spec.Service.Resolution != nil { - service.Resolution = Pointer(int32(*src.Spec.Service.Resolution)) + service.Resolution = smoothoperatorutils.Pointer(int32(*src.Spec.Service.Resolution)) } if src.Spec.Service.DefResolution != nil { - service.DefResolution = Pointer(int32(*src.Spec.Service.DefResolution)) + service.DefResolution = smoothoperatorutils.Pointer(int32(*src.Spec.Service.DefResolution)) } if src.Spec.Service.Mapfile != nil { @@ -154,13 +153,13 @@ func (dst *WMS) ConvertFrom(srcRaw conversion.Hub) error { dst.Spec.Kubernetes = NewV2KubernetesObject(src.Spec.Lifecycle, src.Spec.PodSpecPatch, src.Spec.HorizontalPodAutoscalerPatch) - dst.Spec.Options = ConvertOptionsV3ToV2(&src.Spec.Options) + dst.Spec.Options = ConvertOptionsV3ToV2(src.Spec.Options) service := WMSService{ Title: src.Spec.Service.Title, Abstract: src.Spec.Service.Abstract, Keywords: src.Spec.Service.Keywords, - AccessConstraints: src.Spec.Service.AccessConstraints, + AccessConstraints: &src.Spec.Service.AccessConstraints, Extent: nil, DataEPSG: src.Spec.Service.DataEPSG, Layers: []WMSLayer{}, @@ -187,11 +186,11 @@ func (dst *WMS) ConvertFrom(srcRaw conversion.Hub) error { } if src.Spec.Service.DefResolution != nil { - service.DefResolution = Pointer(int(*src.Spec.Service.DefResolution)) + service.DefResolution = smoothoperatorutils.Pointer(int(*src.Spec.Service.DefResolution)) } if src.Spec.Service.Resolution != nil { - service.Resolution = Pointer(int(*src.Spec.Service.Resolution)) + service.Resolution = smoothoperatorutils.Pointer(int(*src.Spec.Service.Resolution)) } if src.Spec.Service.StylingAssets != nil { @@ -216,7 +215,7 @@ func (dst *WMS) ConvertFrom(srcRaw conversion.Hub) error { } if src.Spec.Service.MaxSize != nil { - service.Maxsize = Pointer(float64(*src.Spec.Service.MaxSize)) + service.Maxsize = smoothoperatorutils.Pointer(float64(*src.Spec.Service.MaxSize)) } service.Layers = mapV3LayerToV2Layers(src.Spec.Service.Layer, nil, src.Spec.Service.DataEPSG) @@ -227,9 +226,9 @@ func (dst *WMS) ConvertFrom(srcRaw conversion.Hub) error { if service.Extent == nil { service.Extent = l.Extent } else { - bbox := Pointer(sharedModel.ExtentToBBox(*service.Extent)).DeepCopy() + bbox := smoothoperatorutils.Pointer(sharedModel.ExtentToBBox(*service.Extent)).DeepCopy() bbox.Combine(sharedModel.ExtentToBBox(*l.Extent)) - service.Extent = Pointer(bbox.ToExtent()) + service.Extent = smoothoperatorutils.Pointer(bbox.ToExtent()) } } } @@ -322,7 +321,7 @@ func (v2Service WMSService) MapLayersToV3() pdoknlv3.Layer { Keywords: v2Service.Keywords, Layers: []pdoknlv3.Layer{}, BoundingBoxes: boundingBoxes, - Visible: smoothoperatorutils.Pointer(true), + Visible: true, } // adding the bottom layers to the middle layers they are grouped by @@ -361,7 +360,7 @@ func (v2Layer WMSLayer) MapToV3(v2Service WMSService) pdoknlv3.Layer { BoundingBoxes: []pdoknlv3.WMSBoundingBox{}, MinScaleDenominator: nil, MaxScaleDenominator: nil, - Visible: v2Layer.Visible, + Visible: smoothoperatorutils.PointerVal(v2Layer.Visible, true), } if v2Layer.SourceMetadataIdentifier != nil { @@ -393,11 +392,11 @@ func (v2Layer WMSLayer) MapToV3(v2Service WMSService) pdoknlv3.Layer { } if v2Layer.MinScale != nil { - layer.MinScaleDenominator = Pointer(strconv.FormatFloat(*v2Layer.MinScale, 'f', -1, 64)) + layer.MinScaleDenominator = smoothoperatorutils.Pointer(strconv.FormatFloat(*v2Layer.MinScale, 'f', -1, 64)) } if v2Layer.MaxScale != nil { - layer.MaxScaleDenominator = Pointer(strconv.FormatFloat(*v2Layer.MaxScale, 'f', -1, 64)) + layer.MaxScaleDenominator = smoothoperatorutils.Pointer(strconv.FormatFloat(*v2Layer.MaxScale, 'f', -1, 64)) } for _, style := range v2Layer.Styles { @@ -418,7 +417,7 @@ func (v2Layer WMSLayer) MapToV3(v2Service WMSService) pdoknlv3.Layer { } if v2Layer.Data != nil { - layer.Data = Pointer(ConvertV2DataToV3(*v2Layer.Data)) + layer.Data = smoothoperatorutils.Pointer(ConvertV2DataToV3(*v2Layer.Data)) } return layer @@ -444,7 +443,7 @@ func mapV3LayerToV2Layers(v3Layer pdoknlv3.Layer, parent *pdoknlv3.Layer, servic Styles: []Style{}, } - v2Layer.Visible = v3Layer.Visible + v2Layer.Visible = &v3Layer.Visible if parent != nil { v2Layer.Group = parent.Name @@ -460,7 +459,7 @@ func mapV3LayerToV2Layers(v3Layer pdoknlv3.Layer, parent *pdoknlv3.Layer, servic for _, bb := range v3Layer.BoundingBoxes { if bb.CRS == serviceEPSG { - v2Layer.Extent = Pointer(bb.BBox.ToExtent()) + v2Layer.Extent = smoothoperatorutils.Pointer(bb.BBox.ToExtent()) } } @@ -498,7 +497,7 @@ func mapV3LayerToV2Layers(v3Layer pdoknlv3.Layer, parent *pdoknlv3.Layer, servic } if v3Layer.Data != nil { - v2Layer.Data = Pointer(ConvertV3DataToV2(*v3Layer.Data)) + v2Layer.Data = smoothoperatorutils.Pointer(ConvertV3DataToV2(*v3Layer.Data)) } layers = append(layers, v2Layer) diff --git a/api/v3/shared_types.go b/api/v3/shared_types.go index f214252..b6de5ab 100644 --- a/api/v3/shared_types.go +++ b/api/v3/shared_types.go @@ -41,7 +41,7 @@ type WMSWFS interface { PodSpecPatch() *corev1.PodSpec HorizontalPodAutoscalerPatch() *HorizontalPodAutoscalerPatch Type() ServiceType - Options() *Options + Options() Options HasPostgisData() bool // Sha1 hash of the objects name ID() string @@ -71,24 +71,36 @@ type Options struct { // ValidateRequests enables request validation against the service schema. // +kubebuilder:default:=true - ValidateRequests *bool `json:"validateRequests,omitempty"` + ValidateRequests bool `json:"validateRequests"` // RewriteGroupToDataLayers merges group layers into individual data layers. // +kubebuilder:default:=false - RewriteGroupToDataLayers *bool `json:"rewriteGroupToDataLayers,omitempty"` + RewriteGroupToDataLayers bool `json:"rewriteGroupToDataLayers"` // DisableWebserviceProxy disables the built-in proxy for external web services. // +kubebuilder:default:=false - DisableWebserviceProxy *bool `json:"disableWebserviceProxy,omitempty"` + DisableWebserviceProxy bool `json:"disableWebserviceProxy"` // Whether to prefetch data from blob storage, and store it on the local filesystem. // If `false`, the data will be served directly out of blob storage // +kubebuilder:default:=true - PrefetchData *bool `json:"prefetchData,omitempty"` + PrefetchData bool `json:"prefetchData"` // ValidateChildStyleNameEqual ensures child style names match the parent style. // +kubebuilder:default=false - ValidateChildStyleNameEqual *bool `json:"validateChildStyleNameEqual,omitempty"` + ValidateChildStyleNameEqual bool `json:"validateChildStyleNameEqual"` +} + +func GetDefaultOptions() *Options { + return &Options{ + IncludeIngress: true, + AutomaticCasing: true, + ValidateRequests: true, + RewriteGroupToDataLayers: false, + DisableWebserviceProxy: false, + PrefetchData: true, + ValidateChildStyleNameEqual: false, + } } // Inspire holds INSPIRE-specific metadata for the service. @@ -107,8 +119,9 @@ type Inspire struct { Language string `json:"language"` } +// +kubebuilder:validation:XValidation:rule="(has(self.csw) || has(self.custom)) && !(has(self.csw) && has(self.custom))", message="metadataUrl should have csw or custom, not both" type MetadataURL struct { - // CSW describes a metadata record via a metadataIdentifier (UUID). + // CSW describes a metadata record via a metadataIdentifier (UUID) as defined in the OwnerInfo. CSW *Metadata `json:"csw"` // Custom allows arbitrary href @@ -195,16 +208,19 @@ type TIF struct { // +kubebuilder:validation:MinLength:=1 BlobKey string `json:"blobKey"` - // Resample method + // This option can be used to control the resampling kernel used sampling raster images, optional // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:Pattern=`(NEAREST|AVERAGE|BILINEAR)` Resample *string `json:"resample,omitempty"` - // Offsite color for nodata removal + // Sets the color index to treat as transparent for raster layers, optional, hex or rgb // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:Pattern=`(#[0-9A-F]{6}([0-9A-F]{2})?)|([0-9]{1,3}\s[0-9]{1,3}\s[0-9]{1,3})` Offsite *string `json:"offsite,omitempty"` - // Include class names in GetFeatureInfo responses - GetFeatureInfoIncludesClass *bool `json:"getFeatureInfoIncludesClass,omitempty"` + // "When a band represents nominal or ordinal data the class name (from styling) can be included in the getFeatureInfo" + // +kubebuilder:default:=false + GetFeatureInfoIncludesClass bool `json:"getFeatureInfoIncludesClass,omitempty"` } // Column maps a source column name to an optional alias for output. @@ -280,7 +296,7 @@ func Sha1HashOfName[O WMSWFS](obj O) string { return hex.EncodeToString(s.Sum(nil)) } -func (o *Options) UseWebserviceProxy() bool { +func (o Options) UseWebserviceProxy() bool { // options.DisableWebserviceProxy not set or false - return o != nil && (o.DisableWebserviceProxy == nil || !*o.DisableWebserviceProxy) + return !o.DisableWebserviceProxy } diff --git a/api/v3/wfs_types.go b/api/v3/wfs_types.go index a8e099f..1b70793 100644 --- a/api/v3/wfs_types.go +++ b/api/v3/wfs_types.go @@ -74,7 +74,8 @@ type WFSSpec struct { // Optional strategic merge patch for the pod in the deployment. E.g. to patch the resources or add extra env vars. PodSpecPatch *corev1.PodSpec `json:"podSpecPatch,omitempty"` HorizontalPodAutoscalerPatch *HorizontalPodAutoscalerPatch `json:"horizontalPodAutoscalerPatch,omitempty"` - Options Options `json:"options,omitempty"` + // TODO omitting the options field or setting an empty value results in incorrect defaulting of the options + Options Options `json:"options"` // service configuration Service WFSService `json:"service"` @@ -220,8 +221,8 @@ func (wfs *WFS) HorizontalPodAutoscalerPatch() *HorizontalPodAutoscalerPatch { return wfs.Spec.HorizontalPodAutoscalerPatch } -func (wfs *WFS) Options() *Options { - return &wfs.Spec.Options +func (wfs *WFS) Options() Options { + return wfs.Spec.Options } func (wfs *WFS) ID() string { diff --git a/api/v3/wms_types.go b/api/v3/wms_types.go index d855ee9..c298e6f 100644 --- a/api/v3/wms_types.go +++ b/api/v3/wms_types.go @@ -57,7 +57,8 @@ type WMSSpec struct { HorizontalPodAutoscalerPatch *HorizontalPodAutoscalerPatch `json:"horizontalPodAutoscalerPatch,omitempty"` // Optional options for the configuration of the service. - Options Options `json:"options,omitempty"` + // TODO omitting the options field or setting an empty value results in incorrect defaulting of the options + Options Options `json:"options"` // Service specification Service WMSService `json:"service"` @@ -84,17 +85,10 @@ type WMSService struct { // +kubebuilder:validation:MinLength:=1 OwnerInfoRef string `json:"ownerInfoRef"` - // TODO ?? - // +kubebuilder:validation:MinLength:=1 - Fees *string `json:"fees,omitempty"` - // AccessConstraints (licence) that are applicable to the service // +kubebuilder:validation:Pattern:=`https?://.*` // +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl" - AccessConstraints *string `json:"accessConstraints,omitempty"` - - // TODO?? - MaxSize *int32 `json:"maxSize,omitempty"` + AccessConstraints string `json:"accessConstraints,omitempty"` // Optional specification Inspire themes and ids Inspire *Inspire `json:"inspire,omitempty"` @@ -104,10 +98,13 @@ type WMSService struct { //nolint:tagliatelle DataEPSG string `json:"dataEPSG"` - // TODO ?? + // Mapfile setting: Sets the maximum size (in pixels) for both dimensions of the image from a getMap request. + MaxSize *int32 `json:"maxSize,omitempty"` + + // Mapfile setting: Sets the RESOLUTION field in the mapfile, not used when service.mapfile is configured Resolution *int32 `json:"resolution,omitempty"` - // TODO ?? + // Mapfile setting: Sets the DEFRESOLUTION field in the mapfile, not used when service.mapfile is configured DefResolution *int32 `json:"defResolution,omitempty"` // Optional. Required files for the styling of the service @@ -139,6 +136,10 @@ type ConfigMapRef struct { // +kubebuilder:validation:XValidation:message="A layer should have sublayers or data, not both", rule="(has(self.data) || has(self.layers)) && !(has(self.data) && has(self.layers))" // +kubebuilder:validation:XValidation:message="A layer should have keywords when visible", rule="!self.visible || has(self.keywords)" +// +kubebuilder:validation:XValidation:message="A layer should have a title when visible", rule="!self.visible || has(self.title)" +// +kubebuilder:validation:XValidation:message="A layer should have an abstract when visible", rule="!self.visible || has(self.abstract)" +// +kubebuilder:validation:XValidation:message="A layer should have an authority when visible and has a name", rule="!(self.visible && has(self.name)) || has(self.authority)" +// +kubebuilder:validation:XValidation:message="A layer should have a datasetMetadataUrl when visible and has a name", rule="!(self.visible && has(self.name)) || has(self.datasetMetadataUrl)" type Layer struct { // Name of the layer, required for layers on the 2nd or 3rd level // +kubebuilder:validations:MinLength:=1 @@ -157,11 +158,12 @@ type Layer struct { Keywords []string `json:"keywords,omitempty"` // BoundingBoxes of the layer. If omitted the boundingboxes of the parent layer of the service is used. + // +kubebuilder:validations:MinItems:=1 BoundingBoxes []WMSBoundingBox `json:"boundingBoxes,omitempty"` // Whether or not the layer is visible. At least one of the layers must be visible. // +kubebuilder:default:=true - Visible *bool `json:"visible,omitempty"` + Visible bool `json:"visible"` // TODO ?? Authority *Authority `json:"authority,omitempty"` @@ -181,13 +183,14 @@ type Layer struct { // +kubebuilder:validations:MinItems:=1 Styles []Style `json:"styles,omitempty"` - // TODO ?? + // Mapfile setting, sets "LABEL_NO_CLIP=ON" LabelNoClip bool `json:"labelNoClip,omitempty"` // Data (gpkg/postgis/tif) used by the layer Data *Data `json:"data,omitempty"` // Sublayers of the layer + // +kubebuilder:validations:MinItems:=1 Layers []Layer `json:"layers,omitempty"` } @@ -229,11 +232,21 @@ type Style struct { Legend *Legend `json:"legend,omitempty"` } -// TODO add validations + descriptions type Legend struct { - Width int32 `json:"width"` - Height int32 `json:"height"` - Format string `json:"format"` + // The width of the legend in px, defaults to 78 + // + kubebuilder:default=78 + Width int32 `json:"width,omitempty"` + + // The height of the legend in px, defaults to 20 + // + kubebuilder:default=20 + Height int32 `json:"height,omitempty"` + + // Format of the legend, defaults to image/png + // +kubebuilder:default="image/png" + Format string `json:"format,omitempty"` + + // Location of the legend on the blobstore + // +kubebuilder:validation:MinLength:=1 BlobKey string `json:"blobKey"` } @@ -313,9 +326,8 @@ type AnnotatedLayer struct { func (wmsService *WMSService) GetAnnotatedLayers() []AnnotatedLayer { result := make([]AnnotatedLayer, 0) - firstLayer := AnnotatedLayer{} if wmsService.Layer.Name != nil && len(*wmsService.Layer.Name) > 0 { - firstLayer = AnnotatedLayer{ + firstLayer := AnnotatedLayer{ GroupName: nil, IsTopLayer: wmsService.Layer.IsTopLayer(), IsGroupLayer: wmsService.Layer.IsGroupLayer(), @@ -436,10 +448,6 @@ func (layer *Layer) IsTopLayer() bool { return false } -func (layer *Layer) IsVisible() bool { - return layer.Visible == nil || *layer.Visible -} - func (layer *Layer) hasBoundingBoxForCRS(crs string) bool { for _, bbox := range layer.BoundingBoxes { if bbox.CRS == crs { @@ -540,8 +548,8 @@ func (wms *WMS) HorizontalPodAutoscalerPatch() *HorizontalPodAutoscalerPatch { return wms.Spec.HorizontalPodAutoscalerPatch } -func (wms *WMS) Options() *Options { - return &wms.Spec.Options +func (wms *WMS) Options() Options { + return wms.Spec.Options } func (wms *WMS) ID() string { diff --git a/api/v3/wms_validation.go b/api/v3/wms_validation.go index 0439d03..e44c233 100644 --- a/api/v3/wms_validation.go +++ b/api/v3/wms_validation.go @@ -72,15 +72,8 @@ func ValidateWMS(wms *WMS, warnings *[]string, reasons *[]string) { } } - var rewriteGroupToDataLayers = false - if wms.Spec.Options.RewriteGroupToDataLayers != nil { - rewriteGroupToDataLayers = *wms.Spec.Options.RewriteGroupToDataLayers - } - - var validateChildStyleNameEqual = true - if wms.Spec.Options.ValidateChildStyleNameEqual != nil { - validateChildStyleNameEqual = *wms.Spec.Options.ValidateChildStyleNameEqual - } + var rewriteGroupToDataLayers = wms.Spec.Options.RewriteGroupToDataLayers + var validateChildStyleNameEqual = wms.Spec.Options.ValidateChildStyleNameEqual equalChildStyleNames := map[string][]string{} if rewriteGroupToDataLayers && validateChildStyleNameEqual { @@ -117,7 +110,7 @@ func ValidateWMS(wms *WMS, warnings *[]string, reasons *[]string) { } //nolint:nestif - if !*layer.Visible { + if !layer.Visible { if layer.Title != nil { *warnings = append(*warnings, sharedValidation.FormatValidationWarning("layer.title is not used when layer.visible=false", wms.GroupVersionKind(), wms.GetName())) } @@ -143,7 +136,7 @@ func ValidateWMS(wms *WMS, warnings *[]string, reasons *[]string) { } } - if layer.Visible != nil && *layer.Visible { + if layer.Visible { var fields []string hasVisibleLayer = true @@ -186,7 +179,7 @@ func ValidateWMS(wms *WMS, warnings *[]string, reasons *[]string) { } if layerType == GroupLayer || layerType == TopLayer { - if !*layer.Visible { + if !layer.Visible { layerReasons = append(layerReasons, layerType+" must be visible") } if layer.Data != nil { diff --git a/api/v3/zz_generated.deepcopy.go b/api/v3/zz_generated.deepcopy.go index 5656f07..7891d3b 100644 --- a/api/v3/zz_generated.deepcopy.go +++ b/api/v3/zz_generated.deepcopy.go @@ -318,11 +318,6 @@ func (in *Layer) DeepCopyInto(out *Layer) { *out = make([]WMSBoundingBox, len(*in)) copy(*out, *in) } - if in.Visible != nil { - in, out := &in.Visible, &out.Visible - *out = new(bool) - **out = **in - } if in.Authority != nil { in, out := &in.Authority, &out.Authority *out = new(Authority) @@ -448,31 +443,6 @@ func (in *MetadataURL) DeepCopy() *MetadataURL { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Options) DeepCopyInto(out *Options) { *out = *in - if in.ValidateRequests != nil { - in, out := &in.ValidateRequests, &out.ValidateRequests - *out = new(bool) - **out = **in - } - if in.RewriteGroupToDataLayers != nil { - in, out := &in.RewriteGroupToDataLayers, &out.RewriteGroupToDataLayers - *out = new(bool) - **out = **in - } - if in.DisableWebserviceProxy != nil { - in, out := &in.DisableWebserviceProxy, &out.DisableWebserviceProxy - *out = new(bool) - **out = **in - } - if in.PrefetchData != nil { - in, out := &in.PrefetchData, &out.PrefetchData - *out = new(bool) - **out = **in - } - if in.ValidateChildStyleNameEqual != nil { - in, out := &in.ValidateChildStyleNameEqual, &out.ValidateChildStyleNameEqual - *out = new(bool) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Options. @@ -582,11 +552,6 @@ func (in *TIF) DeepCopyInto(out *TIF) { *out = new(string) **out = **in } - if in.GetFeatureInfoIncludesClass != nil { - in, out := &in.GetFeatureInfoIncludesClass, &out.GetFeatureInfoIncludesClass - *out = new(bool) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TIF. @@ -738,7 +703,7 @@ func (in *WFSSpec) DeepCopyInto(out *WFSSpec) { *out = new(HorizontalPodAutoscalerPatch) (*in).DeepCopyInto(*out) } - in.Options.DeepCopyInto(&out.Options) + out.Options = in.Options in.Service.DeepCopyInto(&out.Service) } @@ -835,26 +800,16 @@ func (in *WMSService) DeepCopyInto(out *WMSService) { *out = make([]string, len(*in)) copy(*out, *in) } - if in.Fees != nil { - in, out := &in.Fees, &out.Fees - *out = new(string) - **out = **in - } - if in.AccessConstraints != nil { - in, out := &in.AccessConstraints, &out.AccessConstraints - *out = new(string) - **out = **in + if in.Inspire != nil { + in, out := &in.Inspire, &out.Inspire + *out = new(Inspire) + (*in).DeepCopyInto(*out) } if in.MaxSize != nil { in, out := &in.MaxSize, &out.MaxSize *out = new(int32) **out = **in } - if in.Inspire != nil { - in, out := &in.Inspire, &out.Inspire - *out = new(Inspire) - (*in).DeepCopyInto(*out) - } if in.Resolution != nil { in, out := &in.Resolution, &out.Resolution *out = new(int32) @@ -906,7 +861,7 @@ func (in *WMSSpec) DeepCopyInto(out *WMSSpec) { *out = new(HorizontalPodAutoscalerPatch) (*in).DeepCopyInto(*out) } - in.Options.DeepCopyInto(&out.Options) + out.Options = in.Options in.Service.DeepCopyInto(&out.Service) } diff --git a/config/crd/bases/pdok.nl_wfs.yaml b/config/crd/bases/pdok.nl_wfs.yaml index b29734d..073261b 100644 --- a/config/crd/bases/pdok.nl_wfs.yaml +++ b/config/crd/bases/pdok.nl_wfs.yaml @@ -156,18 +156,22 @@ spec: the operator properties: automaticCasing: + default: true type: boolean disableWebserviceProxy: type: boolean includeIngress: + default: true type: boolean prefetchData: + default: true type: boolean rewriteGroupToDataLayers: type: boolean validateChildStyleNameEqual: type: boolean validateRequests: + default: true type: boolean required: - automaticCasing @@ -1093,7 +1097,12 @@ spec: type: boolean required: - automaticCasing + - disableWebserviceProxy - includeIngress + - prefetchData + - rewriteGroupToDataLayers + - validateChildStyleNameEqual + - validateRequests type: object podSpecPatch: description: Optional strategic merge patch for the pod in the deployment. @@ -1316,16 +1325,23 @@ spec: pattern: \.(tif?f|vrt)$ type: string getFeatureInfoIncludesClass: - description: Include class names in GetFeatureInfo - responses + default: false + description: '"When a band represents nominal or + ordinal data the class name (from styling) can + be included in the getFeatureInfo"' type: boolean offsite: - description: Offsite color for nodata removal + description: Sets the color index to treat as transparent + for raster layers, optional, hex or rgb minLength: 1 + pattern: (#[0-9A-F]{6}([0-9A-F]{2})?)|([0-9]{1,3}\s[0-9]{1,3}\s[0-9]{1,3}) type: string resample: - description: Resample method + description: This option can be used to control + the resampling kernel used sampling raster images, + optional minLength: 1 + pattern: (NEAREST|AVERAGE|BILINEAR) type: string required: - blobKey @@ -1340,7 +1356,7 @@ spec: properties: csw: description: CSW describes a metadata record via a metadataIdentifier - (UUID). + (UUID) as defined in the OwnerInfo. properties: metadataIdentifier: description: MetadataIdentifier is the record's @@ -1369,6 +1385,10 @@ spec: required: - csw type: object + x-kubernetes-validations: + - message: metadataUrl should have csw or custom, not both + rule: (has(self.csw) || has(self.custom)) && !(has(self.csw) + && has(self.custom)) keywords: description: Keywords of the feature items: @@ -1410,7 +1430,7 @@ spec: properties: csw: description: CSW describes a metadata record via a metadataIdentifier - (UUID). + (UUID) as defined in the OwnerInfo. properties: metadataIdentifier: description: MetadataIdentifier is the record's UUID @@ -1438,6 +1458,10 @@ spec: required: - csw type: object + x-kubernetes-validations: + - message: metadataUrl should have csw or custom, not both + rule: (has(self.csw) || has(self.custom)) && !(has(self.csw) + && has(self.custom)) spatialDatasetIdentifier: description: SpatialDatasetIdentifier is the ID uniquely identifying the dataset. @@ -1517,6 +1541,7 @@ spec: - url type: object required: + - options - service type: object status: diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index 3c6f470..d1f8cd9 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -149,18 +149,22 @@ spec: description: WMSWFSOptions is the struct with options available in the operator properties: automaticCasing: + default: true type: boolean disableWebserviceProxy: type: boolean includeIngress: + default: true type: boolean prefetchData: + default: true type: boolean rewriteGroupToDataLayers: type: boolean validateChildStyleNameEqual: type: boolean validateRequests: + default: true type: boolean required: - automaticCasing @@ -1087,7 +1091,12 @@ spec: type: boolean required: - automaticCasing + - disableWebserviceProxy - includeIngress + - prefetchData + - rewriteGroupToDataLayers + - validateChildStyleNameEqual + - validateRequests type: object podSpecPatch: description: Optional strategic merge patch for the pod in the deployment. E.g. to patch the resources or add extra env vars. @@ -1110,11 +1119,9 @@ spec: pattern: (EPSG|CRS):\d+ type: string defResolution: + description: 'Mapfile setting: Sets the DEFRESOLUTION field in the mapfile, not used when service.mapfile is configured' format: int32 type: integer - fees: - minLength: 1 - type: string inspire: description: Optional specification Inspire themes and ids properties: @@ -1126,7 +1133,7 @@ spec: description: ServiceMetadataURL references the CSW or custom metadata record. properties: csw: - description: CSW describes a metadata record via a metadataIdentifier (UUID). + description: CSW describes a metadata record via a metadataIdentifier (UUID) as defined in the OwnerInfo. properties: metadataIdentifier: description: MetadataIdentifier is the record's UUID @@ -1154,6 +1161,9 @@ spec: required: - csw type: object + x-kubernetes-validations: + - message: metadataUrl should have csw or custom, not both + rule: (has(self.csw) || has(self.custom)) && !(has(self.csw) && has(self.custom)) spatialDatasetIdentifier: description: SpatialDatasetIdentifier is the ID uniquely identifying the dataset. minLength: 1 @@ -1312,15 +1322,18 @@ spec: pattern: \.(tif?f|vrt)$ type: string getFeatureInfoIncludesClass: - description: Include class names in GetFeatureInfo responses + default: false + description: '"When a band represents nominal or ordinal data the class name (from styling) can be included in the getFeatureInfo"' type: boolean offsite: - description: Offsite color for nodata removal + description: Sets the color index to treat as transparent for raster layers, optional, hex or rgb minLength: 1 + pattern: (#[0-9A-F]{6}([0-9A-F]{2})?)|([0-9]{1,3}\s[0-9]{1,3}\s[0-9]{1,3}) type: string resample: - description: Resample method + description: This option can be used to control the resampling kernel used sampling raster images, optional minLength: 1 + pattern: (NEAREST|AVERAGE|BILINEAR) type: string required: - blobKey @@ -1333,7 +1346,7 @@ spec: description: Links to metadata properties: csw: - description: CSW describes a metadata record via a metadataIdentifier (UUID). + description: CSW describes a metadata record via a metadataIdentifier (UUID) as defined in the OwnerInfo. properties: metadataIdentifier: description: MetadataIdentifier is the record's UUID @@ -1361,12 +1374,16 @@ spec: required: - csw type: object + x-kubernetes-validations: + - message: metadataUrl should have csw or custom, not both + rule: (has(self.csw) || has(self.custom)) && !(has(self.csw) && has(self.custom)) keywords: description: Keywords of the layer, required if the layer is visible items: type: string type: array labelNoClip: + description: Mapfile setting, sets "LABEL_NO_CLIP=ON" type: boolean layers: description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' @@ -1513,15 +1530,18 @@ spec: pattern: \.(tif?f|vrt)$ type: string getFeatureInfoIncludesClass: - description: Include class names in GetFeatureInfo responses + default: false + description: '"When a band represents nominal or ordinal data the class name (from styling) can be included in the getFeatureInfo"' type: boolean offsite: - description: Offsite color for nodata removal + description: Sets the color index to treat as transparent for raster layers, optional, hex or rgb minLength: 1 + pattern: (#[0-9A-F]{6}([0-9A-F]{2})?)|([0-9]{1,3}\s[0-9]{1,3}\s[0-9]{1,3}) type: string resample: - description: Resample method + description: This option can be used to control the resampling kernel used sampling raster images, optional minLength: 1 + pattern: (NEAREST|AVERAGE|BILINEAR) type: string required: - blobKey @@ -1534,7 +1554,7 @@ spec: description: Links to metadata properties: csw: - description: CSW describes a metadata record via a metadataIdentifier (UUID). + description: CSW describes a metadata record via a metadataIdentifier (UUID) as defined in the OwnerInfo. properties: metadataIdentifier: description: MetadataIdentifier is the record's UUID @@ -1562,12 +1582,16 @@ spec: required: - csw type: object + x-kubernetes-validations: + - message: metadataUrl should have csw or custom, not both + rule: (has(self.csw) || has(self.custom)) && !(has(self.csw) && has(self.custom)) keywords: description: Keywords of the layer, required if the layer is visible items: type: string type: array labelNoClip: + description: Mapfile setting, sets "LABEL_NO_CLIP=ON" type: boolean layers: description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' @@ -1714,15 +1738,18 @@ spec: pattern: \.(tif?f|vrt)$ type: string getFeatureInfoIncludesClass: - description: Include class names in GetFeatureInfo responses + default: false + description: '"When a band represents nominal or ordinal data the class name (from styling) can be included in the getFeatureInfo"' type: boolean offsite: - description: Offsite color for nodata removal + description: Sets the color index to treat as transparent for raster layers, optional, hex or rgb minLength: 1 + pattern: (#[0-9A-F]{6}([0-9A-F]{2})?)|([0-9]{1,3}\s[0-9]{1,3}\s[0-9]{1,3}) type: string resample: - description: Resample method + description: This option can be used to control the resampling kernel used sampling raster images, optional minLength: 1 + pattern: (NEAREST|AVERAGE|BILINEAR) type: string required: - blobKey @@ -1735,7 +1762,7 @@ spec: description: Links to metadata properties: csw: - description: CSW describes a metadata record via a metadataIdentifier (UUID). + description: CSW describes a metadata record via a metadataIdentifier (UUID) as defined in the OwnerInfo. properties: metadataIdentifier: description: MetadataIdentifier is the record's UUID @@ -1763,12 +1790,16 @@ spec: required: - csw type: object + x-kubernetes-validations: + - message: metadataUrl should have csw or custom, not both + rule: (has(self.csw) || has(self.custom)) && !(has(self.csw) && has(self.custom)) keywords: description: Keywords of the layer, required if the layer is visible items: type: string type: array labelNoClip: + description: Mapfile setting, sets "LABEL_NO_CLIP=ON" type: boolean maxscaledenominator: description: The maximum scale at which this layer functions @@ -1790,20 +1821,23 @@ spec: legend: properties: blobKey: + description: Location of the legend on the blobstore + minLength: 1 type: string format: + default: image/png + description: Format of the legend, defaults to image/png type: string height: + description: The height of the legend in px, defaults to 20 format: int32 type: integer width: + description: The width of the legend in px, defaults to 78 format: int32 type: integer required: - blobKey - - format - - height - - width type: object name: type: string @@ -1823,11 +1857,20 @@ spec: description: Whether or not the layer is visible. At least one of the layers must be visible. type: boolean required: + - visible - name type: object x-kubernetes-validations: - message: A layer should have keywords when visible - rule: '!self.visible || has(self.keywords)' + rule: self.visible == false || has(self.keywords) + - message: A layer should have a title when visible + rule: '!self.visible || has(self.title)' + - message: A layer should have an abstract when visible + rule: '!self.visible || has(self.abstract)' + - message: A layer should have an authority when visible and has a name + rule: '!(self.visible && has(self.name)) || has(self.authority)' + - message: A layer should have a datasetMetadataUrl when visible and has a name + rule: '!(self.visible && has(self.name)) || has(self.datasetMetadataUrl)' type: array maxscaledenominator: description: The maximum scale at which this layer functions @@ -1849,20 +1892,23 @@ spec: legend: properties: blobKey: + description: Location of the legend on the blobstore + minLength: 1 type: string format: + default: image/png + description: Format of the legend, defaults to image/png type: string height: + description: The height of the legend in px, defaults to 20 format: int32 type: integer width: + description: The width of the legend in px, defaults to 78 format: int32 type: integer required: - blobKey - - format - - height - - width type: object name: type: string @@ -1882,13 +1928,22 @@ spec: description: Whether or not the layer is visible. At least one of the layers must be visible. type: boolean required: + - visible - name type: object x-kubernetes-validations: - message: A layer should have sublayers or data, not both rule: (has(self.data) || has(self.layers)) && !(has(self.data) && has(self.layers)) - message: A layer should have keywords when visible - rule: '!self.visible || has(self.keywords)' + rule: self.visible == false || has(self.keywords) + - message: A layer should have a title when visible + rule: '!self.visible || has(self.title)' + - message: A layer should have an abstract when visible + rule: '!self.visible || has(self.abstract)' + - message: A layer should have an authority when visible and has a name + rule: '!(self.visible && has(self.name)) || has(self.authority)' + - message: A layer should have a datasetMetadataUrl when visible and has a name + rule: '!(self.visible && has(self.name)) || has(self.datasetMetadataUrl)' type: array maxscaledenominator: description: The maximum scale at which this layer functions @@ -1910,20 +1965,23 @@ spec: legend: properties: blobKey: + description: Location of the legend on the blobstore + minLength: 1 type: string format: + default: image/png + description: Format of the legend, defaults to image/png type: string height: + description: The height of the legend in px, defaults to 20 format: int32 type: integer width: + description: The width of the legend in px, defaults to 78 format: int32 type: integer required: - blobKey - - format - - height - - width type: object name: type: string @@ -1942,12 +2000,22 @@ spec: default: true description: Whether or not the layer is visible. At least one of the layers must be visible. type: boolean + required: + - visible type: object x-kubernetes-validations: - message: A layer should have sublayers or data, not both rule: (has(self.data) || has(self.layers)) && !(has(self.data) && has(self.layers)) - message: A layer should have keywords when visible - rule: '!self.visible || has(self.keywords)' + rule: self.visible == false || has(self.keywords) + - message: A layer should have a title when visible + rule: '!self.visible || has(self.title)' + - message: A layer should have an abstract when visible + rule: '!self.visible || has(self.abstract)' + - message: A layer should have an authority when visible and has a name + rule: '!(self.visible && has(self.name)) || has(self.authority)' + - message: A layer should have a datasetMetadataUrl when visible and has a name + rule: '!(self.visible && has(self.name)) || has(self.datasetMetadataUrl)' mapfile: description: Custom mapfile properties: @@ -1977,6 +2045,7 @@ spec: - configMapKeyRef type: object maxSize: + description: 'Mapfile setting: Sets the maximum size (in pixels) for both dimensions of the image from a getMap request.' format: int32 type: integer ownerInfoRef: @@ -1984,6 +2053,7 @@ spec: minLength: 1 type: string resolution: + description: 'Mapfile setting: Sets the RESOLUTION field in the mapfile, not used when service.mapfile is configured' format: int32 type: integer stylingAssets: @@ -2028,6 +2098,7 @@ spec: - url type: object required: + - options - service type: object status: diff --git a/internal/controller/blobdownload/blob_download.go b/internal/controller/blobdownload/blob_download.go index 301de58..6751da7 100644 --- a/internal/controller/blobdownload/blob_download.go +++ b/internal/controller/blobdownload/blob_download.go @@ -3,7 +3,6 @@ package blobdownload import ( _ "embed" "fmt" - smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" "k8s.io/utils/strings/slices" "regexp" "strings" @@ -86,15 +85,13 @@ func GetBlobDownloadInitContainer[O pdoknlv3.WMSWFS](obj O, image, blobsConfigNa corev1.ResourceCPU: resourceCPU, } - if options := obj.Options(); options != nil { - if options.PrefetchData != nil && *options.PrefetchData { - mount := corev1.VolumeMount{ - Name: mapserver.ConfigMapBlobDownloadVolumeName, - MountPath: "/srv/scripts", - ReadOnly: true, - } - initContainer.VolumeMounts = append(initContainer.VolumeMounts, mount) + if obj.Options().PrefetchData { + mount := corev1.VolumeMount{ + Name: mapserver.ConfigMapBlobDownloadVolumeName, + MountPath: "/srv/scripts", + ReadOnly: true, } + initContainer.VolumeMounts = append(initContainer.VolumeMounts, mount) } return &initContainer, nil @@ -107,13 +104,13 @@ func GetArgs[W pdoknlv3.WMSWFS](webservice W) (args string, err error) { case *pdoknlv3.WFS: if WFS, ok := any(webservice).(*pdoknlv3.WFS); ok { createConfig(&sb) - downloadGeopackage(&sb, smoothoperatorutils.PointerVal(WFS.Spec.Options.PrefetchData, false)) + downloadGeopackage(&sb, WFS.Spec.Options.PrefetchData) // In case of WFS no downloads are needed for TIFFs, styling assets and legends } case *pdoknlv3.WMS: if WMS, ok := any(webservice).(*pdoknlv3.WMS); ok { createConfig(&sb) - downloadGeopackage(&sb, smoothoperatorutils.PointerVal(WMS.Spec.Options.PrefetchData, false)) + downloadGeopackage(&sb, WMS.Spec.Options.PrefetchData) if err = downloadTiffs(&sb, WMS); err != nil { return "", err } @@ -143,7 +140,7 @@ func downloadGeopackage(sb *strings.Builder, prefetchData bool) { } func downloadTiffs(sb *strings.Builder, wms *pdoknlv3.WMS) error { - if !*wms.Spec.Options.PrefetchData { + if !wms.Spec.Options.PrefetchData { return nil } diff --git a/internal/controller/blobdownload/blob_download_test.go b/internal/controller/blobdownload/blob_download_test.go index 206a54b..48c70b9 100644 --- a/internal/controller/blobdownload/blob_download_test.go +++ b/internal/controller/blobdownload/blob_download_test.go @@ -83,7 +83,7 @@ func TestGetArgsForWFS(t *testing.T) { Title: "wfs-prefetch-service-title", }, Options: v3.Options{ - PrefetchData: smoothoperatorutils.Pointer(true), + PrefetchData: true, }, }, }, @@ -100,7 +100,7 @@ func TestGetArgsForWFS(t *testing.T) { Title: "wfs-noprefetch-service-title", }, Options: v3.Options{ - PrefetchData: smoothoperatorutils.Pointer(false), + PrefetchData: false, }, }, }, @@ -206,7 +206,7 @@ func TestGetArgsForWMS(t *testing.T) { }, }, Options: v3.Options{ - PrefetchData: smoothoperatorutils.Pointer(true), + PrefetchData: true, }, }, }, @@ -273,7 +273,7 @@ func TestGetArgsForWMS(t *testing.T) { }, }, Options: v3.Options{ - PrefetchData: smoothoperatorutils.Pointer(true), + PrefetchData: true, }, }, }, diff --git a/internal/controller/capabilitiesgenerator/capabilities_generator_test.go b/internal/controller/capabilitiesgenerator/capabilities_generator_test.go index 07506c2..eae97dd 100644 --- a/internal/controller/capabilitiesgenerator/capabilities_generator_test.go +++ b/internal/controller/capabilitiesgenerator/capabilities_generator_test.go @@ -652,8 +652,8 @@ func TestInputForWMS(t *testing.T) { pdoknlv3.SetHost("http://localhost") contactPersonPrimary := smoothoperatorv1.ContactPersonPrimary{ - ContactPerson: asPtr("KlantContactCenter PDOK"), - ContactOrganization: asPtr("PDOK"), + ContactPerson: smoothoperatorutils.Pointer("KlantContactCenter PDOK"), + ContactOrganization: smoothoperatorutils.Pointer("PDOK"), } ownerInfo := smoothoperatorv1.OwnerInfo{ @@ -667,18 +667,18 @@ func TestInputForWMS(t *testing.T) { WMS: &smoothoperatorv1.WMS{ ContactInformation: smoothoperatorv1.ContactInformation{ ContactPersonPrimary: &contactPersonPrimary, - ContactPosition: asPtr("pointOfContact"), + ContactPosition: smoothoperatorutils.Pointer("pointOfContact"), ContactAddress: &smoothoperatorv1.ContactAddress{ - AddressType: asPtr("Work"), + AddressType: smoothoperatorutils.Pointer("Work"), Address: nil, - City: asPtr("Apeldoorn"), + City: smoothoperatorutils.Pointer("Apeldoorn"), StateOrProvince: nil, PostCode: nil, - Country: asPtr("The Netherlands"), + Country: smoothoperatorutils.Pointer("The Netherlands"), }, ContactVoiceTelephone: nil, ContactFacsimileTelephone: nil, - ContactElectronicMailAddress: asPtr("BeheerPDOK@kadaster.nl"), + ContactElectronicMailAddress: smoothoperatorutils.Pointer("BeheerPDOK@kadaster.nl"), }, }, }, diff --git a/internal/controller/capabilitiesgenerator/mapper.go b/internal/controller/capabilitiesgenerator/mapper.go index 8ea284b..1c0f1de 100644 --- a/internal/controller/capabilitiesgenerator/mapper.go +++ b/internal/controller/capabilitiesgenerator/mapper.go @@ -209,22 +209,10 @@ func MapWMSToCapabilitiesGeneratorInput(wms *pdoknlv3.WMS, ownerInfo *smoothoper canonicalServiceURL := hostBaseURL + "/" + pdoknlv3.GetBaseURLPath(wms) abstract := mapperutils.EscapeQuotes(wms.Spec.Service.Abstract) - var fees *string - if wms.Spec.Service.Fees != nil { - feesPtr := mapperutils.EscapeQuotes(*wms.Spec.Service.Fees) - fees = &feesPtr - } else { - fees = asPtr("NONE") - } maxWidth := 4000 maxHeight := 4000 - accessContraints := wms.Spec.Service.AccessConstraints - if accessContraints == nil || *accessContraints == "" { - accessContraints = smoothoperatorutils.Pointer("https://creativecommons.org/publicdomain/zero/1.0/deed.nl") - } - config := capabilitiesgenerator.Config{ Global: capabilitiesgenerator.Global{ Namespace: mapperutils.GetNamespaceURI("prefix", ownerInfo), @@ -244,8 +232,8 @@ func MapWMSToCapabilitiesGeneratorInput(wms *pdoknlv3.WMS, ownerInfo *smoothoper KeywordList: &wms130.Keywords{Keyword: wms.Spec.Service.Keywords}, OnlineResource: wms130.OnlineResource{Href: &hostBaseURL}, ContactInformation: getContactInformation(ownerInfo), - Fees: fees, - AccessConstraints: accessContraints, + Fees: smoothoperatorutils.Pointer("NONE"), + AccessConstraints: &wms.Spec.Service.AccessConstraints, LayerLimit: nil, MaxWidth: &maxWidth, MaxHeight: &maxHeight, @@ -350,7 +338,7 @@ func getDcpType(url string, fillPost bool) *wms130.DCPType { OnlineResource: wms130.OnlineResource{ Xlink: nil, Type: nil, - Href: asPtr(url), + Href: smoothoperatorutils.Pointer(url), }, } @@ -376,9 +364,9 @@ func getLayers(wms *pdoknlv3.WMS, canonicalURL string) []wms130.Layer { referenceLayer := wms.Spec.Service.Layer title := referenceLayer.Title if title != nil { - title = asPtr(mapperutils.EscapeQuotes(*referenceLayer.Title)) + title = smoothoperatorutils.Pointer(mapperutils.EscapeQuotes(*referenceLayer.Title)) } else { - title = asPtr("") + title = smoothoperatorutils.Pointer("") } defaultCrs := []wms130.CRS{{ @@ -520,11 +508,11 @@ func getLayers(wms *pdoknlv3.WMS, canonicalURL string) []wms130.Layer { } topLayer := wms130.Layer{ - Queryable: asPtr(1), + Queryable: smoothoperatorutils.Pointer(1), Opaque: nil, Name: nil, Title: *title, - Abstract: asPtr(mapperutils.EscapeQuotes(wms.Spec.Service.Abstract)), + Abstract: smoothoperatorutils.Pointer(mapperutils.EscapeQuotes(wms.Spec.Service.Abstract)), KeywordList: &wms130.Keywords{Keyword: referenceLayer.Keywords}, CRS: defaultCrs, EXGeographicBoundingBox: &defaultBoundingBox, @@ -564,12 +552,12 @@ func getLayers(wms *pdoknlv3.WMS, canonicalURL string) []wms130.Layer { if layer.DatasetMetadataURL != nil { metadataUrls = append(metadataUrls, &wms130.MetadataURL{ - Type: asPtr("TC211"), - Format: asPtr("text/plain"), + Type: smoothoperatorutils.Pointer("TC211"), + Format: smoothoperatorutils.Pointer("text/plain"), OnlineResource: wms130.OnlineResource{ Xlink: nil, - Type: asPtr("simple"), - Href: asPtr("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=" + layer.DatasetMetadataURL.CSW.MetadataIdentifier), + Type: smoothoperatorutils.Pointer("simple"), + Href: smoothoperatorutils.Pointer("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=" + layer.DatasetMetadataURL.CSW.MetadataIdentifier), }, }) } @@ -582,7 +570,7 @@ func getLayers(wms *pdoknlv3.WMS, canonicalURL string) []wms130.Layer { } nestedLayer := wms130.Layer{ - Queryable: asPtr(1), + Queryable: smoothoperatorutils.Pointer(1), Opaque: nil, Name: layer.Name, Title: smoothoperatorutils.PointerVal(layer.Title, ""), @@ -616,8 +604,8 @@ func getLayers(wms *pdoknlv3.WMS, canonicalURL string) []wms130.Layer { Format: "image/png", OnlineResource: wms130.OnlineResource{ Xlink: nil, - Type: asPtr("simple"), - Href: asPtr(canonicalURL + "/legend/" + *layer.Name + "/" + style.Name + ".png"), + Type: smoothoperatorutils.Pointer("simple"), + Href: smoothoperatorutils.Pointer(canonicalURL + "/legend/" + *layer.Name + "/" + style.Name + ".png"), }, }, StyleSheetURL: nil, @@ -631,7 +619,3 @@ func getLayers(wms *pdoknlv3.WMS, canonicalURL string) []wms130.Layer { result = append(result, topLayer) return result } - -func asPtr[T any](value T) *T { - return &value -} diff --git a/internal/controller/featureinfogenerator/featureinfo_generator_test.go b/internal/controller/featureinfogenerator/featureinfo_generator_test.go index 86c9dd0..94d5cf0 100644 --- a/internal/controller/featureinfogenerator/featureinfo_generator_test.go +++ b/internal/controller/featureinfogenerator/featureinfo_generator_test.go @@ -109,7 +109,7 @@ func TestGetInput(t *testing.T) { Name: smoothoperatorutils.Pointer("tif-layer-name"), Data: &pdoknlv3.Data{ TIF: &pdoknlv3.TIF{ - GetFeatureInfoIncludesClass: smoothoperatorutils.Pointer(true), + GetFeatureInfoIncludesClass: true, }, }, }, diff --git a/internal/controller/featureinfogenerator/mapper.go b/internal/controller/featureinfogenerator/mapper.go index 238d4ee..f30de28 100644 --- a/internal/controller/featureinfogenerator/mapper.go +++ b/internal/controller/featureinfogenerator/mapper.go @@ -44,7 +44,7 @@ func getProperties(layer *pdoknlv3.Layer) (properties []featureinfo.Property) { case layer.Data.Postgis != nil: properties = getPropertiesForVector(layer.Data.Postgis.Columns) case layer.Data.TIF != nil: - properties = getPropertiesForRaster(layer.Data.TIF.GetFeatureInfoIncludesClass) + properties = getPropertiesForRaster(&layer.Data.TIF.GetFeatureInfoIncludesClass) } return } diff --git a/internal/controller/legendgenerator/legend_generator.go b/internal/controller/legendgenerator/legend_generator.go index 0b05a34..338068c 100644 --- a/internal/controller/legendgenerator/legend_generator.go +++ b/internal/controller/legendgenerator/legend_generator.go @@ -70,7 +70,7 @@ func GetConfigMapData(wms *pdoknlv3.WMS) map[string]string { } addLayerInput(wms, data) - if wms.Spec.Options.RewriteGroupToDataLayers != nil && *wms.Spec.Options.RewriteGroupToDataLayers { + if wms.Spec.Options.RewriteGroupToDataLayers { addLegendFixerConfig(wms, data) } return data diff --git a/internal/controller/legendgenerator/mapper.go b/internal/controller/legendgenerator/mapper.go index e3f9be0..d4c090a 100644 --- a/internal/controller/legendgenerator/mapper.go +++ b/internal/controller/legendgenerator/mapper.go @@ -54,7 +54,7 @@ func addLayerInput(wms *pdoknlv3.WMS, data map[string]string) { } func processLayer(layer *pdoknlv3.Layer, legendReferences *[]LegendReference) { - if !*layer.Visible { + if !layer.Visible { return } for _, style := range layer.Styles { diff --git a/internal/controller/mapfilegenerator/mapper.go b/internal/controller/mapfilegenerator/mapper.go index 30bc47f..0abdd42 100644 --- a/internal/controller/mapfilegenerator/mapper.go +++ b/internal/controller/mapfilegenerator/mapper.go @@ -117,11 +117,6 @@ func getGeopackagePath(data pdoknlv3.Data) *string { func MapWMSToMapfileGeneratorInput(wms *pdoknlv3.WMS, _ *smoothoperatorv1.OwnerInfo) (WMSInput, error) { service := wms.Spec.Service - accessConstraints := service.AccessConstraints - if accessConstraints == nil || *accessConstraints == "" { - accessConstraints = smoothoperatorutils.Pointer("https://creativecommons.org/publicdomain/zero/1.0/deed.nl") - } - datasetOwner := "" if service.Layer.Authority != nil { datasetOwner = service.Layer.Authority.Name @@ -183,7 +178,7 @@ func MapWMSToMapfileGeneratorInput(wms *pdoknlv3.WMS, _ *smoothoperatorv1.OwnerI DataEPSG: service.DataEPSG, EPSGList: epsgs, }, - AccessConstraints: *accessConstraints, + AccessConstraints: service.AccessConstraints, Layers: []WMSLayer{}, GroupLayers: []GroupLayer{}, Symbols: getSymbols(wms), @@ -223,7 +218,7 @@ func getWMSLayer(serviceLayer pdoknlv3.Layer, serviceExtent string, wms *pdoknlv groupName := "" parent := serviceLayer.GetParent(&wms.Spec.Service.Layer) // If the layer falls directly under the toplayer, the groupname is omitted - if !parent.IsTopLayer() && parent.IsGroupLayer() && parent.Name != nil && parent.IsVisible() { + if !parent.IsTopLayer() && parent.IsGroupLayer() && parent.Name != nil && parent.Visible { groupName = *parent.Name } @@ -277,7 +272,7 @@ func getWMSLayer(serviceLayer pdoknlv3.Layer, serviceExtent string, wms *pdoknlv result.GeometryType = &gpkg.GeometryType geopackageConstructedPath := "" - if smoothoperatorutils.PointerVal(wms.Options().PrefetchData, true) { + if wms.Options().PrefetchData { splitBlobKey := strings.Split(gpkg.BlobKey, "/") geopackageConstructedPath = "/srv/data/gpkg/" + splitBlobKey[len(splitBlobKey)-1] } else { diff --git a/internal/controller/mapserver/deployment.go b/internal/controller/mapserver/deployment.go index 1205179..1d01f24 100644 --- a/internal/controller/mapserver/deployment.go +++ b/internal/controller/mapserver/deployment.go @@ -129,21 +129,19 @@ func GetVolumesForDeployment[O pdoknlv3.WMSWFS](obj O, configMapNames types.Hash volumes = append(volumes, lgVolume, figVolume, stylingFilesVolume) } - if options := obj.Options(); options != nil { - if options.PrefetchData != nil && *options.PrefetchData { - vol := newVolumeSource(configMapNames.BlobDownload) - vol.ConfigMap.DefaultMode = smoothoperatorutils.Pointer(int32(0777)) - volumes = append(volumes, v1.Volume{ - Name: ConfigMapBlobDownloadVolumeName, - VolumeSource: vol, - }) - } - if obj.Type() == pdoknlv3.ServiceTypeWMS && options.UseWebserviceProxy() { - volumes = append(volumes, v1.Volume{ - Name: ConfigMapOgcWebserviceProxyVolumeName, - VolumeSource: newVolumeSource(configMapNames.OgcWebserviceProxy), - }) - } + if obj.Options().PrefetchData { + vol := newVolumeSource(configMapNames.BlobDownload) + vol.ConfigMap.DefaultMode = smoothoperatorutils.Pointer(int32(0777)) + volumes = append(volumes, v1.Volume{ + Name: ConfigMapBlobDownloadVolumeName, + VolumeSource: vol, + }) + } + if obj.Type() == pdoknlv3.ServiceTypeWMS && obj.Options().UseWebserviceProxy() { + volumes = append(volumes, v1.Volume{ + Name: ConfigMapOgcWebserviceProxyVolumeName, + VolumeSource: newVolumeSource(configMapNames.OgcWebserviceProxy), + }) } return volumes diff --git a/internal/controller/mapserver/deployment_test.go b/internal/controller/mapserver/deployment_test.go index 92243e1..dae2428 100644 --- a/internal/controller/mapserver/deployment_test.go +++ b/internal/controller/mapserver/deployment_test.go @@ -1,45 +1,44 @@ package mapserver import ( - "encoding/json" "github.com/pdok/mapserver-operator/api/v2beta1" pdoknlv3 "github.com/pdok/mapserver-operator/api/v3" "github.com/pdok/mapserver-operator/internal/controller/types" + smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "sigs.k8s.io/yaml" "testing" -) -const ( - v2WfsString = "apiVersion: pdok.nl/v2beta1\nkind: WFS\nmetadata:\n name: rws-nwbwegen-v1-0\n labels:\n dataset-owner: rws\n dataset: nwbwegen\n service-version: v1_0\n service-type: wfs\n annotations:\n lifecycle-phase: prod\n service-bundle-id: b39c152b-393b-52f5-a50c-e1ffe904b6fb\nspec:\n general:\n datasetOwner: rws\n dataset: nwbwegen\n serviceVersion: v1_0\n kubernetes:\n healthCheck:\n mimetype: text/xml\n querystring: SERVICE=WFS&VERSION=2.0.0&REQUEST=GetCapabilities\n resources:\n limits:\n ephemeralStorage: 1505Mi\n requests:\n ephemeralStorage: 1505Mi\n service:\n title: NWB - Wegen WFS\n abstract:\n Dit is de web feature service van het Nationaal Wegen Bestand (NWB)\n - wegen. Deze dataset bevat alleen de wegvakken en hectometerpunten. Het Nationaal\n Wegen Bestand - Wegen is een digitaal geografisch bestand van alle wegen in\n Nederland. Opgenomen zijn alle wegen die worden beheerd door wegbeheerders als\n het Rijk, provincies, gemeenten en waterschappen, echter alleen voor zover deze\n zijn voorzien van een straatnaam of nummer.\n inspire: true\n metadataIdentifier: a9fa7fff-6365-4885-950c-e9d9848359ee\n authority:\n name: rws\n url: https://www.rijkswaterstaat.nl\n dataEPSG: EPSG:28992\n extent: -59188.44333693248 304984.64144318487 308126.88473339565 858328.516489961\n keywords:\n - Vervoersnetwerken\n - Menselijke gezondheid en veiligheid\n - Geluidsbelasting hoofdwegen (Richtlijn Omgevingslawaai)\n - Nationaal\n - Voertuigen\n - Verkeer\n - Wegvakken\n - Hectometerpunten\n - HVD\n - Mobiliteit\n featureTypes:\n - name: wegvakken\n title: Wegvakken\n abstract:\n Dit featuretype bevat de wegvakken uit het Nationaal Wegen bestand\n (NWB) en bevat gedetailleerde informatie per wegvak zoals straatnaam, wegnummer,\n routenummer, wegbeheerder, huisnummers, enz.\n sourceMetadataIdentifier: 8f0497f0-dbd7-4bee-b85a-5fdec484a7ff\n datasetMetadataIdentifier: a9b7026e-0a81-4813-93bd-ba49e6f28502\n keywords:\n - Vervoersnetwerken\n - Menselijke gezondheid en veiligheid\n - Geluidsbelasting hoofdwegen (Richtlijn Omgevingslawaai)\n - Nationaal\n - Voertuigen\n - Verkeer\n - Wegvakken\n data:\n gpkg:\n table: wegvakken\n geometryType: MultiLineString\n blobKey: geopackages/rws/nwbwegen/1c56dc48-2cf4-4631-8b09-ed385d5368d1/1/nwb_wegen.gpkg\n columns:\n - fid\n - objectid\n - wvk_id\n - wvk_begdat\n - jte_id_beg\n - jte_id_end\n - wegbehsrt\n - wegnummer\n - wegdeelltr\n - hecto_lttr\n - bst_code\n - rpe_code\n - admrichtng\n - rijrichtng\n - stt_naam\n - stt_bron\n - wpsnaam\n - gme_id\n - gme_naam\n - hnrstrlnks\n - hnrstrrhts\n - e_hnr_lnks\n - e_hnr_rhts\n - l_hnr_lnks\n - l_hnr_rhts\n - begafstand\n - endafstand\n - beginkm\n - eindkm\n - pos_tv_wol\n - wegbehcode\n - wegbehnaam\n - distrcode\n - distrnaam\n - dienstcode\n - dienstnaam\n - wegtype\n - wgtype_oms\n - routeltr\n - routenr\n - routeltr2\n - routenr2\n - routeltr3\n - routenr3\n - routeltr4\n - routenr4\n - wegnr_aw\n - wegnr_hmp\n - geobron_id\n - geobron_nm\n - bronjaar\n - openlr\n - bag_orl\n - frc\n - fow\n - alt_naam\n - alt_nr\n - rel_hoogte\n - st_lengthshape\n - name: hectopunten\n title: Hectopunten\n abstract:\n Dit featuretype bevat de hectopunten uit het Nationaal Wegen Bestand\n (NWB) en bevat gedetailleerde informatie per hectopunt zoals hectometrering,\n afstand, zijde en hectoletter.\n sourceMetadataIdentifier: 8f0497f0-dbd7-4bee-b85a-5fdec484a7ff\n datasetMetadataIdentifier: a9b7026e-0a81-4813-93bd-ba49e6f28502\n keywords:\n - Vervoersnetwerken\n - Menselijke gezondheid en veiligheid\n - Geluidsbelasting hoofdwegen (Richtlijn Omgevingslawaai)\n - Nationaal\n - Voertuigen\n - Verkeer\n - Hectometerpunten\n data:\n gpkg:\n blobKey: geopackages/rws/nwbwegen/1c56dc48-2cf4-4631-8b09-ed385d5368d1/1/nwb_wegen.gpkg\n columns:\n - fid\n - objectid\n - hectomtrng\n - afstand\n - wvk_id\n - wvk_begdat\n - zijde\n - hecto_lttr\n geometryType: MultiPoint\n table: hectopunten\n" - expectedVolumeMountsString = "[{\"name\":\"base\",\"mountPath\":\"/srv/data\"},{\"name\":\"data\",\"mountPath\":\"/var/www\"},{\"name\":\"mapserver\",\"mountPath\":\"/srv/mapserver/config/default_mapserver.conf\",\"subPath\":\"default_mapserver.conf\"},{\"name\":\"mapserver\",\"mountPath\":\"/srv/mapserver/config/include.conf\",\"subPath\":\"include.conf\"},{\"name\":\"mapserver\",\"mountPath\":\"/srv/mapserver/config/ogc.lua\",\"subPath\":\"ogc.lua\"},{\"name\":\"mapserver\",\"mountPath\":\"/srv/mapserver/config/scraping-error.xml\",\"subPath\":\"scraping-error.xml\"}]" - expectedEnvVarsString = "[{\"name\":\"SERVICE_TYPE\",\"value\":\"WFS\"},{\"name\":\"MAPSERVER_CONFIG_FILE\",\"value\":\"/srv/mapserver/config/default_mapserver.conf\"},{\"name\":\"AZURE_STORAGE_CONNECTION_STRING\",\"valueFrom\":{\"secretKeyRef\":{\"name\":\"blobs-secret\",\"key\":\"AZURE_STORAGE_CONNECTION_STRING\"}}},{\"name\":\"MS_MAPFILE\",\"value\":\"/srv/data/config/mapfile/service.map\"}]\n" - expectedLivenessProbeString = "{\"exec\":{\"command\":[\"/bin/sh\",\"-c\",\"wget -SO- -T 10 -t 2 'http://127.0.0.1:80/mapserver?SERVICE=wfs\\u0026request=GetCapabilities' 2\\u003e\\u00261 | egrep -aiA10 'HTTP/1.1 200' | egrep -i 'Content-Type: text/xml'\"]},\"initialDelaySeconds\":20,\"timeoutSeconds\":10,\"periodSeconds\":10,\"successThreshold\":1,\"failureThreshold\":3}\n" - expectedReadinessProbeString = "{\"exec\":{\"command\":[\"/bin/sh\",\"-c\",\"wget -SO- -T 10 -t 2 'http://127.0.0.1:80/mapserver?SERVICE=WFS\\u0026VERSION=2.0.0\\u0026REQUEST=GetFeature\\u0026TYPENAMES=wegvakken\\u0026STARTINDEX=0\\u0026COUNT=1' 2\\u003e\\u00261 | egrep -aiA10 'HTTP/1.1 200' | egrep -i 'Content-Type: text/xml'\"]},\"initialDelaySeconds\":20,\"timeoutSeconds\":10,\"periodSeconds\":10,\"successThreshold\":1,\"failureThreshold\":3}\n" - expectedStartupProbeString = "{\"exec\":{\"command\":[\"/bin/sh\",\"-c\",\"wget -SO- -T 10 -t 2 'http://127.0.0.1:80/mapserver?SERVICE=WFS\\u0026VERSION=2.0.0\\u0026REQUEST=GetFeature\\u0026TYPENAMES=wegvakken,hectopunten\\u0026STARTINDEX=0\\u0026COUNT=1' 2\\u003e\\u00261 | egrep -aiA10 'HTTP/1.1 200' | egrep -i 'Content-Type: text/xml'\"]},\"initialDelaySeconds\":20,\"timeoutSeconds\":10,\"periodSeconds\":10,\"successThreshold\":1,\"failureThreshold\":3}\n" + _ "embed" ) +//go:embed test_data/expected_volumemounts.yaml +var expectedVolumeMountsYaml []byte + func TestGetVolumeMountsForDeployment(t *testing.T) { var wfs = getV3() pdoknlv3.SetHost("https://service.pdok.nl") result := GetVolumeMountsForDeployment(wfs, "/srv") - var expectedVolumeMounts []v1.VolumeMount - err := json.Unmarshal([]byte(expectedVolumeMountsString), &expectedVolumeMounts) + var expectedVolumeMounts struct{ VolumeMounts []v1.VolumeMount } + err := yaml.Unmarshal(expectedVolumeMountsYaml, &expectedVolumeMounts) assert.NoError(t, err) - assert.Equal(t, expectedVolumeMounts, result) + assert.Equal(t, expectedVolumeMounts.VolumeMounts, result) } +//go:embed test_data/expected_envvars.yaml +var expectedEnvVarsYaml []byte + func TestGetEnvVarsForDeployment(t *testing.T) { var wfs = getV3() pdoknlv3.SetHost("https://service.pdok.nl") result := GetEnvVarsForDeployment(wfs, "blobs-secret") - var expectedEnvVars []v1.EnvVar - err := json.Unmarshal([]byte(expectedEnvVarsString), &expectedEnvVars) + var expectedEnvVars struct{ EnvVars []v1.EnvVar } + err := yaml.Unmarshal(expectedEnvVarsYaml, &expectedEnvVars) assert.NoError(t, err) - assert.Equal(t, expectedEnvVars, result) + assert.Equal(t, expectedEnvVars.EnvVars, result) } func TestGetResourcesForDeployment(t *testing.T) { @@ -65,19 +64,29 @@ func TestGetResourcesForDeployment(t *testing.T) { assert.Equal(t, expected, result) } +//go:embed test_data/expected_livenessprobe.yaml +var expectedLivenessProbe []byte + +//go:embed test_data/expected_readinessprobe.yaml +var expectedReadinessProbe []byte + +//go:embed test_data/expected_startupprobe.yaml +var expectedStartupProbe []byte + func TestGetProbesForDeployment(t *testing.T) { var wfs = getV3() pdoknlv3.SetHost("https://service.pdok.nl") livenessResult, readinessResult, startupResult, err := GetProbesForDeployment(wfs) assert.NoError(t, err) + var expectedLiveness v1.Probe var expectedReadiness v1.Probe var expectedStartup v1.Probe - err = json.Unmarshal([]byte(expectedLivenessProbeString), &expectedLiveness) + err = yaml.Unmarshal(expectedLivenessProbe, &expectedLiveness) assert.NoError(t, err) - err = json.Unmarshal([]byte(expectedReadinessProbeString), &expectedReadiness) + err = yaml.Unmarshal(expectedReadinessProbe, &expectedReadiness) assert.NoError(t, err) - err = json.Unmarshal([]byte(expectedStartupProbeString), &expectedStartup) + err = yaml.Unmarshal(expectedStartupProbe, &expectedStartup) assert.NoError(t, err) assert.Equal(t, &expectedLiveness, livenessResult) assert.Equal(t, &expectedReadiness, readinessResult) @@ -86,6 +95,7 @@ func TestGetProbesForDeployment(t *testing.T) { func TestGetVolumesForDeployment(t *testing.T) { var wfs = getV3() + wfs.Spec.Options.PrefetchData = false pdoknlv3.SetHost("https://service.pdok.nl") hashedConfigMapNames := types.HashedConfigMapNames{ @@ -102,17 +112,20 @@ func TestGetVolumesForDeployment(t *testing.T) { expected := []v1.Volume{ {Name: "base", VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}, {Name: "data", VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}, - {Name: "mapserver", VolumeSource: v1.VolumeSource{ConfigMap: &v1.ConfigMapVolumeSource{LocalObjectReference: v1.LocalObjectReference{Name: "rws-nwbwegen-v1-0-wfs-mapserver-bb59c7f4f4"}, DefaultMode: v2beta1.Pointer(int32(420))}}}, - {Name: "mapfile-generator-config", VolumeSource: v1.VolumeSource{ConfigMap: &v1.ConfigMapVolumeSource{LocalObjectReference: v1.LocalObjectReference{Name: "rws-nwbwegen-v1-0-wfs-mapfile-generator-bbbtd999dh"}, DefaultMode: v2beta1.Pointer(int32(420))}}}, - {Name: "capabilities-generator-config", VolumeSource: v1.VolumeSource{ConfigMap: &v1.ConfigMapVolumeSource{LocalObjectReference: v1.LocalObjectReference{Name: "rws-nwbwegen-v1-0-wfs-capabilities-generator-6m4mfkgb5d"}, DefaultMode: v2beta1.Pointer(int32(420))}}}, + {Name: "mapserver", VolumeSource: v1.VolumeSource{ConfigMap: &v1.ConfigMapVolumeSource{LocalObjectReference: v1.LocalObjectReference{Name: "rws-nwbwegen-v1-0-wfs-mapserver-bb59c7f4f4"}, DefaultMode: smoothoperatorutils.Pointer(int32(420))}}}, + {Name: "mapfile-generator-config", VolumeSource: v1.VolumeSource{ConfigMap: &v1.ConfigMapVolumeSource{LocalObjectReference: v1.LocalObjectReference{Name: "rws-nwbwegen-v1-0-wfs-mapfile-generator-bbbtd999dh"}, DefaultMode: smoothoperatorutils.Pointer(int32(420))}}}, + {Name: "capabilities-generator-config", VolumeSource: v1.VolumeSource{ConfigMap: &v1.ConfigMapVolumeSource{LocalObjectReference: v1.LocalObjectReference{Name: "rws-nwbwegen-v1-0-wfs-capabilities-generator-6m4mfkgb5d"}, DefaultMode: smoothoperatorutils.Pointer(int32(420))}}}, } assert.Equal(t, expected, result) } +//go:embed test_data/v2_input.yaml +var v2Input []byte + func getV3() *pdoknlv3.WFS { var v2wfs v2beta1.WFS - err := yaml.Unmarshal([]byte(v2WfsString), &v2wfs) + err := yaml.Unmarshal(v2Input, &v2wfs) if err != nil { panic(err) } diff --git a/internal/controller/mapserver/test_data/expected_envvars.yaml b/internal/controller/mapserver/test_data/expected_envvars.yaml new file mode 100644 index 0000000..9cb6eb4 --- /dev/null +++ b/internal/controller/mapserver/test_data/expected_envvars.yaml @@ -0,0 +1,12 @@ +envVars: +- name: SERVICE_TYPE + value: WFS +- name: MAPSERVER_CONFIG_FILE + value: /srv/mapserver/config/default_mapserver.conf +- name: AZURE_STORAGE_CONNECTION_STRING + valueFrom: + secretKeyRef: + key: AZURE_STORAGE_CONNECTION_STRING + name: blobs-secret +- name: MS_MAPFILE + value: /srv/data/config/mapfile/service.map diff --git a/internal/controller/mapserver/test_data/expected_livenessprobe.yaml b/internal/controller/mapserver/test_data/expected_livenessprobe.yaml new file mode 100644 index 0000000..f67d447 --- /dev/null +++ b/internal/controller/mapserver/test_data/expected_livenessprobe.yaml @@ -0,0 +1,11 @@ +exec: + command: + - /bin/sh + - -c + - 'wget -SO- -T 10 -t 2 ''http://127.0.0.1:80/mapserver?SERVICE=wfs&request=GetCapabilities'' + 2>&1 | egrep -aiA10 ''HTTP/1.1 200'' | egrep -i ''Content-Type: text/xml''' +failureThreshold: 3 +initialDelaySeconds: 20 +periodSeconds: 10 +successThreshold: 1 +timeoutSeconds: 10 diff --git a/internal/controller/mapserver/test_data/expected_readinessprobe.yaml b/internal/controller/mapserver/test_data/expected_readinessprobe.yaml new file mode 100644 index 0000000..5eec511 --- /dev/null +++ b/internal/controller/mapserver/test_data/expected_readinessprobe.yaml @@ -0,0 +1,11 @@ +exec: + command: + - /bin/sh + - -c + - 'wget -SO- -T 10 -t 2 ''http://127.0.0.1:80/mapserver?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=wegvakken&STARTINDEX=0&COUNT=1'' + 2>&1 | egrep -aiA10 ''HTTP/1.1 200'' | egrep -i ''Content-Type: text/xml''' +failureThreshold: 3 +initialDelaySeconds: 20 +periodSeconds: 10 +successThreshold: 1 +timeoutSeconds: 10 diff --git a/internal/controller/mapserver/test_data/expected_startupprobe.yaml b/internal/controller/mapserver/test_data/expected_startupprobe.yaml new file mode 100644 index 0000000..4297a3d --- /dev/null +++ b/internal/controller/mapserver/test_data/expected_startupprobe.yaml @@ -0,0 +1,11 @@ +exec: + command: + - /bin/sh + - -c + - 'wget -SO- -T 10 -t 2 ''http://127.0.0.1:80/mapserver?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=wegvakken,hectopunten&STARTINDEX=0&COUNT=1'' + 2>&1 | egrep -aiA10 ''HTTP/1.1 200'' | egrep -i ''Content-Type: text/xml''' +failureThreshold: 3 +initialDelaySeconds: 20 +periodSeconds: 10 +successThreshold: 1 +timeoutSeconds: 10 diff --git a/internal/controller/mapserver/test_data/expected_volumemounts.yaml b/internal/controller/mapserver/test_data/expected_volumemounts.yaml new file mode 100644 index 0000000..d990b62 --- /dev/null +++ b/internal/controller/mapserver/test_data/expected_volumemounts.yaml @@ -0,0 +1,17 @@ +volumeMounts: +- mountPath: /srv/data + name: base +- mountPath: /var/www + name: data +- mountPath: /srv/mapserver/config/default_mapserver.conf + name: mapserver + subPath: default_mapserver.conf +- mountPath: /srv/mapserver/config/include.conf + name: mapserver + subPath: include.conf +- mountPath: /srv/mapserver/config/ogc.lua + name: mapserver + subPath: ogc.lua +- mountPath: /srv/mapserver/config/scraping-error.xml + name: mapserver + subPath: scraping-error.xml diff --git a/internal/controller/mapserver/test_data/v2_input.yaml b/internal/controller/mapserver/test_data/v2_input.yaml new file mode 100644 index 0000000..2dc5261 --- /dev/null +++ b/internal/controller/mapserver/test_data/v2_input.yaml @@ -0,0 +1,165 @@ +apiVersion: pdok.nl/v2beta1 +kind: WFS +metadata: + name: rws-nwbwegen-v1-0 + labels: + dataset-owner: rws + dataset: nwbwegen + service-version: v1_0 + service-type: wfs + annotations: + lifecycle-phase: prod + service-bundle-id: b39c152b-393b-52f5-a50c-e1ffe904b6fb +spec: + general: + datasetOwner: rws + dataset: nwbwegen + serviceVersion: v1_0 + kubernetes: + healthCheck: + mimetype: text/xml + querystring: SERVICE=WFS&VERSION=2.0.0&REQUEST=GetCapabilities + resources: + limits: + ephemeralStorage: 1505Mi + requests: + ephemeralStorage: 1505Mi + service: + title: NWB - Wegen WFS + abstract: + Dit is de web feature service van het Nationaal Wegen Bestand (NWB) + - wegen. Deze dataset bevat alleen de wegvakken en hectometerpunten. Het Nationaal + Wegen Bestand - Wegen is een digitaal geografisch bestand van alle wegen in + Nederland. Opgenomen zijn alle wegen die worden beheerd door wegbeheerders als + het Rijk, provincies, gemeenten en waterschappen, echter alleen voor zover deze + zijn voorzien van een straatnaam of nummer. + inspire: true + metadataIdentifier: a9fa7fff-6365-4885-950c-e9d9848359ee + authority: + name: rws + url: https://www.rijkswaterstaat.nl + dataEPSG: EPSG:28992 + extent: -59188.44333693248 304984.64144318487 308126.88473339565 858328.516489961 + keywords: + - Vervoersnetwerken + - Menselijke gezondheid en veiligheid + - Geluidsbelasting hoofdwegen (Richtlijn Omgevingslawaai) + - Nationaal + - Voertuigen + - Verkeer + - Wegvakken + - Hectometerpunten + - HVD + - Mobiliteit + featureTypes: + - name: wegvakken + title: Wegvakken + abstract: + Dit featuretype bevat de wegvakken uit het Nationaal Wegen bestand + (NWB) en bevat gedetailleerde informatie per wegvak zoals straatnaam, wegnummer, + routenummer, wegbeheerder, huisnummers, enz. + sourceMetadataIdentifier: 8f0497f0-dbd7-4bee-b85a-5fdec484a7ff + datasetMetadataIdentifier: a9b7026e-0a81-4813-93bd-ba49e6f28502 + keywords: + - Vervoersnetwerken + - Menselijke gezondheid en veiligheid + - Geluidsbelasting hoofdwegen (Richtlijn Omgevingslawaai) + - Nationaal + - Voertuigen + - Verkeer + - Wegvakken + data: + gpkg: + table: wegvakken + geometryType: MultiLineString + blobKey: geopackages/rws/nwbwegen/1c56dc48-2cf4-4631-8b09-ed385d5368d1/1/nwb_wegen.gpkg + columns: + - fid + - objectid + - wvk_id + - wvk_begdat + - jte_id_beg + - jte_id_end + - wegbehsrt + - wegnummer + - wegdeelltr + - hecto_lttr + - bst_code + - rpe_code + - admrichtng + - rijrichtng + - stt_naam + - stt_bron + - wpsnaam + - gme_id + - gme_naam + - hnrstrlnks + - hnrstrrhts + - e_hnr_lnks + - e_hnr_rhts + - l_hnr_lnks + - l_hnr_rhts + - begafstand + - endafstand + - beginkm + - eindkm + - pos_tv_wol + - wegbehcode + - wegbehnaam + - distrcode + - distrnaam + - dienstcode + - dienstnaam + - wegtype + - wgtype_oms + - routeltr + - routenr + - routeltr2 + - routenr2 + - routeltr3 + - routenr3 + - routeltr4 + - routenr4 + - wegnr_aw + - wegnr_hmp + - geobron_id + - geobron_nm + - bronjaar + - openlr + - bag_orl + - frc + - fow + - alt_naam + - alt_nr + - rel_hoogte + - st_lengthshape + - name: hectopunten + title: Hectopunten + abstract: + Dit featuretype bevat de hectopunten uit het Nationaal Wegen Bestand + (NWB) en bevat gedetailleerde informatie per hectopunt zoals hectometrering, + afstand, zijde en hectoletter. + sourceMetadataIdentifier: 8f0497f0-dbd7-4bee-b85a-5fdec484a7ff + datasetMetadataIdentifier: a9b7026e-0a81-4813-93bd-ba49e6f28502 + keywords: + - Vervoersnetwerken + - Menselijke gezondheid en veiligheid + - Geluidsbelasting hoofdwegen (Richtlijn Omgevingslawaai) + - Nationaal + - Voertuigen + - Verkeer + - Hectometerpunten + data: + gpkg: + blobKey: geopackages/rws/nwbwegen/1c56dc48-2cf4-4631-8b09-ed385d5368d1/1/nwb_wegen.gpkg + columns: + - fid + - objectid + - hectomtrng + - afstand + - wvk_id + - wvk_begdat + - zijde + - hecto_lttr + geometryType: MultiPoint + table: hectopunten diff --git a/internal/controller/ogcwebserviceproxy/ogc_webservice_proxy.go b/internal/controller/ogcwebserviceproxy/ogc_webservice_proxy.go index 3a29b35..cb72ace 100644 --- a/internal/controller/ogcwebserviceproxy/ogc_webservice_proxy.go +++ b/internal/controller/ogcwebserviceproxy/ogc_webservice_proxy.go @@ -39,12 +39,13 @@ func getCommand(wms *pdoknlv3.WMS) []string { "-s=/input/service-config.yaml", } - if wms.Options() != nil && *wms.Options().ValidateRequests { + if wms.Spec.Options.ValidateRequests { command = append(command, "-v") } - if wms.Options() != nil && *wms.Options().RewriteGroupToDataLayers { + if wms.Spec.Options.RewriteGroupToDataLayers { command = append(command, "-r") } + command = append(command, "-d=15") return command diff --git a/internal/controller/shared_controller.go b/internal/controller/shared_controller.go index 20698c6..4c84583 100644 --- a/internal/controller/shared_controller.go +++ b/internal/controller/shared_controller.go @@ -268,7 +268,7 @@ func getInitContainerForDeployment[R Reconciler, O pdoknlv3.WMSWFS](r R, obj O) } initContainers = append(initContainers, *featureInfoInitContainer) - if *wms.Options().RewriteGroupToDataLayers { + if wms.Spec.Options.RewriteGroupToDataLayers { legendFixerInitContainer := legendgenerator.GetLegendFixerInitContainer(images.MultitoolImage) initContainers = append(initContainers, *legendFixerInitContainer) } diff --git a/internal/controller/test_manifests/v3_wms.yaml b/internal/controller/test_manifests/v3_wms.yaml index bb53569..89c52d3 100644 --- a/internal/controller/test_manifests/v3_wms.yaml +++ b/internal/controller/test_manifests/v3_wms.yaml @@ -10,6 +10,8 @@ metadata: service-version: 1.0.0 name: sample-v3 spec: + options: + automaticCasing: true lifecycle: ttlInDays: 21 podSpecPatch: @@ -33,14 +35,6 @@ spec: target: type: Utilization averageUtilization: 120 - options: - automaticCasing: true - prefetchData: false - includeIngress: false - rewriteGroupToDataLayers: true - validateChildStyleNameEqual: true - disableWebserviceProxy: false - validateRequests: true service: url: https://service.pdok.nl/owner/dataset/wms/1.0.0 title: "Dataset" @@ -79,7 +73,12 @@ spec: maxy: "637049.52" visible: true authority: + name: "test" + url: "https://test.com" + spatialDatasetIdentifier: 1234abcd-1234-abcd-1234-abcd1234abcd datasetMetadataUrl: + csw: + metadataIdentifier: 1234abcd-1234-abcd-1234-abcd1234abcd minscaledenominator: maxscaledenominator: styles: @@ -104,7 +103,12 @@ spec: boundingBoxes: visible: true authority: + name: "test" + url: "https://test.com" + spatialDatasetIdentifier: 1234abcd-1234-abcd-1234-abcd1234abcd datasetMetadataUrl: + csw: + metadataIdentifier: 1234abcd-1234-abcd-1234-abcd1234abcd minscaledenominator: maxscaledenominator: styles: @@ -128,7 +132,12 @@ spec: boundingBoxes: visible: true authority: + name: "test" + url: "https://test.com" + spatialDatasetIdentifier: 1234abcd-1234-abcd-1234-abcd1234abcd datasetMetadataUrl: + csw: + metadataIdentifier: 1234abcd-1234-abcd-1234-abcd1234abcd minscaledenominator: maxscaledenominator: styles: @@ -162,7 +171,12 @@ spec: boundingBoxes: visible: true authority: + name: "test" + url: "https://test.com" + spatialDatasetIdentifier: 1234abcd-1234-abcd-1234-abcd1234abcd datasetMetadataUrl: + csw: + metadataIdentifier: 1234abcd-1234-abcd-1234-abcd1234abcd minscaledenominator: maxscaledenominator: styles: diff --git a/internal/controller/wfs_controller_test.go b/internal/controller/wfs_controller_test.go index 69d45de..4920506 100644 --- a/internal/controller/wfs_controller_test.go +++ b/internal/controller/wfs_controller_test.go @@ -84,7 +84,7 @@ var _ = Describe("WFS Controller", func() { counter++ // Set most used options - sampleWfs.Options().PrefetchData = smoothoperatorutils.Pointer(true) + sampleWfs.Spec.Options.PrefetchData = true By("creating the custom resource for the Kind WFS") err = k8sClient.Get(ctx, typeNamespacedNameWfs, wfs) @@ -348,7 +348,7 @@ var _ = Describe("WFS Controller", func() { sampleWfs, err := getUniqueWFSSample(9999) typeNamespacedNameWfs.Name = sampleWfs.Name Expect(err).NotTo(HaveOccurred()) - sampleWfs.Spec.Options.PrefetchData = smoothoperatorutils.Pointer(false) + sampleWfs.Spec.Options.PrefetchData = false Expect(k8sClient.Create(ctx, sampleWfs.DeepCopy())).To(Succeed()) Expect(k8sClient.Get(ctx, typeNamespacedNameWfs, wfs)).To(Succeed()) diff --git a/internal/controller/wms_controller_test.go b/internal/controller/wms_controller_test.go index 68dd29f..0618244 100644 --- a/internal/controller/wms_controller_test.go +++ b/internal/controller/wms_controller_test.go @@ -82,9 +82,6 @@ var _ = Describe("WMS Controller", func() { typeNamespacedNameWms = getUniqueWmsTypeNamespacedName(counter) counter++ - // Set most used options - sampleWms.Options().PrefetchData = smoothoperatorutils.Pointer(true) - By("creating the custom resource for the Kind WMS") err = k8sClient.Get(ctx, typeNamespacedNameWms, wms) if err != nil && k8serrors.IsNotFound(err) { @@ -263,7 +260,7 @@ var _ = Describe("WMS Controller", func() { containerOgcWebserviceProxy := deployment.Spec.Template.Spec.Containers[2] Expect(containerOgcWebserviceProxy.Name).Should(Equal("ogc-webservice-proxy")) - ogcWebserviceProxyCommands := []string{"/ogc-webservice-proxy", "-h=http://127.0.0.1/", "-t=wms", "-s=/input/service-config.yaml", "-v", "-r", "-d=15"} + ogcWebserviceProxyCommands := []string{"/ogc-webservice-proxy", "-h=http://127.0.0.1/", "-t=wms", "-s=/input/service-config.yaml", "-v", "-d=15"} Expect(containerOgcWebserviceProxy.Command).Should(Equal(ogcWebserviceProxyCommands)) Expect(containerOgcWebserviceProxy.Image).Should(Equal(reconcilerImages.OgcWebserviceProxyImage)) Expect(containerOgcWebserviceProxy.ImagePullPolicy).Should(Equal(corev1.PullIfNotPresent)) @@ -352,18 +349,6 @@ var _ = Describe("WMS Controller", func() { } Expect(legendGeneratorContainer.Env).Should(Equal(env)) - legendFixerContainer, err := getInitContainer("legend-fixer", deployment) - Expect(err).NotTo(HaveOccurred()) - Expect(legendFixerContainer.Image).Should(Equal(reconcilerImages.MultitoolImage)) - Expect(legendFixerContainer.ImagePullPolicy).Should(Equal(corev1.PullIfNotPresent)) - Expect(len(legendFixerContainer.Command)).Should(BeNumerically("==", 2)) - - volumeMounts = []corev1.VolumeMount{ - {Name: "data", MountPath: "/var/www", ReadOnly: false}, - {Name: mapserver.ConfigMapLegendGeneratorVolumeName, MountPath: "/input", ReadOnly: true}, - } - Expect(legendFixerContainer.VolumeMounts).Should(Equal(volumeMounts)) - /** Volumes tests */ @@ -400,7 +385,8 @@ var _ = Describe("WMS Controller", func() { counter++ typeNamespacedNameWms.Name = sampleWms.Name Expect(err).NotTo(HaveOccurred()) - sampleWms.Spec.Options.PrefetchData = smoothoperatorutils.Pointer(false) + sampleWms.Spec.Options.PrefetchData = false + Expect(k8sClient.Create(ctx, sampleWms.DeepCopy())).To(Succeed()) Expect(k8sClient.Get(ctx, typeNamespacedNameWms, wms)).To(Succeed()) @@ -427,7 +413,7 @@ var _ = Describe("WMS Controller", func() { counter++ typeNamespacedNameWms.Name = sampleWms.Name Expect(err).NotTo(HaveOccurred()) - sampleWms.Spec.Options.DisableWebserviceProxy = smoothoperatorutils.Pointer(true) + sampleWms.Spec.Options.DisableWebserviceProxy = true Expect(k8sClient.Create(ctx, sampleWms.DeepCopy())).To(Succeed()) Expect(k8sClient.Get(ctx, typeNamespacedNameWms, wms)).To(Succeed()) @@ -440,7 +426,7 @@ var _ = Describe("WMS Controller", func() { Expect(err).To(HaveOccurred()) }) - It("Should not mount a legend-fixer initcontainer if options.REWRITEGROUPTODATALAYERS is FALSE.", func() { + It("Should mount a legend-fixer initcontainer if options.REWRITEGROUPTODATALAYERS is TRUE.", func() { wmsResource := &pdoknlv3.WMS{} wmsResource.Namespace = namespace wmsResource.Name = typeNamespacedNameWms.Name @@ -454,7 +440,7 @@ var _ = Describe("WMS Controller", func() { counter++ typeNamespacedNameWms.Name = sampleWms.Name Expect(err).NotTo(HaveOccurred()) - sampleWms.Spec.Options.RewriteGroupToDataLayers = smoothoperatorutils.Pointer(false) + sampleWms.Spec.Options.RewriteGroupToDataLayers = true Expect(k8sClient.Create(ctx, sampleWms.DeepCopy())).To(Succeed()) Expect(k8sClient.Get(ctx, typeNamespacedNameWms, wms)).To(Succeed()) @@ -466,11 +452,45 @@ var _ = Describe("WMS Controller", func() { deployment := &appsv1.Deployment{} err = k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: getBareDeployment(wms).GetName()}, deployment) Expect(err).NotTo(HaveOccurred()) - blobDownloadContainer, err := getInitContainer("blob-download", deployment) + blobDownloadContainer, _ := getInitContainer("blob-download", deployment) Expect(blobDownloadContainer.Name).To(BeEquivalentTo("blob-download")) - legendFixer, err := getInitContainer("legend-fixer", deployment) - Expect(err).To(HaveOccurred()) - Expect(legendFixer.Name).To(BeEquivalentTo("")) + legendFixerContainer, err := getInitContainer("legend-fixer", deployment) + Expect(err).NotTo(HaveOccurred()) + Expect(legendFixerContainer.Image).Should(Equal(controllerReconciler.Images.MultitoolImage)) + Expect(legendFixerContainer.ImagePullPolicy).Should(Equal(corev1.PullIfNotPresent)) + Expect(len(legendFixerContainer.Command)).Should(BeNumerically("==", 2)) + + volumeMounts := []corev1.VolumeMount{ + {Name: "data", MountPath: "/var/www", ReadOnly: false}, + {Name: mapserver.ConfigMapLegendGeneratorVolumeName, MountPath: "/input", ReadOnly: true}, + } + Expect(legendFixerContainer.VolumeMounts).Should(Equal(volumeMounts)) + + configMap := getBareConfigMapLegendGenerator(wms) + configMapName, err := getHashedConfigMapNameFromClient(ctx, wms, mapserver.ConfigMapLegendGeneratorVolumeName) + Expect(err).NotTo(HaveOccurred()) + Eventually(func() bool { + err = k8sClient.Get(ctx, client.ObjectKey{Namespace: wms.GetNamespace(), Name: configMapName}, configMap) + return Expect(err).NotTo(HaveOccurred()) + }, "10s", "1s").Should(BeTrue()) + + Expect(configMap.GetName()).To(HavePrefix(wms.GetName() + "-wms-legend-generator-")) + Expect(configMap.GetNamespace()).To(Equal(namespace)) + Expect(configMap.Immutable).To(Equal(smoothoperatorutils.Pointer(true))) + checkWMSLabels(configMap.GetLabels()) + + data, ok := configMap.Data["legend-fixer.sh"] + Expect(ok).To(BeTrue()) + Expect(len(data)).To(BeNumerically(">", 0)) + + _, ok = configMap.Data["remove"] + Expect(ok).To(BeTrue()) + + data, ok = configMap.Data["ogc-webservice-proxy-config.yaml"] + Expect(ok).To(BeTrue()) + Expect(len(data)).To(BeNumerically(">", 0)) + + // actual configMap content is tested in legendgenerator/legend_generator_test.go }) It("ogcWebserviceProxyCommands will not contain command '-v' if options.VALIDATEREQUESTS is FALSE.", func() { @@ -487,7 +507,7 @@ var _ = Describe("WMS Controller", func() { counter++ typeNamespacedNameWms.Name = sampleWms.Name Expect(err).NotTo(HaveOccurred()) - sampleWms.Spec.Options.ValidateRequests = smoothoperatorutils.Pointer(false) + sampleWms.Spec.Options.ValidateRequests = false Expect(k8sClient.Create(ctx, sampleWms.DeepCopy())).To(Succeed()) Expect(k8sClient.Get(ctx, typeNamespacedNameWms, wms)).To(Succeed()) @@ -502,7 +522,7 @@ var _ = Describe("WMS Controller", func() { containerOgcWebserviceProxy := deployment.Spec.Template.Spec.Containers[2] Expect(containerOgcWebserviceProxy.Name).Should(Equal("ogc-webservice-proxy")) - ogcWebserviceProxyCommands := []string{"/ogc-webservice-proxy", "-h=http://127.0.0.1/", "-t=wms", "-s=/input/service-config.yaml", "-r", "-d=15"} + ogcWebserviceProxyCommands := []string{"/ogc-webservice-proxy", "-h=http://127.0.0.1/", "-t=wms", "-s=/input/service-config.yaml", "-d=15"} Expect(containerOgcWebserviceProxy.Command).Should(Equal(ogcWebserviceProxyCommands)) Expect(containerOgcWebserviceProxy.ImagePullPolicy).Should(Equal(corev1.PullIfNotPresent)) Expect(containerOgcWebserviceProxy.Ports[0].ContainerPort).Should(Equal(int32(9111))) @@ -653,19 +673,6 @@ var _ = Describe("WMS Controller", func() { _, ok = configMap.Data["input"] Expect(ok).To(BeTrue()) - - data, ok = configMap.Data["legend-fixer.sh"] - Expect(ok).To(BeTrue()) - Expect(len(data)).To(BeNumerically(">", 0)) - - _, ok = configMap.Data["remove"] - Expect(ok).To(BeTrue()) - - data, ok = configMap.Data["ogc-webservice-proxy-config.yaml"] - Expect(ok).To(BeTrue()) - Expect(len(data)).To(BeNumerically(">", 0)) - - // actual configMap content is tested in legendgenerator/legend_generator_test.go }) It("Should create correct configMapFeatureinfoGenerator manifest.", func() { @@ -928,7 +935,7 @@ var _ = Describe("WMS Controller", func() { {Name: mapserver.ConfigMapLegendGeneratorVolumeName, MountPath: "/input", ReadOnly: true}, {Name: "mapfile", MountPath: "/srv/data/config/mapfile"}, } - legendGeneratorContainer, err := getInitContainer("legend-generator", deployment) + legendGeneratorContainer, _ := getInitContainer("legend-generator", deployment) Expect(legendGeneratorContainer.VolumeMounts).Should(Equal(volumeMounts)) diff --git a/internal/webhook/v3/wms_webhook_test.go b/internal/webhook/v3/wms_webhook_test.go index ed6704a..4ce953d 100644 --- a/internal/webhook/v3/wms_webhook_test.go +++ b/internal/webhook/v3/wms_webhook_test.go @@ -158,7 +158,7 @@ var _ = Describe("WMS Webhook", func() { It("Should deny creation if grouplayer is not visible", func() { nestedLayers1 := obj.Spec.Service.Layer.Layers - nestedLayers1[0].Visible = smoothoperatorutils.Pointer(false) + nestedLayers1[0].Visible = false _, err := validator.ValidateCreate(ctx, obj) Expect(err).To(HaveOccurred()) }) @@ -215,7 +215,7 @@ var _ = Describe("WMS Webhook", func() { It("Should deny creation if there are no visible layers", func() { obj.Spec.Service.Layer.Layers = nil - obj.Spec.Service.Layer.Visible = smoothoperatorutils.Pointer(false) + obj.Spec.Service.Layer.Visible = false _, err := validator.ValidateUpdate(ctx, oldObj, obj) Expect(err).To(HaveOccurred())