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
136 changes: 27 additions & 109 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ import (
"crypto/tls"
"errors"
"flag"
"github.com/go-logr/zapr"
"github.com/pdok/smooth-operator/pkg/integrations/logging"
"github.com/peterbourgon/ff"
"go.uber.org/zap/zapcore"
"os"
"path/filepath"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"github.com/go-logr/zapr"
"github.com/pdok/mapserver-operator/internal/controller/mapfilegenerator"
smoothoperator "github.com/pdok/smooth-operator/api/v1"
"github.com/pdok/smooth-operator/pkg/integrations/logging"
traefikiov1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
"go.uber.org/zap/zapcore"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
Expand All @@ -38,9 +38,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"

Expand Down Expand Up @@ -81,8 +79,7 @@ func init() {
//nolint:gocyclo
func main() {
var metricsAddr string
var metricsCertPath, metricsCertName, metricsCertKey string
var webhookCertPath, webhookCertName, webhookCertKey string
var certDir string
var enableLeaderElection bool
var probeAddr string
var secureMetrics bool
Expand All @@ -93,21 +90,15 @@ func main() {
var multitoolImage, mapfileGeneratorImage, mapserverImage, capabilitiesGeneratorImage, featureinfoGeneratorImage, ogcWebserviceProxyImage, apacheExporterImage string
var slackWebhookURL string
var logLevel int
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.BoolVar(&secureMetrics, "metrics-secure", true,
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.")
flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.")
flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.")
flag.StringVar(&metricsCertPath, "metrics-cert-path", "",
"The directory that contains the metrics server certificate.")
flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.")
flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.")
flag.StringVar(&certDir, "cert-dir", "", "CertDir contains the webhook server key and certificate. Defaults to <temp-dir>/k8s-webhook-server/serving-certs.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.StringVar(&host, "baseurl", "", "The host which is used in the mapserver service.")
Expand All @@ -122,13 +113,21 @@ func main() {
flag.StringVar(&slackWebhookURL, "slack-webhook-url", "", "The webhook url for sending slack messages. Disabled if left empty")
flag.IntVar(&logLevel, "log-level", 0, "The zapcore loglevel. 0 = info, 1 = warn, 2 = error")

flag.Parse()
opts := zap.Options{
Development: true,
}
opts.BindFlags(flag.CommandLine)

if err := ff.Parse(flag.CommandLine, os.Args[1:], ff.WithEnvVarNoPrefix()); err != nil {
setupLog.Error(err, "unable to parse flags")
os.Exit(1)
}

//nolint:gosec
levelEnabler := zapcore.Level(logLevel)
zapLogger, _ := logging.SetupLogger("atom-operator", slackWebhookURL, levelEnabler)

ctrl.SetLogger(zapr.NewLogger(zapLogger))
logrLogger := zapr.NewLogger(zapLogger)
ctrl.SetLogger(logrLogger)

if host == "" {
setupLog.Error(errors.New("baseURL is required"), "A value for baseURL must be specified.")
Expand All @@ -152,83 +151,18 @@ func main() {
tlsOpts = append(tlsOpts, disableHTTP2)
}

// Create watchers for metrics and webhooks certificates
var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher

// Initial webhook TLS options
webhookTLSOpts := tlsOpts

if len(webhookCertPath) > 0 {
setupLog.Info("Initializing webhook certificate watcher using provided certificates",
"webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)

var err error
webhookCertWatcher, err = certwatcher.New(
filepath.Join(webhookCertPath, webhookCertName),
filepath.Join(webhookCertPath, webhookCertKey),
)
if err != nil {
setupLog.Error(err, "Failed to initialize webhook certificate watcher")
os.Exit(1)
}

webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) {
config.GetCertificate = webhookCertWatcher.GetCertificate
})
}

webhookServer := webhook.NewServer(webhook.Options{
TLSOpts: webhookTLSOpts,
CertDir: certDir,
TLSOpts: tlsOpts,
})

// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
// More info:
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/server
// - https://book.kubebuilder.io/reference/metrics.html
metricsServerOptions := metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: secureMetrics,
TLSOpts: tlsOpts,
}

if secureMetrics {
// FilterProvider is used to protect the metrics endpoint with authn/authz.
// These configurations ensure that only authorized users and service accounts
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
// https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/filters#WithAuthenticationAndAuthorization
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
}

// If the certificate is not specified, controller-runtime will automatically
// generate self-signed certificates for the metrics server. While convenient for development and testing,
// this setup is not recommended for production.
//
// TODO(user): If you enable certManager, uncomment the following lines:
// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates
// managed by cert-manager for the metrics server.
// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.
if len(metricsCertPath) > 0 {
setupLog.Info("Initializing metrics certificate watcher using provided certificates",
"metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey)

var err error
metricsCertWatcher, err = certwatcher.New(
filepath.Join(metricsCertPath, metricsCertName),
filepath.Join(metricsCertPath, metricsCertKey),
)
if err != nil {
setupLog.Error(err, "to initialize metrics certificate watcher", "error", err)
os.Exit(1)
}

metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) {
config.GetCertificate = metricsCertWatcher.GetCertificate
})
}

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: secureMetrics,
TLSOpts: tlsOpts,
},
WebhookServer: webhookServer,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
Expand Down Expand Up @@ -303,22 +237,6 @@ func main() {
}
// +kubebuilder:scaffold:builder

if metricsCertWatcher != nil {
setupLog.Info("Adding metrics certificate watcher to manager")
if err := mgr.Add(metricsCertWatcher); err != nil {
setupLog.Error(err, "unable to add metrics certificate watcher to manager")
os.Exit(1)
}
}

if webhookCertWatcher != nil {
setupLog.Info("Adding webhook certificate watcher to manager")
if err := mgr.Add(webhookCertWatcher); err != nil {
setupLog.Error(err, "unable to add webhook certificate watcher to manager")
os.Exit(1)
}
}

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
Expand Down
6 changes: 3 additions & 3 deletions config/crd/bases/pdok.nl_wms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1862,7 +1862,7 @@ spec:
type: object
x-kubernetes-validations:
- message: A layer should have keywords when visible
rule: self.visible == false || has(self.keywords)
rule: '!self.visible || has(self.keywords)'
- message: A layer should have a title when visible
rule: '!self.visible || has(self.title)'
- message: A layer should have an abstract when visible
Expand Down Expand Up @@ -1935,7 +1935,7 @@ spec:
- message: A layer should have sublayers or data, not both
rule: (has(self.data) || has(self.layers)) && !(has(self.data) && has(self.layers))
- message: A layer should have keywords when visible
rule: self.visible == false || has(self.keywords)
rule: '!self.visible || has(self.keywords)'
- message: A layer should have a title when visible
rule: '!self.visible || has(self.title)'
- message: A layer should have an abstract when visible
Expand Down Expand Up @@ -2007,7 +2007,7 @@ spec:
- message: A layer should have sublayers or data, not both
rule: (has(self.data) || has(self.layers)) && !(has(self.data) && has(self.layers))
- message: A layer should have keywords when visible
rule: self.visible == false || has(self.keywords)
rule: '!self.visible || has(self.keywords)'
- message: A layer should have a title when visible
rule: '!self.visible || has(self.title)'
- message: A layer should have an abstract when visible
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
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.18
github.com/peterbourgon/ff v1.7.1
github.com/stretchr/testify v1.10.0
github.com/traefik/traefik/v3 v3.3.4
k8s.io/api v0.32.3
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
Expand Down Expand Up @@ -134,6 +135,7 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand All @@ -155,6 +157,9 @@ github.com/pdok/ogc-specifications v1.0.0-beta7 h1:AFSO8iCYbD1MrjOS2q+PGp2PmSqAH
github.com/pdok/ogc-specifications v1.0.0-beta7/go.mod h1:YDngwkwrWOfc5MYnEYseiv97K1Y9bZXlVzwi/8EaIl8=
github.com/pdok/smooth-operator v0.0.18 h1:cwgfNdnDSQgwgYBcoXszdaCIEdCU/d66VMqpCmv24qQ=
github.com/pdok/smooth-operator v0.0.18/go.mod h1:ohDqrUnmS7wK8TrNHJnFS/mDgf26Yhb8mtRBX3ixdr4=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/peterbourgon/ff v1.7.1 h1:xt1lxTG+Nr2+tFtysY7abFgPoH3Lug8CwYJMOmJRXhk=
github.com/peterbourgon/ff v1.7.1/go.mod h1:fYI5YA+3RDqQRExmFbHnBjEeWzh9TrS8rnRpEq7XIg0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -315,6 +320,7 @@ golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
Expand All @@ -334,6 +340,7 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
27 changes: 17 additions & 10 deletions internal/controller/mapserver/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,9 @@ func GetEnvVarsForDeployment[O pdoknlv3.WMSWFS](obj O, blobsSecretName string) [

// Resources for mapserver container
func GetResourcesForDeployment[O pdoknlv3.WMSWFS](obj O) v1.ResourceRequirements {
minimumEphemeralStorageLimit := resource.MustParse("200M")
resources := v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("800M"),
v1.ResourceEphemeralStorage: minimumEphemeralStorageLimit,
v1.ResourceMemory: resource.MustParse("800M"),
},
Requests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("0.15"),
Expand Down Expand Up @@ -258,16 +256,25 @@ func GetResourcesForDeployment[O pdoknlv3.WMSWFS](obj O) v1.ResourceRequirements
}

if use, _ := mapperutils.UseEphemeralVolume(obj); !use {
value := mapperutils.EphemeralStorageLimit(obj)
minimumEphemeralStorageLimit := resource.MustParse("200M")
ephemeralStorageRequest := mapperutils.EphemeralStorageRequest(obj)
ephemeralStorageLimit := mapperutils.EphemeralStorageLimit(obj)

if objResources.Limits.StorageEphemeral() != nil && objResources.Limits.StorageEphemeral().Value() > minimumEphemeralStorageLimit.Value() {
resources.Limits[v1.ResourceEphemeralStorage] = *value
if ephemeralStorageRequest != nil {
resources.Requests[v1.ResourceEphemeralStorage] = *ephemeralStorageRequest
}
}

ephemeralStorageRequest := mapperutils.EphemeralStorageRequest(obj)
if ephemeralStorageRequest != nil {
resources.Requests[v1.ResourceEphemeralStorage] = *ephemeralStorageRequest
if ephemeralStorageLimit != nil && ephemeralStorageLimit.Value() > minimumEphemeralStorageLimit.Value() {
// Request higher than limit, use request as limit
if ephemeralStorageRequest != nil && ephemeralStorageRequest.Value() > ephemeralStorageLimit.Value() {
resources.Limits[v1.ResourceEphemeralStorage] = *ephemeralStorageRequest
} else {
resources.Limits[v1.ResourceEphemeralStorage] = *ephemeralStorageLimit
}
} else {
// No limit given or the given limit is lower than the default, use default
resources.Limits[v1.ResourceEphemeralStorage] = minimumEphemeralStorageLimit
}
}

return resources
Expand Down
4 changes: 2 additions & 2 deletions internal/controller/mapserver/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ func TestGetResourcesForDeployment(t *testing.T) {
expectedRequest := v1.ResourceList{}

expectedLimits[v1.ResourceMemory] = resource.MustParse("800M")
expectedLimits[v1.ResourceEphemeralStorage] = resource.MustParse("1505Mi")
expectedLimits[v1.ResourceEphemeralStorage] = resource.MustParse("505Mi")

expectedRequest[v1.ResourceCPU] = resource.MustParse("0.15")
expectedRequest[v1.ResourceEphemeralStorage] = resource.MustParse("1505Mi")
expectedRequest[v1.ResourceEphemeralStorage] = resource.MustParse("255Mi")

var expected = v1.ResourceRequirements{
Limits: expectedLimits,
Expand Down
4 changes: 2 additions & 2 deletions internal/controller/mapserver/test_data/v2_input.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ spec:
querystring: SERVICE=WFS&VERSION=2.0.0&REQUEST=GetCapabilities
resources:
limits:
ephemeralStorage: 1505Mi
ephemeralStorage: 505Mi
requests:
ephemeralStorage: 1505Mi
ephemeralStorage: 255Mi
service:
title: NWB - Wegen WFS
abstract:
Expand Down
1 change: 0 additions & 1 deletion internal/controller/shared_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ func mutateDeployment[R Reconciler, O pdoknlv3.WMSWFS](r R, obj O, deployment *a
return err
}
return ctrl.SetControllerReference(obj, deployment, getReconcilerScheme(r))

}

func getInitContainerForDeployment[R Reconciler, O pdoknlv3.WMSWFS](r R, obj O) ([]corev1.Container, error) {
Expand Down
Loading