diff --git a/api/v2beta1/shared_conversion.go b/api/v2beta1/shared_conversion.go index fcb3744..06ab455 100644 --- a/api/v2beta1/shared_conversion.go +++ b/api/v2beta1/shared_conversion.go @@ -45,7 +45,7 @@ func ConvertOptionsV3ToV2(src *pdoknlv3.Options) WMSWFSOptions { } } -func ConvertAutoscaling(src Autoscaling) *autoscalingv2.HorizontalPodAutoscalerSpec { +func ConvertAutoscaling(src Autoscaling) *pdoknlv3.HorizontalPodAutoscalerPatch { var minReplicas *int32 if src.MinReplicas != nil { //nolint:gosec @@ -71,7 +71,7 @@ func ConvertAutoscaling(src Autoscaling) *autoscalingv2.HorizontalPodAutoscalerS }) } - return &autoscalingv2.HorizontalPodAutoscalerSpec{ + return &pdoknlv3.HorizontalPodAutoscalerPatch{ MinReplicas: minReplicas, MaxReplicas: maxReplicas, Metrics: metrics, @@ -196,7 +196,7 @@ func ConvertV3DataToV2(v3 pdoknlv3.Data) Data { return v2 } -func NewV2KubernetesObject(lifecycle *shared_model.Lifecycle, podSpecPatch *corev1.PodSpec, scalingSpec *autoscalingv2.HorizontalPodAutoscalerSpec) Kubernetes { +func NewV2KubernetesObject(lifecycle *shared_model.Lifecycle, podSpecPatch *corev1.PodSpec, scalingSpec *pdoknlv3.HorizontalPodAutoscalerPatch) Kubernetes { kub := Kubernetes{} if lifecycle != nil && lifecycle.TTLInDays != nil { diff --git a/api/v2beta1/wfs_types.go b/api/v2beta1/wfs_types.go index 9b6ad71..91b1c21 100644 --- a/api/v2beta1/wfs_types.go +++ b/api/v2beta1/wfs_types.go @@ -66,7 +66,7 @@ type WFSService struct { Inspire bool `json:"inspire"` Abstract string `json:"abstract"` // +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl" - AccessConstraints string `json:"accessConstraints"` + AccessConstraints *string `json:"accessConstraints,omitempty"` Keywords []string `json:"keywords"` MetadataIdentifier string `json:"metadataIdentifier"` Authority Authority `json:"authority"` diff --git a/api/v2beta1/wms_conversion.go b/api/v2beta1/wms_conversion.go index 46cfb0b..7de7a0a 100644 --- a/api/v2beta1/wms_conversion.go +++ b/api/v2beta1/wms_conversion.go @@ -360,7 +360,7 @@ func (v2Layer WMSLayer) MapToV3(v2Service WMSService) pdoknlv3.Layer { BoundingBoxes: []pdoknlv3.WMSBoundingBox{}, MinScaleDenominator: nil, MaxScaleDenominator: nil, - Visible: &v2Layer.Visible, + Visible: v2Layer.Visible, } if v2Layer.SourceMetadataIdentifier != nil { @@ -443,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 diff --git a/api/v2beta1/wms_types.go b/api/v2beta1/wms_types.go index f505cb2..372c62f 100644 --- a/api/v2beta1/wms_types.go +++ b/api/v2beta1/wms_types.go @@ -58,7 +58,7 @@ type WMSService struct { Title string `json:"title"` Abstract string `json:"abstract"` // +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl" - AccessConstraints string `json:"accessConstraints"` + AccessConstraints *string `json:"accessConstraints,omitempty"` // Pointer for CRD conversion as defaulting is not applied there Keywords []string `json:"keywords"` MetadataIdentifier string `json:"metadataIdentifier"` Authority Authority `json:"authority"` @@ -77,7 +77,7 @@ type WMSService struct { type WMSLayer struct { Name string `json:"name"` Group *string `json:"group,omitempty"` - Visible bool `json:"visible"` + Visible *bool `json:"visible,omitempty"` Title *string `json:"title,omitempty"` Abstract *string `json:"abstract,omitempty"` Keywords []string `json:"keywords,omitempty"` diff --git a/api/v2beta1/zz_generated.deepcopy.go b/api/v2beta1/zz_generated.deepcopy.go index b90a26c..88cf842 100644 --- a/api/v2beta1/zz_generated.deepcopy.go +++ b/api/v2beta1/zz_generated.deepcopy.go @@ -612,6 +612,11 @@ func (in *WFSList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WFSService) DeepCopyInto(out *WFSService) { *out = *in + if in.AccessConstraints != nil { + in, out := &in.AccessConstraints, &out.AccessConstraints + *out = new(string) + **out = **in + } if in.Keywords != nil { in, out := &in.Keywords, &out.Keywords *out = make([]string, len(*in)) @@ -710,6 +715,11 @@ func (in *WMSLayer) DeepCopyInto(out *WMSLayer) { *out = new(string) **out = **in } + if in.Visible != nil { + in, out := &in.Visible, &out.Visible + *out = new(bool) + **out = **in + } if in.Title != nil { in, out := &in.Title, &out.Title *out = new(string) @@ -809,6 +819,11 @@ func (in *WMSList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WMSService) DeepCopyInto(out *WMSService) { *out = *in + if in.AccessConstraints != nil { + in, out := &in.AccessConstraints, &out.AccessConstraints + *out = new(string) + **out = **in + } if in.Keywords != nil { in, out := &in.Keywords, &out.Keywords *out = make([]string, len(*in)) diff --git a/api/v3/shared_types.go b/api/v3/shared_types.go index 9499375..f214252 100644 --- a/api/v3/shared_types.go +++ b/api/v3/shared_types.go @@ -22,6 +22,15 @@ const ( ServiceTypeWFS ServiceType = "WFS" ) +// HorizontalPodAutoscalerPatch - copy of autoscalingv2.HorizontalPodAutoscalerSpec without ScaleTargetRef +// This way we don't have to specify the scaleTargetRef field in the CRD. +type HorizontalPodAutoscalerPatch struct { + MinReplicas *int32 `json:"minReplicas,omitempty"` + MaxReplicas int32 `json:"maxReplicas"` + Metrics []autoscalingv2.MetricSpec `json:"metrics,omitempty"` + Behavior *autoscalingv2.HorizontalPodAutoscalerBehavior `json:"behavior,omitempty"` +} + // WMSWFS is the common interface used for both WMS and WFS resources. // +kubebuilder:object:generate=false type WMSWFS interface { @@ -30,7 +39,7 @@ type WMSWFS interface { Mapfile() *Mapfile PodSpecPatch() *corev1.PodSpec - HorizontalPodAutoscalerPatch() *autoscalingv2.HorizontalPodAutoscalerSpec + HorizontalPodAutoscalerPatch() *HorizontalPodAutoscalerPatch Type() ServiceType Options() *Options HasPostgisData() bool @@ -182,7 +191,7 @@ type Postgis struct { // +kubebuilder:validation:Type=object type TIF struct { // BlobKey to the TIFF file - // +kubebuilder:validation:Pattern=`\.(tif|tiff)$` + // +kubebuilder:validation:Pattern=`\.(tif?f|vrt)$` // +kubebuilder:validation:MinLength:=1 BlobKey string `json:"blobKey"` diff --git a/api/v3/wfs_types.go b/api/v3/wfs_types.go index 90c47f1..a8e099f 100644 --- a/api/v3/wfs_types.go +++ b/api/v3/wfs_types.go @@ -26,7 +26,6 @@ package v3 import ( shared_model "github.com/pdok/smooth-operator/model" - autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -73,9 +72,9 @@ type WFSSpec struct { // +kubebuilder:validation:Schemaless // +kubebuilder:pruning:PreserveUnknownFields // 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 *autoscalingv2.HorizontalPodAutoscalerSpec `json:"horizontalPodAutoscalerPatch,omitempty"` - Options Options `json:"options,omitempty"` + PodSpecPatch *corev1.PodSpec `json:"podSpecPatch,omitempty"` + HorizontalPodAutoscalerPatch *HorizontalPodAutoscalerPatch `json:"horizontalPodAutoscalerPatch,omitempty"` + Options Options `json:"options,omitempty"` // service configuration Service WFSService `json:"service"` @@ -120,9 +119,8 @@ type WFSService struct { // AccessConstraints URL // +kubebuilder:validation:Pattern:="https?://" // +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl" - // +kubebuilder:validation:MinLength:=1 - AccessConstraints string `json:"accessConstraints"` + AccessConstraints *string `json:"accessConstraints,omitempty"` // Default CRS (DataEPSG) // +kubebuilder:validation:Pattern:="^EPSG:(28992|25831|25832|3034|3035|3857|4258|4326)$" @@ -218,7 +216,7 @@ func (wfs *WFS) PodSpecPatch() *corev1.PodSpec { return wfs.Spec.PodSpecPatch } -func (wfs *WFS) HorizontalPodAutoscalerPatch() *autoscalingv2.HorizontalPodAutoscalerSpec { +func (wfs *WFS) HorizontalPodAutoscalerPatch() *HorizontalPodAutoscalerPatch { return wfs.Spec.HorizontalPodAutoscalerPatch } diff --git a/api/v3/wms_types.go b/api/v3/wms_types.go index 60f4721..d855ee9 100644 --- a/api/v3/wms_types.go +++ b/api/v3/wms_types.go @@ -26,7 +26,6 @@ package v3 import ( shared_model "github.com/pdok/smooth-operator/model" - autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "maps" @@ -55,7 +54,7 @@ type WMSSpec struct { PodSpecPatch *corev1.PodSpec `json:"podSpecPatch,omitempty"` // Optional specification for the HorizontalAutoscaler - HorizontalPodAutoscalerPatch *autoscalingv2.HorizontalPodAutoscalerSpec `json:"horizontalPodAutoscalerPatch,omitempty"` + HorizontalPodAutoscalerPatch *HorizontalPodAutoscalerPatch `json:"horizontalPodAutoscalerPatch,omitempty"` // Optional options for the configuration of the service. Options Options `json:"options,omitempty"` @@ -92,7 +91,7 @@ type WMSService struct { // 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"` + AccessConstraints *string `json:"accessConstraints,omitempty"` // TODO?? MaxSize *int32 `json:"maxSize,omitempty"` @@ -139,6 +138,7 @@ 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)" type Layer struct { // Name of the layer, required for layers on the 2nd or 3rd level // +kubebuilder:validations:MinLength:=1 @@ -152,9 +152,9 @@ type Layer struct { // +kubebuilder:validations:MinLength:=1 Abstract *string `json:"abstract,omitempty"` - // Keywords of the layer + // Keywords of the layer, required if the layer is visible // +kubebuilder:validations:MinItems:=1 - Keywords []string `json:"keywords"` + Keywords []string `json:"keywords,omitempty"` // BoundingBoxes of the layer. If omitted the boundingboxes of the parent layer of the service is used. BoundingBoxes []WMSBoundingBox `json:"boundingBoxes,omitempty"` @@ -170,11 +170,11 @@ type Layer struct { DatasetMetadataURL *MetadataURL `json:"datasetMetadataUrl,omitempty"` // The minimum scale at which this layer functions - // +kubebuilder:validation:Pattern:=`^[1-9][0-9]*(.[0-9]+)$` + // +kubebuilder:validation:Pattern:=`^[0-9]+(.[0-9]+)?$` MinScaleDenominator *string `json:"minscaledenominator,omitempty"` // The maximum scale at which this layer functions - // +kubebuilder:validation:Pattern:=`^[1-9][0-9]*(.[0-9]+)$` + // +kubebuilder:validation:Pattern:=`^[1-9][0-9]*(.[0-9]+)?$` MaxScaleDenominator *string `json:"maxscaledenominator,omitempty"` // List of styles used by the layer @@ -313,39 +313,38 @@ type AnnotatedLayer struct { func (wmsService *WMSService) GetAnnotatedLayers() []AnnotatedLayer { result := make([]AnnotatedLayer, 0) - topLayer := wmsService.Layer - annotatedTopLayer := AnnotatedLayer{ - GroupName: nil, - IsTopLayer: true, - IsGroupLayer: topLayer.Name != nil, - IsDataLayer: false, - Layer: topLayer, + firstLayer := AnnotatedLayer{} + if wmsService.Layer.Name != nil && len(*wmsService.Layer.Name) > 0 { + firstLayer = AnnotatedLayer{ + GroupName: nil, + IsTopLayer: wmsService.Layer.IsTopLayer(), + IsGroupLayer: wmsService.Layer.IsGroupLayer(), + IsDataLayer: wmsService.Layer.IsDataLayer(), + Layer: wmsService.Layer, + } + result = append(result, firstLayer) } - result = append(result, annotatedTopLayer) - for _, topLayerChild := range topLayer.Layers { - groupName := topLayer.Name - isGroupLayer := topLayerChild.Layers != nil && len(topLayerChild.Layers) > 0 + for _, subLayer := range wmsService.Layer.Layers { + groupName := wmsService.Layer.Name + isGroupLayer := subLayer.IsGroupLayer() isDataLayer := !isGroupLayer result = append(result, AnnotatedLayer{ GroupName: groupName, IsTopLayer: false, IsGroupLayer: isGroupLayer, IsDataLayer: isDataLayer, - Layer: topLayerChild, + Layer: subLayer, }) - if len(topLayerChild.Layers) > 0 { - for _, middleLayerChild := range topLayerChild.Layers { - groupName = topLayerChild.Name - result = append(result, AnnotatedLayer{ - GroupName: groupName, - IsTopLayer: false, - IsGroupLayer: false, - IsDataLayer: true, - Layer: middleLayerChild, - }) - } + for _, subSubLayer := range subLayer.Layers { + result = append(result, AnnotatedLayer{ + GroupName: subLayer.Name, + IsTopLayer: false, + IsGroupLayer: false, + IsDataLayer: true, + Layer: subSubLayer, + }) } } @@ -423,8 +422,22 @@ func (layer *Layer) IsGroupLayer() bool { return len(layer.Layers) > 0 } -func (layer *Layer) IsTopLayer(service *WMSService) bool { - return layer.Name == service.Layer.Name +// IsTopLayer - a layer is a toplayer if and only if it has sublayers that are group layers. +// In other words the layer is level 1 in a 3 level hierarchy. +func (layer *Layer) IsTopLayer() bool { + if layer.IsGroupLayer() { + for _, childLayer := range layer.Layers { + if childLayer.IsGroupLayer() { + return true + } + } + } + + return false +} + +func (layer *Layer) IsVisible() bool { + return layer.Visible == nil || *layer.Visible } func (layer *Layer) hasBoundingBoxForCRS(crs string) bool { @@ -523,7 +536,7 @@ func (wms *WMS) PodSpecPatch() *corev1.PodSpec { return wms.Spec.PodSpecPatch } -func (wms *WMS) HorizontalPodAutoscalerPatch() *autoscalingv2.HorizontalPodAutoscalerSpec { +func (wms *WMS) HorizontalPodAutoscalerPatch() *HorizontalPodAutoscalerPatch { return wms.Spec.HorizontalPodAutoscalerPatch } diff --git a/api/v3/zz_generated.deepcopy.go b/api/v3/zz_generated.deepcopy.go index 6349167..5656f07 100644 --- a/api/v3/zz_generated.deepcopy.go +++ b/api/v3/zz_generated.deepcopy.go @@ -242,6 +242,38 @@ func (in *Gpkg) DeepCopy() *Gpkg { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HorizontalPodAutoscalerPatch) DeepCopyInto(out *HorizontalPodAutoscalerPatch) { + *out = *in + if in.MinReplicas != nil { + in, out := &in.MinReplicas, &out.MinReplicas + *out = new(int32) + **out = **in + } + if in.Metrics != nil { + in, out := &in.Metrics, &out.Metrics + *out = make([]v2.MetricSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Behavior != nil { + in, out := &in.Behavior, &out.Behavior + *out = new(v2.HorizontalPodAutoscalerBehavior) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerPatch. +func (in *HorizontalPodAutoscalerPatch) DeepCopy() *HorizontalPodAutoscalerPatch { + if in == nil { + return nil + } + out := new(HorizontalPodAutoscalerPatch) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Inspire) DeepCopyInto(out *Inspire) { *out = *in @@ -649,6 +681,11 @@ func (in *WFSService) DeepCopyInto(out *WFSService) { *out = new(string) **out = **in } + if in.AccessConstraints != nil { + in, out := &in.AccessConstraints, &out.AccessConstraints + *out = new(string) + **out = **in + } if in.OtherCrs != nil { in, out := &in.OtherCrs, &out.OtherCrs *out = make([]string, len(*in)) @@ -698,7 +735,7 @@ func (in *WFSSpec) DeepCopyInto(out *WFSSpec) { } if in.HorizontalPodAutoscalerPatch != nil { in, out := &in.HorizontalPodAutoscalerPatch, &out.HorizontalPodAutoscalerPatch - *out = new(v2.HorizontalPodAutoscalerSpec) + *out = new(HorizontalPodAutoscalerPatch) (*in).DeepCopyInto(*out) } in.Options.DeepCopyInto(&out.Options) @@ -803,6 +840,11 @@ func (in *WMSService) DeepCopyInto(out *WMSService) { *out = new(string) **out = **in } + if in.AccessConstraints != nil { + in, out := &in.AccessConstraints, &out.AccessConstraints + *out = new(string) + **out = **in + } if in.MaxSize != nil { in, out := &in.MaxSize, &out.MaxSize *out = new(int32) @@ -861,7 +903,7 @@ func (in *WMSSpec) DeepCopyInto(out *WMSSpec) { } if in.HorizontalPodAutoscalerPatch != nil { in, out := &in.HorizontalPodAutoscalerPatch, &out.HorizontalPodAutoscalerPatch - *out = new(v2.HorizontalPodAutoscalerSpec) + *out = new(HorizontalPodAutoscalerPatch) (*in).DeepCopyInto(*out) } in.Options.DeepCopyInto(&out.Options) diff --git a/config/crd/bases/pdok.nl_wfs.yaml b/config/crd/bases/pdok.nl_wfs.yaml index b2477f7..b29734d 100644 --- a/config/crd/bases/pdok.nl_wfs.yaml +++ b/config/crd/bases/pdok.nl_wfs.yaml @@ -342,7 +342,6 @@ spec: type: string required: - abstract - - accessConstraints - authority - dataEPSG - featureTypes @@ -455,14 +454,14 @@ spec: description: WFSSpec vertegenwoordigt de hoofdstruct voor de YAML-configuratie properties: horizontalPodAutoscalerPatch: - description: HorizontalPodAutoscalerSpec describes the desired functionality - of the HorizontalPodAutoscaler. + description: |- + HorizontalPodAutoscalerPatch - copy of autoscalingv2.HorizontalPodAutoscalerSpec without ScaleTargetRef + This way we don't have to specify the scaleTargetRef field in the CRD. properties: behavior: description: |- - behavior configures the scaling behavior of the target + HorizontalPodAutoscalerBehavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively). - If not set, the default HPAScalingRules for scale up and scale down are used. properties: scaleDown: description: |- @@ -575,21 +574,9 @@ spec: type: object type: object maxReplicas: - description: |- - maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. - It cannot be less that minReplicas. format: int32 type: integer metrics: - description: |- - metrics contains the specifications for which to use to calculate the - desired replica count (the maximum replica count across all metrics will - be used). The desired replica count is calculated multiplying the - ratio between the target value and the current value by the current - number of pods. Ergo, metrics used must decrease as the pod count is - increased, and vice-versa. See the individual metric source types for - more information about how each type of metric must respond. - If not set, the default metric will be set to 80% average CPU utilization. items: description: |- MetricSpec specifies how to scale based on a single metric @@ -1051,39 +1038,11 @@ spec: - type type: object type: array - x-kubernetes-list-type: atomic minReplicas: - description: |- - minReplicas is the lower limit for the number of replicas to which the autoscaler - can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the - alpha feature gate HPAScaleToZero is enabled and at least one Object or External - metric is configured. Scaling is active as long as at least one metric value is - available. format: int32 type: integer - scaleTargetRef: - description: |- - scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics - should be collected, as well as to actually change the replica count. - properties: - apiVersion: - description: apiVersion is the API version of the referent - type: string - kind: - description: 'kind is the kind of the referent; More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'name is the name of the referent; More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - required: - - kind - - name - type: object required: - maxReplicas - - scaleTargetRef type: object lifecycle: description: Optional lifecycle settings @@ -1150,6 +1109,7 @@ spec: type: string accessConstraints: default: https://creativecommons.org/publicdomain/zero/1.0/deed.nl + description: AccessConstraints URL minLength: 1 pattern: https?:// type: string @@ -1353,7 +1313,7 @@ spec: blobKey: description: BlobKey to the TIFF file minLength: 1 - pattern: \.(tif|tiff)$ + pattern: \.(tif?f|vrt)$ type: string getFeatureInfoIncludesClass: description: Include class names in GetFeatureInfo @@ -1548,7 +1508,6 @@ spec: type: string required: - abstract - - accessConstraints - defaultCrs - featureTypes - keywords diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index 226977e..3c6f470 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -319,7 +319,6 @@ spec: required: - name - styles - - visible type: object type: array mapfile: @@ -384,7 +383,6 @@ spec: type: string required: - abstract - - accessConstraints - authority - dataEPSG - inspire @@ -499,9 +497,8 @@ spec: properties: behavior: description: |- - behavior configures the scaling behavior of the target + HorizontalPodAutoscalerBehavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively). - If not set, the default HPAScalingRules for scale up and scale down are used. properties: scaleDown: description: |- @@ -610,21 +607,9 @@ spec: type: object type: object maxReplicas: - description: |- - maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. - It cannot be less that minReplicas. format: int32 type: integer metrics: - description: |- - metrics contains the specifications for which to use to calculate the - desired replica count (the maximum replica count across all metrics will - be used). The desired replica count is calculated multiplying the - ratio between the target value and the current value by the current - number of pods. Ergo, metrics used must decrease as the pod count is - increased, and vice-versa. See the individual metric source types for - more information about how each type of metric must respond. - If not set, the default metric will be set to 80% average CPU utilization. items: description: |- MetricSpec specifies how to scale based on a single metric @@ -1054,37 +1039,11 @@ spec: - type type: object type: array - x-kubernetes-list-type: atomic minReplicas: - description: |- - minReplicas is the lower limit for the number of replicas to which the autoscaler - can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the - alpha feature gate HPAScaleToZero is enabled and at least one Object or External - metric is configured. Scaling is active as long as at least one metric value is - available. format: int32 type: integer - scaleTargetRef: - description: |- - scaleTargetRef points to the target resource to scale, and is used to the pods for which metrics - should be collected, as well as to actually change the replica count. - properties: - apiVersion: - description: apiVersion is the API version of the referent - type: string - kind: - description: 'kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - required: - - kind - - name - type: object required: - maxReplicas - - scaleTargetRef type: object lifecycle: description: Optional lifecycle settings @@ -1350,7 +1309,7 @@ spec: blobKey: description: BlobKey to the TIFF file minLength: 1 - pattern: \.(tif|tiff)$ + pattern: \.(tif?f|vrt)$ type: string getFeatureInfoIncludesClass: description: Include class names in GetFeatureInfo responses @@ -1403,7 +1362,7 @@ spec: - csw type: object keywords: - description: Keywords of the layer + description: Keywords of the layer, required if the layer is visible items: type: string type: array @@ -1551,7 +1510,7 @@ spec: blobKey: description: BlobKey to the TIFF file minLength: 1 - pattern: \.(tif|tiff)$ + pattern: \.(tif?f|vrt)$ type: string getFeatureInfoIncludesClass: description: Include class names in GetFeatureInfo responses @@ -1604,7 +1563,7 @@ spec: - csw type: object keywords: - description: Keywords of the layer + description: Keywords of the layer, required if the layer is visible items: type: string type: array @@ -1752,7 +1711,7 @@ spec: blobKey: description: BlobKey to the TIFF file minLength: 1 - pattern: \.(tif|tiff)$ + pattern: \.(tif?f|vrt)$ type: string getFeatureInfoIncludesClass: description: Include class names in GetFeatureInfo responses @@ -1805,7 +1764,7 @@ spec: - csw type: object keywords: - description: Keywords of the layer + description: Keywords of the layer, required if the layer is visible items: type: string type: array @@ -1813,11 +1772,11 @@ spec: type: boolean maxscaledenominator: description: The maximum scale at which this layer functions - pattern: ^[1-9][0-9]*(.[0-9]+)$ + pattern: ^[1-9][0-9]*(.[0-9]+)?$ type: string minscaledenominator: description: The minimum scale at which this layer functions - pattern: ^[1-9][0-9]*(.[0-9]+)$ + pattern: ^[0-9]+(.[0-9]+)?$ type: string name: description: Name of the layer, required for layers on the 2nd or 3rd level @@ -1864,17 +1823,19 @@ spec: description: Whether or not the layer is visible. At least one of the layers must be visible. type: boolean required: - - keywords - name type: object + x-kubernetes-validations: + - message: A layer should have keywords when visible + rule: '!self.visible || has(self.keywords)' type: array maxscaledenominator: description: The maximum scale at which this layer functions - pattern: ^[1-9][0-9]*(.[0-9]+)$ + pattern: ^[1-9][0-9]*(.[0-9]+)?$ type: string minscaledenominator: description: The minimum scale at which this layer functions - pattern: ^[1-9][0-9]*(.[0-9]+)$ + pattern: ^[0-9]+(.[0-9]+)?$ type: string name: description: Name of the layer, required for layers on the 2nd or 3rd level @@ -1921,20 +1882,21 @@ spec: description: Whether or not the layer is visible. At least one of the layers must be visible. type: boolean required: - - keywords - 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)' type: array maxscaledenominator: description: The maximum scale at which this layer functions - pattern: ^[1-9][0-9]*(.[0-9]+)$ + pattern: ^[1-9][0-9]*(.[0-9]+)?$ type: string minscaledenominator: description: The minimum scale at which this layer functions - pattern: ^[1-9][0-9]*(.[0-9]+)$ + pattern: ^[0-9]+(.[0-9]+)?$ type: string name: description: Name of the layer, required for layers on the 2nd or 3rd level @@ -1980,12 +1942,12 @@ spec: default: true description: Whether or not the layer is visible. At least one of the layers must be visible. type: boolean - required: - - keywords 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)' mapfile: description: Custom mapfile properties: diff --git a/internal/controller/capabilitiesgenerator/capabilities_generator_test.go b/internal/controller/capabilitiesgenerator/capabilities_generator_test.go index 759927c..07506c2 100644 --- a/internal/controller/capabilitiesgenerator/capabilities_generator_test.go +++ b/internal/controller/capabilitiesgenerator/capabilities_generator_test.go @@ -558,7 +558,7 @@ func TestGetInputForWFS(t *testing.T) { Title: "some Service title", Abstract: "some \"Service\" abstract", Keywords: []string{"service-keyword-1", "service-keyword-2", "infoFeatureAccessService"}, - AccessConstraints: "http://creativecommons.org/publicdomain/zero/1.0/deed.nl", + AccessConstraints: smoothoperatorutils.Pointer("http://creativecommons.org/publicdomain/zero/1.0/deed.nl"), Inspire: &pdoknlv3.Inspire{ ServiceMetadataURL: pdoknlv3.MetadataURL{ CSW: &pdoknlv3.Metadata{ diff --git a/internal/controller/capabilitiesgenerator/mapper.go b/internal/controller/capabilitiesgenerator/mapper.go index 05afeb5..8ea284b 100644 --- a/internal/controller/capabilitiesgenerator/mapper.go +++ b/internal/controller/capabilitiesgenerator/mapper.go @@ -46,7 +46,7 @@ func MapWFSToCapabilitiesGeneratorInput(wfs *pdoknlv3.WFS, ownerInfo *smoothoper ServiceIdentification: wfs200.ServiceIdentification{ Title: mapperutils.EscapeQuotes(wfs.Spec.Service.Title), Abstract: mapperutils.EscapeQuotes(wfs.Spec.Service.Abstract), - AccessConstraints: wfs.Spec.Service.AccessConstraints, + AccessConstraints: *wfs.Spec.Service.AccessConstraints, Keywords: &wsc110.Keywords{ Keyword: wfs.Spec.Service.Keywords, }, @@ -221,8 +221,8 @@ func MapWMSToCapabilitiesGeneratorInput(wms *pdoknlv3.WMS, ownerInfo *smoothoper maxHeight := 4000 accessContraints := wms.Spec.Service.AccessConstraints - if accessContraints == "" { - accessContraints = "https://creativecommons.org/publicdomain/zero/1.0/deed.nl" + if accessContraints == nil || *accessContraints == "" { + accessContraints = smoothoperatorutils.Pointer("https://creativecommons.org/publicdomain/zero/1.0/deed.nl") } config := capabilitiesgenerator.Config{ @@ -245,7 +245,7 @@ func MapWMSToCapabilitiesGeneratorInput(wms *pdoknlv3.WMS, ownerInfo *smoothoper OnlineResource: wms130.OnlineResource{Href: &hostBaseURL}, ContactInformation: getContactInformation(ownerInfo), Fees: fees, - AccessConstraints: &accessContraints, + AccessConstraints: accessContraints, LayerLimit: nil, MaxWidth: &maxWidth, MaxHeight: &maxHeight, diff --git a/internal/controller/mapfilegenerator/mapfile_generator_test.go b/internal/controller/mapfilegenerator/mapfile_generator_test.go index 47878ab..a922e22 100644 --- a/internal/controller/mapfilegenerator/mapfile_generator_test.go +++ b/internal/controller/mapfilegenerator/mapfile_generator_test.go @@ -130,7 +130,7 @@ func TestGetConfigForWFS(t *testing.T) { Title: "some Service title", Abstract: "some \"Service\" abstract", Keywords: []string{"service-keyword-1", "service-keyword-2", "infoFeatureAccessService"}, - AccessConstraints: "http://creativecommons.org/publicdomain/zero/1.0/deed.nl", + AccessConstraints: smoothoperatorutils.Pointer("http://creativecommons.org/publicdomain/zero/1.0/deed.nl"), Bbox: &pdoknlv3.Bbox{ DefaultCRS: shared_model.BBox{ MinX: "0.0", @@ -317,6 +317,7 @@ func TestGetConfigForWMSWithGroupLayersAndTopGroupLayer(t *testing.T) { inputStruct, err := MapWMSToMapfileGeneratorInput(&wms, ownerInfo) assert.NoError(t, err) expected := WMSInput{} + assert.NoError(t, err) err = json.Unmarshal([]byte(WMSGroupAndToplayerConfig), &expected) assert.NoError(t, err) diff --git a/internal/controller/mapfilegenerator/mapper.go b/internal/controller/mapfilegenerator/mapper.go index a306484..30bc47f 100644 --- a/internal/controller/mapfilegenerator/mapper.go +++ b/internal/controller/mapfilegenerator/mapper.go @@ -118,8 +118,8 @@ func MapWMSToMapfileGeneratorInput(wms *pdoknlv3.WMS, _ *smoothoperatorv1.OwnerI service := wms.Spec.Service accessConstraints := service.AccessConstraints - if accessConstraints == "" { - accessConstraints = "https://creativecommons.org/publicdomain/zero/1.0/deed.nl" + if accessConstraints == nil || *accessConstraints == "" { + accessConstraints = smoothoperatorutils.Pointer("https://creativecommons.org/publicdomain/zero/1.0/deed.nl") } datasetOwner := "" @@ -183,7 +183,7 @@ func MapWMSToMapfileGeneratorInput(wms *pdoknlv3.WMS, _ *smoothoperatorv1.OwnerI DataEPSG: service.DataEPSG, EPSGList: epsgs, }, - AccessConstraints: accessConstraints, + AccessConstraints: *accessConstraints, Layers: []WMSLayer{}, GroupLayers: []GroupLayer{}, Symbols: getSymbols(wms), @@ -199,7 +199,7 @@ func MapWMSToMapfileGeneratorInput(wms *pdoknlv3.WMS, _ *smoothoperatorv1.OwnerI if annotatedLayer.IsDataLayer { layer := getWMSLayer(annotatedLayer.Layer, extent, wms) result.Layers = append(result.Layers, layer) - } else if annotatedLayer.IsGroupLayer && *annotatedLayer.Layer.Visible { + } else if annotatedLayer.IsGroupLayer && !annotatedLayer.IsTopLayer { groupLayer := GroupLayer{ Name: *annotatedLayer.Layer.Name, Title: smoothoperatorutils.PointerVal(annotatedLayer.Layer.Title, ""), @@ -222,7 +222,8 @@ func getWMSLayer(serviceLayer pdoknlv3.Layer, serviceExtent string, wms *pdoknlv groupName := "" parent := serviceLayer.GetParent(&wms.Spec.Service.Layer) - if parent.IsGroupLayer() && parent.Name != nil && *parent.Visible { + // If the layer falls directly under the toplayer, the groupname is omitted + if !parent.IsTopLayer() && parent.IsGroupLayer() && parent.Name != nil && parent.IsVisible() { groupName = *parent.Name }