From b194b58e4d3571d593c919d0769e6c4f3e0e442a Mon Sep 17 00:00:00 2001 From: Wouter Remijn Date: Wed, 4 Jun 2025 14:44:09 +0200 Subject: [PATCH 1/2] wip --- api/v3/shared_types.go | 1 - api/v3/wms_types.go | 104 +++++----- config/crd/bases/pdok.nl_wfs.yaml | 1 - config/crd/bases/pdok.nl_wms.yaml | 194 +++++------------- config/crd/update_openapi.go | 28 ++- config/samples/v3_wms.yaml | 178 ++++------------ .../capabilitiesgenerator/mapper.go | 1 + 7 files changed, 167 insertions(+), 340 deletions(-) diff --git a/api/v3/shared_types.go b/api/v3/shared_types.go index db94855..9a046ae 100644 --- a/api/v3/shared_types.go +++ b/api/v3/shared_types.go @@ -196,7 +196,6 @@ type Postgis struct { // GeometryType of the table // +kubebuilder:validation:Pattern=`^(Multi)?(Point|LineString|Polygon)$` - // +kubebuilder:validation:MinLength:=1 GeometryType string `json:"geometryType"` // Columns to expose from table diff --git a/api/v3/wms_types.go b/api/v3/wms_types.go index 071ff35..0272794 100644 --- a/api/v3/wms_types.go +++ b/api/v3/wms_types.go @@ -48,6 +48,36 @@ const ( // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// +kubebuilder:conversion:hub +// +kubebuilder:subresource:status +// versionName=v3 +// +kubebuilder:resource:categories=pdok +// +kubebuilder:resource:path=wms + +// WMS is the Schema for the wms API. +type WMS struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec WMSSpec `json:"spec"` + Status smoothoperatormodel.OperatorStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// WMSList contains a list of WMS. +type WMSList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []WMS `json:"items"` +} + +func init() { + SchemeBuilder.Register(&WMS{}, &WMSList{}) +} + // WMSSpec defines the desired state of WMS. // +kubebuilder:validation:XValidation:rule="!has(self.ingressRouteUrls) || self.ingressRouteUrls.exists_one(x, x.url == self.service.url)",messageExpression="'ingressRouteUrls should include service.url '+self.service.url" type WMSSpec struct { @@ -145,9 +175,11 @@ func (wmsService WMSService) KeywordsIncludingInspireKeyword() []string { // HealthCheck is the struct with all fields to configure custom healthchecks // +kubebuilder:validation:XValidation:rule="!has(self.querystring) || has(self.mimetype)",message="mimetype is required when a querystring is used" -// +kubebuilder:validation:XValidation:rule="(has(self.boundingbox) || has(self.querystring)) && !(has(self.querystring) && has(self.boundingbox))", message="healthcheck should have querystring + mimetype or boundingbox, not both" +// +kubebuilder:validation:XValidation:rule="(has(self.boundingbox) || has(self.querystring)) && !(has(self.querystring) && has(self.boundingbox))", message="healthcheck should have exactly 1 of querystring + mimetype or boundingbox" +// +kubebuilder:validation:XValidation:rule="(has(self.boundingbox) || has(self.mimetype)) && !(has(self.mimetype) && has(self.boundingbox))", message="healthcheck should have exactly 1 of querystring + mimetype or boundingbox" type HealthCheckWMS struct { - // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:XValidation:rule="self.contains('Service=WMS')",message="a valid healthcheck contains 'Service=WMS'" + // +kubebuilder:validation:XValidation:rule="self.contains('Request=')",message="a valid healthcheck contains 'Request='" Querystring *string `json:"querystring,omitempty"` // +kubebuilder:validation:Pattern=(image/png|text/xml|text/html) Mimetype *string `json:"mimetype,omitempty"` @@ -157,52 +189,51 @@ type HealthCheckWMS struct { // +kubebuilder:validation:XValidation:message="Either blobKeys or configMapRefs is required",rule="has(self.blobKeys) || has(self.configMapRefs)" type StylingAssets struct { - // +kubebuilder:validations:MinItems:=1 + // +kubebuilder:validation:MinItems:=1 BlobKeys []string `json:"blobKeys,omitempty"` - // +kubebuilder:validations:MinItems:=1 + // +kubebuilder:validation:MinItems:=1 ConfigMapRefs []ConfigMapRef `json:"configMapRefs,omitempty"` } type ConfigMapRef struct { - // +kubebuilder:validations:MinLength:=1 + // +kubebuilder:validation:MinLength:=1 Name string `json:"name"` - // +kubebuilder:validations:MinItems:=1 + // +kubebuilder:validation:MinItems:=1 Keys []string `json:"keys,omitempty"` } -// +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 exactly one of sublayers or data", rule="(has(self.data) || has(self.layers)) && !(has(self.data) && has(self.layers))" // +kubebuilder:validation:XValidation:message="A layer with data attribute should have styling", rule="!has(self.data) || has(self.styles)" // +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 + // +kubebuilder:validation:MinLength:=1 Name *string `json:"name,omitempty"` // Title of the layer - // +kubebuilder:validations:MinLength:=1 + // +kubebuilder:validation:MinLength:=1 Title *string `json:"title,omitempty"` // Abstract of the layer - // +kubebuilder:validations:MinLength:=1 + // +kubebuilder:validation:MinLength:=1 Abstract *string `json:"abstract,omitempty"` // Keywords of the layer, required if the layer is visible - // +kubebuilder:validations:MinItems:=1 + // +kubebuilder:validation:MinItems:=1 + // +kubebuilder:validation:items:MinLength:=1 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 + // +kubebuilder:validation: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"` + Visible bool `json:"visible,omitempty"` // TODO ?? Authority *Authority `json:"authority,omitempty"` @@ -219,7 +250,7 @@ type Layer struct { MaxScaleDenominator *string `json:"maxscaledenominator,omitempty"` // List of styles used by the layer - // +kubebuilder:validations:MinItems:=1 + // +kubebuilder:validation:MinItems:=1 Styles []Style `json:"styles,omitempty"` // Mapfile setting, sets "LABEL_NO_CLIP=ON" @@ -229,7 +260,8 @@ type Layer struct { Data *Data `json:"data,omitempty"` // Sublayers of the layer - // +kubebuilder:validations:MinItems:=1 + // +kubebuilder:validation:MinItems:=1 + // +kubebuilder:validation:Type=array Layers []Layer `json:"layers,omitempty"` } @@ -256,16 +288,16 @@ type Authority struct { } type Style struct { - // +kubebuilder:validations:MinLength:=1 + // +kubebuilder:validation:MinLength:=1 Name string `json:"name"` - // +kubebuilder:validations:MinLength:=1 + // +kubebuilder:validation:MinLength:=1 Title *string `json:"title,omitempty"` - // +kubebuilder:validations:MinLength:=1 + // +kubebuilder:validation:MinLength:=1 Abstract *string `json:"abstract,omitempty"` - // +kubebuilder:validations:MinLength:=1 + // +kubebuilder:validation:MinLength:=1 Visualization *string `json:"visualization,omitempty"` Legend *Legend `json:"legend,omitempty"` @@ -289,36 +321,6 @@ type Legend struct { BlobKey string `json:"blobKey"` } -// +kubebuilder:object:root=true -// +kubebuilder:storageversion -// +kubebuilder:conversion:hub -// +kubebuilder:subresource:status -// versionName=v3 -// +kubebuilder:resource:categories=pdok -// +kubebuilder:resource:path=wms - -// WMS is the Schema for the wms API. -type WMS struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec WMSSpec `json:"spec,omitempty"` - Status smoothoperatormodel.OperatorStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// WMSList contains a list of WMS. -type WMSList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []WMS `json:"items"` -} - -func init() { - SchemeBuilder.Register(&WMS{}, &WMSList{}) -} - func (wmsService *WMSService) GetBoundingBox() WMSBoundingBox { var boundingBox *WMSBoundingBox diff --git a/config/crd/bases/pdok.nl_wfs.yaml b/config/crd/bases/pdok.nl_wfs.yaml index d88840b..98e5dbb 100644 --- a/config/crd/bases/pdok.nl_wfs.yaml +++ b/config/crd/bases/pdok.nl_wfs.yaml @@ -1295,7 +1295,6 @@ spec: type: array geometryType: description: GeometryType of the table - minLength: 1 pattern: ^(Multi)?(Point|LineString|Polygon)$ type: string tableName: diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index 59c3a72..b6d0e9d 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -526,14 +526,20 @@ spec: pattern: (image/png|text/xml|text/html) type: string querystring: - minLength: 1 type: string + x-kubernetes-validations: + - message: a valid healthcheck contains 'Service=WMS' + rule: self.contains('Service=WMS') + - message: a valid healthcheck contains 'Request=' + rule: self.contains('Request=') type: object x-kubernetes-validations: - message: mimetype is required when a querystring is used rule: '!has(self.querystring) || has(self.mimetype)' - - message: healthcheck should have querystring + mimetype or boundingbox, not both + - message: healthcheck should have exactly 1 of querystring + mimetype or boundingbox rule: (has(self.boundingbox) || has(self.querystring)) && !(has(self.querystring) && has(self.boundingbox)) + - message: healthcheck should have exactly 1 of querystring + mimetype or boundingbox + rule: (has(self.boundingbox) || has(self.mimetype)) && !(has(self.mimetype) && has(self.boundingbox)) horizontalPodAutoscalerPatch: description: Optional specification for the HorizontalAutoscaler properties: @@ -1273,6 +1279,7 @@ spec: properties: abstract: description: Abstract of the layer + minLength: 1 type: string authority: properties: @@ -1322,111 +1329,8 @@ spec: - bbox - crs type: object + minItems: 1 type: array - data: - description: Data (gpkg/postgis/tif) used by the layer - properties: - gpkg: - description: Gpkg configures a GeoPackage file source - properties: - blobKey: - description: Blobkey identifies the location/bucket of the .gpkg file - pattern: ^.+\/.+\/.+\.gpkg$ - type: string - columns: - description: Columns to visualize for this table - items: - description: Column maps a source column name to an optional alias for output. - properties: - alias: - description: Alias for the column in the service output. - minLength: 1 - type: string - name: - description: Name of the column in the data source. - minLength: 1 - type: string - required: - - name - type: object - minItems: 1 - type: array - geometryType: - description: GeometryType of the table, must match an OGC type - pattern: ^(Multi)?(Point|LineString|Polygon)$ - type: string - tableName: - description: TableName is the table within the geopackage - minLength: 1 - type: string - required: - - blobKey - - columns - - geometryType - - tableName - type: object - postgis: - description: Postgis configures a Postgis table source - properties: - columns: - description: Columns to expose from table - items: - description: Column maps a source column name to an optional alias for output. - properties: - alias: - description: Alias for the column in the service output. - minLength: 1 - type: string - name: - description: Name of the column in the data source. - minLength: 1 - type: string - required: - - name - type: object - minItems: 1 - type: array - geometryType: - description: GeometryType of the table - minLength: 1 - pattern: ^(Multi)?(Point|LineString|Polygon)$ - type: string - tableName: - description: TableName in postGIS - minLength: 1 - type: string - required: - - columns - - geometryType - - tableName - type: object - tif: - description: TIF configures a GeoTIF raster source - properties: - blobKey: - description: BlobKey to the TIFF file - pattern: ^.+\/.+\/.+\.(tif?f|vrt)$ - type: string - getFeatureInfoIncludesClass: - 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: Sets the color index to treat as transparent for raster layers, optional, hex or rgb - 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: - default: NEAREST - description: This option can be used to control the resampling kernel used sampling raster images, optional - pattern: (NEAREST|AVERAGE|BILINEAR) - type: string - required: - - blobKey - type: object - type: object - x-kubernetes-validations: - - message: Atleast one of the datasource should be provided (postgis, gpkg, tif) - rule: has(self.gpkg) || has(self.tif) || has(self.postgis) datasetMetadataUrl: description: Links to metadata properties: @@ -1462,11 +1366,10 @@ spec: keywords: description: Keywords of the layer, required if the layer is visible items: + minLength: 1 type: string + minItems: 1 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]' items: @@ -1474,6 +1377,7 @@ spec: properties: abstract: description: Abstract of the layer + minLength: 1 type: string authority: properties: @@ -1523,6 +1427,7 @@ spec: - bbox - crs type: object + minItems: 1 type: array data: description: Data (gpkg/postgis/tif) used by the layer @@ -1589,7 +1494,6 @@ spec: type: array geometryType: description: GeometryType of the table - minLength: 1 pattern: ^(Multi)?(Point|LineString|Polygon)$ type: string tableName: @@ -1663,7 +1567,9 @@ spec: keywords: description: Keywords of the layer, required if the layer is visible items: + minLength: 1 type: string + minItems: 1 type: array labelNoClip: description: Mapfile setting, sets "LABEL_NO_CLIP=ON" @@ -1675,6 +1581,7 @@ spec: properties: abstract: description: Abstract of the layer + minLength: 1 type: string authority: properties: @@ -1724,6 +1631,7 @@ spec: - bbox - crs type: object + minItems: 1 type: array data: description: Data (gpkg/postgis/tif) used by the layer @@ -1790,7 +1698,6 @@ spec: type: array geometryType: description: GeometryType of the table - minLength: 1 pattern: ^(Multi)?(Point|LineString|Polygon)$ type: string tableName: @@ -1864,7 +1771,9 @@ spec: keywords: description: Keywords of the layer, required if the layer is visible items: + minLength: 1 type: string + minItems: 1 type: array labelNoClip: description: Mapfile setting, sets "LABEL_NO_CLIP=ON" @@ -1879,12 +1788,14 @@ spec: type: string name: description: Name of the layer, required for layers on the 2nd or 3rd level + minLength: 1 type: string styles: description: List of styles used by the layer items: properties: abstract: + minLength: 1 type: string legend: properties: @@ -1908,24 +1819,28 @@ spec: - blobKey type: object name: + minLength: 1 type: string title: + minLength: 1 type: string visualization: + minLength: 1 type: string required: - name type: object + minItems: 1 type: array title: description: Title of the layer + minLength: 1 type: string visible: default: true 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: @@ -1937,10 +1852,7 @@ spec: 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)' + minItems: 1 type: array maxscaledenominator: description: The maximum scale at which this layer functions @@ -1952,12 +1864,14 @@ spec: type: string name: description: Name of the layer, required for layers on the 2nd or 3rd level + minLength: 1 type: string styles: description: List of styles used by the layer items: properties: abstract: + minLength: 1 type: string legend: properties: @@ -1981,28 +1895,32 @@ spec: - blobKey type: object name: + minLength: 1 type: string title: + minLength: 1 type: string visualization: + minLength: 1 type: string required: - name type: object + minItems: 1 type: array title: description: Title of the layer + minLength: 1 type: string visible: default: true 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 + - message: A layer should have exactly one of sublayers or data rule: (has(self.data) || has(self.layers)) && !(has(self.data) && has(self.layers)) - message: A layer with data attribute should have styling rule: '!has(self.data) || has(self.styles)' @@ -2012,10 +1930,7 @@ spec: 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)' + minItems: 1 type: array maxscaledenominator: description: The maximum scale at which this layer functions @@ -2027,12 +1942,14 @@ spec: type: string name: description: Name of the layer, required for layers on the 2nd or 3rd level + minLength: 1 type: string styles: description: List of styles used by the layer items: properties: abstract: + minLength: 1 type: string legend: properties: @@ -2056,40 +1973,37 @@ spec: - blobKey type: object name: + minLength: 1 type: string title: + minLength: 1 type: string visualization: + minLength: 1 type: string required: - name type: object + minItems: 1 type: array title: description: Title of the layer + minLength: 1 type: string visible: default: true description: Whether or not the layer is visible. At least one of the layers must be visible. type: boolean required: - - visible + - title + - abstract + - keywords + - layers 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 with data attribute should have styling - rule: '!has(self.data) || has(self.styles)' - - message: A layer should have keywords when visible - rule: '!self.visible || 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)' + - fieldPath: .visible + message: TopLayer must be visible + rule: self.visible mapfile: description: Custom mapfile properties: @@ -2134,6 +2048,7 @@ spec: blobKeys: items: type: string + minItems: 1 type: array configMapRefs: items: @@ -2141,12 +2056,15 @@ spec: keys: items: type: string + minItems: 1 type: array name: + minLength: 1 type: string required: - name type: object + minItems: 1 type: array type: object x-kubernetes-validations: @@ -2245,6 +2163,8 @@ spec: description: The result of creating or updating of each derived resource for this Atom. type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/update_openapi.go b/config/crd/update_openapi.go index 6913be2..8e2cfea 100644 --- a/config/crd/update_openapi.go +++ b/config/crd/update_openapi.go @@ -84,19 +84,27 @@ func updateLayersV3(version *v1.CustomResourceDefinitionVersion) { // Level 2 layerSpecLevel2 := layer.DeepCopy() layerSpecLevel2.Required = append(layerSpecLevel2.Required, "name") - layerSpecLevel2.Properties["layers"] = v1.JSONSchemaProps{ - Type: "array", - Description: "[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]", - Items: &v1.JSONSchemaPropsOrArray{Schema: layerSpecLevel3}, + bottomLayers := layerSpecLevel2.Properties["layers"] + bottomLayers.Description = "[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]" + bottomLayers.Items = &v1.JSONSchemaPropsOrArray{Schema: layerSpecLevel3} + layerSpecLevel2.Properties["layers"] = bottomLayers + + // Level 1 + layerSpecLevel1 := layer.DeepCopy() + layerSpecLevel1.Required = append(layerSpecLevel1.Required, "title", "abstract", "keywords", "layers") + layerSpecLevel1.XValidations = []v1.ValidationRule{ + {Rule: "self.visible", Message: "TopLayer must be visible", FieldPath: ".visible"}, } + delete(layerSpecLevel1.Properties, "data") + delete(layerSpecLevel1.Properties, "labelNoClip") - layer.Properties["layers"] = v1.JSONSchemaProps{ - Type: "array", - Description: "[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]", - Items: &v1.JSONSchemaPropsOrArray{Schema: layerSpecLevel2}, - } + midLayers := layerSpecLevel1.Properties["layers"] + midLayers.Description = "[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]" + midLayers.Items = &v1.JSONSchemaPropsOrArray{Schema: layerSpecLevel2} + + layerSpecLevel1.Properties["layers"] = midLayers - service.Properties["layer"] = layer + service.Properties["layer"] = *layerSpecLevel1 spec.Properties["service"] = service schema.Properties["spec"] = spec version.Schema = &v1.CustomResourceValidation{ diff --git a/config/samples/v3_wms.yaml b/config/samples/v3_wms.yaml index 99dfdc2..5a11178 100644 --- a/config/samples/v3_wms.yaml +++ b/config/samples/v3_wms.yaml @@ -1,152 +1,50 @@ apiVersion: pdok.nl/v3 kind: WMS metadata: - labels: - app.kubernetes.io/name: mapserver-operator - app.kubernetes.io/managed-by: kustomize - dataset: dataset - dataset-owner: owner - service-type: wms - service-version: 1.0.0 - name: sample-v3 + name: sample spec: - lifecycle: - ttlInDays: 21 - podSpecPatch: - containers: - - name: mapserver - resources: - limits: - memory: 12M - ephemeral-storage: 2G - horizontalPodAutoscalerPatch: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: wms-sample-v3 - maxReplicas: 5 - minReplicas: 2 - metrics: - - type: "Resource" - resource: - name: cpu - target: - type: Utilization - averageUtilization: 60 - options: - automaticCasing: true - prefetchData: false - includeIngress: false - rewriteGroupToDataLayers: true - validateChildStyleNameEqual: true + podSpecPatch: {} + healthCheck: + querystring: "f" + mimetype: "image/png" service: - url: https://service.pdok.nl/owner/dataset/wms/1.0.0 - title: "Dataset" - abstract: "Dataset abstract ..." + prefix: "prefix" + url: "https://test.test/path" + title: "title" + abstract: "abstract" keywords: - - keyword1 - - keyword2 - ownerInfoRef: - fees: "" - accessConstraints: "http://creativecommons.org/publicdomain/zero/1.0/deed.nl" - maxSize: - inspire: - serviceMetadataUrl: - csw: - metadataIdentifier: 1234abcd-1234-abcd-1234-abcd1234abcd - spatialDatasetIdentifier: abcd1234-abcd-1234-abcd-1234abcd1234 - language: "dut" - dataEPSG: EPSG:28992 - resolution: - defResolution: - stylingAssets: - mapfile: + - "keyword" + ownerInfoRef: "owner" + dataEPSG: "EPSG:28992" layer: - name: top-layer-name - title: Top "Layer" Title - abstract: Top "Layer" Abstract + title: "title" + abstract: "abstract" keywords: - - top-layer-keyword-1 - - top-layer-keyword-2 - boundingBoxes: - - crs: EPSG:28992 - bbox: - minx: "482.06" - maxx: "306602.42" - miny: "284182.97" - maxy: "637049.52" + - "keyword" visible: true - authority: - datasetMetadataUrl: - minscaledenominator: - maxscaledenominator: - style: - labelNoClip: false - data: layers: - - name: group-layer-name - title: Group "Layer" Title - abstract: Group "Abstract" Abstract - keywords: - - group-layer-keyword-1 - - group-layer-keyword-2 - boundingBoxes: + - name: "visible" visible: true - authority: - datasetMetadataUrl: - minscaledenominator: - maxscaledenominator: + title: "title" + abstract: "abstract" + keywords: + - keyword + data: + gpkg: + blobKey: "container/path/file.gpkg" + columns: + - name: "column" + geometryType: "Point" + tableName: "table" + styles: + - name: "name" + - name: "not visible" + visible: false + data: + postgis: + columns: + - name: "column" + geometryType: "Point" + tableName: "table" styles: - labelNoClip: false - layers: - - name: gpkg-layer-name - title: GPKG "Layer" Title - abstract: GPKG "Abstract" Abstract - keywords: - - gpkg-layer-keyword-1 - - gpkg-layer-keyword-2 - boundingBoxes: - visible: true - authority: - datasetMetadataUrl: - minscaledenominator: - maxscaledenominator: - styles: - - name: gpkg-layer-style-name - title: gpkg-layer-style-title - visualization: gpkg-layer-style.style - labelNoClip: false - data: - gpkg: - blobKey: "geopackages-bucket/key/gpkg-layer-data.gpkg" - tableName: "table-1" - geometryType: "MultiPolygon" - columns: - - name: "column-1" - alias: "alias-column-1" - - name: "column-2" - - name: "column-3" - alias: "alias-column-3" - - name: tif-layer-name - title: TIF "Layer" Title - abstract: TIF "Abstract" Abstract - keywords: - - tif-layer-keyword-1 - - tif-layer-keyword-2 - boundingBoxes: - visible: true - authority: - datasetMetadataUrl: - minscaledenominator: - maxscaledenominator: - styles: - - name: tif-layer-style-1-name - title: tif-layer-style-1-title - visualization: tif-layer-style-1.style - labelNoClip: false - data: - tif: - blobKey: "tifs-bucket/key/tif-layer-data.tif" - offsite: "#FF00FF" - resample: "AVERAGE" - getFeatureInfoIncludesClass: true + - name: "name" diff --git a/internal/controller/capabilitiesgenerator/mapper.go b/internal/controller/capabilitiesgenerator/mapper.go index 5ae5c87..4b81d73 100644 --- a/internal/controller/capabilitiesgenerator/mapper.go +++ b/internal/controller/capabilitiesgenerator/mapper.go @@ -450,6 +450,7 @@ func mapLayer(layer pdoknlv3.Layer, canonicalURL string, authorityURL *wms130.Au Title: mapperutils.EscapeQuotes(smoothoperatorutils.PointerVal(layer.Title, "")), Abstract: smoothoperatorutils.Pointer(mapperutils.EscapeQuotes(smoothoperatorutils.PointerVal(layer.Abstract, ""))), KeywordList: &wms130.Keywords{Keyword: layer.Keywords}, + // TODO //CRS: defaultCrs, //EXGeographicBoundingBox: &defaultBoundingBox, //BoundingBox: allDefaultBoundingBoxes, From d18119322ac55041db839cd1d9731107f167f779 Mon Sep 17 00:00:00 2001 From: Wouter Remijn Date: Thu, 5 Jun 2025 14:16:22 +0200 Subject: [PATCH 2/2] wms service validation --- api/v2beta1/shared_conversion.go | 18 +-- api/v2beta1/wfs_conversion.go | 28 ++--- api/v2beta1/wms_conversion.go | 37 ++++-- api/v3/shared_types.go | 88 +++++++++----- api/v3/wfs_types.go | 53 +++------ api/v3/wms_types.go | 67 ++++++----- api/v3/zz_generated.deepcopy.go | 109 ++++++++++++++---- config/crd/bases/pdok.nl_wfs.yaml | 22 +--- config/crd/bases/pdok.nl_wms.yaml | 34 +++--- config/samples/v3_wms.yaml | 3 - .../blobdownload/blob_download_test.go | 28 ++--- .../capabilities_generator_test.go | 23 ++-- .../capabilitiesgenerator/mapper.go | 7 +- .../test_data/wms_input.yaml | 1 - .../featureinfo_generator_test.go | 2 +- .../controller/mapfilegenerator/mapper.go | 2 +- .../configmap-capabilities-generator.yaml | 2 +- .../expected/configmap-mapfile-generator.yaml | 2 +- .../wms/complete/expected/deployment.yaml | 4 +- .../test_data/wms/complete/input/wms.yaml | 5 +- .../configmap-capabilities-generator.yaml | 2 +- .../custom-mapfile/expected/deployment.yaml | 2 +- .../configmap-capabilities-generator.yaml | 2 +- .../wms/minimal/expected/deployment.yaml | 2 +- .../configmap-capabilities-generator.yaml | 2 +- .../wms/noprefetch/expected/deployment.yaml | 2 +- .../configmap-capabilities-generator.yaml | 2 +- .../wms/patches/expected/deployment.yaml | 2 +- 28 files changed, 313 insertions(+), 238 deletions(-) diff --git a/api/v2beta1/shared_conversion.go b/api/v2beta1/shared_conversion.go index d2cdcfa..154d642 100644 --- a/api/v2beta1/shared_conversion.go +++ b/api/v2beta1/shared_conversion.go @@ -20,13 +20,17 @@ func ConvertOptionsV2ToV3(src *WMSWFSOptions) *pdoknlv3.Options { } return &pdoknlv3.Options{ - AutomaticCasing: src.AutomaticCasing, - IncludeIngress: src.IncludeIngress, - 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), + BaseOptions: pdoknlv3.BaseOptions{ + AutomaticCasing: src.AutomaticCasing, + IncludeIngress: src.IncludeIngress, + PrefetchData: smoothoperatorutils.PointerVal(src.PrefetchData, defaults.PrefetchData), + }, + WMSOptions: pdoknlv3.WMSOptions{ + 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), + }, } } diff --git a/api/v2beta1/wfs_conversion.go b/api/v2beta1/wfs_conversion.go index fc39d8c..5d046e3 100644 --- a/api/v2beta1/wfs_conversion.go +++ b/api/v2beta1/wfs_conversion.go @@ -63,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).BaseOptions if src.Spec.Kubernetes.HealthCheck != nil { dst.Spec.HealthCheck = &pdoknlv3.HealthCheckWFS{ @@ -92,15 +92,17 @@ func (src *WFS) ToV3(dst *pdoknlv3.WFS) error { return err } service := pdoknlv3.WFSService{ - Prefix: src.Spec.General.Dataset, - URL: *url, - OwnerInfoRef: "pdok", - Title: src.Spec.Service.Title, - Abstract: src.Spec.Service.Abstract, - Keywords: src.Spec.Service.Keywords, - Fees: nil, - AccessConstraints: smoothoperatormodel.URL{URL: accessConstraints}, - DefaultCrs: src.Spec.Service.DataEPSG, + BaseService: pdoknlv3.BaseService{ + Prefix: src.Spec.General.Dataset, + URL: *url, + OwnerInfoRef: "pdok", + Title: src.Spec.Service.Title, + Abstract: src.Spec.Service.Abstract, + Keywords: src.Spec.Service.Keywords, + Fees: nil, + AccessConstraints: smoothoperatormodel.URL{URL: accessConstraints}, + }, + DefaultCrs: src.Spec.Service.DataEPSG, OtherCrs: []string{ "EPSG:25831", "EPSG:25832", @@ -144,14 +146,14 @@ func (src *WFS) ToV3(dst *pdoknlv3.WFS) error { // TODO - where to place the MetadataIdentifier and FeatureTypes[0].SourceMetadataIdentifier if the service is not inspire? if src.Spec.Service.Inspire { - service.Inspire = &pdoknlv3.Inspire{ + service.Inspire = &pdoknlv3.WFSInspire{Inspire: pdoknlv3.Inspire{ ServiceMetadataURL: pdoknlv3.MetadataURL{ CSW: &pdoknlv3.Metadata{ MetadataIdentifier: src.Spec.Service.MetadataIdentifier, }, }, + Language: "dut"}, SpatialDatasetIdentifier: src.Spec.Service.FeatureTypes[0].SourceMetadataIdentifier, - Language: "dut", } } @@ -203,7 +205,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(&pdoknlv3.Options{BaseOptions: *src.Spec.Options}) if src.Spec.HealthCheck != nil { dst.Spec.Kubernetes.HealthCheck = &HealthCheck{ diff --git a/api/v2beta1/wms_conversion.go b/api/v2beta1/wms_conversion.go index 0eba2ce..9f04f0b 100644 --- a/api/v2beta1/wms_conversion.go +++ b/api/v2beta1/wms_conversion.go @@ -30,6 +30,8 @@ import ( "strconv" "strings" + "k8s.io/utils/ptr" + pdoknlv3 "github.com/pdok/mapserver-operator/api/v3" smoothoperatormodel "github.com/pdok/smooth-operator/model" smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" @@ -47,7 +49,7 @@ func (src *WMS) ConvertTo(dstRaw conversion.Hub) error { return src.ToV3(dst) } -//nolint:gosec +//nolint:gosec,cyclop,funlen func (src *WMS) ToV3(target *pdoknlv3.WMS) error { dst := target @@ -79,20 +81,32 @@ func (src *WMS) ToV3(target *pdoknlv3.WMS) error { return err } - service := pdoknlv3.WMSService{ + accessConstraints, err := url.Parse("https://creativecommons.org/publicdomain/zero/1.0/deed.nl") + if err != nil { + return err + } + if src.Spec.Service.AccessConstraints != nil { + accessConstraints, err = url.Parse(*src.Spec.Service.AccessConstraints) + if err != nil { + return err + } + } + + service := pdoknlv3.WMSService{BaseService: pdoknlv3.BaseService{ Prefix: src.Spec.General.Dataset, URL: *url, OwnerInfoRef: "pdok", Title: src.Spec.Service.Title, Abstract: src.Spec.Service.Abstract, Keywords: src.Spec.Service.Keywords, - AccessConstraints: smoothoperatorutils.PointerVal(src.Spec.Service.AccessConstraints, "https://creativecommons.org/publicdomain/zero/1.0/deed.nl"), - MaxSize: nil, - Resolution: nil, - DefResolution: nil, - Inspire: nil, - DataEPSG: src.Spec.Service.DataEPSG, - Layer: src.Spec.Service.MapLayersToV3(), + AccessConstraints: smoothoperatormodel.URL{URL: accessConstraints}, + }, + Inspire: nil, + MaxSize: nil, + Resolution: nil, + DefResolution: nil, + DataEPSG: src.Spec.Service.DataEPSG, + Layer: src.Spec.Service.MapLayersToV3(), } if src.Spec.Service.Maxsize != nil { @@ -120,8 +134,7 @@ func (src *WMS) ToV3(target *pdoknlv3.WMS) error { MetadataIdentifier: src.Spec.Service.MetadataIdentifier, }, }, - SpatialDatasetIdentifier: *src.Spec.Service.Layers[0].SourceMetadataIdentifier, - Language: "dut", + Language: "dut", } } else { // Annotation to be able to convert back to v2 @@ -185,7 +198,7 @@ func (dst *WMS) ConvertFrom(srcRaw conversion.Hub) error { Title: src.Spec.Service.Title, Abstract: src.Spec.Service.Abstract, Keywords: src.Spec.Service.Keywords, - AccessConstraints: &src.Spec.Service.AccessConstraints, + AccessConstraints: ptr.To(src.Spec.Service.AccessConstraints.String()), Extent: nil, DataEPSG: src.Spec.Service.DataEPSG, Layers: []WMSLayer{}, diff --git a/api/v3/shared_types.go b/api/v3/shared_types.go index 9a046ae..e17ee51 100644 --- a/api/v3/shared_types.go +++ b/api/v3/shared_types.go @@ -37,7 +37,7 @@ type WMSWFS interface { metav1.Object GroupKind() schema.GroupKind - Inspire() *Inspire + Inspire() *WFSInspire Mapfile() *Mapfile PodSpecPatch() corev1.PodSpec HorizontalPodAutoscalerPatch() *HorizontalPodAutoscalerPatch @@ -66,9 +66,8 @@ type Mapfile struct { ConfigMapKeyRef corev1.ConfigMapKeySelector `json:"configMapKeyRef"` } -// Options configures optional behaviors of the operator, like ingress, casing, and data prefetching. -// +kubebuilder:validation:Type=object -type Options struct { +// BaseOptions for all apis +type BaseOptions struct { // IncludeIngress dictates whether to deploy an Ingress or ensure none exists. // +kubebuilder:default:=true IncludeIngress bool `json:"includeIngress"` @@ -77,40 +76,73 @@ type Options struct { // +kubebuilder:default:=true AutomaticCasing bool `json:"automaticCasing"` - // ValidateRequests enables request validation against the service schema. - // +kubebuilder:default:=true - ValidateRequests bool `json:"validateRequests"` - - // RewriteGroupToDataLayers merges group layers into individual data layers. - // +kubebuilder:default:=false - RewriteGroupToDataLayers bool `json:"rewriteGroupToDataLayers"` - - // DisableWebserviceProxy disables the built-in proxy for external web services. - // +kubebuilder:default:=false - 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"` +} - // ValidateChildStyleNameEqual ensures child style names match the parent style. - // +kubebuilder:default=false - ValidateChildStyleNameEqual bool `json:"validateChildStyleNameEqual"` +// Options configures optional behaviors of the operator, like ingress, casing, and data prefetching. +// +kubebuilder:validation:Type=object +type Options struct { + BaseOptions `json:",inline"` + WMSOptions `json:",inline"` } func GetDefaultOptions() *Options { return &Options{ - IncludeIngress: true, - AutomaticCasing: true, - ValidateRequests: true, - RewriteGroupToDataLayers: false, - DisableWebserviceProxy: false, - PrefetchData: true, - ValidateChildStyleNameEqual: false, + BaseOptions: BaseOptions{ + IncludeIngress: true, + AutomaticCasing: true, + PrefetchData: true, + }, + WMSOptions: WMSOptions{ + ValidateRequests: true, + RewriteGroupToDataLayers: false, + DisableWebserviceProxy: false, + ValidateChildStyleNameEqual: false, + }, } } +// BaseService holds all shared Services field for all apis +type BaseService struct { + // Geonovum subdomein + // +kubebuilder:validation:MinLength:=1 + Prefix string `json:"prefix"` + + // URL of the service + URL smoothoperatormodel.URL `json:"url"` + + // External Mapfile reference + Mapfile *Mapfile `json:"mapfile,omitempty"` + + // Reference to OwnerInfo CR + // +kubebuilder:validation:MinLength:=1 + OwnerInfoRef string `json:"ownerInfoRef"` + + // Service title + // +kubebuilder:validation:MinLength:=1 + Title string `json:"title"` + + // Service abstract + // +kubebuilder:validation:MinLength:=1 + Abstract string `json:"abstract"` + + // Keywords for capabilities + // +kubebuilder:validation:MinItems:=1 + // +kubebuilder:validation:items:MinLength:=1 + Keywords []string `json:"keywords"` + + // Optional Fees + // +kubebuilder:validation:MinLength:=1 + Fees *string `json:"fees,omitempty"` + + // AccessConstraints URL + // +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl" + AccessConstraints smoothoperatormodel.URL `json:"accessConstraints,omitempty"` +} + // Inspire holds INSPIRE-specific metadata for the service. // +kubebuilder:validation:Type=object type Inspire struct { @@ -118,10 +150,6 @@ type Inspire struct { // +kubebuilder:validation:Type=object ServiceMetadataURL MetadataURL `json:"serviceMetadataUrl"` - // SpatialDatasetIdentifier is the ID uniquely identifying the dataset. - // +kubebuilder:validation:Pattern:=`^[0-9a-zA-Z]{8}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{12}$` - SpatialDatasetIdentifier string `json:"spatialDatasetIdentifier"` - // Language of the INSPIRE metadata record // +kubebuilder:validation:Pattern:=`bul|cze|dan|dut|eng|est|fin|fre|ger|gre|hun|gle|ita|lav|lit|mlt|pol|por|rum|slo|slv|spa|swe` Language string `json:"language"` diff --git a/api/v3/wfs_types.go b/api/v3/wfs_types.go index 858cbda..7405947 100644 --- a/api/v3/wfs_types.go +++ b/api/v3/wfs_types.go @@ -82,7 +82,8 @@ type WFSSpec struct { PodSpecPatch corev1.PodSpec `json:"podSpecPatch"` HorizontalPodAutoscalerPatch *HorizontalPodAutoscalerPatch `json:"horizontalPodAutoscalerPatch,omitempty"` // TODO omitting the options field or setting an empty value results in incorrect defaulting of the options - Options *Options `json:"options,omitempty"` + // Options configures optional behaviors of the operator, like ingress, casing, and data prefetching. + Options *BaseOptions `json:"options,omitempty"` // Custom healthcheck options HealthCheck *HealthCheckWFS `json:"healthCheck,omitempty"` @@ -97,43 +98,10 @@ type WFSSpec struct { // +kubebuilder:validation:XValidation:message="otherCrs can't contain the defaultCrs",rule="!has(self.otherCrs) || (has(self.otherCrs) && !(self.defaultCrs in self.otherCrs))",fieldPath=".otherCrs" type WFSService struct { - // Geonovum subdomein - // +kubebuilder:validation:MinLength:=1 - Prefix string `json:"prefix"` - - // URL of the service - URL smoothoperatormodel.URL `json:"url"` - - // Config for Inspire services - Inspire *Inspire `json:"inspire,omitempty"` - - // External Mapfile reference - Mapfile *Mapfile `json:"mapfile,omitempty"` - - // Reference to OwnerInfo CR - // +kubebuilder:validation:MinLength:=1 - OwnerInfoRef string `json:"ownerInfoRef"` - - // Service title - // +kubebuilder:validation:MinLength:=1 - Title string `json:"title"` - - // Service abstract - // +kubebuilder:validation:MinLength:=1 - Abstract string `json:"abstract"` - - // Keywords for capabilities - // +kubebuilder:validation:MinItems:=1 - // +kubebuilder:validation:items:MinLength:=1 - Keywords []string `json:"keywords"` - - // Optional Fees - // +kubebuilder:validation:MinLength:=1 - Fees *string `json:"fees,omitempty"` + BaseService `json:",inline"` - // AccessConstraints URL - // +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl" - AccessConstraints smoothoperatormodel.URL `json:"accessConstraints,omitempty"` + // Inspire holds INSPIRE-specific metadata for the service. + Inspire *WFSInspire `json:"inspire,omitempty"` // Default CRS (DataEPSG) // +kubebuilder:validation:Pattern:="^EPSG:(28992|25831|25832|3034|3035|3857|4258|4326)$" @@ -175,6 +143,13 @@ type HealthCheckWFS struct { Mimetype string `json:"mimetype"` } +type WFSInspire struct { + Inspire `json:",inline"` + // SpatialDatasetIdentifier is the ID uniquely identifying the dataset. + // +kubebuilder:validation:Pattern:=`^[0-9a-zA-Z]{8}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{12}$` + SpatialDatasetIdentifier string `json:"spatialDatasetIdentifier"` +} + type Bbox struct { // EXTENT/wfs_extent in mapfile //nolint:tagliatelle @@ -240,7 +215,7 @@ func (wfs *WFS) GroupKind() schema.GroupKind { return schema.GroupKind{Group: GroupVersion.Group, Kind: wfs.Kind} } -func (wfs *WFS) Inspire() *Inspire { +func (wfs *WFS) Inspire() *WFSInspire { return wfs.Spec.Service.Inspire } @@ -276,7 +251,7 @@ func (wfs *WFS) Options() Options { return *GetDefaultOptions() } - return *wfs.Spec.Options + return Options{BaseOptions: *wfs.Spec.Options} } func (wfs *WFS) URL() smoothoperatormodel.URL { diff --git a/api/v3/wms_types.go b/api/v3/wms_types.go index 0272794..e1ae48f 100644 --- a/api/v3/wms_types.go +++ b/api/v3/wms_types.go @@ -109,35 +109,9 @@ type WMSSpec struct { } type WMSService struct { - // +kubebuilder:validation:MinLength:=1 - Prefix string `json:"prefix"` - - // URL of the service - URL smoothoperatormodel.URL `json:"url"` - - // Title of the service - // +kubebuilder:validation:MinLength:=1 - Title string `json:"title"` - - // Abstract (short description) of the service - // +kubebuilder:validation:MinLength:=1 - Abstract string `json:"abstract"` - - // Keywords of the service - // +kubebuilder:validation:MinItems:=1 - // +kubebuilder:validation:items:MinLength:=1 - Keywords []string `json:"keywords"` - - // Reference to a CR of Kind OwnerInfo - // +kubebuilder:validation:MinLength:=1 - OwnerInfoRef string `json:"ownerInfoRef"` + BaseService `json:",inline"` - // 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"` - - // Optional specification Inspire themes and ids + // Config for Inspire services Inspire *Inspire `json:"inspire,omitempty"` // CRS of the data @@ -146,6 +120,7 @@ type WMSService struct { DataEPSG string `json:"dataEPSG"` // Mapfile setting: Sets the maximum size (in pixels) for both dimensions of the image from a getMap request. + // +kubebuilder:validation:Minimum:=1 MaxSize *int32 `json:"maxSize,omitempty"` // Mapfile setting: Sets the RESOLUTION field in the mapfile, not used when service.mapfile is configured @@ -187,9 +162,12 @@ type HealthCheckWMS struct { Boundingbox *smoothoperatormodel.BBox `json:"boundingbox,omitempty"` } -// +kubebuilder:validation:XValidation:message="Either blobKeys or configMapRefs is required",rule="has(self.blobKeys) || has(self.configMapRefs)" +// StylingAssets contains the files references needed for styling +// +kubebuilder:validation:XValidation:message="At least one of blobKeys or configMapRefs is required",rule="has(self.blobKeys) || has(self.configMapRefs)" type StylingAssets struct { + // BlobKeys contains symbol image (.png/.svg) or font (.ttf) keys on blob storage, format: container/key/file.(png|ttf) // +kubebuilder:validation:MinItems:=1 + // +kubebuilder:validation:items:Pattern:=^.+\/.+\/.+\.(png|ttf|svg)$ BlobKeys []string `json:"blobKeys,omitempty"` // +kubebuilder:validation:MinItems:=1 @@ -197,10 +175,13 @@ type StylingAssets struct { } type ConfigMapRef struct { + // Name is the name of the ConfigMap // +kubebuilder:validation:MinLength:=1 Name string `json:"name"` + // Keys contains styling assets that contain mapfile code (.style|.symbol), required if you use symbols in your styles // +kubebuilder:validation:MinItems:=1 + // +kubebuilder:validation:items:Pattern:=^\S*.\.(style|symbol) Keys []string `json:"keys,omitempty"` } @@ -321,6 +302,27 @@ type Legend struct { BlobKey string `json:"blobKey"` } +// WMSOptions are the Options exclusively used by the WMS +// +kubebuilder:validation:Type=object +type WMSOptions struct { + + // ValidateRequests enables request validation against the service schema. + // +kubebuilder:default:=true + ValidateRequests bool `json:"validateRequests"` + + // RewriteGroupToDataLayers merges group layers into individual data layers. + // +kubebuilder:default:=false + RewriteGroupToDataLayers bool `json:"rewriteGroupToDataLayers"` + + // DisableWebserviceProxy disables the built-in proxy for external web services. + // +kubebuilder:default:=false + DisableWebserviceProxy bool `json:"disableWebserviceProxy"` + + // ValidateChildStyleNameEqual ensures child style names match the parent style. + // +kubebuilder:default=false + ValidateChildStyleNameEqual bool `json:"validateChildStyleNameEqual"` +} + func (wmsService *WMSService) GetBoundingBox() WMSBoundingBox { var boundingBox *WMSBoundingBox @@ -588,8 +590,11 @@ func (wms *WMS) GroupKind() schema.GroupKind { return schema.GroupKind{Group: GroupVersion.Group, Kind: wms.Kind} } -func (wms *WMS) Inspire() *Inspire { - return wms.Spec.Service.Inspire +func (wms *WMS) Inspire() *WFSInspire { + if wms.Spec.Service.Inspire != nil { + return &WFSInspire{Inspire: *wms.Spec.Service.Inspire} + } + return nil } func (wms *WMS) Mapfile() *Mapfile { diff --git a/api/v3/zz_generated.deepcopy.go b/api/v3/zz_generated.deepcopy.go index 86badb8..8dfe8eb 100644 --- a/api/v3/zz_generated.deepcopy.go +++ b/api/v3/zz_generated.deepcopy.go @@ -70,6 +70,53 @@ func (in *Authority) DeepCopy() *Authority { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BaseOptions) DeepCopyInto(out *BaseOptions) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BaseOptions. +func (in *BaseOptions) DeepCopy() *BaseOptions { + if in == nil { + return nil + } + out := new(BaseOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BaseService) DeepCopyInto(out *BaseService) { + *out = *in + in.URL.DeepCopyInto(&out.URL) + if in.Mapfile != nil { + in, out := &in.Mapfile, &out.Mapfile + *out = new(Mapfile) + (*in).DeepCopyInto(*out) + } + if in.Keywords != nil { + in, out := &in.Keywords, &out.Keywords + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Fees != nil { + in, out := &in.Fees, &out.Fees + *out = new(string) + **out = **in + } + in.AccessConstraints.DeepCopyInto(&out.AccessConstraints) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BaseService. +func (in *BaseService) DeepCopy() *BaseService { + if in == nil { + return nil + } + out := new(BaseService) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Bbox) DeepCopyInto(out *Bbox) { *out = *in @@ -501,6 +548,8 @@ 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 + out.BaseOptions = in.BaseOptions + out.WMSOptions = in.WMSOptions } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Options. @@ -644,6 +693,22 @@ func (in *WFS) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WFSInspire) DeepCopyInto(out *WFSInspire) { + *out = *in + in.Inspire.DeepCopyInto(&out.Inspire) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WFSInspire. +func (in *WFSInspire) DeepCopy() *WFSInspire { + if in == nil { + return nil + } + out := new(WFSInspire) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WFSList) DeepCopyInto(out *WFSList) { *out = *in @@ -679,28 +744,12 @@ 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 - in.URL.DeepCopyInto(&out.URL) + in.BaseService.DeepCopyInto(&out.BaseService) if in.Inspire != nil { in, out := &in.Inspire, &out.Inspire - *out = new(Inspire) - (*in).DeepCopyInto(*out) - } - if in.Mapfile != nil { - in, out := &in.Mapfile, &out.Mapfile - *out = new(Mapfile) + *out = new(WFSInspire) (*in).DeepCopyInto(*out) } - if in.Keywords != nil { - in, out := &in.Keywords, &out.Keywords - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Fees != nil { - in, out := &in.Fees, &out.Fees - *out = new(string) - **out = **in - } - in.AccessConstraints.DeepCopyInto(&out.AccessConstraints) if in.OtherCrs != nil { in, out := &in.OtherCrs, &out.OtherCrs *out = make([]string, len(*in)) @@ -751,7 +800,7 @@ func (in *WFSSpec) DeepCopyInto(out *WFSSpec) { } if in.Options != nil { in, out := &in.Options, &out.Options - *out = new(Options) + *out = new(BaseOptions) **out = **in } if in.HealthCheck != nil { @@ -855,14 +904,24 @@ 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) { +func (in *WMSOptions) DeepCopyInto(out *WMSOptions) { *out = *in - in.URL.DeepCopyInto(&out.URL) - if in.Keywords != nil { - in, out := &in.Keywords, &out.Keywords - *out = make([]string, len(*in)) - copy(*out, *in) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WMSOptions. +func (in *WMSOptions) DeepCopy() *WMSOptions { + if in == nil { + return nil } + out := new(WMSOptions) + in.DeepCopyInto(out) + return out +} + +// 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 + in.BaseService.DeepCopyInto(&out.BaseService) if in.Inspire != nil { in, out := &in.Inspire, &out.Inspire *out = new(Inspire) diff --git a/config/crd/bases/pdok.nl_wfs.yaml b/config/crd/bases/pdok.nl_wfs.yaml index 98e5dbb..6a21384 100644 --- a/config/crd/bases/pdok.nl_wfs.yaml +++ b/config/crd/bases/pdok.nl_wfs.yaml @@ -1077,10 +1077,6 @@ spec: default: true description: AutomaticCasing enables automatic conversion from snake_case to camelCase. type: boolean - disableWebserviceProxy: - default: false - description: DisableWebserviceProxy disables the built-in proxy for external web services. - type: boolean includeIngress: default: true description: IncludeIngress dictates whether to deploy an Ingress or ensure none exists. @@ -1091,26 +1087,10 @@ spec: 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 type: boolean - rewriteGroupToDataLayers: - default: false - description: RewriteGroupToDataLayers merges group layers into individual data layers. - type: boolean - validateChildStyleNameEqual: - default: false - description: ValidateChildStyleNameEqual ensures child style names match the parent style. - type: boolean - validateRequests: - default: true - description: ValidateRequests enables request validation against the service schema. - type: boolean required: - automaticCasing - - disableWebserviceProxy - includeIngress - prefetchData - - rewriteGroupToDataLayers - - validateChildStyleNameEqual - - validateRequests type: object podSpecPatch: description: Strategic merge patch for the pod in the deployment. E.g. to patch the resources or add extra env vars. @@ -1394,7 +1374,7 @@ spec: minLength: 1 type: string inspire: - description: Config for Inspire services + description: Inspire holds INSPIRE-specific metadata for the service. properties: language: description: Language of the INSPIRE metadata record diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index b6d0e9d..326d403 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -1203,13 +1203,13 @@ spec: description: Service specification properties: abstract: - description: Abstract (short description) of the service + description: Service abstract minLength: 1 type: string accessConstraints: default: https://creativecommons.org/publicdomain/zero/1.0/deed.nl - description: AccessConstraints (licence) that are applicable to the service - pattern: https?://.* + description: AccessConstraints URL + pattern: ^https?://.+/.+ type: string dataEPSG: description: CRS of the data @@ -1219,8 +1219,12 @@ spec: description: 'Mapfile setting: Sets the DEFRESOLUTION field in the mapfile, not used when service.mapfile is configured' format: int32 type: integer + fees: + description: Optional Fees + minLength: 1 + type: string inspire: - description: Optional specification Inspire themes and ids + description: Config for Inspire services properties: language: description: Language of the INSPIRE metadata record @@ -1258,17 +1262,12 @@ spec: x-kubernetes-validations: - message: metadataUrl should have exactly 1 of csw or custom rule: (has(self.csw) || has(self.custom)) && !(has(self.csw) && has(self.custom)) - spatialDatasetIdentifier: - description: SpatialDatasetIdentifier is the ID uniquely identifying the dataset. - pattern: ^[0-9a-zA-Z]{8}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{4}\-[0-9a-zA-Z]{12}$ - type: string required: - language - serviceMetadataUrl - - spatialDatasetIdentifier type: object keywords: - description: Keywords of the service + description: Keywords for capabilities items: minLength: 1 type: string @@ -2005,7 +2004,7 @@ spec: message: TopLayer must be visible rule: self.visible mapfile: - description: Custom mapfile + description: External Mapfile reference properties: configMapKeyRef: description: Selects a key from a ConfigMap. @@ -2030,12 +2029,14 @@ spec: maxSize: description: 'Mapfile setting: Sets the maximum size (in pixels) for both dimensions of the image from a getMap request.' format: int32 + minimum: 1 type: integer ownerInfoRef: - description: Reference to a CR of Kind OwnerInfo + description: Reference to OwnerInfo CR minLength: 1 type: string prefix: + description: Geonovum subdomein minLength: 1 type: string resolution: @@ -2046,7 +2047,9 @@ spec: description: Optional. Required files for the styling of the service properties: blobKeys: + description: 'BlobKeys contains symbol image (.png/.svg) or font (.ttf) keys on blob storage, format: container/key/file.(png|ttf)' items: + pattern: ^.+\/.+\/.+\.(png|ttf|svg)$ type: string minItems: 1 type: array @@ -2054,11 +2057,14 @@ spec: items: properties: keys: + description: Keys contains styling assets that contain mapfile code (.style|.symbol), required if you use symbols in your styles items: + pattern: ^\S*.\.(style|symbol) type: string minItems: 1 type: array name: + description: Name is the name of the ConfigMap minLength: 1 type: string required: @@ -2068,10 +2074,10 @@ spec: type: array type: object x-kubernetes-validations: - - message: Either blobKeys or configMapRefs is required + - message: At least one of blobKeys or configMapRefs is required rule: has(self.blobKeys) || has(self.configMapRefs) title: - description: Title of the service + description: Service title minLength: 1 type: string url: diff --git a/config/samples/v3_wms.yaml b/config/samples/v3_wms.yaml index 5a11178..9580629 100644 --- a/config/samples/v3_wms.yaml +++ b/config/samples/v3_wms.yaml @@ -4,9 +4,6 @@ metadata: name: sample spec: podSpecPatch: {} - healthCheck: - querystring: "f" - mimetype: "image/png" service: prefix: "prefix" url: "https://test.test/path" diff --git a/internal/controller/blobdownload/blob_download_test.go b/internal/controller/blobdownload/blob_download_test.go index 23ac615..b33701f 100644 --- a/internal/controller/blobdownload/blob_download_test.go +++ b/internal/controller/blobdownload/blob_download_test.go @@ -81,10 +81,10 @@ func TestGetArgsForWFS(t *testing.T) { args: args{ WFS: &pdoknlv3.WFS{ Spec: pdoknlv3.WFSSpec{ - Service: pdoknlv3.WFSService{ + Service: pdoknlv3.WFSService{BaseService: pdoknlv3.BaseService{ Title: "wfs-prefetch-service-title", - }, - Options: &pdoknlv3.Options{ + }}, + Options: &pdoknlv3.BaseOptions{ PrefetchData: true, }, }, @@ -98,10 +98,10 @@ func TestGetArgsForWFS(t *testing.T) { args: args{ WFS: &pdoknlv3.WFS{ Spec: pdoknlv3.WFSSpec{ - Service: pdoknlv3.WFSService{ + Service: pdoknlv3.WFSService{BaseService: pdoknlv3.BaseService{ Title: "wfs-noprefetch-service-title", - }, - Options: &pdoknlv3.Options{ + }}, + Options: &pdoknlv3.BaseOptions{ PrefetchData: false, }, }, @@ -144,8 +144,8 @@ func TestGetArgsForWMS(t *testing.T) { args: args{ WMS: pdoknlv3.WMS{ Spec: pdoknlv3.WMSSpec{ - Service: pdoknlv3.WMSService{ - Title: "wms-gpkg-service-title", + Service: pdoknlv3.WMSService{BaseService: pdoknlv3.BaseService{ + Title: "wms-gpkg-service-title"}, Layer: pdoknlv3.Layer{ Name: smoothoperatorutils.Pointer("wms-gpkg-layer-name"), Title: smoothoperatorutils.Pointer("wms-gpkg-layer-title"), @@ -207,9 +207,9 @@ func TestGetArgsForWMS(t *testing.T) { }, }, }, - Options: &pdoknlv3.Options{ + Options: &pdoknlv3.Options{BaseOptions: pdoknlv3.BaseOptions{ PrefetchData: true, - }, + }}, }, }, }, @@ -221,8 +221,8 @@ func TestGetArgsForWMS(t *testing.T) { args: args{ WMS: pdoknlv3.WMS{ Spec: pdoknlv3.WMSSpec{ - Service: pdoknlv3.WMSService{ - Title: "wms-tif-service-title", + Service: pdoknlv3.WMSService{BaseService: pdoknlv3.BaseService{ + Title: "wms-tif-service-title"}, Layer: pdoknlv3.Layer{ Name: smoothoperatorutils.Pointer("wms-tif-layer-name"), Title: smoothoperatorutils.Pointer("wms-tif-layer-title"), @@ -274,9 +274,9 @@ func TestGetArgsForWMS(t *testing.T) { }, }, }, - Options: &pdoknlv3.Options{ + Options: &pdoknlv3.Options{BaseOptions: pdoknlv3.BaseOptions{ PrefetchData: true, - }, + }}, }, }, }, diff --git a/internal/controller/capabilitiesgenerator/capabilities_generator_test.go b/internal/controller/capabilitiesgenerator/capabilities_generator_test.go index 4e7e0d4..88829e8 100644 --- a/internal/controller/capabilitiesgenerator/capabilities_generator_test.go +++ b/internal/controller/capabilitiesgenerator/capabilities_generator_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" + yamlv3 "sigs.k8s.io/yaml/goyaml.v3" "testing" @@ -51,19 +52,20 @@ func TestGetInputForWFS(t *testing.T) { }, }, Spec: pdoknlv3.WFSSpec{ - Service: pdoknlv3.WFSService{ + Service: pdoknlv3.WFSService{BaseService: pdoknlv3.BaseService{ URL: smoothoperatormodel.URL{URL: url}, + Prefix: "prefix", Title: "some Service title", Abstract: "some \"Service\" abstract", Keywords: []string{"service-keyword-1", "service-keyword-2", "infoFeatureAccessService"}, - AccessConstraints: smoothoperatormodel.URL{URL: accessConstraints}, - Inspire: &pdoknlv3.Inspire{ + AccessConstraints: smoothoperatormodel.URL{URL: accessConstraints}}, + Inspire: &pdoknlv3.WFSInspire{Inspire: pdoknlv3.Inspire{ ServiceMetadataURL: pdoknlv3.MetadataURL{ CSW: &pdoknlv3.Metadata{ MetadataIdentifier: "metameta-meta-meta-meta-metametameta", }, }, - Language: "dut", + Language: "dut"}, SpatialDatasetIdentifier: "datadata-data-data-data-datadatadata", }, DefaultCrs: "EPSG:28992", @@ -109,7 +111,6 @@ func TestGetInputForWFS(t *testing.T) { }, }, }, - Prefix: "prefix", }, }, }, @@ -143,9 +144,9 @@ func TestGetInputForWFS(t *testing.T) { wantMap := capabilitiesgenerator.Config{} gotMap := capabilitiesgenerator.Config{} - err = yaml.Unmarshal([]byte(WFSInput), &wantMap) + err = yamlv3.Unmarshal([]byte(WFSInput), &wantMap) assert.NoError(t, err) - err = yaml.Unmarshal([]byte(gotInput), &gotMap) + err = yamlv3.Unmarshal([]byte(gotInput), &gotMap) assert.NoError(t, err) diff := cmp.Diff(wantMap, gotMap) @@ -203,11 +204,11 @@ func TestInputForWMS(t *testing.T) { input, err := GetInput(&wms, &ownerInfo) assert.NoError(t, err) - wantMap := make(map[string]interface{}) - gotMap := make(map[string]interface{}) - err = yaml.Unmarshal([]byte(WMSInput), &wantMap) + wantMap := capabilitiesgenerator.Config{} + gotMap := capabilitiesgenerator.Config{} + err = yamlv3.Unmarshal([]byte(WMSInput), &wantMap) assert.NoError(t, err) - err = yaml.Unmarshal([]byte(input), &gotMap) + err = yamlv3.Unmarshal([]byte(input), &gotMap) assert.NoError(t, err) diff := cmp.Diff(wantMap, gotMap) diff --git a/internal/controller/capabilitiesgenerator/mapper.go b/internal/controller/capabilitiesgenerator/mapper.go index 4b81d73..b02b946 100644 --- a/internal/controller/capabilitiesgenerator/mapper.go +++ b/internal/controller/capabilitiesgenerator/mapper.go @@ -6,6 +6,8 @@ import ( "strconv" "strings" + "k8s.io/utils/ptr" + "github.com/pdok/ogc-specifications/pkg/wms130" "github.com/cbroglie/mustache" @@ -58,6 +60,7 @@ func MapWFSToCapabilitiesGeneratorInput(wfs *pdoknlv3.WFS, ownerInfo *smoothoper Keywords: &wsc110.Keywords{ Keyword: wfs.Spec.Service.KeywordsIncludingInspireKeyword(), }, + Fees: wfs.Spec.Service.Fees, }, Capabilities: wfs200.Capabilities{ FeatureTypeList: *featureTypeList, @@ -293,8 +296,8 @@ func MapWMSToCapabilitiesGeneratorInput(wms *pdoknlv3.WMS, ownerInfo *smoothoper KeywordList: &wms130.Keywords{Keyword: wms.Spec.Service.KeywordsIncludingInspireKeyword()}, OnlineResource: wms130.OnlineResource{Href: smoothoperatorutils.Pointer(wms.URL().Scheme + "://" + wms.URL().Host)}, ContactInformation: getContactInformation(ownerInfo), - Fees: smoothoperatorutils.Pointer("NONE"), - AccessConstraints: &wms.Spec.Service.AccessConstraints, + Fees: wms.Spec.Service.Fees, + AccessConstraints: ptr.To(wms.Spec.Service.AccessConstraints.String()), OptionalConstraints: &wms130.OptionalConstraints{ MaxWidth: int(smoothoperatorutils.PointerVal(wms.Spec.Service.MaxSize, 4000)), MaxHeight: int(smoothoperatorutils.PointerVal(wms.Spec.Service.MaxSize, 4000)), diff --git a/internal/controller/capabilitiesgenerator/test_data/wms_input.yaml b/internal/controller/capabilitiesgenerator/test_data/wms_input.yaml index d22aba0..018cbcc 100644 --- a/internal/controller/capabilitiesgenerator/test_data/wms_input.yaml +++ b/internal/controller/capabilitiesgenerator/test_data/wms_input.yaml @@ -48,7 +48,6 @@ services: contactVoiceTelephone: null contactFacsimileTelephone: null contactElectronicMailAddress: BeheerPDOK@kadaster.nl - fees: NONE accessConstraints: https://creativecommons.org/publicdomain/zero/1.0/deed.nl optionalConstraints: maxWidth: 4000 diff --git a/internal/controller/featureinfogenerator/featureinfo_generator_test.go b/internal/controller/featureinfogenerator/featureinfo_generator_test.go index 4ee726e..8a040ac 100644 --- a/internal/controller/featureinfogenerator/featureinfo_generator_test.go +++ b/internal/controller/featureinfogenerator/featureinfo_generator_test.go @@ -75,7 +75,7 @@ func TestGetInput(t *testing.T) { wms: &pdoknlv3.WMS{ Spec: pdoknlv3.WMSSpec{ Options: &pdoknlv3.Options{ - AutomaticCasing: true, + BaseOptions: pdoknlv3.BaseOptions{AutomaticCasing: true}, }, Service: pdoknlv3.WMSService{ Layer: pdoknlv3.Layer{ diff --git a/internal/controller/mapfilegenerator/mapper.go b/internal/controller/mapfilegenerator/mapper.go index 2b90580..dda65dd 100644 --- a/internal/controller/mapfilegenerator/mapper.go +++ b/internal/controller/mapfilegenerator/mapper.go @@ -201,7 +201,7 @@ func MapWMSToMapfileGeneratorInput(wms *pdoknlv3.WMS, ownerInfo *smoothoperatorv DataEPSG: service.DataEPSG, EPSGList: epsgs, }, - AccessConstraints: service.AccessConstraints, + AccessConstraints: service.AccessConstraints.String(), Layers: []WMSLayer{}, GroupLayers: []GroupLayer{}, Symbols: getSymbols(wms), diff --git a/internal/controller/test_data/wms/complete/expected/configmap-capabilities-generator.yaml b/internal/controller/test_data/wms/complete/expected/configmap-capabilities-generator.yaml index c4fd3b9..465c1d5 100644 --- a/internal/controller/test_data/wms/complete/expected/configmap-capabilities-generator.yaml +++ b/internal/controller/test_data/wms/complete/expected/configmap-capabilities-generator.yaml @@ -233,7 +233,7 @@ metadata: service-type: wms service-version: v1_0 theme: '2016' - name: complete-wms-capabilities-generator-fd9d6mmcct + name: complete-wms-capabilities-generator-657b9fg7m8 namespace: default ownerReferences: - apiVersion: pdok.nl/v3 diff --git a/internal/controller/test_data/wms/complete/expected/configmap-mapfile-generator.yaml b/internal/controller/test_data/wms/complete/expected/configmap-mapfile-generator.yaml index 9027f83..e44fdf6 100644 --- a/internal/controller/test_data/wms/complete/expected/configmap-mapfile-generator.yaml +++ b/internal/controller/test_data/wms/complete/expected/configmap-mapfile-generator.yaml @@ -168,7 +168,7 @@ metadata: service-type: wms service-version: v1_0 theme: '2016' - name: complete-wms-mapfile-generator-m8m7bf46f7 + name: complete-wms-mapfile-generator-gd8tbt5k2b namespace: default ownerReferences: - apiVersion: pdok.nl/v3 diff --git a/internal/controller/test_data/wms/complete/expected/deployment.yaml b/internal/controller/test_data/wms/complete/expected/deployment.yaml index 8cad625..aa4ff80 100644 --- a/internal/controller/test_data/wms/complete/expected/deployment.yaml +++ b/internal/controller/test_data/wms/complete/expected/deployment.yaml @@ -378,11 +378,11 @@ spec: name: complete-wms-init-scripts-fft29bbtdd name: init-scripts - configMap: - name: complete-wms-capabilities-generator-fd9d6mmcct + name: complete-wms-capabilities-generator-657b9fg7m8 defaultMode: 420 name: capabilities-generator-config - configMap: - name: complete-wms-mapfile-generator-m8m7bf46f7 + name: complete-wms-mapfile-generator-gd8tbt5k2b defaultMode: 420 name: mapfile-generator-config - name: styling-files diff --git a/internal/controller/test_data/wms/complete/input/wms.yaml b/internal/controller/test_data/wms/complete/input/wms.yaml index 7f1d36d..7342554 100644 --- a/internal/controller/test_data/wms/complete/input/wms.yaml +++ b/internal/controller/test_data/wms/complete/input/wms.yaml @@ -57,7 +57,6 @@ spec: serviceMetadataUrl: csw: metadataIdentifier: metameta-meta-meta-meta-metametameta - spatialDatasetIdentifier: bronbron-bron-bron-bron-bronbronbron keywords: - service-keyword-1 - service-keyword-2 @@ -206,6 +205,10 @@ spec: minx: "2" miny: "4" crs: EPSG:28992 + keywords: + - keyword + title: title + abstract: abstract data: tif: blobKey: ${BLOBS_TIF_BUCKET}/key/file.tif diff --git a/internal/controller/test_data/wms/custom-mapfile/expected/configmap-capabilities-generator.yaml b/internal/controller/test_data/wms/custom-mapfile/expected/configmap-capabilities-generator.yaml index 14bce3f..9e6361b 100644 --- a/internal/controller/test_data/wms/custom-mapfile/expected/configmap-capabilities-generator.yaml +++ b/internal/controller/test_data/wms/custom-mapfile/expected/configmap-capabilities-generator.yaml @@ -139,7 +139,7 @@ metadata: inspire: "false" service-type: wms service-version: v1_0 - name: custom-mapfile-wms-capabilities-generator-bmtt8d9582 + name: custom-mapfile-wms-capabilities-generator-f244chh92c namespace: default ownerReferences: - apiVersion: pdok.nl/v3 diff --git a/internal/controller/test_data/wms/custom-mapfile/expected/deployment.yaml b/internal/controller/test_data/wms/custom-mapfile/expected/deployment.yaml index 48b13b5..58915d9 100644 --- a/internal/controller/test_data/wms/custom-mapfile/expected/deployment.yaml +++ b/internal/controller/test_data/wms/custom-mapfile/expected/deployment.yaml @@ -328,7 +328,7 @@ spec: name: custom-mapfile-wms-init-scripts-fft29bbtdd name: init-scripts - configMap: - name: custom-mapfile-wms-capabilities-generator-bmtt8d9582 + name: custom-mapfile-wms-capabilities-generator-f244chh92c defaultMode: 420 name: capabilities-generator-config - configMap: diff --git a/internal/controller/test_data/wms/minimal/expected/configmap-capabilities-generator.yaml b/internal/controller/test_data/wms/minimal/expected/configmap-capabilities-generator.yaml index 77446e2..78c9274 100644 --- a/internal/controller/test_data/wms/minimal/expected/configmap-capabilities-generator.yaml +++ b/internal/controller/test_data/wms/minimal/expected/configmap-capabilities-generator.yaml @@ -139,7 +139,7 @@ metadata: inspire: "false" service-type: wms service-version: v1_0 - name: minimal-wms-capabilities-generator-bmtt8d9582 + name: minimal-wms-capabilities-generator-f244chh92c namespace: default ownerReferences: - apiVersion: pdok.nl/v3 diff --git a/internal/controller/test_data/wms/minimal/expected/deployment.yaml b/internal/controller/test_data/wms/minimal/expected/deployment.yaml index f742a9e..63ab811 100644 --- a/internal/controller/test_data/wms/minimal/expected/deployment.yaml +++ b/internal/controller/test_data/wms/minimal/expected/deployment.yaml @@ -342,7 +342,7 @@ spec: name: minimal-wms-init-scripts-fft29bbtdd name: init-scripts - configMap: - name: minimal-wms-capabilities-generator-bmtt8d9582 + name: minimal-wms-capabilities-generator-f244chh92c defaultMode: 420 name: capabilities-generator-config - configMap: diff --git a/internal/controller/test_data/wms/noprefetch/expected/configmap-capabilities-generator.yaml b/internal/controller/test_data/wms/noprefetch/expected/configmap-capabilities-generator.yaml index 1e738b0..131dd30 100644 --- a/internal/controller/test_data/wms/noprefetch/expected/configmap-capabilities-generator.yaml +++ b/internal/controller/test_data/wms/noprefetch/expected/configmap-capabilities-generator.yaml @@ -139,7 +139,7 @@ metadata: inspire: "false" service-type: wms service-version: v1_0 - name: noprefetch-wms-capabilities-generator-bmtt8d9582 + name: noprefetch-wms-capabilities-generator-f244chh92c namespace: default ownerReferences: - apiVersion: pdok.nl/v3 diff --git a/internal/controller/test_data/wms/noprefetch/expected/deployment.yaml b/internal/controller/test_data/wms/noprefetch/expected/deployment.yaml index e0c2ff0..9a38f26 100644 --- a/internal/controller/test_data/wms/noprefetch/expected/deployment.yaml +++ b/internal/controller/test_data/wms/noprefetch/expected/deployment.yaml @@ -334,7 +334,7 @@ spec: defaultMode: 420 name: ogc-webservice-proxy-config - configMap: - name: noprefetch-wms-capabilities-generator-bmtt8d9582 + name: noprefetch-wms-capabilities-generator-f244chh92c defaultMode: 420 name: capabilities-generator-config - configMap: diff --git a/internal/controller/test_data/wms/patches/expected/configmap-capabilities-generator.yaml b/internal/controller/test_data/wms/patches/expected/configmap-capabilities-generator.yaml index 97e9f32..2a19a3e 100644 --- a/internal/controller/test_data/wms/patches/expected/configmap-capabilities-generator.yaml +++ b/internal/controller/test_data/wms/patches/expected/configmap-capabilities-generator.yaml @@ -139,7 +139,7 @@ metadata: inspire: "false" service-type: wms service-version: v1_0 - name: patches-wms-capabilities-generator-2765hcfg8h + name: patches-wms-capabilities-generator-4t652g2fm9 namespace: default ownerReferences: - apiVersion: pdok.nl/v3 diff --git a/internal/controller/test_data/wms/patches/expected/deployment.yaml b/internal/controller/test_data/wms/patches/expected/deployment.yaml index db16d9d..c4b56ac 100644 --- a/internal/controller/test_data/wms/patches/expected/deployment.yaml +++ b/internal/controller/test_data/wms/patches/expected/deployment.yaml @@ -349,7 +349,7 @@ spec: name: patches-wms-init-scripts-fft29bbtdd name: init-scripts - configMap: - name: patches-wms-capabilities-generator-2765hcfg8h + name: patches-wms-capabilities-generator-4t652g2fm9 defaultMode: 420 name: capabilities-generator-config - configMap: