Skip to content

Commit c8802cd

Browse files
author
Léon van der Kaap
committed
Merge branch 'master' into PDOK-17793-wfs-molecule-tests
2 parents 95a086c + 93f9b29 commit c8802cd

File tree

26 files changed

+2213
-1625
lines changed

26 files changed

+2213
-1625
lines changed

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Kubebuilder DevContainer",
3-
"image": "docker.io/golang:1.23",
3+
"image": "docker.io/golang:1.24",
44
"features": {
55
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
66
"ghcr.io/devcontainers/features/git:1": {}

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ ARG TARGETOS
44
ARG TARGETARCH
55

66
#COPY --from=repos ./smooth-operator /smooth-operator
7+
#COPY --from=repos ./ogc-specifications /ogc-specifications
78

89
WORKDIR /workspace
910
# Copy the Go Modules manifests

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ help: ## Display this help.
4444
.PHONY: manifests
4545
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
4646
$(CONTROLLER_GEN) rbac:roleName=manager-role crd:allowDangerousTypes=true webhook paths="./..." output:crd:artifacts:config=config/crd/bases
47+
go run config/crd/update_openapi.go config/crd/bases
4748
## allowDangerousTypes=true for v2beta structs
4849

4950
.PHONY: generate

api/v2beta1/wms_conversion.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ package v2beta1
2626

2727
import (
2828
"errors"
29+
smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util"
2930
"log"
3031
"sigs.k8s.io/controller-runtime/pkg/conversion"
3132
"strconv"
@@ -320,6 +321,7 @@ func (v2Service WMSService) MapLayersToV3() pdoknlv3.Layer {
320321
Keywords: v2Service.Keywords,
321322
Layers: &[]pdoknlv3.Layer{},
322323
BoundingBoxes: boundingBoxes,
324+
Visible: smoothoperatorutils.Pointer(true),
323325
}
324326

325327
// adding the bottom layers to the middle layers they are grouped by
@@ -424,7 +426,7 @@ func (v2Layer WMSLayer) MapToV3(v2Service WMSService) pdoknlv3.Layer {
424426
func mapV3LayerToV2Layers(v3Layer pdoknlv3.Layer, parent *pdoknlv3.Layer, serviceEPSG string) []WMSLayer {
425427
var layers []WMSLayer
426428

427-
if parent == nil && *v3Layer.Name == "wms" {
429+
if parent == nil && v3Layer.Name == nil {
428430
// Default top layer, do not include in v2 layers
429431
if v3Layer.Layers != nil {
430432
for _, childLayer := range *v3Layer.Layers {
@@ -441,7 +443,7 @@ func mapV3LayerToV2Layers(v3Layer pdoknlv3.Layer, parent *pdoknlv3.Layer, servic
441443
Styles: []Style{},
442444
}
443445

444-
v2Layer.Visible = PointerVal(v3Layer.Visible, true)
446+
v2Layer.Visible = *v3Layer.Visible
445447

446448
if parent != nil {
447449
v2Layer.Group = parent.Name

api/v2beta1/wms_types.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ type WMSSpec struct {
5454

5555
// WMSService is the struct for all service level fields
5656
type WMSService struct {
57-
Inspire bool `json:"inspire"`
58-
Title string `json:"title"`
59-
Abstract string `json:"abstract"`
57+
Inspire bool `json:"inspire"`
58+
Title string `json:"title"`
59+
Abstract string `json:"abstract"`
60+
// +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl"
6061
AccessConstraints string `json:"accessConstraints"`
6162
Keywords []string `json:"keywords"`
6263
MetadataIdentifier string `json:"metadataIdentifier"`
@@ -86,7 +87,7 @@ type WMSLayer struct {
8687
Extent *string `json:"extent,omitempty"`
8788
MinScale *float64 `json:"minScale,omitempty"`
8889
MaxScale *float64 `json:"maxScale,omitempty"`
89-
LabelNoClip bool `json:"labelNoClip"`
90+
LabelNoClip bool `json:"labelNoClip,omitempty"`
9091
Data *Data `json:"data,omitempty"`
9192
}
9293

api/v3/wms_types.go

Lines changed: 135 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -45,65 +45,148 @@ const (
4545

4646
// WMSSpec defines the desired state of WMS.
4747
type WMSSpec struct {
48-
Lifecycle *shared_model.Lifecycle `json:"lifecycle"`
48+
// Optional lifecycle settings
49+
Lifecycle *shared_model.Lifecycle `json:"lifecycle,omitempty"`
4950

5051
// +kubebuilder:validation:Type=object
5152
// +kubebuilder:validation:Schemaless
5253
// +kubebuilder:pruning:PreserveUnknownFields
5354
// Optional strategic merge patch for the pod in the deployment. E.g. to patch the resources or add extra env vars.
54-
PodSpecPatch *corev1.PodSpec `json:"podSpecPatch,omitempty"`
55-
HorizontalPodAutoscalerPatch *autoscalingv2.HorizontalPodAutoscalerSpec `json:"horizontalPodAutoscalerPatch"`
56-
Options Options `json:"options,omitempty"`
57-
Service WMSService `json:"service"`
55+
PodSpecPatch *corev1.PodSpec `json:"podSpecPatch,omitempty"`
56+
57+
// Optional specification for the HorizontalAutoscaler
58+
HorizontalPodAutoscalerPatch *autoscalingv2.HorizontalPodAutoscalerSpec `json:"horizontalPodAutoscalerPatch,omitempty"`
59+
60+
// Optional options for the configuration of the service.
61+
Options Options `json:"options,omitempty"`
62+
63+
// Service specification
64+
Service WMSService `json:"service"`
5865
}
5966

6067
type WMSService struct {
61-
URL string `json:"url"`
62-
Title string `json:"title"`
63-
Abstract string `json:"abstract"`
64-
Keywords []string `json:"keywords"`
65-
OwnerInfoRef string `json:"ownerInfoRef"`
66-
Fees *string `json:"fees,omitempty"`
68+
// URL of the service
69+
// +kubebuilder:validation:Format:=uri
70+
URL string `json:"url"`
71+
72+
// Title of the service
73+
// +kubebuilder:validation:MinLength:=1
74+
Title string `json:"title"`
75+
76+
// Abstract (short description) of the service
77+
// +kubebuilder:validation:MinLength:=1
78+
Abstract string `json:"abstract"`
79+
80+
// Keywords of the service
81+
// +kubebuilder:validation:MinItems:=1
82+
Keywords []string `json:"keywords"`
83+
84+
// Reference to a CR of Kind OwnerInfo
85+
// +kubebuilder:validation:MinLength:=1
86+
OwnerInfoRef string `json:"ownerInfoRef"`
87+
88+
// TODO ??
89+
// +kubebuilder:validation:MinLength:=1
90+
Fees *string `json:"fees,omitempty"`
91+
92+
// AccessConstraints (licence) that are applicable to the service
6793
// +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl"
68-
AccessConstraints string `json:"accessConstraints"`
69-
MaxSize *int32 `json:"maxSize,omitempty"`
70-
Inspire *Inspire `json:"inspire,omitempty"`
94+
AccessConstraints string `json:"accessConstraints"`
95+
96+
// TODO??
97+
MaxSize *int32 `json:"maxSize,omitempty"`
98+
99+
// Optional specification Inspire themes and ids
100+
Inspire *Inspire `json:"inspire,omitempty"`
101+
102+
// CRS of the data
103+
// +kubebuilder:validation:Pattern:=`(EPSG|CRS):\d+`
71104
//nolint:tagliatelle
72-
DataEPSG string `json:"dataEPSG"`
73-
Resolution *int32 `json:"resolution,omitempty"`
74-
DefResolution *int32 `json:"defResolution,omitempty"`
105+
DataEPSG string `json:"dataEPSG"`
106+
107+
// TODO ??
108+
Resolution *int32 `json:"resolution,omitempty"`
109+
110+
// TODO ??
111+
DefResolution *int32 `json:"defResolution,omitempty"`
112+
113+
// Optional. Required files for the styling of the service
75114
StylingAssets *StylingAssets `json:"stylingAssets,omitempty"`
76-
Mapfile *Mapfile `json:"mapfile,omitempty"`
77-
Layer Layer `json:"layer"`
115+
116+
// Custom mapfile
117+
Mapfile *Mapfile `json:"mapfile,omitempty"`
118+
119+
// Toplayer
120+
Layer Layer `json:"layer"`
78121
}
79122

123+
// +kubebuilder:validation:XValidation:message="Either blobKeys or configMapRefs is required",rule="has(self.blobKeys) || has(self.configMapRefs)"
80124
type StylingAssets struct {
81-
BlobKeys []string `json:"blobKeys"`
82-
ConfigMapRefs []ConfigMapRef `json:"configMapRefs"`
125+
// +kubebuilder:validations:MinItems:=1
126+
BlobKeys []string `json:"blobKeys,omitempty"`
127+
128+
// +kubebuilder:validations:MinItems:=1
129+
ConfigMapRefs []ConfigMapRef `json:"configMapRefs,omitempty"`
83130
}
84131

85132
type ConfigMapRef struct {
86-
Name string `json:"name"`
133+
// +kubebuilder:validations:MinLength:=1
134+
Name string `json:"name"`
135+
136+
// +kubebuilder:validations:MinItems:=1
87137
Keys []string `json:"keys,omitempty"`
88138
}
89139

140+
// +kubebuilder:validation:XValidation:message="A layer can only have data or layers, not both.", rule="has(self.data) || has(self.layers)"
90141
type Layer struct {
91-
Name *string `json:"name"`
92-
Title *string `json:"title,omitempty"`
93-
Abstract *string `json:"abstract,omitempty"`
94-
Keywords []string `json:"keywords"`
95-
BoundingBoxes []WMSBoundingBox `json:"boundingBoxes"`
96-
Visible *bool `json:"visible,omitempty"`
97-
Authority *Authority `json:"authority,omitempty"`
98-
DatasetMetadataURL *MetadataURL `json:"datasetMetadataUrl,omitempty"`
99-
MinScaleDenominator *string `json:"minscaledenominator,omitempty"`
100-
MaxScaleDenominator *string `json:"maxscaledenominator,omitempty"`
101-
Styles []Style `json:"styles"`
102-
LabelNoClip bool `json:"labelNoClip"`
103-
Data *Data `json:"data,omitempty"`
104-
// Nested structs do not work in crd generation
105-
// +kubebuilder:pruning:PreserveUnknownFields
106-
// +kubebuilder:validation:Schemaless
142+
// Name of the layer, required for layers on the 2nd or 3rd level
143+
// +kubebuilder:validations:MinLength:=1
144+
Name *string `json:"name,omitempty"`
145+
146+
// Title of the layer
147+
// +kubebuilder:validations:MinLength:=1
148+
Title *string `json:"title,omitempty"`
149+
150+
// Abstract of the layer
151+
// +kubebuilder:validations:MinLength:=1
152+
Abstract *string `json:"abstract,omitempty"`
153+
154+
// Keywords of the layer
155+
// +kubebuilder:validations:MinItems:=1
156+
Keywords []string `json:"keywords"`
157+
158+
// BoundingBoxes of the layer. If omitted the boundingboxes of the parent layer of the service is used.
159+
BoundingBoxes []WMSBoundingBox `json:"boundingBoxes,omitempty"`
160+
161+
// Whether or not the layer is visible. At least one of the layers must be visible.
162+
// +kubebuilder:default:=true
163+
Visible *bool `json:"visible,omitempty"`
164+
165+
// TODO ??
166+
Authority *Authority `json:"authority,omitempty"`
167+
168+
// Links to metadata
169+
DatasetMetadataURL *MetadataURL `json:"datasetMetadataUrl,omitempty"`
170+
171+
// The minimum scale at which this layer functions
172+
// +kubebuilder:validation:Pattern:=`^[1-9][0-9]*(.[0-9]+)$`
173+
MinScaleDenominator *string `json:"minscaledenominator,omitempty"`
174+
175+
// The maximum scale at which this layer functions
176+
// +kubebuilder:validation:Pattern:=`^[1-9][0-9]*(.[0-9]+)$`
177+
MaxScaleDenominator *string `json:"maxscaledenominator,omitempty"`
178+
179+
// List of styles used by the layer
180+
// +kubebuilder:validations:MinItems:=1
181+
Styles []Style `json:"styles,omitempty"`
182+
183+
// TODO ??
184+
LabelNoClip bool `json:"labelNoClip,omitempty"`
185+
186+
// Data (gpkg/postgis/tif) used by the layer
187+
Data *Data `json:"data,omitempty"`
188+
189+
// Sublayers of the layer
107190
Layers *[]Layer `json:"layers,omitempty"`
108191
}
109192

@@ -130,13 +213,22 @@ type Authority struct {
130213
}
131214

132215
type Style struct {
133-
Name string `json:"name"`
134-
Title *string `json:"title"`
135-
Abstract *string `json:"abstract"`
136-
Visualization *string `json:"visualization"`
137-
Legend *Legend `json:"legend"`
216+
// +kubebuilder:validations:MinLength:=1
217+
Name string `json:"name"`
218+
219+
// +kubebuilder:validations:MinLength:=1
220+
Title *string `json:"title,omitempty"`
221+
222+
// +kubebuilder:validations:MinLength:=1
223+
Abstract *string `json:"abstract,omitempty"`
224+
225+
// +kubebuilder:validations:MinLength:=1
226+
Visualization *string `json:"visualization,omitempty"`
227+
228+
Legend *Legend `json:"legend,omitempty"`
138229
}
139230

231+
// TODO add validations + descriptions
140232
type Legend struct {
141233
Width int32 `json:"width"`
142234
Height int32 `json:"height"`

api/v3/wms_validation.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,28 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) {
9595
wms.Spec.Service.Layer.setInheritedBoundingBoxes()
9696
for _, layer := range wms.Spec.Service.Layer.GetAllLayers() {
9797
var layerReasons []string
98-
if slices.Contains(names, *layer.Name) {
99-
*reasons = append(*reasons, fmt.Sprintf("layer names must be unique, layer.name '%s' is duplicated", *layer.Name))
98+
99+
layerType := layer.GetLayerType(&service)
100+
var layerName string
101+
if layer.Name == nil {
102+
if layerType != TopLayer {
103+
layerReasons = append(layerReasons, "layer.Name is required (except for the toplayer)")
104+
}
105+
layerName = "unnamed:" + layerType
106+
} else {
107+
layerName = *layer.Name
100108
}
101-
names = append(names, *layer.Name)
109+
110+
if slices.Contains(names, layerName) {
111+
layerReasons = append(layerReasons, fmt.Sprintf("layer names must be unique, layer.name '%s' is duplicated", layerName))
112+
}
113+
names = append(names, layerName)
114+
102115
if service.Mapfile != nil && layer.BoundingBoxes != nil {
103116
*warnings = append(*warnings, sharedValidation.FormatValidationWarning("layer.boundingBoxes is not used when service.mapfile is configured", wms.GroupVersionKind(), wms.GetName()))
104117
}
105118
if service.Mapfile == nil && service.DataEPSG != "EPSG:28992" && !layer.hasBoundingBoxForCRS(service.DataEPSG) {
106-
*reasons = append(*reasons, "layer.boundingBoxes must contain a boundingBox for CRS '"+service.DataEPSG+"' when service.dataEPSG is not 'EPSG:28992'")
119+
layerReasons = append(layerReasons, "layer.boundingBoxes must contain a boundingBox for CRS '"+service.DataEPSG+"' when service.dataEPSG is not 'EPSG:28992'")
107120
}
108121

109122
//nolint:nestif
@@ -155,7 +168,7 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) {
155168
}
156169
}
157170
if !rewriteGroupToDataLayers && validateChildStyleNameEqual {
158-
equalStylesNames, ok := equalChildStyleNames[*layer.Name]
171+
equalStylesNames, ok := equalChildStyleNames[layerName]
159172
if ok {
160173
for _, styleName := range equalStylesNames {
161174
layerReasons = append(layerReasons, fmt.Sprintf("invalid style: '%s': style.name from parent layer must not be set on a child layer", styleName))
@@ -174,7 +187,7 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) {
174187
}
175188
}
176189
}
177-
layerType := layer.GetLayerType(&service)
190+
178191
if layerType == GroupLayer || layerType == TopLayer {
179192
if !*layer.Visible {
180193
layerReasons = append(layerReasons, layerType+" must be visible")
@@ -189,7 +202,7 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) {
189202
}
190203
}
191204
if len(layerReasons) != 0 {
192-
*reasons = append(*reasons, fmt.Sprintf("%s '%s' is invalid: ", layerType, *layer.Name)+strings.Join(layerReasons, ", "))
205+
*reasons = append(*reasons, fmt.Sprintf("%s '%s' is invalid: ", layerType, layerName)+strings.Join(layerReasons, ", "))
193206
}
194207
}
195208

@@ -204,6 +217,12 @@ func findEqualChildStyleNames(layer *Layer, equalStyleNames *map[string][]string
204217
}
205218
equalChildStyleNames := map[string][]string{}
206219
for _, childLayer := range *layer.Layers {
220+
if childLayer.Name == nil {
221+
// Name check is done elsewhere
222+
// To prevent errors here we just continue
223+
continue
224+
}
225+
207226
var equalStyles []string
208227
for _, style := range layer.Styles {
209228
for _, childStyle := range childLayer.Styles {

0 commit comments

Comments
 (0)