diff --git a/cmd/main.go b/cmd/main.go index 442451e..012cafc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -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" @@ -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" @@ -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 @@ -93,7 +90,7 @@ 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, @@ -101,13 +98,7 @@ func main() { "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 /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.") @@ -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.") @@ -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/controller-runtime@v0.20.0/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/controller-runtime@v0.20.0/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, @@ -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) diff --git a/config/crd/bases/pdok.nl_wms.yaml b/config/crd/bases/pdok.nl_wms.yaml index d1f8cd9..4ab317e 100644 --- a/config/crd/bases/pdok.nl_wms.yaml +++ b/config/crd/bases/pdok.nl_wms.yaml @@ -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 @@ -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 @@ -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 diff --git a/go.mod b/go.mod index 72f3ce5..6aae133 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 8bb82e8..996a9d8 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= @@ -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= @@ -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= @@ -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= diff --git a/internal/controller/mapserver/deployment.go b/internal/controller/mapserver/deployment.go index 1d01f24..a35d80e 100644 --- a/internal/controller/mapserver/deployment.go +++ b/internal/controller/mapserver/deployment.go @@ -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"), @@ -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 diff --git a/internal/controller/mapserver/deployment_test.go b/internal/controller/mapserver/deployment_test.go index dae2428..4a0c998 100644 --- a/internal/controller/mapserver/deployment_test.go +++ b/internal/controller/mapserver/deployment_test.go @@ -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, diff --git a/internal/controller/mapserver/test_data/v2_input.yaml b/internal/controller/mapserver/test_data/v2_input.yaml index 2dc5261..77d496f 100644 --- a/internal/controller/mapserver/test_data/v2_input.yaml +++ b/internal/controller/mapserver/test_data/v2_input.yaml @@ -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: diff --git a/internal/controller/shared_controller.go b/internal/controller/shared_controller.go index 4c84583..19effd3 100644 --- a/internal/controller/shared_controller.go +++ b/internal/controller/shared_controller.go @@ -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) {