From f73c03186e5bb948b1c3f94bad616c8fd587e950 Mon Sep 17 00:00:00 2001 From: Jelle Dijkstra Date: Tue, 29 Apr 2025 16:58:15 +0200 Subject: [PATCH 1/5] Layers OpenAPI --- Makefile | 1 + api/v2beta1/wms_conversion.go | 6 +- api/v2beta1/wms_types.go | 9 +- api/v3/wms_types.go | 54 +-- api/v3/wms_validation.go | 35 +- api/v3/zz_generated.deepcopy.go | 15 + cmd/update_openapi.go | 76 ++++ config/crd/bases/pdok.nl_wms.yaml | 423 +++++++++++++++++- config/manager/kustomization.yaml | 2 +- .../controller/blobdownload/blob_download.go | 32 +- .../controller/featureinfogenerator/mapper.go | 3 +- internal/controller/legendgenerator/mapper.go | 2 +- .../mapfilegenerator/mapfile_generator.go | 5 + .../controller/mapfilegenerator/mapper.go | 4 +- .../ogc_webservice_proxy.go | 3 +- internal/controller/shared_controller.go | 12 +- internal/controller/wms_controller.go | 12 +- internal/webhook/v3/wms_webhook_test.go | 4 +- 18 files changed, 613 insertions(+), 85 deletions(-) create mode 100644 cmd/update_openapi.go diff --git a/Makefile b/Makefile index 527f65a..1ed5242 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=manager-role crd:allowDangerousTypes=true webhook paths="./..." output:crd:artifacts:config=config/crd/bases + go run cmd/update_openapi.go config/crd/bases ## allowDangerousTypes=true for v2beta structs .PHONY: generate diff --git a/api/v2beta1/wms_conversion.go b/api/v2beta1/wms_conversion.go index 1b6634d..08acd5e 100644 --- a/api/v2beta1/wms_conversion.go +++ b/api/v2beta1/wms_conversion.go @@ -26,6 +26,7 @@ package v2beta1 import ( "errors" + smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" "log" "sigs.k8s.io/controller-runtime/pkg/conversion" "strconv" @@ -320,6 +321,7 @@ func (v2Service WMSService) MapLayersToV3() pdoknlv3.Layer { Keywords: v2Service.Keywords, Layers: &[]pdoknlv3.Layer{}, BoundingBoxes: boundingBoxes, + Visible: smoothoperatorutils.Pointer(true), } // adding the bottom layers to the middle layers they are grouped by @@ -424,7 +426,7 @@ func (v2Layer WMSLayer) MapToV3(v2Service WMSService) pdoknlv3.Layer { func mapV3LayerToV2Layers(v3Layer pdoknlv3.Layer, parent *pdoknlv3.Layer, serviceEPSG string) []WMSLayer { var layers []WMSLayer - if parent == nil && *v3Layer.Name == "wms" { + if parent == nil && v3Layer.Name == nil { // Default top layer, do not include in v2 layers if v3Layer.Layers != nil { for _, childLayer := range *v3Layer.Layers { @@ -441,7 +443,7 @@ func mapV3LayerToV2Layers(v3Layer pdoknlv3.Layer, parent *pdoknlv3.Layer, servic Styles: []Style{}, } - v2Layer.Visible = PointerVal(v3Layer.Visible, true) + 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 065cde3..f505cb2 100644 --- a/api/v2beta1/wms_types.go +++ b/api/v2beta1/wms_types.go @@ -54,9 +54,10 @@ type WMSSpec struct { // WMSService is the struct for all service level fields type WMSService struct { - Inspire bool `json:"inspire"` - Title string `json:"title"` - Abstract string `json:"abstract"` + Inspire bool `json:"inspire"` + Title string `json:"title"` + Abstract string `json:"abstract"` + // +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl" AccessConstraints string `json:"accessConstraints"` Keywords []string `json:"keywords"` MetadataIdentifier string `json:"metadataIdentifier"` @@ -86,7 +87,7 @@ type WMSLayer struct { Extent *string `json:"extent,omitempty"` MinScale *float64 `json:"minScale,omitempty"` MaxScale *float64 `json:"maxScale,omitempty"` - LabelNoClip bool `json:"labelNoClip"` + LabelNoClip bool `json:"labelNoClip,omitempty"` Data *Data `json:"data,omitempty"` } diff --git a/api/v3/wms_types.go b/api/v3/wms_types.go index a030227..0c6734f 100644 --- a/api/v3/wms_types.go +++ b/api/v3/wms_types.go @@ -45,14 +45,14 @@ const ( // WMSSpec defines the desired state of WMS. type WMSSpec struct { - Lifecycle *shared_model.Lifecycle `json:"lifecycle"` + Lifecycle *shared_model.Lifecycle `json:"lifecycle,omitempty"` // +kubebuilder:validation:Type=object // +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"` + HorizontalPodAutoscalerPatch *autoscalingv2.HorizontalPodAutoscalerSpec `json:"horizontalPodAutoscalerPatch,omitempty"` Options Options `json:"options,omitempty"` Service WMSService `json:"service"` } @@ -77,9 +77,10 @@ type WMSService struct { Layer Layer `json:"layer"` } +// +kubebuilder:validation:XValidation:message="Either blobKeys or configMapRefs is required",rule="has(self.blobKeys) || has(self.configMapRefs)" type StylingAssets struct { - BlobKeys []string `json:"blobKeys"` - ConfigMapRefs []ConfigMapRef `json:"configMapRefs"` + BlobKeys []string `json:"blobKeys,omitempty"` + ConfigMapRefs []ConfigMapRef `json:"configMapRefs,omitempty"` } type ConfigMapRef struct { @@ -88,23 +89,26 @@ type ConfigMapRef struct { } type Layer struct { - Name *string `json:"name"` - Title *string `json:"title,omitempty"` - Abstract *string `json:"abstract,omitempty"` - Keywords []string `json:"keywords"` - BoundingBoxes []WMSBoundingBox `json:"boundingBoxes"` - Visible *bool `json:"visible,omitempty"` - Authority *Authority `json:"authority,omitempty"` - DatasetMetadataURL *MetadataURL `json:"datasetMetadataUrl,omitempty"` - MinScaleDenominator *string `json:"minscaledenominator,omitempty"` - MaxScaleDenominator *string `json:"maxscaledenominator,omitempty"` - Styles []Style `json:"styles"` - LabelNoClip bool `json:"labelNoClip"` - Data *Data `json:"data,omitempty"` - // Nested structs do not work in crd generation - // +kubebuilder:pruning:PreserveUnknownFields - // +kubebuilder:validation:Schemaless - Layers *[]Layer `json:"layers,omitempty"` + // +kubebuilder:validations:MinLength:=1 + Name *string `json:"name,omitempty"` + Title *string `json:"title,omitempty"` + Abstract *string `json:"abstract,omitempty"` + Keywords []string `json:"keywords"` + BoundingBoxes []WMSBoundingBox `json:"boundingBoxes"` + // +kubebuilder:default:=true + Visible *bool `json:"visible,omitempty"` + Authority *Authority `json:"authority,omitempty"` + DatasetMetadataURL *MetadataURL `json:"datasetMetadataUrl,omitempty"` + MinScaleDenominator *string `json:"minscaledenominator,omitempty"` + MaxScaleDenominator *string `json:"maxscaledenominator,omitempty"` + Styles []Style `json:"styles,omitempty"` + LabelNoClip bool `json:"labelNoClip,omitempty"` + Data *Data `json:"data,omitempty"` + Layers *[]Layer `json:"layers,omitempty"` +} + +type RandomType struct { + FieldA string `json:"fieldA"` } type WMSBoundingBox struct { @@ -131,10 +135,10 @@ type Authority struct { type Style struct { Name string `json:"name"` - Title *string `json:"title"` - Abstract *string `json:"abstract"` - Visualization *string `json:"visualization"` - Legend *Legend `json:"legend"` + Title *string `json:"title,omitempty"` + Abstract *string `json:"abstract,omitempty"` + Visualization *string `json:"visualization,omitempty"` + Legend *Legend `json:"legend,omitempty"` } type Legend struct { diff --git a/api/v3/wms_validation.go b/api/v3/wms_validation.go index aa7dde4..f3f0e7a 100644 --- a/api/v3/wms_validation.go +++ b/api/v3/wms_validation.go @@ -20,6 +20,8 @@ func (wms *WMS) ValidateCreate() ([]string, error) { validateWMS(wms, &warnings, &reasons) if len(reasons) > 0 { + //yaml, _ := yaml2.Marshal(wms) + //fmt.Println(yaml) return warnings, fmt.Errorf("%s", strings.Join(reasons, ". ")) } @@ -95,15 +97,28 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) { wms.Spec.Service.Layer.setInheritedBoundingBoxes() for _, layer := range wms.Spec.Service.Layer.GetAllLayers() { var layerReasons []string - if slices.Contains(names, *layer.Name) { - *reasons = append(*reasons, fmt.Sprintf("layer names must be unique, layer.name '%s' is duplicated", *layer.Name)) + + layerType := layer.GetLayerType(&service) + var layerName string + if layer.Name == nil { + if layerType != TopLayer { + layerReasons = append(layerReasons, "layer.Name is required (except for the toplayer)") + } + layerName = "unnamed:" + layerType + } else { + layerName = *layer.Name } - names = append(names, *layer.Name) + + if slices.Contains(names, layerName) { + layerReasons = append(layerReasons, fmt.Sprintf("layer names must be unique, layer.name '%s' is duplicated", layer.Name)) + } + names = append(names, layerName) + if service.Mapfile != nil && layer.BoundingBoxes != nil { *warnings = append(*warnings, sharedValidation.FormatValidationWarning("layer.boundingBoxes is not used when service.mapfile is configured", wms.GroupVersionKind(), wms.GetName())) } if service.Mapfile == nil && service.DataEPSG != "EPSG:28992" && !layer.hasBoundingBoxForCRS(service.DataEPSG) { - *reasons = append(*reasons, "layer.boundingBoxes must contain a boundingBox for CRS '"+service.DataEPSG+"' when service.dataEPSG is not 'EPSG:28992'") + layerReasons = append(layerReasons, "layer.boundingBoxes must contain a boundingBox for CRS '"+service.DataEPSG+"' when service.dataEPSG is not 'EPSG:28992'") } //nolint:nestif @@ -155,7 +170,7 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) { } } if !rewriteGroupToDataLayers && validateChildStyleNameEqual { - equalStylesNames, ok := equalChildStyleNames[*layer.Name] + equalStylesNames, ok := equalChildStyleNames[layerName] if ok { for _, styleName := range equalStylesNames { layerReasons = append(layerReasons, fmt.Sprintf("invalid style: '%s': style.name from parent layer must not be set on a child layer", styleName)) @@ -174,7 +189,7 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) { } } } - layerType := layer.GetLayerType(&service) + if layerType == GroupLayer || layerType == TopLayer { if !*layer.Visible { layerReasons = append(layerReasons, layerType+" must be visible") @@ -189,7 +204,7 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) { } } if len(layerReasons) != 0 { - *reasons = append(*reasons, fmt.Sprintf("%s '%s' is invalid: ", layerType, *layer.Name)+strings.Join(layerReasons, ", ")) + *reasons = append(*reasons, fmt.Sprintf("%s '%s' is invalid: ", layerType, layer.Name)+strings.Join(layerReasons, ", ")) } } @@ -204,6 +219,12 @@ func findEqualChildStyleNames(layer *Layer, equalStyleNames *map[string][]string } equalChildStyleNames := map[string][]string{} for _, childLayer := range *layer.Layers { + if childLayer.Name == nil { + // Name check is done elsewhere + // To prevent errors here we just continue + continue + } + var equalStyles []string for _, style := range layer.Styles { for _, childStyle := range childLayer.Styles { diff --git a/api/v3/zz_generated.deepcopy.go b/api/v3/zz_generated.deepcopy.go index ce8b7cd..c210328 100644 --- a/api/v3/zz_generated.deepcopy.go +++ b/api/v3/zz_generated.deepcopy.go @@ -479,6 +479,21 @@ func (in *Postgis) DeepCopy() *Postgis { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RandomType) DeepCopyInto(out *RandomType) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RandomType. +func (in *RandomType) DeepCopy() *RandomType { + if in == nil { + return nil + } + out := new(RandomType) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Style) DeepCopyInto(out *Style) { *out = *in diff --git a/cmd/update_openapi.go b/cmd/update_openapi.go new file mode 100644 index 0000000..17c8234 --- /dev/null +++ b/cmd/update_openapi.go @@ -0,0 +1,76 @@ +package main + +import ( + "github.com/pkg/errors" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "os" + "path/filepath" + "sigs.k8s.io/yaml" +) + +// Usage: go run ./update_layersv3_openapi.go +func main() { + crdDir := os.Args[1] + + updateWMSV3Layers(crdDir) +} + +func updateWMSV3Layers(crdDir string) { + path := filepath.Join(crdDir, "pdok.nl_wms.yaml") + + if _, err := os.Stat(path); os.IsNotExist(err) { + panic(errors.Wrap(err, "WMS v3 manifest not found")) + } + + content, _ := os.ReadFile(path) + crd := &v1.CustomResourceDefinition{} + err := yaml.Unmarshal(content, &crd) + if err != nil { + panic(err) + } + + versions := make([]v1.CustomResourceDefinitionVersion, 0) + for _, version := range crd.Spec.Versions { + if version.Name == "v3" { + schema := version.Schema.OpenAPIV3Schema + spec := schema.Properties["spec"] + service := spec.Properties["service"] + layer := service.Properties["layer"] + + // Level 3 + layerSpecLevel3 := layer.DeepCopy() + layerSpecLevel3.Required = append(layerSpecLevel3.Required, "name") + delete(layerSpecLevel3.Properties, "layers") + + // 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}, + } + + layer.Properties["layers"] = v1.JSONSchemaProps{ + Type: "array", + Description: "[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]", + Items: &v1.JSONSchemaPropsOrArray{Schema: layerSpecLevel2}, + } + + service.Properties["layer"] = layer + spec.Properties["service"] = service + schema.Properties["spec"] = spec + version.Schema = &v1.CustomResourceValidation{ + OpenAPIV3Schema: schema, + } + + versions = append(versions, version) + } else { + versions = append(versions, version) + } + } + + crd.Spec.Versions = versions + updatedContent, _ := yaml.Marshal(crd) + os.WriteFile(path, updatedContent, os.ModePerm) +} diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index f1e4355..915b2a4 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -1,9 +1,9 @@ ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.17.1 + creationTimestamp: null name: wms.pdok.nl spec: group: pdok.nl @@ -179,6 +179,7 @@ spec: abstract: type: string accessConstraints: + default: https://creativecommons.org/publicdomain/zero/1.0/deed.nl type: string authority: description: Authority is a struct for the authority fields in @@ -330,7 +331,6 @@ spec: visible: type: boolean required: - - labelNoClip - name - styles - visible @@ -1379,8 +1379,401 @@ spec: labelNoClip: type: boolean layers: - description: Nested structs do not work in crd generation - x-kubernetes-preserve-unknown-fields: true + description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' + items: + properties: + abstract: + type: string + authority: + properties: + name: + type: string + spatialDatasetIdentifier: + type: string + url: + type: string + required: + - name + - spatialDatasetIdentifier + - url + type: object + boundingBoxes: + items: + properties: + bbox: + description: BBox defines a bounding box with + coordinates + properties: + maxx: + description: Rechtsonder X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + maxy: + description: Rechtsonder Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + minx: + description: Linksboven X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + miny: + description: Linksboven Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + required: + - maxx + - maxy + - minx + - miny + type: object + crs: + type: string + required: + - bbox + - crs + type: object + type: array + data: + properties: + gpkg: + properties: + blobKey: + type: string + columns: + items: + properties: + alias: + type: string + name: + type: string + required: + - name + type: object + type: array + geometryType: + type: string + tableName: + type: string + required: + - blobKey + - columns + - geometryType + - tableName + type: object + postgis: + description: Postgis - reference to table in a Postgres + database + properties: + columns: + items: + properties: + alias: + type: string + name: + type: string + required: + - name + type: object + type: array + geometryType: + type: string + tableName: + type: string + required: + - columns + - geometryType + - tableName + type: object + tif: + properties: + blobKey: + type: string + getFeatureInfoIncludesClass: + type: boolean + offsite: + type: string + resample: + type: string + required: + - blobKey + type: object + type: object + datasetMetadataUrl: + properties: + csw: + properties: + metadataIdentifier: + type: string + required: + - metadataIdentifier + type: object + custom: + properties: + href: + type: string + type: + type: string + required: + - href + - type + type: object + required: + - csw + type: object + keywords: + items: + type: string + type: array + labelNoClip: + type: boolean + layers: + description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' + items: + properties: + abstract: + type: string + authority: + properties: + name: + type: string + spatialDatasetIdentifier: + type: string + url: + type: string + required: + - name + - spatialDatasetIdentifier + - url + type: object + boundingBoxes: + items: + properties: + bbox: + description: BBox defines a bounding box + with coordinates + properties: + maxx: + description: Rechtsonder X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + maxy: + description: Rechtsonder Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + minx: + description: Linksboven X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + miny: + description: Linksboven Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + required: + - maxx + - maxy + - minx + - miny + type: object + crs: + type: string + required: + - bbox + - crs + type: object + type: array + data: + properties: + gpkg: + properties: + blobKey: + type: string + columns: + items: + properties: + alias: + type: string + name: + type: string + required: + - name + type: object + type: array + geometryType: + type: string + tableName: + type: string + required: + - blobKey + - columns + - geometryType + - tableName + type: object + postgis: + description: Postgis - reference to table + in a Postgres database + properties: + columns: + items: + properties: + alias: + type: string + name: + type: string + required: + - name + type: object + type: array + geometryType: + type: string + tableName: + type: string + required: + - columns + - geometryType + - tableName + type: object + tif: + properties: + blobKey: + type: string + getFeatureInfoIncludesClass: + type: boolean + offsite: + type: string + resample: + type: string + required: + - blobKey + type: object + type: object + datasetMetadataUrl: + properties: + csw: + properties: + metadataIdentifier: + type: string + required: + - metadataIdentifier + type: object + custom: + properties: + href: + type: string + type: + type: string + required: + - href + - type + type: object + required: + - csw + type: object + keywords: + items: + type: string + type: array + labelNoClip: + type: boolean + maxscaledenominator: + type: string + minscaledenominator: + type: string + name: + type: string + styles: + items: + properties: + abstract: + type: string + legend: + properties: + blobKey: + type: string + format: + type: string + height: + format: int32 + type: integer + width: + format: int32 + type: integer + required: + - blobKey + - format + - height + - width + type: object + name: + type: string + title: + type: string + visualization: + type: string + required: + - name + type: object + type: array + title: + type: string + visible: + default: true + type: boolean + required: + - boundingBoxes + - keywords + - name + type: object + type: array + maxscaledenominator: + type: string + minscaledenominator: + type: string + name: + type: string + styles: + items: + properties: + abstract: + type: string + legend: + properties: + blobKey: + type: string + format: + type: string + height: + format: int32 + type: integer + width: + format: int32 + type: integer + required: + - blobKey + - format + - height + - width + type: object + name: + type: string + title: + type: string + visualization: + type: string + required: + - name + type: object + type: array + title: + type: string + visible: + default: true + type: boolean + required: + - boundingBoxes + - keywords + - name + type: object + type: array maxscaledenominator: type: string minscaledenominator: @@ -1417,23 +1810,17 @@ spec: visualization: type: string required: - - abstract - - legend - name - - title - - visualization type: object type: array title: type: string visible: + default: true type: boolean required: - boundingBoxes - keywords - - labelNoClip - - name - - styles type: object mapfile: properties: @@ -1490,10 +1877,10 @@ spec: - name type: object type: array - required: - - blobKeys - - configMapRefs type: object + x-kubernetes-validations: + - message: Either blobKeys or configMapRefs is required + rule: has(self.blobKeys) || has(self.configMapRefs) title: type: string url: @@ -1509,8 +1896,6 @@ spec: - url type: object required: - - horizontalPodAutoscalerPatch - - lifecycle - service type: object status: @@ -1589,3 +1974,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 22755e0..39851b8 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: local-registry:5000/mapserver-operator - newTag: v3.0.10 + newTag: v3.0.24 diff --git a/internal/controller/blobdownload/blob_download.go b/internal/controller/blobdownload/blob_download.go index fd3c7f5..0c44945 100644 --- a/internal/controller/blobdownload/blob_download.go +++ b/internal/controller/blobdownload/blob_download.go @@ -162,6 +162,7 @@ func downloadStylingAssets(sb *strings.Builder, wms *pdoknlv3.WMS) error { return nil } + generatedFontsList := false re := regexp.MustCompile(`.*\.(ttf)$`) for _, blobKey := range wms.Spec.Service.StylingAssets.BlobKeys { fileName, err := getFilenameFromBlobKey(blobKey) @@ -180,26 +181,35 @@ func downloadStylingAssets(sb *strings.Builder, wms *pdoknlv3.WMS) error { return err } writeLine(sb, "echo %s %s >> %s/fonts.list;", fileRoot, fileName, fontsPath) + generatedFontsList = true } } - writeLine(sb, "echo 'generated fonts.list:';") - writeLine(sb, "cat %v/fonts.list;", fontsPath) + + if generatedFontsList { + writeLine(sb, "echo 'generated fonts.list:';") + writeLine(sb, "cat %v/fonts.list;", fontsPath) + } + return nil } func downloadLegends(sb *strings.Builder, wms *pdoknlv3.WMS) error { - for _, layer := range wms.GetAllLayersWithLegend() { - writeLine(sb, "mkdir -p %s/%s;", legendPath, *layer.Name) - for _, style := range layer.Styles { - writeLine(sb, "rclone copyto blobs:/%s %s/%s/%s.png || exit 1;", style.Legend.BlobKey, legendPath, *layer.Name, style.Name) - fileName, err := getFilenameFromBlobKey(style.Legend.BlobKey) - if err != nil { - return err + layers := wms.GetAllLayersWithLegend() + if len(layers) > 0 { + for _, layer := range layers { + writeLine(sb, "mkdir -p %s/%s;", legendPath, layer.Name) + for _, style := range layer.Styles { + writeLine(sb, "rclone copyto blobs:/%s %s/%s/%s.png || exit 1;", style.Legend.BlobKey, legendPath, layer.Name, style.Name) + fileName, err := getFilenameFromBlobKey(style.Legend.BlobKey) + if err != nil { + return err + } + writeLine(sb, "Copied legend %s to %s/%s/%s.png;", fileName, legendPath, layer.Name, style.Name) } - writeLine(sb, "Copied legend %s to %s/%s/%s.png;", fileName, legendPath, *layer.Name, style.Name) } + writeLine(sb, "chown -R 999:999 %s", legendPath) } - writeLine(sb, "chown -R 999:999 %s", legendPath) + return nil } diff --git a/internal/controller/featureinfogenerator/mapper.go b/internal/controller/featureinfogenerator/mapper.go index a5462f3..238d4ee 100644 --- a/internal/controller/featureinfogenerator/mapper.go +++ b/internal/controller/featureinfogenerator/mapper.go @@ -3,6 +3,7 @@ package featureinfogenerator import ( featureinfo "github.com/pdok/featureinfo-generator/pkg/types" pdoknlv3 "github.com/pdok/mapserver-operator/api/v3" + smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" ) const ( @@ -28,7 +29,7 @@ func MapWMSToFeatureinfoGeneratorInput(wms *pdoknlv3.WMS) (*featureinfo.Scheme, parentLayer := layer.GetParent(&wms.Spec.Service.Layer) if parentLayer != nil && parentLayer.IsGroupLayer() { - l.GroupName = *parentLayer.Name + l.GroupName = smoothoperatorutils.PointerVal(parentLayer.Name, "") } input.Layers = append(input.Layers, l) diff --git a/internal/controller/legendgenerator/mapper.go b/internal/controller/legendgenerator/mapper.go index a461b28..270c7dc 100644 --- a/internal/controller/legendgenerator/mapper.go +++ b/internal/controller/legendgenerator/mapper.go @@ -54,7 +54,7 @@ func addLayerInput(wms *pdoknlv3.WMS, data map[string]string) { } func processLayer(layer *pdoknlv3.Layer, legendReferences *[]LegendReference) { - if layer.Visible == nil || !*layer.Visible { + if !*layer.Visible { return } for _, style := range layer.Styles { diff --git a/internal/controller/mapfilegenerator/mapfile_generator.go b/internal/controller/mapfilegenerator/mapfile_generator.go index 825ce44..250a3a3 100644 --- a/internal/controller/mapfilegenerator/mapfile_generator.go +++ b/internal/controller/mapfilegenerator/mapfile_generator.go @@ -30,6 +30,11 @@ func GetMapfileGeneratorInitContainer[O pdoknlv3.WMSWFS](obj O, image, postgisCo }, } + if obj.Type() == pdoknlv3.ServiceTypeWMS { + stylingFilesVolAm := corev1.VolumeMount{Name: "styling-files", MountPath: "/styling", ReadOnly: true} + initContainer.VolumeMounts = append(initContainer.VolumeMounts, stylingFilesVolAm) + } + // Additional mapfile-generator configuration if obj.HasPostgisData() { initContainer.EnvFrom = []corev1.EnvFromSource{ diff --git a/internal/controller/mapfilegenerator/mapper.go b/internal/controller/mapfilegenerator/mapper.go index 3d6b91a..a306484 100644 --- a/internal/controller/mapfilegenerator/mapper.go +++ b/internal/controller/mapfilegenerator/mapper.go @@ -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 == nil || *annotatedLayer.Layer.Visible) { + } else if annotatedLayer.IsGroupLayer && *annotatedLayer.Layer.Visible { groupLayer := GroupLayer{ Name: *annotatedLayer.Layer.Name, Title: smoothoperatorutils.PointerVal(annotatedLayer.Layer.Title, ""), @@ -222,7 +222,7 @@ 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 != nil && *parent.Visible) { + if parent.IsGroupLayer() && parent.Name != nil && *parent.Visible { groupName = *parent.Name } diff --git a/internal/controller/ogcwebserviceproxy/ogc_webservice_proxy.go b/internal/controller/ogcwebserviceproxy/ogc_webservice_proxy.go index b27b210..85d0b95 100644 --- a/internal/controller/ogcwebserviceproxy/ogc_webservice_proxy.go +++ b/internal/controller/ogcwebserviceproxy/ogc_webservice_proxy.go @@ -3,6 +3,7 @@ package ogcwebserviceproxy import ( pdoknlv3 "github.com/pdok/mapserver-operator/api/v3" "github.com/pdok/mapserver-operator/internal/controller/mapserver" + smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" yaml "sigs.k8s.io/yaml/goyaml.v3" @@ -74,7 +75,7 @@ func MapWMSToOgcWebserviceProxyConfig(wms *pdoknlv3.WMS) (config Config, err err } } if len(dataLayers) > 0 { - config.GroupLayers[*layer.Name] = dataLayers + config.GroupLayers[smoothoperatorutils.PointerVal(layer.Name, "")] = dataLayers } } } diff --git a/internal/controller/shared_controller.go b/internal/controller/shared_controller.go index b2dd5eb..20698c6 100644 --- a/internal/controller/shared_controller.go +++ b/internal/controller/shared_controller.go @@ -1076,7 +1076,7 @@ func createOrUpdateAllForWMSWFS[R Reconciler, O pdoknlv3.WMSWFS](ctx context.Con // region ConfigMap-LegendGenerator { - configMapLg := getBareConfigMapLegendGenerator(obj) + configMapLg := getBareConfigMapLegendGenerator(wms) if err = mutateConfigMapLegendGenerator(wmsReconciler, wms, configMapLg); err != nil { return operationResults, err } @@ -1092,7 +1092,7 @@ func createOrUpdateAllForWMSWFS[R Reconciler, O pdoknlv3.WMSWFS](ctx context.Con // region ConfigMap-FeatureinfoGenerator { - configMapFig := getBareConfigMapFeatureinfoGenerator(obj) + configMapFig := getBareConfigMapFeatureinfoGenerator(wms) if err = mutateConfigMapFeatureinfoGenerator(wmsReconciler, wms, configMapFig); err != nil { return operationResults, err } @@ -1109,7 +1109,7 @@ func createOrUpdateAllForWMSWFS[R Reconciler, O pdoknlv3.WMSWFS](ctx context.Con // region ConfigMap-OgcWebserviceProxy { - configMapOwp := getBareConfigMapOgcWebserviceProxy(obj) + configMapOwp := getBareConfigMapOgcWebserviceProxy(wms) if err = mutateConfigMapOgcWebserviceProxy(wmsReconciler, wms, configMapOwp); err != nil { return operationResults, err } @@ -1253,7 +1253,7 @@ func deleteAllForWMSWFS[R Reconciler, O pdoknlv3.WMSWFS](ctx context.Context, r wmsReconciler := (*WMSReconciler)(r) // ConfigMap-LegendGenerator - cmLg := getBareConfigMapLegendGenerator(obj) + cmLg := getBareConfigMapLegendGenerator(wms) err = mutateConfigMapLegendGenerator(wmsReconciler, wms, cmLg) if err != nil { return err @@ -1261,7 +1261,7 @@ func deleteAllForWMSWFS[R Reconciler, O pdoknlv3.WMSWFS](ctx context.Context, r objects = append(objects, cmLg) // ConfigMap-FeatureInfo - cmFi := getBareConfigMapFeatureinfoGenerator(obj) + cmFi := getBareConfigMapFeatureinfoGenerator(wms) err = mutateConfigMapFeatureinfoGenerator(wmsReconciler, wms, cmFi) if err != nil { return err @@ -1269,7 +1269,7 @@ func deleteAllForWMSWFS[R Reconciler, O pdoknlv3.WMSWFS](ctx context.Context, r objects = append(objects, cmFi) // ConfigMap-OgcWebserviceProxy - cmOwp := getBareConfigMapOgcWebserviceProxy(obj) + cmOwp := getBareConfigMapOgcWebserviceProxy(wms) err = mutateConfigMapOgcWebserviceProxy(wmsReconciler, wms, cmOwp) if err != nil { return err diff --git a/internal/controller/wms_controller.go b/internal/controller/wms_controller.go index c2e1013..fc20722 100644 --- a/internal/controller/wms_controller.go +++ b/internal/controller/wms_controller.go @@ -132,10 +132,10 @@ func (r *WMSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result return result, err } -func getBareConfigMapLegendGenerator(obj metav1.Object) *corev1.ConfigMap { +func getBareConfigMapLegendGenerator(obj *pdoknlv3.WMS) *corev1.ConfigMap { return &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: obj.GetName() + "-legend-generator", + Name: getSuffixedName(obj, "legend-generator"), Namespace: obj.GetNamespace(), }, } @@ -162,10 +162,10 @@ func mutateConfigMapLegendGenerator(r *WMSReconciler, wms *pdoknlv3.WMS, configM } -func getBareConfigMapFeatureinfoGenerator(obj metav1.Object) *corev1.ConfigMap { +func getBareConfigMapFeatureinfoGenerator(obj *pdoknlv3.WMS) *corev1.ConfigMap { return &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: obj.GetName() + "-featureinfo-generator", + Name: getSuffixedName(obj, "featureinfo-generator"), Namespace: obj.GetNamespace(), }, } @@ -195,10 +195,10 @@ func mutateConfigMapFeatureinfoGenerator(r *WMSReconciler, wms *pdoknlv3.WMS, co return smoothoperatorutils.AddHashSuffix(configMap) } -func getBareConfigMapOgcWebserviceProxy(obj metav1.Object) *corev1.ConfigMap { +func getBareConfigMapOgcWebserviceProxy(obj *pdoknlv3.WMS) *corev1.ConfigMap { return &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: obj.GetName() + "-ogc-webservice-proxy", + Name: getSuffixedName(obj, "ogc-webservice-proxy"), Namespace: obj.GetNamespace(), }, } diff --git a/internal/webhook/v3/wms_webhook_test.go b/internal/webhook/v3/wms_webhook_test.go index 7c40e7a..cbeabe1 100644 --- a/internal/webhook/v3/wms_webhook_test.go +++ b/internal/webhook/v3/wms_webhook_test.go @@ -158,7 +158,7 @@ var _ = Describe("WMS Webhook", func() { It("Should deny creation if grouplayer is not visible", func() { nestedLayers1 := *obj.Spec.Service.Layer.Layers - nestedLayers1[0].Visible = smoothoperatorutils.Pointer(false) + nestedLayers1[0].Visible = false _, err := validator.ValidateCreate(ctx, obj) Expect(err).To(HaveOccurred()) }) @@ -215,7 +215,7 @@ var _ = Describe("WMS Webhook", func() { It("Should deny creation if there are no visible layers", func() { obj.Spec.Service.Layer.Layers = nil - obj.Spec.Service.Layer.Visible = smoothoperatorutils.Pointer(false) + obj.Spec.Service.Layer.Visible = false _, err := validator.ValidateUpdate(ctx, oldObj, obj) Expect(err).To(HaveOccurred()) From 476ab237d03c88e89252787a18eb8bf760233526 Mon Sep 17 00:00:00 2001 From: Jelle Dijkstra Date: Wed, 30 Apr 2025 09:02:43 +0200 Subject: [PATCH 2/5] Working blobdownload + mapfilegenerator --- Makefile | 2 +- api/v3/wms_types.go | 2 +- api/v3/wms_validation.go | 4 +-- config/crd/bases/pdok.nl_wms.yaml | 3 -- {cmd => config/crd}/update_openapi.go | 0 config/manager/kustomization.yaml | 2 +- .../controller/blobdownload/blob_download.go | 6 ++-- internal/controller/mapserver/deployment.go | 32 +++++++++---------- internal/controller/shared_controller_test.go | 7 ++-- internal/controller/wms_controller_test.go | 4 +-- internal/webhook/v3/wms_webhook_test.go | 4 +-- 11 files changed, 32 insertions(+), 34 deletions(-) rename {cmd => config/crd}/update_openapi.go (100%) diff --git a/Makefile b/Makefile index 1ed5242..bdc20dd 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=manager-role crd:allowDangerousTypes=true webhook paths="./..." output:crd:artifacts:config=config/crd/bases - go run cmd/update_openapi.go config/crd/bases + go run config/crd/update_openapi.go config/crd/bases ## allowDangerousTypes=true for v2beta structs .PHONY: generate diff --git a/api/v3/wms_types.go b/api/v3/wms_types.go index 0c6734f..6d8accf 100644 --- a/api/v3/wms_types.go +++ b/api/v3/wms_types.go @@ -94,7 +94,7 @@ type Layer struct { Title *string `json:"title,omitempty"` Abstract *string `json:"abstract,omitempty"` Keywords []string `json:"keywords"` - BoundingBoxes []WMSBoundingBox `json:"boundingBoxes"` + BoundingBoxes []WMSBoundingBox `json:"boundingBoxes,omitempty"` // +kubebuilder:default:=true Visible *bool `json:"visible,omitempty"` Authority *Authority `json:"authority,omitempty"` diff --git a/api/v3/wms_validation.go b/api/v3/wms_validation.go index f3f0e7a..20fd7b9 100644 --- a/api/v3/wms_validation.go +++ b/api/v3/wms_validation.go @@ -110,7 +110,7 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) { } if slices.Contains(names, layerName) { - layerReasons = append(layerReasons, fmt.Sprintf("layer names must be unique, layer.name '%s' is duplicated", layer.Name)) + layerReasons = append(layerReasons, fmt.Sprintf("layer names must be unique, layer.name '%s' is duplicated", layerName)) } names = append(names, layerName) @@ -204,7 +204,7 @@ func validateWMS(wms *WMS, warnings *[]string, reasons *[]string) { } } if len(layerReasons) != 0 { - *reasons = append(*reasons, fmt.Sprintf("%s '%s' is invalid: ", layerType, layer.Name)+strings.Join(layerReasons, ", ")) + *reasons = append(*reasons, fmt.Sprintf("%s '%s' is invalid: ", layerType, layerName)+strings.Join(layerReasons, ", ")) } } diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index 915b2a4..9704a26 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -1719,7 +1719,6 @@ spec: default: true type: boolean required: - - boundingBoxes - keywords - name type: object @@ -1769,7 +1768,6 @@ spec: default: true type: boolean required: - - boundingBoxes - keywords - name type: object @@ -1819,7 +1817,6 @@ spec: default: true type: boolean required: - - boundingBoxes - keywords type: object mapfile: diff --git a/cmd/update_openapi.go b/config/crd/update_openapi.go similarity index 100% rename from cmd/update_openapi.go rename to config/crd/update_openapi.go diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 39851b8..541cd44 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: local-registry:5000/mapserver-operator - newTag: v3.0.24 + newTag: v3.0.26 diff --git a/internal/controller/blobdownload/blob_download.go b/internal/controller/blobdownload/blob_download.go index 0c44945..301de58 100644 --- a/internal/controller/blobdownload/blob_download.go +++ b/internal/controller/blobdownload/blob_download.go @@ -197,14 +197,14 @@ func downloadLegends(sb *strings.Builder, wms *pdoknlv3.WMS) error { layers := wms.GetAllLayersWithLegend() if len(layers) > 0 { for _, layer := range layers { - writeLine(sb, "mkdir -p %s/%s;", legendPath, layer.Name) + writeLine(sb, "mkdir -p %s/%s;", legendPath, *layer.Name) for _, style := range layer.Styles { - writeLine(sb, "rclone copyto blobs:/%s %s/%s/%s.png || exit 1;", style.Legend.BlobKey, legendPath, layer.Name, style.Name) + writeLine(sb, "rclone copyto blobs:/%s %s/%s/%s.png || exit 1;", style.Legend.BlobKey, legendPath, *layer.Name, style.Name) fileName, err := getFilenameFromBlobKey(style.Legend.BlobKey) if err != nil { return err } - writeLine(sb, "Copied legend %s to %s/%s/%s.png;", fileName, legendPath, layer.Name, style.Name) + writeLine(sb, "Copied legend %s to %s/%s/%s.png;", fileName, legendPath, *layer.Name, style.Name) } } writeLine(sb, "chown -R 999:999 %s", legendPath) diff --git a/internal/controller/mapserver/deployment.go b/internal/controller/mapserver/deployment.go index 20c8d88..bff32e7 100644 --- a/internal/controller/mapserver/deployment.go +++ b/internal/controller/mapserver/deployment.go @@ -103,26 +103,26 @@ func GetVolumesForDeployment[O pdoknlv3.WMSWFS](obj O, configMapNames types.Hash Name: ConfigMapFeatureinfoGeneratorVolumeName, VolumeSource: newVolumeSource(configMapNames.FeatureInfoGenerator), } + + wms, _ := any(obj).(*pdoknlv3.WMS) + stylingFilesVolumeProjections := []v1.VolumeProjection{} + if wms.Spec.Service.StylingAssets != nil && wms.Spec.Service.StylingAssets.ConfigMapRefs != nil { + for _, cf := range wms.Spec.Service.StylingAssets.ConfigMapRefs { + stylingFilesVolumeProjections = append(stylingFilesVolumeProjections, v1.VolumeProjection{ + ConfigMap: &v1.ConfigMapProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: cf.Name, + }, + }, + }) + } + } + stylingFilesVolume := v1.Volume{ Name: ConfigMapStylingFilesVolumeName, VolumeSource: v1.VolumeSource{ Projected: &v1.ProjectedVolumeSource{ - Sources: []v1.VolumeProjection{ - { - ConfigMap: &v1.ConfigMapProjection{ - LocalObjectReference: v1.LocalObjectReference{ - Name: "gpkg-styling", - }, - }, - }, - { - ConfigMap: &v1.ConfigMapProjection{ - LocalObjectReference: v1.LocalObjectReference{ - Name: "tif-styling", - }, - }, - }, - }, + Sources: stylingFilesVolumeProjections, }, }, } diff --git a/internal/controller/shared_controller_test.go b/internal/controller/shared_controller_test.go index 2eb3b75..8f8a322 100644 --- a/internal/controller/shared_controller_test.go +++ b/internal/controller/shared_controller_test.go @@ -85,7 +85,8 @@ func getExpectedObjects[O pdoknlv3.WMSWFS](ctx context.Context, obj O, includeBl } if obj.Type() == pdoknlv3.ServiceTypeWMS { - cm = getBareConfigMapLegendGenerator(obj) + wms, _ := any(obj).(*pdoknlv3.WMS) + cm = getBareConfigMapLegendGenerator(wms) hashedName, err = getHashedConfigMapNameFromClient(ctx, obj, mapserver.ConfigMapLegendGeneratorVolumeName) if err != nil { return objects, err @@ -93,7 +94,7 @@ func getExpectedObjects[O pdoknlv3.WMSWFS](ctx context.Context, obj O, includeBl cm.Name = hashedName objects = append(objects, cm) - cm = getBareConfigMapFeatureinfoGenerator(obj) + cm = getBareConfigMapFeatureinfoGenerator(wms) hashedName, err = getHashedConfigMapNameFromClient(ctx, obj, mapserver.ConfigMapFeatureinfoGeneratorVolumeName) if err != nil { return objects, err @@ -102,7 +103,7 @@ func getExpectedObjects[O pdoknlv3.WMSWFS](ctx context.Context, obj O, includeBl objects = append(objects, cm) if obj.Options().UseWebserviceProxy() { - cm = getBareConfigMapOgcWebserviceProxy(obj) + cm = getBareConfigMapOgcWebserviceProxy(wms) hashedName, err = getHashedConfigMapNameFromClient(ctx, obj, mapserver.ConfigMapOgcWebserviceProxyVolumeName) if err != nil { return objects, err diff --git a/internal/controller/wms_controller_test.go b/internal/controller/wms_controller_test.go index 0bd4d03..5a83a49 100644 --- a/internal/controller/wms_controller_test.go +++ b/internal/controller/wms_controller_test.go @@ -641,7 +641,7 @@ var _ = Describe("WMS Controller", func() { return Expect(err).NotTo(HaveOccurred()) }, "10s", "1s").Should(BeTrue()) - Expect(configMap.GetName()).To(HavePrefix(wms.GetName() + "-legend-generator-")) + Expect(configMap.GetName()).To(HavePrefix(wms.GetName() + "-wms-legend-generator-")) Expect(configMap.GetNamespace()).To(Equal(namespace)) Expect(configMap.Immutable).To(Equal(smoothoperatorutils.Pointer(true))) checkWMSLabels(configMap.GetLabels()) @@ -681,7 +681,7 @@ var _ = Describe("WMS Controller", func() { return Expect(err).NotTo(HaveOccurred()) }, "10s", "1s").Should(BeTrue()) - Expect(configMap.GetName()).To(HavePrefix(wms.GetName() + "-featureinfo-generator")) + Expect(configMap.GetName()).To(HavePrefix(wms.GetName() + "-wms-featureinfo-generator")) Expect(configMap.GetNamespace()).To(Equal(namespace)) Expect(configMap.Immutable).To(Equal(smoothoperatorutils.Pointer(true))) checkWMSLabels(configMap.GetLabels()) diff --git a/internal/webhook/v3/wms_webhook_test.go b/internal/webhook/v3/wms_webhook_test.go index cbeabe1..7c40e7a 100644 --- a/internal/webhook/v3/wms_webhook_test.go +++ b/internal/webhook/v3/wms_webhook_test.go @@ -158,7 +158,7 @@ var _ = Describe("WMS Webhook", func() { It("Should deny creation if grouplayer is not visible", func() { nestedLayers1 := *obj.Spec.Service.Layer.Layers - nestedLayers1[0].Visible = false + nestedLayers1[0].Visible = smoothoperatorutils.Pointer(false) _, err := validator.ValidateCreate(ctx, obj) Expect(err).To(HaveOccurred()) }) @@ -215,7 +215,7 @@ var _ = Describe("WMS Webhook", func() { It("Should deny creation if there are no visible layers", func() { obj.Spec.Service.Layer.Layers = nil - obj.Spec.Service.Layer.Visible = false + obj.Spec.Service.Layer.Visible = smoothoperatorutils.Pointer(false) _, err := validator.ValidateUpdate(ctx, oldObj, obj) Expect(err).To(HaveOccurred()) From 6ca1ae83dbc802657cff1fbd13a901159171e724 Mon Sep 17 00:00:00 2001 From: Jelle Dijkstra Date: Wed, 30 Apr 2025 15:40:56 +0200 Subject: [PATCH 3/5] Running WMS --- Dockerfile | 1 + api/v3/wms_validation.go | 2 - cmd/main.go | 2 +- config/crd/bases/pdok.nl_wms.yaml | 3533 ++++++++--------- config/crd/update_openapi.go | 22 +- config/manager/kustomization.yaml | 2 +- go.mod | 8 +- go.sum | 8 +- .../capabilities_generator_test.go | 81 +- 9 files changed, 1785 insertions(+), 1874 deletions(-) diff --git a/Dockerfile b/Dockerfile index 051474c..735a9dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ ARG TARGETOS ARG TARGETARCH #COPY --from=repos ./smooth-operator /smooth-operator +#COPY --from=repos ./ogc-specifications /ogc-specifications WORKDIR /workspace # Copy the Go Modules manifests diff --git a/api/v3/wms_validation.go b/api/v3/wms_validation.go index 20fd7b9..a905a3c 100644 --- a/api/v3/wms_validation.go +++ b/api/v3/wms_validation.go @@ -20,8 +20,6 @@ func (wms *WMS) ValidateCreate() ([]string, error) { validateWMS(wms, &warnings, &reasons) if len(reasons) > 0 { - //yaml, _ := yaml2.Marshal(wms) - //fmt.Println(yaml) return warnings, fmt.Errorf("%s", strings.Join(reasons, ". ")) } diff --git a/cmd/main.go b/cmd/main.go index f9a14aa..d7626d0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -53,7 +53,7 @@ const ( defaultMapfileGeneratorImage = "acrpdokprodman.azurecr.io/pdok/mapfile-generator:1.9.5" defaultMapserverImage = "acrpdokprodman.azurecr.io/mirror/docker.io/pdok/mapserver:8.4.0-4-nl" defaultCapabilitiesGeneratorImage = "acrpdokprodman.azurecr.io/mirror/docker.io/pdok/ogc-capabilities-generator:1.0.0-beta7" - defaultFeatureinfoGeneratorImage = "acrpdokprodman.azurecr.io/mirror/docker.io/pdok/featureinfo-generator:v1.4.0-beta1" + defaultFeatureinfoGeneratorImage = "acrpdokprodman.azurecr.io/mirror/docker.io/pdok/featureinfo-generator:1.4.0-beta1" defaultOgcWebserviceProxyImage = "acrpdokprodman.azurecr.io/pdok/ogc-webservice-proxy:0.1.8" defaultApacheExporterImage = "acrpdokprodman.azurecr.io/mirror/docker.io/lusotycoon/apache-exporter:v0.7.0" diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index 9704a26..319d522 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -14,1966 +14,1891 @@ spec: singular: wms scope: Namespaced versions: - - name: v2beta1 - schema: - openAPIV3Schema: - description: WMS is the Schema for the wms API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: WMSSpec is the struct for all fields defined in the WMS CRD - properties: - general: - description: General is the struct with all generic fields for the - crds - properties: - dataVersion: - type: string - dataset: - type: string - datasetOwner: - type: string - serviceVersion: - type: string - theme: - type: string - required: - - dataset - - datasetOwner - type: object - kubernetes: - description: Kubernetes is the struct with all fields that can be - defined in kubernetes fields in the crds - properties: - autoscaling: - description: Autoscaling is the struct with all fields to configure - autoscalers for the crs - properties: - averageCpuUtilization: - type: integer - maxReplicas: - type: integer - minReplicas: - type: integer - type: object - healthCheck: - description: HealthCheck is the struct with all fields to configure - healthchecks for the crs - properties: - boundingbox: - type: string - mimetype: - type: string - querystring: - type: string - type: object - lifecycle: - description: Lifecycle is the struct with the fields to configure - lifecycle settings for the resources - properties: - ttlInDays: - type: integer - type: object - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. + - name: v2beta1 + schema: + openAPIV3Schema: + description: WMS is the Schema for the wms API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: WMSSpec is the struct for all fields defined in the WMS CRD + properties: + general: + description: General is the struct with all generic fields for the crds + properties: + dataVersion: + type: string + dataset: + type: string + datasetOwner: + type: string + serviceVersion: + type: string + theme: + type: string + required: + - dataset + - datasetOwner + type: object + kubernetes: + description: Kubernetes is the struct with all fields that can be defined in kubernetes fields in the crds + properties: + autoscaling: + description: Autoscaling is the struct with all fields to configure autoscalers for the crs + properties: + averageCpuUtilization: + type: integer + maxReplicas: + type: integer + minReplicas: + type: integer + type: object + healthCheck: + description: HealthCheck is the struct with all fields to configure healthchecks for the crs + properties: + boundingbox: + type: string + mimetype: + type: string + querystring: + type: string + type: object + lifecycle: + description: Lifecycle is the struct with the fields to configure lifecycle settings for the resources + properties: + ttlInDays: + type: integer + type: object + resources: + description: ResourceRequirements describes the compute resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - type: object - options: - description: WMSWFSOptions is the struct with options available in - the operator - properties: - automaticCasing: - type: boolean - disableWebserviceProxy: - type: boolean - includeIngress: - type: boolean - prefetchData: - type: boolean - rewriteGroupToDataLayers: - type: boolean - validateChildStyleNameEqual: - type: boolean - validateRequests: - type: boolean - required: - - automaticCasing - - includeIngress - type: object - service: - description: WMSService is the struct for all service level fields - properties: - abstract: - type: string - accessConstraints: - default: https://creativecommons.org/publicdomain/zero/1.0/deed.nl - type: string - authority: - description: Authority is a struct for the authority fields in - WMS and WFS crds - properties: - name: - type: string - url: - type: string - required: - - name - - url - type: object - dataEPSG: - type: string - defResolution: - type: integer - extent: - type: string - inspire: - type: boolean - keywords: - items: + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object + options: + description: WMSWFSOptions is the struct with options available in the operator + properties: + automaticCasing: + type: boolean + disableWebserviceProxy: + type: boolean + includeIngress: + type: boolean + prefetchData: + type: boolean + rewriteGroupToDataLayers: + type: boolean + validateChildStyleNameEqual: + type: boolean + validateRequests: + type: boolean + required: + - automaticCasing + - includeIngress + type: object + service: + description: WMSService is the struct for all service level fields + properties: + abstract: + type: string + accessConstraints: + default: https://creativecommons.org/publicdomain/zero/1.0/deed.nl type: string - type: array - layers: - items: - description: WMSLayer is the struct for all layer level fields + authority: + description: Authority is a struct for the authority fields in WMS and WFS crds properties: - abstract: + name: type: string - data: - description: Data is a struct for the data field for a WMSLayer - or WFS FeatureType - properties: - gpkg: - description: GPKG is a struct for the gpkg field for - a WMSLayer or WFS FeatureType - properties: - aliases: - additionalProperties: + url: + type: string + required: + - name + - url + type: object + dataEPSG: + type: string + defResolution: + type: integer + extent: + type: string + inspire: + type: boolean + keywords: + items: + type: string + type: array + layers: + items: + description: WMSLayer is the struct for all layer level fields + properties: + abstract: + type: string + data: + description: Data is a struct for the data field for a WMSLayer or WFS FeatureType + properties: + gpkg: + description: GPKG is a struct for the gpkg field for a WMSLayer or WFS FeatureType + properties: + aliases: + additionalProperties: + type: string + description: In a new version Aliases should become part of Columns + type: object + blobKey: type: string - description: In a new version Aliases should become - part of Columns - type: object - blobKey: - type: string - columns: - items: + columns: + items: + type: string + type: array + geometryType: type: string - type: array - geometryType: - type: string - table: - type: string - required: - - blobKey - - columns - - geometryType - - table - type: object - postgis: - description: |- - Postgis is a struct for the Postgis db config for a WMSLayer or WFS FeatureType - connection details are passed through the environment - properties: - aliases: - additionalProperties: + table: type: string - description: In a new version Aliases should become - part of Columns - type: object - columns: - items: + required: + - blobKey + - columns + - geometryType + - table + type: object + postgis: + description: |- + Postgis is a struct for the Postgis db config for a WMSLayer or WFS FeatureType + connection details are passed through the environment + properties: + aliases: + additionalProperties: + type: string + description: In a new version Aliases should become part of Columns + type: object + columns: + items: + type: string + type: array + geometryType: type: string - type: array - geometryType: - type: string - table: - type: string - required: - - columns - - geometryType - - table - type: object - tif: - description: Tif is a struct for the Tif field for a - WMSLayer + table: + type: string + required: + - columns + - geometryType + - table + type: object + tif: + description: Tif is a struct for the Tif field for a WMSLayer + properties: + blobKey: + type: string + getFeatureInfoIncludesClass: + type: boolean + offsite: + type: string + resample: + type: string + required: + - blobKey + type: object + type: object + datasetMetadataIdentifier: + type: string + extent: + type: string + group: + type: string + keywords: + items: + type: string + type: array + labelNoClip: + type: boolean + maxScale: + type: number + minScale: + type: number + name: + type: string + sourceMetadataIdentifier: + type: string + styles: + items: + description: Style is the struct for all style level fields properties: - blobKey: + abstract: type: string - getFeatureInfoIncludesClass: - type: boolean - offsite: + legendFile: + description: LegendFile is the struct containing the location of the legendfile + properties: + blobKey: + type: string + required: + - blobKey + type: object + name: type: string - resample: + title: + type: string + visualization: type: string required: - - blobKey + - name type: object + type: array + title: + type: string + visible: + type: boolean + required: + - name + - styles + - visible + type: object + type: array + mapfile: + description: Mapfile contains the ConfigMapKeyRef containing a mapfile + properties: + configMapKeyRef: + description: Selects a key from a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + required: + - key type: object - datasetMetadataIdentifier: - type: string - extent: - type: string - group: - type: string - keywords: + x-kubernetes-map-type: atomic + required: + - configMapKeyRef + type: object + maxSize: + type: number + metadataIdentifier: + type: string + resolution: + type: integer + stylingAssets: + description: StylingAssets is the struct containing the location of styling assets + properties: + blobKeys: items: type: string type: array - labelNoClip: - type: boolean - maxScale: - type: number - minScale: - type: number - name: - type: string - sourceMetadataIdentifier: - type: string - styles: + configMapRefs: items: - description: Style is the struct for all style level fields + description: |- + ConfigMapRef contains all the config map name and all keys in that mapserver that are relevant + the Keys can be empty, so that the v1 WMS can convert to the v2beta1 WMS properties: - abstract: + keys: + items: + type: string + type: array + name: + type: string + required: + - name + type: object + type: array + type: object + title: + type: string + required: + - abstract + - accessConstraints + - authority + - dataEPSG + - inspire + - keywords + - layers + - metadataIdentifier + - title + type: object + required: + - general + - kubernetes + - options + - service + type: object + status: + description: Status - The status for custom resources managed by the operator-sdk. + properties: + conditions: + items: + description: |- + Condition - the condition for the ansible operator + https://github.com/operator-framework/operator-sdk/blob/master/internal/ansible/controller/status/types.go#L101 + properties: + ansibleResult: + description: ResultAnsible - encapsulation of the ansible result. 'AnsibleResult' is turned around in struct to comply with linting + properties: + changed: + type: integer + completion: + type: string + failures: + type: integer + ok: + type: integer + skipped: + type: integer + required: + - changed + - completion + - failures + - ok + - skipped + type: object + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + description: ConditionStatus specifies a string for field ConditionType + type: string + type: + description: ConditionType specifies a string for field ConditionType + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + deployment: + type: string + resources: + items: + description: Resources is the struct for the resources field within status + properties: + apiversion: + type: string + kind: + type: string + name: + type: string + type: object + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} + - name: v3 + schema: + openAPIV3Schema: + description: WMS is the Schema for the wms API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: WMSSpec defines the desired state of WMS. + properties: + horizontalPodAutoscalerPatch: + description: HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler. + properties: + behavior: + description: |- + behavior 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: |- + scaleDown is scaling policy for scaling Down. + If not set, the default value is to allow to scale down to minReplicas pods, with a + 300 second stabilization window (i.e., the highest recommendation for + the last 300sec is used). + properties: + policies: + description: |- + policies is a list of potential scaling polices which can be used during scaling. + At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid + items: + description: HPAScalingPolicy is a single policy which must hold true for a specified past interval. + properties: + periodSeconds: + description: |- + periodSeconds specifies the window of time for which the policy should hold true. + PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). + format: int32 + type: integer + type: + description: type is used to specify the scaling policy. + type: string + value: + description: |- + value contains the amount of change which is permitted by the policy. + It must be greater than zero + format: int32 + type: integer + required: + - periodSeconds + - type + - value + type: object + type: array + x-kubernetes-list-type: atomic + selectPolicy: + description: |- + selectPolicy is used to specify which policy should be used. + If not set, the default value Max is used. + type: string + stabilizationWindowSeconds: + description: |- + stabilizationWindowSeconds is the number of seconds for which past recommendations should be + considered while scaling up or scaling down. + StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). + If not set, use the default values: + - For scale up: 0 (i.e. no stabilization is done). + - For scale down: 300 (i.e. the stabilization window is 300 seconds long). + format: int32 + type: integer + type: object + scaleUp: + description: |- + scaleUp is scaling policy for scaling Up. + If not set, the default value is the higher of: + * increase no more than 4 pods per 60 seconds + * double the number of pods per 60 seconds + No stabilization is used. + properties: + policies: + description: |- + policies is a list of potential scaling polices which can be used during scaling. + At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid + items: + description: HPAScalingPolicy is a single policy which must hold true for a specified past interval. + properties: + periodSeconds: + description: |- + periodSeconds specifies the window of time for which the policy should hold true. + PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). + format: int32 + type: integer + type: + description: type is used to specify the scaling policy. + type: string + value: + description: |- + value contains the amount of change which is permitted by the policy. + It must be greater than zero + format: int32 + type: integer + required: + - periodSeconds + - type + - value + type: object + type: array + x-kubernetes-list-type: atomic + selectPolicy: + description: |- + selectPolicy is used to specify which policy should be used. + If not set, the default value Max is used. + type: string + stabilizationWindowSeconds: + description: |- + stabilizationWindowSeconds is the number of seconds for which past recommendations should be + considered while scaling up or scaling down. + StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). + If not set, use the default values: + - For scale up: 0 (i.e. no stabilization is done). + - For scale down: 300 (i.e. the stabilization window is 300 seconds long). + format: int32 + type: integer + 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 + (only `type` and one other matching field should be set at once). + properties: + containerResource: + description: |- + containerResource refers to a resource metric (such as those specified in + requests and limits) known to Kubernetes describing a single container in + each pod of the current scale target (e.g. CPU or memory). Such metrics are + built in to Kubernetes, and have special scaling options on top of those + available to normal per-pod metrics using the "pods" source. + properties: + container: + description: container is the name of the container in the pods of the scaling target type: string - legendFile: - description: LegendFile is the struct containing the - location of the legendfile + name: + description: name is the name of the resource in question. + type: string + target: + description: target specifies the target value for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the metric type is Utilization, Value, or AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - container + - name + - target + type: object + external: + description: |- + external refers to a global metric that is not associated + with any Kubernetes object. It allows autoscaling based on information + coming from components running outside of cluster + (for example length of queue in cloud messaging service, or + QPS from loadbalancer running outside of cluster). + properties: + metric: + description: metric identifies the target metric by name and selector + properties: + name: + description: name is the name of the given metric + type: string + selector: + description: |- + selector is the string-encoded form of a standard kubernetes label selector for the given metric + When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. + When unset, just the metricName will be used to gather metrics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - name + type: object + target: + description: target specifies the target value for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the metric type is Utilization, Value, or AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - metric + - target + type: object + object: + description: |- + object refers to a metric describing a single kubernetes object + (for example, hits-per-second on an Ingress object). + properties: + describedObject: + description: describedObject specifies the descriptions of a object,such as kind,name apiVersion + 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 + metric: + description: metric identifies the target metric by name and selector + properties: + name: + description: name is the name of the given metric + type: string + selector: + description: |- + selector is the string-encoded form of a standard kubernetes label selector for the given metric + When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. + When unset, just the metricName will be used to gather metrics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - name + type: object + target: + description: target specifies the target value for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the metric type is Utilization, Value, or AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - describedObject + - metric + - target + type: object + pods: + description: |- + pods refers to a metric describing each pod in the current scale target + (for example, transactions-processed-per-second). The values will be + averaged together before being compared to the target value. + properties: + metric: + description: metric identifies the target metric by name and selector properties: - blobKey: + name: + description: name is the name of the given metric type: string + selector: + description: |- + selector is the string-encoded form of a standard kubernetes label selector for the given metric + When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. + When unset, just the metricName will be used to gather metrics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic required: - - blobKey + - name + type: object + target: + description: target specifies the target value for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the metric type is Utilization, Value, or AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type type: object + required: + - metric + - target + type: object + resource: + description: |- + resource refers to a resource metric (such as those specified in + requests and limits) known to Kubernetes describing each pod in the + current scale target (e.g. CPU or memory). Such metrics are built in to + Kubernetes, and have special scaling options on top of those available + to normal per-pod metrics using the "pods" source. + properties: name: + description: name is the name of the resource in question. type: string - title: - type: string - visualization: - type: string + target: + description: target specifies the target value for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the metric type is Utilization, Value, or AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object required: - - name + - name + - target type: object - type: array - title: - type: string - visible: - type: boolean - required: - - name - - styles - - visible - type: object - type: array - mapfile: - description: Mapfile contains the ConfigMapKeyRef containing a - mapfile - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - default: "" + type: description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type is the type of metric source. It should be one of "ContainerResource", "External", + "Object", "Pods" or "Resource", each mapping to a matching field in the object. type: string - optional: - description: Specify whether the ConfigMap or its key - must be defined - type: boolean required: - - key + - type type: object - x-kubernetes-map-type: atomic - required: - - configMapKeyRef - type: object - maxSize: - type: number - metadataIdentifier: - type: string - resolution: - type: integer - stylingAssets: - description: StylingAssets is the struct containing the location - of styling assets - properties: - blobKeys: - items: - type: string - type: array - configMapRefs: - items: - description: |- - ConfigMapRef contains all the config map name and all keys in that mapserver that are relevant - the Keys can be empty, so that the v1 WMS can convert to the v2beta1 WMS - properties: - keys: - items: - type: string - type: array - name: - type: string - required: - - name - type: object - type: array - type: object - title: - type: string - required: - - abstract - - accessConstraints - - authority - - dataEPSG - - inspire - - keywords - - layers - - metadataIdentifier - - title - type: object - required: - - general - - kubernetes - - options - - service - type: object - status: - description: Status - The status for custom resources managed by the operator-sdk. - properties: - conditions: - items: - description: |- - Condition - the condition for the ansible operator - https://github.com/operator-framework/operator-sdk/blob/master/internal/ansible/controller/status/types.go#L101 - properties: - ansibleResult: - description: ResultAnsible - encapsulation of the ansible result. - 'AnsibleResult' is turned around in struct to comply with - linting + 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: - changed: - type: integer - completion: + 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 - failures: - type: integer - ok: - type: integer - skipped: - type: integer required: - - changed - - completion - - failures - - ok - - skipped + - kind + - name type: object - lastTransitionTime: - format: date-time - type: string - message: - type: string - reason: - type: string - status: - description: ConditionStatus specifies a string for field ConditionType - type: string - type: - description: ConditionType specifies a string for field ConditionType - type: string required: - - lastTransitionTime - - message - - reason - - status - - type + - maxReplicas + - scaleTargetRef + type: object + lifecycle: + properties: + ttlInDays: + format: int32 + type: integer + type: object + options: + properties: + automaticCasing: + default: true + type: boolean + disableWebserviceProxy: + default: false + type: boolean + includeIngress: + default: true + type: boolean + prefetchData: + default: true + type: boolean + rewriteGroupToDataLayers: + default: false + type: boolean + validateChildStyleNameEqual: + type: boolean + validateRequests: + default: true + type: boolean + required: + - automaticCasing + - includeIngress type: object - type: array - deployment: - type: string - resources: - items: - description: Resources is the struct for the resources field within - status + podSpecPatch: + description: Optional strategic merge patch for the pod in the deployment. E.g. to patch the resources or add extra env vars. + type: object + x-kubernetes-preserve-unknown-fields: true + service: properties: - apiversion: + abstract: type: string - kind: + accessConstraints: + default: https://creativecommons.org/publicdomain/zero/1.0/deed.nl type: string - name: + dataEPSG: type: string - type: object - type: array - type: object - type: object - served: true - storage: false - subresources: - status: {} - - name: v3 - schema: - openAPIV3Schema: - description: WMS is the Schema for the wms API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: WMSSpec defines the desired state of WMS. - properties: - horizontalPodAutoscalerPatch: - description: HorizontalPodAutoscalerSpec describes the desired functionality - of the HorizontalPodAutoscaler. - properties: - behavior: - description: |- - behavior 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: |- - scaleDown is scaling policy for scaling Down. - If not set, the default value is to allow to scale down to minReplicas pods, with a - 300 second stabilization window (i.e., the highest recommendation for - the last 300sec is used). - properties: - policies: - description: |- - policies is a list of potential scaling polices which can be used during scaling. - At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid - items: - description: HPAScalingPolicy is a single policy which - must hold true for a specified past interval. + defResolution: + format: int32 + type: integer + fees: + type: string + inspire: + properties: + language: + type: string + serviceMetadataUrl: + properties: + csw: properties: - periodSeconds: - description: |- - periodSeconds specifies the window of time for which the policy should hold true. - PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). - format: int32 - type: integer - type: - description: type is used to specify the scaling - policy. + metadataIdentifier: type: string - value: - description: |- - value contains the amount of change which is permitted by the policy. - It must be greater than zero - format: int32 - type: integer required: - - periodSeconds - - type - - value + - metadataIdentifier type: object - type: array - x-kubernetes-list-type: atomic - selectPolicy: - description: |- - selectPolicy is used to specify which policy should be used. - If not set, the default value Max is used. - type: string - stabilizationWindowSeconds: - description: |- - stabilizationWindowSeconds is the number of seconds for which past recommendations should be - considered while scaling up or scaling down. - StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). - If not set, use the default values: - - For scale up: 0 (i.e. no stabilization is done). - - For scale down: 300 (i.e. the stabilization window is 300 seconds long). - format: int32 - type: integer - type: object - scaleUp: - description: |- - scaleUp is scaling policy for scaling Up. - If not set, the default value is the higher of: - * increase no more than 4 pods per 60 seconds - * double the number of pods per 60 seconds - No stabilization is used. - properties: - policies: - description: |- - policies is a list of potential scaling polices which can be used during scaling. - At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid - items: - description: HPAScalingPolicy is a single policy which - must hold true for a specified past interval. + custom: properties: - periodSeconds: - description: |- - periodSeconds specifies the window of time for which the policy should hold true. - PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). - format: int32 - type: integer + href: + type: string type: - description: type is used to specify the scaling - policy. type: string - value: - description: |- - value contains the amount of change which is permitted by the policy. - It must be greater than zero - format: int32 - type: integer required: - - periodSeconds - - type - - value + - href + - type type: object - type: array - x-kubernetes-list-type: atomic - selectPolicy: - description: |- - selectPolicy is used to specify which policy should be used. - If not set, the default value Max is used. - type: string - stabilizationWindowSeconds: - description: |- - stabilizationWindowSeconds is the number of seconds for which past recommendations should be - considered while scaling up or scaling down. - StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). - If not set, use the default values: - - For scale up: 0 (i.e. no stabilization is done). - - For scale down: 300 (i.e. the stabilization window is 300 seconds long). - format: int32 - type: integer - 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 - (only `type` and one other matching field should be set at once). + required: + - csw + type: object + spatialDatasetIdentifier: + type: string + required: + - language + - serviceMetadataUrl + - spatialDatasetIdentifier + type: object + keywords: + items: + type: string + type: array + layer: properties: - containerResource: - description: |- - containerResource refers to a resource metric (such as those specified in - requests and limits) known to Kubernetes describing a single container in - each pod of the current scale target (e.g. CPU or memory). Such metrics are - built in to Kubernetes, and have special scaling options on top of those - available to normal per-pod metrics using the "pods" source. + abstract: + type: string + authority: properties: - container: - description: container is the name of the container - in the pods of the scaling target - type: string name: - description: name is the name of the resource in question. type: string - target: - description: target specifies the target value for the - given metric - properties: - averageUtilization: - description: |- - averageUtilization is the target value of the average of the - resource metric across all relevant pods, represented as a percentage of - the requested value of the resource for the pods. - Currently only valid for Resource metric source type - format: int32 - type: integer - averageValue: - anyOf: - - type: integer - - type: string - description: |- - averageValue is the target value of the average of the - metric across all relevant pods (as a quantity) - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: - description: type represents whether the metric - type is Utilization, Value, or AverageValue - type: string - value: - anyOf: - - type: integer - - type: string - description: value is the target value of the metric - (as a quantity). - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - required: - - type - type: object + spatialDatasetIdentifier: + type: string + url: + type: string required: - - container - - name - - target + - name + - spatialDatasetIdentifier + - url type: object - external: - description: |- - external refers to a global metric that is not associated - with any Kubernetes object. It allows autoscaling based on information - coming from components running outside of cluster - (for example length of queue in cloud messaging service, or - QPS from loadbalancer running outside of cluster). + boundingBoxes: + items: + properties: + bbox: + description: BBox defines a bounding box with coordinates + properties: + maxx: + description: Rechtsonder X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + maxy: + description: Rechtsonder Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + minx: + description: Linksboven X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + miny: + description: Linksboven Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + required: + - maxx + - maxy + - minx + - miny + type: object + crs: + type: string + required: + - bbox + - crs + type: object + type: array + data: properties: - metric: - description: metric identifies the target metric by - name and selector + gpkg: properties: - name: - description: name is the name of the given metric + blobKey: type: string - selector: - description: |- - selector is the string-encoded form of a standard kubernetes label selector for the given metric - When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - When unset, just the metricName will be used to gather metrics. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: + columns: + items: + properties: + alias: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - name - type: object - target: - description: target specifies the target value for the - given metric - properties: - averageUtilization: - description: |- - averageUtilization is the target value of the average of the - resource metric across all relevant pods, represented as a percentage of - the requested value of the resource for the pods. - Currently only valid for Resource metric source type - format: int32 - type: integer - averageValue: - anyOf: - - type: integer - - type: string - description: |- - averageValue is the target value of the average of the - metric across all relevant pods (as a quantity) - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: - description: type represents whether the metric - type is Utilization, Value, or AverageValue - type: string - value: - anyOf: - - type: integer - - type: string - description: value is the target value of the metric - (as a quantity). - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - required: - - type - type: object - required: - - metric - - target - type: object - object: - description: |- - object refers to a metric describing a single kubernetes object - (for example, hits-per-second on an Ingress object). - properties: - describedObject: - description: describedObject specifies the descriptions - of a object,such as kind,name apiVersion - 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' + name: + type: string + required: + - name + type: object + type: array + geometryType: type: string - name: - description: 'name is the name of the referent; - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + tableName: type: string required: - - kind - - name + - blobKey + - columns + - geometryType + - tableName type: object - metric: - description: metric identifies the target metric by - name and selector + postgis: + description: Postgis - reference to table in a Postgres database properties: - name: - description: name is the name of the given metric - type: string - selector: - description: |- - selector is the string-encoded form of a standard kubernetes label selector for the given metric - When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - When unset, just the metricName will be used to gather metrics. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: + columns: + items: + properties: + alias: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - name - type: object - target: - description: target specifies the target value for the - given metric - properties: - averageUtilization: - description: |- - averageUtilization is the target value of the average of the - resource metric across all relevant pods, represented as a percentage of - the requested value of the resource for the pods. - Currently only valid for Resource metric source type - format: int32 - type: integer - averageValue: - anyOf: - - type: integer - - type: string - description: |- - averageValue is the target value of the average of the - metric across all relevant pods (as a quantity) - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: - description: type represents whether the metric - type is Utilization, Value, or AverageValue + name: + type: string + required: + - name + type: object + type: array + geometryType: type: string - value: - anyOf: - - type: integer - - type: string - description: value is the target value of the metric - (as a quantity). - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - required: - - type - type: object - required: - - describedObject - - metric - - target - type: object - pods: - description: |- - pods refers to a metric describing each pod in the current scale target - (for example, transactions-processed-per-second). The values will be - averaged together before being compared to the target value. - properties: - metric: - description: metric identifies the target metric by - name and selector - properties: - name: - description: name is the name of the given metric + tableName: type: string - selector: - description: |- - selector is the string-encoded form of a standard kubernetes label selector for the given metric - When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. - When unset, just the metricName will be used to gather metrics. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic required: - - name + - columns + - geometryType + - tableName type: object - target: - description: target specifies the target value for the - given metric + tif: properties: - averageUtilization: - description: |- - averageUtilization is the target value of the average of the - resource metric across all relevant pods, represented as a percentage of - the requested value of the resource for the pods. - Currently only valid for Resource metric source type - format: int32 - type: integer - averageValue: - anyOf: - - type: integer - - type: string - description: |- - averageValue is the target value of the average of the - metric across all relevant pods (as a quantity) - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: - description: type represents whether the metric - type is Utilization, Value, or AverageValue + blobKey: + type: string + getFeatureInfoIncludesClass: + type: boolean + offsite: + type: string + resample: type: string - value: - anyOf: - - type: integer - - type: string - description: value is the target value of the metric - (as a quantity). - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true required: - - type + - blobKey type: object - required: - - metric - - target type: object - resource: - description: |- - resource refers to a resource metric (such as those specified in - requests and limits) known to Kubernetes describing each pod in the - current scale target (e.g. CPU or memory). Such metrics are built in to - Kubernetes, and have special scaling options on top of those available - to normal per-pod metrics using the "pods" source. + datasetMetadataUrl: properties: - name: - description: name is the name of the resource in question. - type: string - target: - description: target specifies the target value for the - given metric + csw: properties: - averageUtilization: - description: |- - averageUtilization is the target value of the average of the - resource metric across all relevant pods, represented as a percentage of - the requested value of the resource for the pods. - Currently only valid for Resource metric source type - format: int32 - type: integer - averageValue: - anyOf: - - type: integer - - type: string - description: |- - averageValue is the target value of the average of the - metric across all relevant pods (as a quantity) - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: - description: type represents whether the metric - type is Utilization, Value, or AverageValue + metadataIdentifier: type: string - value: - anyOf: - - type: integer - - type: string - description: value is the target value of the metric - (as a quantity). - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true required: - - type + - metadataIdentifier type: object - required: - - name - - target - type: object - type: - description: |- - type is the type of metric source. It should be one of "ContainerResource", "External", - "Object", "Pods" or "Resource", each mapping to a matching field in the object. - type: string - required: - - 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: - properties: - ttlInDays: - format: int32 - type: integer - type: object - options: - properties: - automaticCasing: - default: true - type: boolean - disableWebserviceProxy: - default: false - type: boolean - includeIngress: - default: true - type: boolean - prefetchData: - default: true - type: boolean - rewriteGroupToDataLayers: - default: false - type: boolean - validateChildStyleNameEqual: - type: boolean - validateRequests: - default: true - type: boolean - required: - - automaticCasing - - includeIngress - type: object - podSpecPatch: - description: Optional strategic merge patch for the pod in the deployment. - E.g. to patch the resources or add extra env vars. - type: object - x-kubernetes-preserve-unknown-fields: true - service: - properties: - abstract: - type: string - accessConstraints: - default: https://creativecommons.org/publicdomain/zero/1.0/deed.nl - type: string - dataEPSG: - type: string - defResolution: - format: int32 - type: integer - fees: - type: string - inspire: - properties: - language: - type: string - serviceMetadataUrl: - properties: - csw: - properties: - metadataIdentifier: - type: string - required: - - metadataIdentifier - type: object - custom: - properties: - href: - type: string - type: - type: string - required: - - href - - type - type: object - required: - - csw - type: object - spatialDatasetIdentifier: - type: string - required: - - language - - serviceMetadataUrl - - spatialDatasetIdentifier - type: object - keywords: - items: - type: string - type: array - layer: - properties: - abstract: - type: string - authority: - properties: - name: - type: string - spatialDatasetIdentifier: - type: string - url: - type: string - required: - - name - - spatialDatasetIdentifier - - url - type: object - boundingBoxes: - items: - properties: - bbox: - description: BBox defines a bounding box with coordinates + custom: properties: - maxx: - description: Rechtsonder X coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + href: type: string - maxy: - description: Rechtsonder Y coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ - type: string - minx: - description: Linksboven X coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ - type: string - miny: - description: Linksboven Y coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: type: string required: - - maxx - - maxy - - minx - - miny + - href + - type type: object - crs: - type: string required: - - bbox - - crs + - csw type: object - type: array - data: - properties: - gpkg: + keywords: + items: + type: string + type: array + labelNoClip: + type: boolean + layers: + description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' + items: properties: - blobKey: + abstract: type: string - columns: - items: - properties: - alias: - type: string - name: - type: string - required: + authority: + properties: + name: + type: string + spatialDatasetIdentifier: + type: string + url: + type: string + required: - name - type: object - type: array - geometryType: - type: string - tableName: - type: string - required: - - blobKey - - columns - - geometryType - - tableName - type: object - postgis: - description: Postgis - reference to table in a Postgres - database - properties: - columns: + - spatialDatasetIdentifier + - url + type: object + boundingBoxes: items: properties: - alias: - type: string - name: + bbox: + description: BBox defines a bounding box with coordinates + properties: + maxx: + description: Rechtsonder X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + maxy: + description: Rechtsonder Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + minx: + description: Linksboven X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + miny: + description: Linksboven Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + required: + - maxx + - maxy + - minx + - miny + type: object + crs: type: string required: - - name + - bbox + - crs type: object type: array - geometryType: - type: string - tableName: - type: string - required: - - columns - - geometryType - - tableName - type: object - tif: - properties: - blobKey: - type: string - getFeatureInfoIncludesClass: - type: boolean - offsite: - type: string - resample: - type: string - required: - - blobKey - type: object - type: object - datasetMetadataUrl: - properties: - csw: - properties: - metadataIdentifier: - type: string - required: - - metadataIdentifier - type: object - custom: - properties: - href: - type: string - type: - type: string - required: - - href - - type - type: object - required: - - csw - type: object - keywords: - items: - type: string - type: array - labelNoClip: - type: boolean - layers: - description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' - items: - properties: - abstract: - type: string - authority: - properties: - name: - type: string - spatialDatasetIdentifier: - type: string - url: - type: string - required: - - name - - spatialDatasetIdentifier - - url - type: object - boundingBoxes: - items: + data: properties: - bbox: - description: BBox defines a bounding box with - coordinates + gpkg: properties: - maxx: - description: Rechtsonder X coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + blobKey: type: string - maxy: - description: Rechtsonder Y coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + columns: + items: + properties: + alias: + type: string + name: + type: string + required: + - name + type: object + type: array + geometryType: + type: string + tableName: type: string - minx: - description: Linksboven X coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + required: + - blobKey + - columns + - geometryType + - tableName + type: object + postgis: + description: Postgis - reference to table in a Postgres database + properties: + columns: + items: + properties: + alias: + type: string + name: + type: string + required: + - name + type: object + type: array + geometryType: type: string - miny: - description: Linksboven Y coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + tableName: type: string required: - - maxx - - maxy - - minx - - miny + - columns + - geometryType + - tableName + type: object + tif: + properties: + blobKey: + type: string + getFeatureInfoIncludesClass: + type: boolean + offsite: + type: string + resample: + type: string + required: + - blobKey + type: object + type: object + datasetMetadataUrl: + properties: + csw: + properties: + metadataIdentifier: + type: string + required: + - metadataIdentifier + type: object + custom: + properties: + href: + type: string + type: + type: string + required: + - href + - type type: object - crs: - type: string required: - - bbox - - crs + - csw type: object - type: array - data: - properties: - gpkg: + keywords: + items: + type: string + type: array + labelNoClip: + type: boolean + layers: + description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' + items: properties: - blobKey: + abstract: type: string - columns: - items: - properties: - alias: - type: string - name: - type: string - required: + authority: + properties: + name: + type: string + spatialDatasetIdentifier: + type: string + url: + type: string + required: - name - type: object - type: array - geometryType: - type: string - tableName: - type: string - required: - - blobKey - - columns - - geometryType - - tableName - type: object - postgis: - description: Postgis - reference to table in a Postgres - database - properties: - columns: + - spatialDatasetIdentifier + - url + type: object + boundingBoxes: items: properties: - alias: - type: string - name: + bbox: + description: BBox defines a bounding box with coordinates + properties: + maxx: + description: Rechtsonder X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + maxy: + description: Rechtsonder Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + minx: + description: Linksboven X coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + miny: + description: Linksboven Y coördinaat + pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + type: string + required: + - maxx + - maxy + - minx + - miny + type: object + crs: type: string required: - - name + - bbox + - crs type: object type: array - geometryType: - type: string - tableName: - type: string - required: - - columns - - geometryType - - tableName - type: object - tif: - properties: - blobKey: - type: string - getFeatureInfoIncludesClass: - type: boolean - offsite: - type: string - resample: - type: string - required: - - blobKey - type: object - type: object - datasetMetadataUrl: - properties: - csw: - properties: - metadataIdentifier: - type: string - required: - - metadataIdentifier - type: object - custom: - properties: - href: - type: string - type: - type: string - required: - - href - - type - type: object - required: - - csw - type: object - keywords: - items: - type: string - type: array - labelNoClip: - type: boolean - layers: - description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' - items: - properties: - abstract: - type: string - authority: - properties: - name: - type: string - spatialDatasetIdentifier: - type: string - url: - type: string - required: - - name - - spatialDatasetIdentifier - - url - type: object - boundingBoxes: - items: + data: properties: - bbox: - description: BBox defines a bounding box - with coordinates + gpkg: properties: - maxx: - description: Rechtsonder X coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + blobKey: + type: string + columns: + items: + properties: + alias: + type: string + name: + type: string + required: + - name + type: object + type: array + geometryType: type: string - maxy: - description: Rechtsonder Y coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + tableName: type: string - minx: - description: Linksboven X coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + required: + - blobKey + - columns + - geometryType + - tableName + type: object + postgis: + description: Postgis - reference to table in a Postgres database + properties: + columns: + items: + properties: + alias: + type: string + name: + type: string + required: + - name + type: object + type: array + geometryType: type: string - miny: - description: Linksboven Y coördinaat - pattern: ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ + tableName: type: string required: - - maxx - - maxy - - minx - - miny + - columns + - geometryType + - tableName + type: object + tif: + properties: + blobKey: + type: string + getFeatureInfoIncludesClass: + type: boolean + offsite: + type: string + resample: + type: string + required: + - blobKey + type: object + type: object + datasetMetadataUrl: + properties: + csw: + properties: + metadataIdentifier: + type: string + required: + - metadataIdentifier + type: object + custom: + properties: + href: + type: string + type: + type: string + required: + - href + - type type: object - crs: - type: string required: - - bbox - - crs + - csw type: object - type: array - data: - properties: - gpkg: - properties: - blobKey: - type: string - columns: - items: - properties: - alias: - type: string - name: - type: string - required: - - name - type: object - type: array - geometryType: - type: string - tableName: - type: string - required: - - blobKey - - columns - - geometryType - - tableName - type: object - postgis: - description: Postgis - reference to table - in a Postgres database - properties: - columns: - items: - properties: - alias: - type: string - name: - type: string - required: - - name - type: object - type: array - geometryType: - type: string - tableName: - type: string - required: - - columns - - geometryType - - tableName - type: object - tif: + keywords: + items: + type: string + type: array + labelNoClip: + type: boolean + maxscaledenominator: + type: string + minscaledenominator: + type: string + name: + type: string + styles: + items: properties: - blobKey: - type: string - getFeatureInfoIncludesClass: - type: boolean - offsite: + abstract: type: string - resample: - type: string - required: - - blobKey - type: object - type: object - datasetMetadataUrl: - properties: - csw: - properties: - metadataIdentifier: + legend: + properties: + blobKey: + type: string + format: + type: string + height: + format: int32 + type: integer + width: + format: int32 + type: integer + required: + - blobKey + - format + - height + - width + type: object + name: type: string - required: - - metadataIdentifier - type: object - custom: - properties: - href: + title: type: string - type: + visualization: type: string required: - - href - - type + - name type: object - required: - - csw - type: object - keywords: - items: + type: array + title: type: string - type: array - labelNoClip: - type: boolean - maxscaledenominator: - type: string - minscaledenominator: - type: string - name: - type: string - styles: - items: + visible: + default: true + type: boolean + required: + - keywords + - name + type: object + type: array + maxscaledenominator: + type: string + minscaledenominator: + type: string + name: + type: string + styles: + items: + properties: + abstract: + type: string + legend: properties: - abstract: - type: string - legend: - properties: - blobKey: - type: string - format: - type: string - height: - format: int32 - type: integer - width: - format: int32 - type: integer - required: - - blobKey - - format - - height - - width - type: object - name: + blobKey: type: string - title: - type: string - visualization: + format: type: string + height: + format: int32 + type: integer + width: + format: int32 + type: integer required: - - name + - blobKey + - format + - height + - width type: object - type: array - title: - type: string - visible: - default: true - type: boolean - required: - - keywords - - name - type: object - type: array - maxscaledenominator: - type: string - minscaledenominator: - type: string - name: - type: string - styles: - items: + name: + type: string + title: + type: string + visualization: + type: string + required: + - name + type: object + type: array + title: + type: string + visible: + default: true + type: boolean + required: + - keywords + - name + type: object + type: array + maxscaledenominator: + type: string + minscaledenominator: + type: string + name: + type: string + styles: + items: + properties: + abstract: + type: string + legend: properties: - abstract: - type: string - legend: - properties: - blobKey: - type: string - format: - type: string - height: - format: int32 - type: integer - width: - format: int32 - type: integer - required: - - blobKey - - format - - height - - width - type: object - name: - type: string - title: + blobKey: type: string - visualization: + format: type: string + height: + format: int32 + type: integer + width: + format: int32 + type: integer required: - - name + - blobKey + - format + - height + - width type: object - type: array - title: - type: string - visible: - default: true - type: boolean - required: - - keywords - - name - type: object - type: array - maxscaledenominator: - type: string - minscaledenominator: - type: string - name: - type: string - styles: - items: - properties: - abstract: - type: string - legend: - properties: - blobKey: - type: string - format: - type: string - height: - format: int32 - type: integer - width: - format: int32 - type: integer - required: - - blobKey - - format - - height - - width - type: object - name: - type: string - title: - type: string - visualization: - type: string - required: - - name - type: object - type: array - title: - type: string - visible: - default: true - type: boolean - required: - - keywords - type: object - mapfile: - properties: - configMapKeyRef: - description: Selects a key from a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - optional: - description: Specify whether the ConfigMap or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - required: - - configMapKeyRef - type: object - maxSize: - format: int32 - type: integer - ownerInfoRef: - type: string - resolution: - format: int32 - type: integer - stylingAssets: - properties: - blobKeys: - items: + name: + type: string + title: + type: string + visualization: + type: string + required: + - name + type: object + type: array + title: type: string - type: array - configMapRefs: - items: + visible: + default: true + type: boolean + required: + - keywords + type: object + mapfile: + properties: + configMapKeyRef: + description: Selects a key from a ConfigMap. properties: - keys: - items: - type: string - type: array + key: + description: The key to select. + type: string name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean required: - - name + - key type: object - type: array - type: object - x-kubernetes-validations: - - message: Either blobKeys or configMapRefs is required - rule: has(self.blobKeys) || has(self.configMapRefs) - title: - type: string - url: - type: string - required: - - abstract - - accessConstraints - - dataEPSG - - keywords - - layer - - ownerInfoRef - - title - - url - type: object - required: - - service - type: object - status: - description: OperatorStatus defines the observed state of an Atom/WFS/WMS/.... - properties: - conditions: - description: |- - Each condition contains details for one aspect of the current state of this Atom. - Known .status.conditions.type are: "Reconciled" - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 + x-kubernetes-map-type: atomic + required: + - configMapKeyRef + type: object + maxSize: + format: int32 type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + ownerInfoRef: type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown + resolution: + format: int32 + type: integer + stylingAssets: + properties: + blobKeys: + items: + type: string + type: array + configMapRefs: + items: + properties: + keys: + items: + type: string + type: array + name: + type: string + required: + - name + type: object + type: array + type: object + x-kubernetes-validations: + - message: Either blobKeys or configMapRefs is required + rule: has(self.blobKeys) || has(self.configMapRefs) + title: type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + url: type: string required: - - lastTransitionTime - - message - - reason - - status - - type + - abstract + - accessConstraints + - dataEPSG + - keywords + - layer + - ownerInfoRef + - title + - url type: object - type: array - operationResults: - additionalProperties: - description: OperationResult is the action result of a CreateOrUpdate - call. - type: string - description: The result of creating or updating of each derived resource - for this Atom. - type: object - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null + required: + - service + type: object + status: + description: OperatorStatus defines the observed state of an Atom/WFS/WMS/.... + properties: + conditions: + description: |- + Each condition contains details for one aspect of the current state of this Atom. + Known .status.conditions.type are: "Reconciled" + items: + description: Condition contains details for one aspect of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + operationResults: + additionalProperties: + description: OperationResult is the action result of a CreateOrUpdate call. + type: string + description: The result of creating or updating of each derived resource for this Atom. + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/update_openapi.go b/config/crd/update_openapi.go index 17c8234..c3cb5ac 100644 --- a/config/crd/update_openapi.go +++ b/config/crd/update_openapi.go @@ -2,10 +2,11 @@ package main import ( "github.com/pkg/errors" + goyaml "gopkg.in/yaml.v3" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "os" "path/filepath" - "sigs.k8s.io/yaml" + kyaml "sigs.k8s.io/yaml" ) // Usage: go run ./update_layersv3_openapi.go @@ -24,7 +25,7 @@ func updateWMSV3Layers(crdDir string) { content, _ := os.ReadFile(path) crd := &v1.CustomResourceDefinition{} - err := yaml.Unmarshal(content, &crd) + err := kyaml.Unmarshal(content, &crd) if err != nil { panic(err) } @@ -71,6 +72,19 @@ func updateWMSV3Layers(crdDir string) { } crd.Spec.Versions = versions - updatedContent, _ := yaml.Marshal(crd) - os.WriteFile(path, updatedContent, os.ModePerm) + updatedContent, _ := kyaml.Marshal(crd) + + // Remove the 'status' field from the yaml + var rawData map[string]interface{} + goyaml.Unmarshal(updatedContent, &rawData) + delete(rawData, "status") + + f, _ := os.OpenFile(path, os.O_TRUNC|os.O_WRONLY, 0644) + defer f.Close() + + enc := goyaml.NewEncoder(f) + defer enc.Close() + + enc.SetIndent(2) + _ = enc.Encode(rawData) } diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 541cd44..ebe5985 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: local-registry:5000/mapserver-operator - newTag: v3.0.26 + newTag: v3.0.3 diff --git a/go.mod b/go.mod index d71612e..5f7ce28 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 github.com/pdok/featureinfo-generator v1.4.0-beta1 - github.com/pdok/ogc-capabilities-generator v1.0.0-beta6 - github.com/pdok/ogc-specifications v1.0.0-beta5 + github.com/pdok/ogc-capabilities-generator v1.0.0-beta7 + github.com/pdok/ogc-specifications v1.0.0-beta7 github.com/pdok/smooth-operator v0.0.16 github.com/stretchr/testify v1.10.0 github.com/traefik/traefik/v3 v3.3.4 @@ -96,7 +96,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect @@ -132,7 +132,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 - k8s.io/apiextensions-apiserver v0.32.3 // indirect + k8s.io/apiextensions-apiserver v0.32.3 k8s.io/apiserver v0.32.3 // indirect k8s.io/component-base v0.32.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect diff --git a/go.sum b/go.sum index e41b868..aa9db0b 100644 --- a/go.sum +++ b/go.sum @@ -149,10 +149,10 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pdok/featureinfo-generator v1.4.0-beta1 h1:ZZO5OtK7yW5ozXFrl1OaxKl0MK3XF5YGFh2692JZVN8= github.com/pdok/featureinfo-generator v1.4.0-beta1/go.mod h1:02Ryu7ZRkeha8SCfS6VYWCdKYZh0llskrfFgp6xRCjk= -github.com/pdok/ogc-capabilities-generator v1.0.0-beta6 h1:yn0ZyMruJ2gmdx7E78LEamtmsxMkFPjFcZYk12mNtcE= -github.com/pdok/ogc-capabilities-generator v1.0.0-beta6/go.mod h1:slk89sAgmWU5NCIKwGyciQWnm0RHmwhJYQ431E2CwSk= -github.com/pdok/ogc-specifications v1.0.0-beta5 h1:j7JrXUeW55mVU9ZmJ67r8V6DhbKdgTDiGE1YKFpRWx4= -github.com/pdok/ogc-specifications v1.0.0-beta5/go.mod h1:YDngwkwrWOfc5MYnEYseiv97K1Y9bZXlVzwi/8EaIl8= +github.com/pdok/ogc-capabilities-generator v1.0.0-beta7 h1:w0dP2RQX0KEEovrq57NCM1w+Hlizg9mQlgdRBEqKX2g= +github.com/pdok/ogc-capabilities-generator v1.0.0-beta7/go.mod h1:slk89sAgmWU5NCIKwGyciQWnm0RHmwhJYQ431E2CwSk= +github.com/pdok/ogc-specifications v1.0.0-beta7 h1:AFSO8iCYbD1MrjOS2q+PGp2PmSqAH+O7cuA7JeePCXE= +github.com/pdok/ogc-specifications v1.0.0-beta7/go.mod h1:YDngwkwrWOfc5MYnEYseiv97K1Y9bZXlVzwi/8EaIl8= github.com/pdok/smooth-operator v0.0.16 h1:1vurI2TPDHmTaTu0YeG1B9mIIa5MT6GPWhixiZ/styg= github.com/pdok/smooth-operator v0.0.16/go.mod h1:ohDqrUnmS7wK8TrNHJnFS/mDgf26Yhb8mtRBX3ixdr4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/internal/controller/capabilitiesgenerator/capabilities_generator_test.go b/internal/controller/capabilitiesgenerator/capabilities_generator_test.go index bf84b77..92b5970 100644 --- a/internal/controller/capabilitiesgenerator/capabilities_generator_test.go +++ b/internal/controller/capabilitiesgenerator/capabilities_generator_test.go @@ -241,24 +241,15 @@ services: - HVD - Mobiliteit crs: - - namespace: EPSG - code: 28992 - - namespace: EPSG - code: 25831 - - namespace: EPSG - code: 25832 - - namespace: EPSG - code: 3034 - - namespace: EPSG - code: 3035 - - namespace: EPSG - code: 3857 - - namespace: EPSG - code: 4258 - - namespace: EPSG - code: 4326 - - namespace: CRS - code: 84 + - EPSG:28992 + - EPSG:25831 + - EPSG:25832 + - EPSG:3034 + - EPSG:3035 + - EPSG:3857 + - EPSG:4258 + - EPSG:4326 + - CRS:84 exGeographicBoundingBox: westBoundLongitude: 2.52713 eastBoundLongitude: 7.37403 @@ -336,24 +327,15 @@ services: - Verkeer - Wegvakken crs: - - namespace: EPSG - code: 28992 - - namespace: EPSG - code: 25831 - - namespace: EPSG - code: 25832 - - namespace: EPSG - code: 3034 - - namespace: EPSG - code: 3035 - - namespace: EPSG - code: 3857 - - namespace: EPSG - code: 4258 - - namespace: EPSG - code: 4326 - - namespace: CRS - code: 84 + - EPSG:28992 + - EPSG:25831 + - EPSG:25832 + - EPSG:3034 + - EPSG:3035 + - EPSG:3857 + - EPSG:4258 + - EPSG:4326 + - CRS:84 exGeographicBoundingBox: westBoundLongitude: 2.52713 eastBoundLongitude: 7.37403 @@ -451,24 +433,15 @@ services: - Verkeer - Hectometerpunten crs: - - namespace: EPSG - code: 28992 - - namespace: EPSG - code: 25831 - - namespace: EPSG - code: 25832 - - namespace: EPSG - code: 3034 - - namespace: EPSG - code: 3035 - - namespace: EPSG - code: 3857 - - namespace: EPSG - code: 4258 - - namespace: EPSG - code: 4326 - - namespace: CRS - code: 84 + - EPSG:28992 + - EPSG:25831 + - EPSG:25832 + - EPSG:3034 + - EPSG:3035 + - EPSG:3857 + - EPSG:4258 + - EPSG:4326 + - CRS:84 exGeographicBoundingBox: westBoundLongitude: 2.52713 eastBoundLongitude: 7.37403 From 3e897888842018170b1d7b997fc8d844e1f8db8d Mon Sep 17 00:00:00 2001 From: Jelle Dijkstra Date: Wed, 30 Apr 2025 16:52:24 +0200 Subject: [PATCH 4/5] Added validations --- api/v3/wms_types.go | 166 +++++++++++++++++++++++------- config/crd/bases/pdok.nl_wms.yaml | 73 ++++++++++++- 2 files changed, 199 insertions(+), 40 deletions(-) diff --git a/api/v3/wms_types.go b/api/v3/wms_types.go index 6d8accf..5bc900d 100644 --- a/api/v3/wms_types.go +++ b/api/v3/wms_types.go @@ -45,70 +45,149 @@ const ( // WMSSpec defines the desired state of WMS. type WMSSpec struct { + // Optional lifecycle settings Lifecycle *shared_model.Lifecycle `json:"lifecycle,omitempty"` // +kubebuilder:validation:Type=object // +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"` + PodSpecPatch *corev1.PodSpec `json:"podSpecPatch,omitempty"` + + // Optional specification for the HorizontalAutoscaler HorizontalPodAutoscalerPatch *autoscalingv2.HorizontalPodAutoscalerSpec `json:"horizontalPodAutoscalerPatch,omitempty"` - Options Options `json:"options,omitempty"` - Service WMSService `json:"service"` + + // Optional options for the configuration of the service. + Options Options `json:"options,omitempty"` + + // Service specification + Service WMSService `json:"service"` } type WMSService struct { - URL string `json:"url"` - Title string `json:"title"` - Abstract string `json:"abstract"` - Keywords []string `json:"keywords"` - OwnerInfoRef string `json:"ownerInfoRef"` - Fees *string `json:"fees,omitempty"` + // URL of the service + // +kubebuilder:validation:Format:=uri + URL string `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 + Keywords []string `json:"keywords"` + + // Reference to a CR of Kind OwnerInfo + // +kubebuilder:validation:MinLength:=1 + OwnerInfoRef string `json:"ownerInfoRef"` + + // TODO ?? + // +kubebuilder:validation:MinLength:=1 + Fees *string `json:"fees,omitempty"` + + // AccessConstraints (licence) that are applicable to the service // +kubebuilder:default="https://creativecommons.org/publicdomain/zero/1.0/deed.nl" - AccessConstraints string `json:"accessConstraints"` - MaxSize *int32 `json:"maxSize,omitempty"` - Inspire *Inspire `json:"inspire,omitempty"` + AccessConstraints string `json:"accessConstraints"` + + // TODO?? + MaxSize *int32 `json:"maxSize,omitempty"` + + // Optional specification Inspire themes and ids + Inspire *Inspire `json:"inspire,omitempty"` + + // CRS of the data + // +kubebuilder:validation:Pattern:=`(EPSG|CRS):\d+` //nolint:tagliatelle - DataEPSG string `json:"dataEPSG"` - Resolution *int32 `json:"resolution,omitempty"` - DefResolution *int32 `json:"defResolution,omitempty"` + DataEPSG string `json:"dataEPSG"` + + // TODO ?? + Resolution *int32 `json:"resolution,omitempty"` + + // TODO ?? + DefResolution *int32 `json:"defResolution,omitempty"` + + // Optional. Required files for the styling of the service StylingAssets *StylingAssets `json:"stylingAssets,omitempty"` - Mapfile *Mapfile `json:"mapfile,omitempty"` - Layer Layer `json:"layer"` + + // Custom mapfile + Mapfile *Mapfile `json:"mapfile,omitempty"` + + // Toplayer + Layer Layer `json:"layer"` } // +kubebuilder:validation:XValidation:message="Either blobKeys or configMapRefs is required",rule="has(self.blobKeys) || has(self.configMapRefs)" type StylingAssets struct { - BlobKeys []string `json:"blobKeys,omitempty"` + // +kubebuilder:validations:MinItems:=1 + BlobKeys []string `json:"blobKeys,omitempty"` + + // +kubebuilder:validations:MinItems:=1 ConfigMapRefs []ConfigMapRef `json:"configMapRefs,omitempty"` } type ConfigMapRef struct { - Name string `json:"name"` + // +kubebuilder:validations:MinLength:=1 + Name string `json:"name"` + + // +kubebuilder:validations:MinItems:=1 Keys []string `json:"keys,omitempty"` } +// +kubebuilder:validation:XValidation:message="A layer can only have data or layers, not both.", rule="empty(self.data) || empty(self.layers)" type Layer struct { + // Name of the layer, required for layers on the 2nd or 3rd level + // +kubebuilder:validations:MinLength:=1 + Name *string `json:"name,omitempty"` + + // Title of the layer // +kubebuilder:validations:MinLength:=1 - Name *string `json:"name,omitempty"` - Title *string `json:"title,omitempty"` - Abstract *string `json:"abstract,omitempty"` - Keywords []string `json:"keywords"` + Title *string `json:"title,omitempty"` + + // Abstract of the layer + // +kubebuilder:validations:MinLength:=1 + Abstract *string `json:"abstract,omitempty"` + + // Keywords of the layer + // +kubebuilder:validations:MinItems:=1 + Keywords []string `json:"keywords"` + + // BoundingBoxes of the layer. If omitted the boundingboxes of the parent layer of the service is used. BoundingBoxes []WMSBoundingBox `json:"boundingBoxes,omitempty"` + + // Whether or not the layer is visible. At least one of the layers must be visible. // +kubebuilder:default:=true - Visible *bool `json:"visible,omitempty"` - Authority *Authority `json:"authority,omitempty"` - DatasetMetadataURL *MetadataURL `json:"datasetMetadataUrl,omitempty"` - MinScaleDenominator *string `json:"minscaledenominator,omitempty"` - MaxScaleDenominator *string `json:"maxscaledenominator,omitempty"` - Styles []Style `json:"styles,omitempty"` - LabelNoClip bool `json:"labelNoClip,omitempty"` - Data *Data `json:"data,omitempty"` - Layers *[]Layer `json:"layers,omitempty"` -} + Visible *bool `json:"visible,omitempty"` + + // TODO ?? + Authority *Authority `json:"authority,omitempty"` + + // Links to metadata + DatasetMetadataURL *MetadataURL `json:"datasetMetadataUrl,omitempty"` + + // The minimum scale at which this layer functions + // +kubebuilder:validation:Pattern:=`^[1-9][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]+)$` + MaxScaleDenominator *string `json:"maxscaledenominator,omitempty"` + + // List of styles used by the layer + // +kubebuilder:validations:MinItems:=1 + Styles []Style `json:"styles,omitempty"` -type RandomType struct { - FieldA string `json:"fieldA"` + // TODO ?? + LabelNoClip bool `json:"labelNoClip,omitempty"` + + // Data (gpkg/postgis/tif) used by the layer + Data *Data `json:"data,omitempty"` + + // Sublayers of the layer + Layers *[]Layer `json:"layers,omitempty"` } type WMSBoundingBox struct { @@ -134,13 +213,22 @@ type Authority struct { } type Style struct { - Name string `json:"name"` - Title *string `json:"title,omitempty"` - Abstract *string `json:"abstract,omitempty"` + // +kubebuilder:validations:MinLength:=1 + Name string `json:"name"` + + // +kubebuilder:validations:MinLength:=1 + Title *string `json:"title,omitempty"` + + // +kubebuilder:validations:MinLength:=1 + Abstract *string `json:"abstract,omitempty"` + + // +kubebuilder:validations:MinLength:=1 Visualization *string `json:"visualization,omitempty"` - Legend *Legend `json:"legend,omitempty"` + + Legend *Legend `json:"legend,omitempty"` } +// TODO add validations + descriptions type Legend struct { Width int32 `json:"width"` Height int32 `json:"height"` diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index 319d522..a33504b 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -495,7 +495,7 @@ spec: description: WMSSpec defines the desired state of WMS. properties: horizontalPodAutoscalerPatch: - description: HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler. + description: Optional specification for the HorizontalAutoscaler properties: behavior: description: |- @@ -1087,12 +1087,14 @@ spec: - scaleTargetRef type: object lifecycle: + description: Optional lifecycle settings properties: ttlInDays: format: int32 type: integer type: object options: + description: Optional options for the configuration of the service. properties: automaticCasing: default: true @@ -1123,20 +1125,28 @@ spec: type: object x-kubernetes-preserve-unknown-fields: true service: + description: Service specification properties: abstract: + description: Abstract (short description) of the service + minLength: 1 type: string accessConstraints: default: https://creativecommons.org/publicdomain/zero/1.0/deed.nl + description: AccessConstraints (licence) that are applicable to the service type: string dataEPSG: + description: CRS of the data + pattern: (EPSG|CRS):\d+ type: string defResolution: format: int32 type: integer fees: + minLength: 1 type: string inspire: + description: Optional specification Inspire themes and ids properties: language: type: string @@ -1170,12 +1180,16 @@ spec: - spatialDatasetIdentifier type: object keywords: + description: Keywords of the service items: type: string + minItems: 1 type: array layer: + description: Toplayer properties: abstract: + description: Abstract of the layer type: string authority: properties: @@ -1191,6 +1205,7 @@ spec: - url type: object boundingBoxes: + description: BoundingBoxes of the layer. If omitted the boundingboxes of the parent layer of the service is used. items: properties: bbox: @@ -1226,6 +1241,7 @@ spec: type: object type: array data: + description: Data (gpkg/postgis/tif) used by the layer properties: gpkg: properties: @@ -1290,6 +1306,7 @@ spec: type: object type: object datasetMetadataUrl: + description: Links to metadata properties: csw: properties: @@ -1312,6 +1329,7 @@ spec: - csw type: object keywords: + description: Keywords of the layer items: type: string type: array @@ -1320,8 +1338,10 @@ spec: layers: description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' items: + description: Toplayer properties: abstract: + description: Abstract of the layer type: string authority: properties: @@ -1337,6 +1357,7 @@ spec: - url type: object boundingBoxes: + description: BoundingBoxes of the layer. If omitted the boundingboxes of the parent layer of the service is used. items: properties: bbox: @@ -1372,6 +1393,7 @@ spec: type: object type: array data: + description: Data (gpkg/postgis/tif) used by the layer properties: gpkg: properties: @@ -1436,6 +1458,7 @@ spec: type: object type: object datasetMetadataUrl: + description: Links to metadata properties: csw: properties: @@ -1458,6 +1481,7 @@ spec: - csw type: object keywords: + description: Keywords of the layer items: type: string type: array @@ -1466,8 +1490,10 @@ spec: layers: description: '[OpenAPI spec injected by mapserver-operator/cmd/update_openapi.go]' items: + description: Toplayer properties: abstract: + description: Abstract of the layer type: string authority: properties: @@ -1483,6 +1509,7 @@ spec: - url type: object boundingBoxes: + description: BoundingBoxes of the layer. If omitted the boundingboxes of the parent layer of the service is used. items: properties: bbox: @@ -1518,6 +1545,7 @@ spec: type: object type: array data: + description: Data (gpkg/postgis/tif) used by the layer properties: gpkg: properties: @@ -1582,6 +1610,7 @@ spec: type: object type: object datasetMetadataUrl: + description: Links to metadata properties: csw: properties: @@ -1604,18 +1633,25 @@ spec: - csw type: object keywords: + description: Keywords of the layer items: type: string type: array labelNoClip: type: boolean maxscaledenominator: + description: The maximum scale at which this layer functions + 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]+)$ type: string name: + description: Name of the layer, required for layers on the 2nd or 3rd level type: string styles: + description: List of styles used by the layer items: properties: abstract: @@ -1649,22 +1685,33 @@ spec: type: object type: array title: + description: Title of the layer 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: - keywords - name type: object + x-kubernetes-validations: + - message: A layer can only have data or layers, not both. + rule: empty(self.data) || empty(self.layers) type: array maxscaledenominator: + description: The maximum scale at which this layer functions + 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]+)$ type: string name: + description: Name of the layer, required for layers on the 2nd or 3rd level type: string styles: + description: List of styles used by the layer items: properties: abstract: @@ -1698,22 +1745,33 @@ spec: type: object type: array title: + description: Title of the layer 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: - keywords - name type: object + x-kubernetes-validations: + - message: A layer can only have data or layers, not both. + rule: empty(self.data) || empty(self.layers) type: array maxscaledenominator: + description: The maximum scale at which this layer functions + 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]+)$ type: string name: + description: Name of the layer, required for layers on the 2nd or 3rd level type: string styles: + description: List of styles used by the layer items: properties: abstract: @@ -1747,14 +1805,20 @@ spec: type: object type: array title: + description: Title of the layer 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: - keywords type: object + x-kubernetes-validations: + - message: A layer can only have data or layers, not both. + rule: empty(self.data) || empty(self.layers) mapfile: + description: Custom mapfile properties: configMapKeyRef: description: Selects a key from a ConfigMap. @@ -1785,11 +1849,14 @@ spec: format: int32 type: integer ownerInfoRef: + description: Reference to a CR of Kind OwnerInfo + minLength: 1 type: string resolution: format: int32 type: integer stylingAssets: + description: Optional. Required files for the styling of the service properties: blobKeys: items: @@ -1813,8 +1880,12 @@ spec: - message: Either blobKeys or configMapRefs is required rule: has(self.blobKeys) || has(self.configMapRefs) title: + description: Title of the service + minLength: 1 type: string url: + description: URL of the service + format: uri type: string required: - abstract From f4b692c2f29686873393734fd5765f4144362675 Mon Sep 17 00:00:00 2001 From: Jelle Dijkstra Date: Wed, 30 Apr 2025 17:15:04 +0200 Subject: [PATCH 5/5] Fixed tests --- api/v3/wms_types.go | 2 +- api/v3/zz_generated.deepcopy.go | 15 --------------- config/crd/bases/pdok.nl_wms.yaml | 7 ++----- config/crd/update_openapi.go | 8 ++++++++ internal/controller/test_manifests/v3_wms.yaml | 2 +- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/api/v3/wms_types.go b/api/v3/wms_types.go index 5bc900d..c10bb8a 100644 --- a/api/v3/wms_types.go +++ b/api/v3/wms_types.go @@ -137,7 +137,7 @@ type ConfigMapRef struct { Keys []string `json:"keys,omitempty"` } -// +kubebuilder:validation:XValidation:message="A layer can only have data or layers, not both.", rule="empty(self.data) || empty(self.layers)" +// +kubebuilder:validation:XValidation:message="A layer can only have data or layers, not both.", rule="has(self.data) || has(self.layers)" type Layer struct { // Name of the layer, required for layers on the 2nd or 3rd level // +kubebuilder:validations:MinLength:=1 diff --git a/api/v3/zz_generated.deepcopy.go b/api/v3/zz_generated.deepcopy.go index c210328..ce8b7cd 100644 --- a/api/v3/zz_generated.deepcopy.go +++ b/api/v3/zz_generated.deepcopy.go @@ -479,21 +479,6 @@ func (in *Postgis) DeepCopy() *Postgis { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RandomType) DeepCopyInto(out *RandomType) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RandomType. -func (in *RandomType) DeepCopy() *RandomType { - if in == nil { - return nil - } - out := new(RandomType) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Style) DeepCopyInto(out *Style) { *out = *in diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index a33504b..213758d 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -1695,9 +1695,6 @@ spec: - keywords - name type: object - x-kubernetes-validations: - - message: A layer can only have data or layers, not both. - rule: empty(self.data) || empty(self.layers) type: array maxscaledenominator: description: The maximum scale at which this layer functions @@ -1757,7 +1754,7 @@ spec: type: object x-kubernetes-validations: - message: A layer can only have data or layers, not both. - rule: empty(self.data) || empty(self.layers) + rule: has(self.data) || has(self.layers) type: array maxscaledenominator: description: The maximum scale at which this layer functions @@ -1816,7 +1813,7 @@ spec: type: object x-kubernetes-validations: - message: A layer can only have data or layers, not both. - rule: empty(self.data) || empty(self.layers) + rule: has(self.data) || has(self.layers) mapfile: description: Custom mapfile properties: diff --git a/config/crd/update_openapi.go b/config/crd/update_openapi.go index c3cb5ac..d5984df 100644 --- a/config/crd/update_openapi.go +++ b/config/crd/update_openapi.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" kyaml "sigs.k8s.io/yaml" + "strings" ) // Usage: go run ./update_layersv3_openapi.go @@ -42,6 +43,13 @@ func updateWMSV3Layers(crdDir string) { layerSpecLevel3 := layer.DeepCopy() layerSpecLevel3.Required = append(layerSpecLevel3.Required, "name") delete(layerSpecLevel3.Properties, "layers") + xvals := v1.ValidationRules{} + for _, xval := range layerSpecLevel3.XValidations { + if !strings.Contains(xval.Rule, "self.layers") { + xvals = append(xvals, xval) + } + } + layerSpecLevel3.XValidations = xvals // Level 2 layerSpecLevel2 := layer.DeepCopy() diff --git a/internal/controller/test_manifests/v3_wms.yaml b/internal/controller/test_manifests/v3_wms.yaml index b6bc7c1..bb53569 100644 --- a/internal/controller/test_manifests/v3_wms.yaml +++ b/internal/controller/test_manifests/v3_wms.yaml @@ -49,7 +49,7 @@ spec: - keyword1 - keyword2 ownerInfoRef: - fees: "" + fees: "test" accessConstraints: "http://creativecommons.org/publicdomain/zero/1.0/deed.nl" maxSize: inspire: