Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 config/crd/update_openapi.go config/crd/bases
## allowDangerousTypes=true for v2beta structs

.PHONY: generate
Expand Down
6 changes: 4 additions & 2 deletions api/v2beta1/wms_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import (
"errors"
smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util"
"log"
"sigs.k8s.io/controller-runtime/pkg/conversion"
"strconv"
Expand Down Expand Up @@ -320,6 +321,7 @@
Keywords: v2Service.Keywords,
Layers: &[]pdoknlv3.Layer{},
BoundingBoxes: boundingBoxes,
Visible: smoothoperatorutils.Pointer(true),
}

// adding the bottom layers to the middle layers they are grouped by
Expand Down Expand Up @@ -421,10 +423,10 @@
return layer
}

func mapV3LayerToV2Layers(v3Layer pdoknlv3.Layer, parent *pdoknlv3.Layer, serviceEPSG string) []WMSLayer {

Check failure on line 426 in api/v2beta1/wms_conversion.go

View workflow job for this annotation

GitHub Actions / Run on Ubuntu

calculated cyclomatic complexity for function mapV3LayerToV2Layers is 20, max is 15 (cyclop)

Check failure on line 426 in api/v2beta1/wms_conversion.go

View workflow job for this annotation

GitHub Actions / Run on Ubuntu

calculated cyclomatic complexity for function mapV3LayerToV2Layers is 20, max is 15 (cyclop)
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 {
Expand All @@ -441,7 +443,7 @@
Styles: []Style{},
}

v2Layer.Visible = PointerVal(v3Layer.Visible, true)
v2Layer.Visible = *v3Layer.Visible

if parent != nil {
v2Layer.Group = parent.Name
Expand Down
9 changes: 5 additions & 4 deletions api/v2beta1/wms_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
}

Expand Down
178 changes: 135 additions & 43 deletions api/v3/wms_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,65 +45,148 @@ const (

// WMSSpec defines the desired state of WMS.
type WMSSpec struct {
Lifecycle *shared_model.Lifecycle `json:"lifecycle"`
// 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"`
HorizontalPodAutoscalerPatch *autoscalingv2.HorizontalPodAutoscalerSpec `json:"horizontalPodAutoscalerPatch"`
Options Options `json:"options,omitempty"`
Service WMSService `json:"service"`
PodSpecPatch *corev1.PodSpec `json:"podSpecPatch,omitempty"`

// Optional specification for the HorizontalAutoscaler
HorizontalPodAutoscalerPatch *autoscalingv2.HorizontalPodAutoscalerSpec `json:"horizontalPodAutoscalerPatch,omitempty"`

// 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"`
ConfigMapRefs []ConfigMapRef `json:"configMapRefs"`
// +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="has(self.data) || has(self.layers)"
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
// 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
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"`

// 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"`

// 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"`
}

Expand All @@ -130,13 +213,22 @@ 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"`
// +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"`
}

// TODO add validations + descriptions
type Legend struct {
Width int32 `json:"width"`
Height int32 `json:"height"`
Expand Down
33 changes: 26 additions & 7 deletions api/v3/wms_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,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", layerName))
}
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
Expand Down Expand Up @@ -155,7 +168,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))
Expand All @@ -174,7 +187,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")
Expand All @@ -189,7 +202,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, ", "))
}
}

Expand All @@ -204,6 +217,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 {
Expand Down
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
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"

Expand All @@ -76,7 +76,7 @@
}

//nolint:gocyclo
func main() {

Check failure on line 79 in cmd/main.go

View workflow job for this annotation

GitHub Actions / Run on Ubuntu

calculated cyclomatic complexity for function main is 24, max is 15 (cyclop)

Check failure on line 79 in cmd/main.go

View workflow job for this annotation

GitHub Actions / Run on Ubuntu

calculated cyclomatic complexity for function main is 24, max is 15 (cyclop)
var metricsAddr string
var metricsCertPath, metricsCertName, metricsCertKey string
var webhookCertPath, webhookCertName, webhookCertKey string
Expand Down
Loading
Loading