From 9c5ea9cb02acdc391ee4a29f65b3506062244ccd Mon Sep 17 00:00:00 2001 From: Periklis Tsirakidis Date: Mon, 15 Jan 2024 10:37:57 +0100 Subject: [PATCH 1/8] operator: Replace deprecated ctrl-runtime cfg with custom package --- operator/apis/config/v1/doc.go | 4 + .../apis/config/v1/projectconfig_types.go | 65 +++++++++++- .../apis/config/v1/zz_generated.deepcopy.go | 99 +++++++++++++++++++ operator/go.mod | 2 +- operator/internal/config/config.go | 98 ++++++++++++++++++ operator/internal/config/options.go | 82 +++++++++++++++ operator/main.go | 3 +- 7 files changed, 349 insertions(+), 4 deletions(-) create mode 100644 operator/internal/config/config.go create mode 100644 operator/internal/config/options.go diff --git a/operator/apis/config/v1/doc.go b/operator/apis/config/v1/doc.go index 032708a3bd20..3e963d0531c5 100644 --- a/operator/apis/config/v1/doc.go +++ b/operator/apis/config/v1/doc.go @@ -18,3 +18,7 @@ var ( // AddToScheme adds the types in this group-version to the given scheme. AddToScheme = SchemeBuilder.AddToScheme ) + +func init() { + SchemeBuilder.Register(&ControllerManagerConfiguration{}) +} diff --git a/operator/apis/config/v1/projectconfig_types.go b/operator/apis/config/v1/projectconfig_types.go index b6a80175266b..488f7b2cb64f 100644 --- a/operator/apis/config/v1/projectconfig_types.go +++ b/operator/apis/config/v1/projectconfig_types.go @@ -2,7 +2,7 @@ package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - cfg "sigs.k8s.io/controller-runtime/pkg/config/v1alpha1" //nolint:staticcheck + configv1alpha1 "k8s.io/component-base/config/v1alpha1" ) // BuiltInCertManagement is the configuration for the built-in facility to generate and rotate @@ -142,6 +142,67 @@ const ( TLSProfileModernType TLSProfileType = "Modern" ) +// ControllerManagerConfigurationSpec defines the desired state of GenericControllerManagerConfiguration. +type ControllerManagerConfigurationSpec struct { + // LeaderElection is the LeaderElection config to be used when configuring + // the manager.Manager leader election + // +optional + LeaderElection *configv1alpha1.LeaderElectionConfiguration `json:"leaderElection,omitempty"` + + // Metrics contains the controller metrics configuration + // +optional + Metrics ControllerMetrics `json:"metrics,omitempty"` + + // Health contains the controller health configuration + // +optional + Health ControllerHealth `json:"health,omitempty"` + + // Webhook contains the controllers webhook configuration + // +optional + Webhook ControllerWebhook `json:"webhook,omitempty"` +} + +// ControllerMetrics defines the metrics configs. +type ControllerMetrics struct { + // BindAddress is the TCP address that the controller should bind to + // for serving prometheus metrics. + // It can be set to "0" to disable the metrics serving. + // +optional + BindAddress string `json:"bindAddress,omitempty"` +} + +// ControllerHealth defines the health configs. +type ControllerHealth struct { + // HealthProbeBindAddress is the TCP address that the controller should bind to + // for serving health probes + // It can be set to "0" or "" to disable serving the health probe. + // +optional + HealthProbeBindAddress string `json:"healthProbeBindAddress,omitempty"` +} + +// ControllerWebhook defines the webhook server for the controller. +type ControllerWebhook struct { + // Port is the port that the webhook server serves at. + // It is used to set webhook.Server.Port. + // +optional + Port *int `json:"port,omitempty"` +} + +//+kubebuilder:object:root=true + +// ControllerManagerConfiguration is the Schema for the GenericControllerManagerConfigurations API. +type ControllerManagerConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // ControllerManagerConfiguration returns the contfigurations for controllers + ControllerManagerConfigurationSpec `json:",inline"` +} + +// Complete returns the configuration for controller-runtime. +func (c *ControllerManagerConfigurationSpec) Complete() (ControllerManagerConfigurationSpec, error) { + return *c, nil +} + //+kubebuilder:object:root=true // ProjectConfig is the Schema for the projectconfigs API @@ -149,7 +210,7 @@ type ProjectConfig struct { metav1.TypeMeta `json:",inline"` // ControllerManagerConfigurationSpec returns the contfigurations for controllers - cfg.ControllerManagerConfigurationSpec `json:",inline"` + ControllerManagerConfigurationSpec `json:",inline"` Gates FeatureGates `json:"featureGates,omitempty"` } diff --git a/operator/apis/config/v1/zz_generated.deepcopy.go b/operator/apis/config/v1/zz_generated.deepcopy.go index ef20274e286e..f047818445aa 100644 --- a/operator/apis/config/v1/zz_generated.deepcopy.go +++ b/operator/apis/config/v1/zz_generated.deepcopy.go @@ -6,6 +6,7 @@ package v1 import ( runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/component-base/config/v1alpha1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -23,6 +24,104 @@ func (in *BuiltInCertManagement) DeepCopy() *BuiltInCertManagement { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControllerHealth) DeepCopyInto(out *ControllerHealth) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerHealth. +func (in *ControllerHealth) DeepCopy() *ControllerHealth { + if in == nil { + return nil + } + out := new(ControllerHealth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControllerManagerConfiguration) DeepCopyInto(out *ControllerManagerConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ControllerManagerConfigurationSpec.DeepCopyInto(&out.ControllerManagerConfigurationSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerManagerConfiguration. +func (in *ControllerManagerConfiguration) DeepCopy() *ControllerManagerConfiguration { + if in == nil { + return nil + } + out := new(ControllerManagerConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ControllerManagerConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControllerManagerConfigurationSpec) DeepCopyInto(out *ControllerManagerConfigurationSpec) { + *out = *in + if in.LeaderElection != nil { + in, out := &in.LeaderElection, &out.LeaderElection + *out = new(v1alpha1.LeaderElectionConfiguration) + (*in).DeepCopyInto(*out) + } + out.Metrics = in.Metrics + out.Health = in.Health + in.Webhook.DeepCopyInto(&out.Webhook) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerManagerConfigurationSpec. +func (in *ControllerManagerConfigurationSpec) DeepCopy() *ControllerManagerConfigurationSpec { + if in == nil { + return nil + } + out := new(ControllerManagerConfigurationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControllerMetrics) DeepCopyInto(out *ControllerMetrics) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerMetrics. +func (in *ControllerMetrics) DeepCopy() *ControllerMetrics { + if in == nil { + return nil + } + out := new(ControllerMetrics) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControllerWebhook) DeepCopyInto(out *ControllerWebhook) { + *out = *in + if in.Port != nil { + in, out := &in.Port, &out.Port + *out = new(int) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerWebhook. +func (in *ControllerWebhook) DeepCopy() *ControllerWebhook { + if in == nil { + return nil + } + out := new(ControllerWebhook) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FeatureGates) DeepCopyInto(out *FeatureGates) { *out = *in diff --git a/operator/go.mod b/operator/go.mod index 4ffc3899d11c..0ee7c037f16c 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -23,6 +23,7 @@ require ( k8s.io/apimachinery v0.27.7 k8s.io/apiserver v0.27.7 k8s.io/client-go v0.27.7 + k8s.io/component-base v0.27.7 k8s.io/utils v0.0.0-20230505201702-9f6742963106 sigs.k8s.io/controller-runtime v0.15.3 sigs.k8s.io/yaml v1.3.0 @@ -150,7 +151,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.27.7 // indirect - k8s.io/component-base v0.27.7 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/operator/internal/config/config.go b/operator/internal/config/config.go new file mode 100644 index 000000000000..48d6402b696d --- /dev/null +++ b/operator/internal/config/config.go @@ -0,0 +1,98 @@ +package v1 + +import ( + "errors" + "fmt" + "os" + "sync" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + configv1 "github.com/grafana/loki/operator/apis/config/v1" +) + +var ( + errSchemeNotSupplied = errors.New("scheme not supplied to controller configuration loader") + errConfigFileLoading = errors.New("could not read file at path") + errRuntimeObjectDecoding = errors.New("could not decode file into runtime.Object") +) + +// ControllerManagerConfiguration defines the functions necessary to parse a config file +// and to configure the Options struct for the ctrl.Manager. +type ControllerManagerConfiguration interface { + runtime.Object + + // Complete returns the versioned configuration + Complete() (configv1.ControllerManagerConfigurationSpec, error) +} + +// DeferredFileLoader is used to configure the decoder for loading controller +// runtime component config types. +type DeferredFileLoader struct { + ControllerManagerConfiguration + path string + scheme *runtime.Scheme + once sync.Once + err error +} + +// File will set up the deferred file loader for the configuration +// this will also configure the defaults for the loader if nothing is +// +// Defaults: +// * Path: "./config.yaml" +// * Kind: GenericControllerManagerConfiguration +func File() *DeferredFileLoader { + scheme := runtime.NewScheme() + utilruntime.Must(configv1.AddToScheme(scheme)) + return &DeferredFileLoader{ + path: "./config.yaml", + ControllerManagerConfiguration: &configv1.ControllerManagerConfiguration{}, + scheme: scheme, + } +} + +// Complete will use sync.Once to set the scheme. +func (d *DeferredFileLoader) Complete() (configv1.ControllerManagerConfigurationSpec, error) { + d.once.Do(d.loadFile) + if d.err != nil { + return configv1.ControllerManagerConfigurationSpec{}, d.err + } + return d.ControllerManagerConfiguration.Complete() +} + +// AtPath will set the path to load the file for the decoder. +func (d *DeferredFileLoader) AtPath(path string) *DeferredFileLoader { + d.path = path + return d +} + +// OfKind will set the type to be used for decoding the file into. +func (d *DeferredFileLoader) OfKind(obj ControllerManagerConfiguration) *DeferredFileLoader { + d.ControllerManagerConfiguration = obj + return d +} + +// loadFile is used from the mutex.Once to load the file. +func (d *DeferredFileLoader) loadFile() { + if d.scheme == nil { + d.err = errSchemeNotSupplied + return + } + + content, err := os.ReadFile(d.path) + if err != nil { + d.err = fmt.Errorf("%w %s", errConfigFileLoading, d.path) + return + } + + codecs := serializer.NewCodecFactory(d.scheme) + + // Regardless of if the bytes are of any external version, + // it will be read successfully and converted into the internal version + if err = runtime.DecodeInto(codecs.UniversalDecoder(), content, d.ControllerManagerConfiguration); err != nil { + d.err = errRuntimeObjectDecoding + } +} diff --git a/operator/internal/config/options.go b/operator/internal/config/options.go new file mode 100644 index 000000000000..0f75a76c2e25 --- /dev/null +++ b/operator/internal/config/options.go @@ -0,0 +1,82 @@ +package v1 + +import ( + "reflect" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + configv1 "github.com/grafana/loki/operator/apis/config/v1" +) + +// OptionsAndFrom will use a supplied type and convert to Options +// any options already set on Options will be ignored, this is used to allow +// cli flags to override anything specified in the config file. +func OptionsAndFrom(o manager.Options, loader ControllerManagerConfiguration) (manager.Options, error) { + newObj, err := loader.Complete() + if err != nil { + return o, err + } + + o = setLeaderElectionConfig(o, newObj) + + if o.MetricsBindAddress == "" && newObj.Metrics.BindAddress != "" { + o.MetricsBindAddress = newObj.Metrics.BindAddress + } + + if o.HealthProbeBindAddress == "" && newObj.Health.HealthProbeBindAddress != "" { + o.HealthProbeBindAddress = newObj.Health.HealthProbeBindAddress + } + + //nolint:staticcheck + if o.Port == 0 && newObj.Webhook.Port != nil { + o.Port = *newObj.Webhook.Port + } + + //nolint:staticcheck + if o.WebhookServer == nil { + o.WebhookServer = webhook.NewServer(webhook.Options{ + Port: o.Port, + }) + } + + return o, nil +} + +func setLeaderElectionConfig(o manager.Options, obj configv1.ControllerManagerConfigurationSpec) manager.Options { + if obj.LeaderElection == nil { + // The source does not have any configuration; noop + return o + } + + if !o.LeaderElection && obj.LeaderElection.LeaderElect != nil { + o.LeaderElection = *obj.LeaderElection.LeaderElect + } + + if o.LeaderElectionResourceLock == "" && obj.LeaderElection.ResourceLock != "" { + o.LeaderElectionResourceLock = obj.LeaderElection.ResourceLock + } + + if o.LeaderElectionNamespace == "" && obj.LeaderElection.ResourceNamespace != "" { + o.LeaderElectionNamespace = obj.LeaderElection.ResourceNamespace + } + + if o.LeaderElectionID == "" && obj.LeaderElection.ResourceName != "" { + o.LeaderElectionID = obj.LeaderElection.ResourceName + } + + if o.LeaseDuration == nil && !reflect.DeepEqual(obj.LeaderElection.LeaseDuration, metav1.Duration{}) { + o.LeaseDuration = &obj.LeaderElection.LeaseDuration.Duration + } + + if o.RenewDeadline == nil && !reflect.DeepEqual(obj.LeaderElection.RenewDeadline, metav1.Duration{}) { + o.RenewDeadline = &obj.LeaderElection.RenewDeadline.Duration + } + + if o.RetryPeriod == nil && !reflect.DeepEqual(obj.LeaderElection.RetryPeriod, metav1.Duration{}) { + o.RetryPeriod = &obj.LeaderElection.RetryPeriod.Duration + } + + return o +} diff --git a/operator/main.go b/operator/main.go index 6b101175407e..e064ec8dd8c0 100644 --- a/operator/main.go +++ b/operator/main.go @@ -21,6 +21,7 @@ import ( lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" lokiv1beta1 "github.com/grafana/loki/operator/apis/loki/v1beta1" lokictrl "github.com/grafana/loki/operator/controllers/loki" + operatorcfg "github.com/grafana/loki/operator/internal/config" "github.com/grafana/loki/operator/internal/metrics" "github.com/grafana/loki/operator/internal/operator" "github.com/grafana/loki/operator/internal/validation" @@ -62,7 +63,7 @@ func main() { ctrlCfg := ctrlconfigv1.ProjectConfig{} options := ctrl.Options{Scheme: scheme} if configFile != "" { - options, err = options.AndFrom(ctrl.ConfigFile().AtPath(configFile).OfKind(&ctrlCfg)) //nolint:staticcheck + options, err = operatorcfg.OptionsAndFrom(options, operatorcfg.File().AtPath(configFile).OfKind(&ctrlCfg)) if err != nil { logger.Error(err, "failed to parse controller manager config file") os.Exit(1) From 6c667eb418d59636f628fe72ada7929c583cc373 Mon Sep 17 00:00:00 2001 From: Periklis Tsirakidis Date: Mon, 15 Jan 2024 11:12:37 +0100 Subject: [PATCH 2/8] Fix docs generation --- operator/config/docs/config.json | 22 +--- operator/docs/operator/feature-gates.md | 164 +++++++++++++----------- 2 files changed, 96 insertions(+), 90 deletions(-) diff --git a/operator/config/docs/config.json b/operator/config/docs/config.json index 3d912e857d2b..2d12922c9df9 100644 --- a/operator/config/docs/config.json +++ b/operator/config/docs/config.json @@ -4,7 +4,9 @@ ], "hideTypePatterns": [ "ParseError$", - "List$" + "List$", + "ControllerManagerConfiguration$", + "ControllerManagerConfigurationSpec$" ], "externalPackages": [ { @@ -38,22 +40,6 @@ { "typeMatchPrefix": "^k8s\\.io/component-base/config/v1alpha1\\.LeaderElectionConfiguration$", "docsURLTemplate": "https://pkg.go.dev/k8s.io/component-base/config#LeaderElectionConfiguration" - }, - { - "typeMatchPrefix": "^sigs\\.k8s\\.io/controller-runtime/pkg/config/v1alpha1\\.ControllerConfigurationSpec$", - "docsURLTemplate": "https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/config/v1alpha1#ControllerConfigurationSpec" - }, - { - "typeMatchPrefix": "^sigs\\.k8s\\.io/controller-runtime/pkg/config/v1alpha1\\.ControllerMetrics$", - "docsURLTemplate": "https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/config/v1alpha1#ControllerMetrics" - }, - { - "typeMatchPrefix": "^sigs\\.k8s\\.io/controller-runtime/pkg/config/v1alpha1\\.ControllerHealth$", - "docsURLTemplate": "https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/config/v1alpha1#ControllerHealth" - }, - { - "typeMatchPrefix": "^sigs\\.k8s\\.io/controller-runtime/pkg/config/v1alpha1\\.ControllerWebhook$", - "docsURLTemplate": "https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/config/v1alpha1#ControllerWebhook" } ], "typeDisplayNamePrefixOverrides": { @@ -66,4 +52,4 @@ "github.com/grafana/loki/operator/apis/loki/config/v1": "Feature Gates" }, "markdownDisabled": false -} \ No newline at end of file +} diff --git a/operator/docs/operator/feature-gates.md b/operator/docs/operator/feature-gates.md index 7475dfa8a1cc..a1831c936f9b 100644 --- a/operator/docs/operator/feature-gates.md +++ b/operator/docs/operator/feature-gates.md @@ -98,6 +98,92 @@ The refresh is applied to all LokiStack certificates at once.

+## ControllerHealth { #config-loki-grafana-com-v1-ControllerHealth } +
+

ControllerHealth defines the health configs.

+
+ + + + + + + + + + + + + +
FieldDescription
+healthProbeBindAddress
+ +string + +
+(Optional) +

HealthProbeBindAddress is the TCP address that the controller should bind to +for serving health probes +It can be set to “0” or “” to disable serving the health probe.

+
+ +## ControllerMetrics { #config-loki-grafana-com-v1-ControllerMetrics } +
+

ControllerMetrics defines the metrics configs.

+
+ + + + + + + + + + + + + +
FieldDescription
+bindAddress
+ +string + +
+(Optional) +

BindAddress is the TCP address that the controller should bind to +for serving prometheus metrics. +It can be set to “0” to disable the metrics serving.

+
+ +## ControllerWebhook { #config-loki-grafana-com-v1-ControllerWebhook } +
+

ControllerWebhook defines the webhook server for the controller.

+
+ + + + + + + + + + + + + +
FieldDescription
+port
+ +int + +
+(Optional) +

Port is the port that the webhook server serves at. +It is used to set webhook.Server.Port.

+
+ ## FeatureGates { #config-loki-grafana-com-v1-FeatureGates }

(Appears on:ProjectConfig) @@ -429,25 +515,6 @@ bool -syncPeriod
- - -Kubernetes meta/v1.Duration - - - - -(Optional) -

SyncPeriod determines the minimum frequency at which watched resources are -reconciled. A lower period will correct entropy more quickly, but reduce -responsiveness to change if there are many watched resources. Change this -value only if you know what you are doing. Defaults to 10 hours if unset. -there will a 10 percent jitter between the SyncPeriod of all controllers -so that all controllers will not send list requests simultaneously.

- - - - leaderElection
@@ -463,57 +530,10 @@ the manager.Manager leader election

-cacheNamespace
- -string - - - -(Optional) -

CacheNamespace if specified restricts the manager’s cache to watch objects in -the desired namespace Defaults to all namespaces

-

Note: If a namespace is specified, controllers can still Watch for a -cluster-scoped resource (e.g Node). For namespaced resources the cache -will only hold objects from the desired namespace.

- - - - -gracefulShutDown
- -
-Kubernetes meta/v1.Duration - - - - -

GracefulShutdownTimeout is the duration given to runnable to stop before the manager actually returns on stop. -To disable graceful shutdown, set to time.Duration(0) -To use graceful shutdown without timeout, set to a negative duration, e.G. time.Duration(-1) -The graceful shutdown is skipped for safety reasons in case the leader election lease is lost.

- - - - -controller
- - -K8S Controller-runtime v1alpha1.ControllerConfigurationSpec - - - - -(Optional) -

Controller contains global configuration options for controllers -registered within this manager.

- - - - metrics
- -K8S Controller-runtime v1alpha1.ControllerMetrics + +ControllerMetrics @@ -526,8 +546,8 @@ K8S Controller-runtime v1alpha1.ControllerMetrics health
- -K8S Controller-runtime v1alpha1.ControllerHealth + +ControllerHealth @@ -540,8 +560,8 @@ K8S Controller-runtime v1alpha1.ControllerHealth webhook
- -K8S Controller-runtime v1alpha1.ControllerWebhook + +ControllerWebhook From 40e805862f6405eb78c19699f570a92af3663d6c Mon Sep 17 00:00:00 2001 From: Robert Jacob Date: Tue, 16 Jan 2024 19:03:54 +0100 Subject: [PATCH 3/8] Change package name --- operator/internal/config/config.go | 2 +- operator/internal/config/options.go | 2 +- operator/main.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/operator/internal/config/config.go b/operator/internal/config/config.go index 48d6402b696d..78ca4c29c89e 100644 --- a/operator/internal/config/config.go +++ b/operator/internal/config/config.go @@ -1,4 +1,4 @@ -package v1 +package config import ( "errors" diff --git a/operator/internal/config/options.go b/operator/internal/config/options.go index 0f75a76c2e25..900a83c6bdb9 100644 --- a/operator/internal/config/options.go +++ b/operator/internal/config/options.go @@ -1,4 +1,4 @@ -package v1 +package config import ( "reflect" diff --git a/operator/main.go b/operator/main.go index e064ec8dd8c0..e62a44967e15 100644 --- a/operator/main.go +++ b/operator/main.go @@ -21,7 +21,7 @@ import ( lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" lokiv1beta1 "github.com/grafana/loki/operator/apis/loki/v1beta1" lokictrl "github.com/grafana/loki/operator/controllers/loki" - operatorcfg "github.com/grafana/loki/operator/internal/config" + "github.com/grafana/loki/operator/internal/config" "github.com/grafana/loki/operator/internal/metrics" "github.com/grafana/loki/operator/internal/operator" "github.com/grafana/loki/operator/internal/validation" @@ -63,7 +63,7 @@ func main() { ctrlCfg := ctrlconfigv1.ProjectConfig{} options := ctrl.Options{Scheme: scheme} if configFile != "" { - options, err = operatorcfg.OptionsAndFrom(options, operatorcfg.File().AtPath(configFile).OfKind(&ctrlCfg)) + options, err = config.OptionsAndFrom(options, config.File().AtPath(configFile).OfKind(&ctrlCfg)) if err != nil { logger.Error(err, "failed to parse controller manager config file") os.Exit(1) From 3c297b2c3ffb027aa6e4b0b9452bdfe23c1ada4f Mon Sep 17 00:00:00 2001 From: Robert Jacob Date: Tue, 16 Jan 2024 19:11:15 +0100 Subject: [PATCH 4/8] Move loader logic out of main --- operator/internal/config/options.go | 28 ++++++++++++++++++++++++---- operator/main.go | 12 ++++-------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/operator/internal/config/options.go b/operator/internal/config/options.go index 900a83c6bdb9..1ce8892fb78d 100644 --- a/operator/internal/config/options.go +++ b/operator/internal/config/options.go @@ -1,19 +1,39 @@ package config import ( + "fmt" "reflect" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/webhook" configv1 "github.com/grafana/loki/operator/apis/config/v1" ) -// OptionsAndFrom will use a supplied type and convert to Options -// any options already set on Options will be ignored, this is used to allow -// cli flags to override anything specified in the config file. -func OptionsAndFrom(o manager.Options, loader ControllerManagerConfiguration) (manager.Options, error) { +// LoadConfig initializes the controller configuration, optionally overriding the defaults +// from a provided configuration file. +func LoadConfig(scheme *runtime.Scheme, configFile string) (*configv1.ProjectConfig, ctrl.Options, error) { + options := ctrl.Options{Scheme: scheme} + if configFile == "" { + return &configv1.ProjectConfig{}, options, nil + } + + ctrlCfg := &configv1.ProjectConfig{} + if configFile != "" { + var err error + options, err = optionsAndFrom(options, File().AtPath(configFile).OfKind(ctrlCfg)) + if err != nil { + return nil, options, fmt.Errorf("failed to parse controller manager config file: %w", err) + } + } + + return ctrlCfg, options, nil +} + +func optionsAndFrom(o manager.Options, loader ControllerManagerConfiguration) (manager.Options, error) { newObj, err := loader.Complete() if err != nil { return o, err diff --git a/operator/main.go b/operator/main.go index e62a44967e15..ffa16608707c 100644 --- a/operator/main.go +++ b/operator/main.go @@ -60,14 +60,10 @@ func main() { var err error - ctrlCfg := ctrlconfigv1.ProjectConfig{} - options := ctrl.Options{Scheme: scheme} - if configFile != "" { - options, err = config.OptionsAndFrom(options, config.File().AtPath(configFile).OfKind(&ctrlCfg)) - if err != nil { - logger.Error(err, "failed to parse controller manager config file") - os.Exit(1) - } + ctrlCfg, options, err := config.LoadConfig(scheme, configFile) + if err != nil { + logger.Error(err, "failed to load operator configuration") + os.Exit(1) } if ctrlCfg.Gates.LokiStackAlerts && !ctrlCfg.Gates.ServiceMonitors { From 2285ceec6784ebe4250bac2d49ce7e1cf509f694 Mon Sep 17 00:00:00 2001 From: Robert Jacob Date: Tue, 16 Jan 2024 19:26:29 +0100 Subject: [PATCH 5/8] Simplify reading config file --- operator/internal/config/loader.go | 25 +++++++++++++++++++++ operator/internal/config/options.go | 34 +++++++++++------------------ 2 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 operator/internal/config/loader.go diff --git a/operator/internal/config/loader.go b/operator/internal/config/loader.go new file mode 100644 index 000000000000..dc9c8e989d89 --- /dev/null +++ b/operator/internal/config/loader.go @@ -0,0 +1,25 @@ +package config + +import ( + "fmt" + configv1 "github.com/grafana/loki/operator/apis/config/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "os" +) + +func loadConfigFile(scheme *runtime.Scheme, configFile string) (*configv1.ProjectConfig, error) { + content, err := os.ReadFile(configFile) + if err != nil { + return nil, fmt.Errorf("%w %s", errConfigFileLoading, configFile) + } + + codecs := serializer.NewCodecFactory(scheme) + + outConfig := &configv1.ProjectConfig{} + if err = runtime.DecodeInto(codecs.UniversalDecoder(), content, outConfig); err != nil { + return nil, fmt.Errorf("could not decode file into runtime.Object: %w", err) + } + + return outConfig, nil +} diff --git a/operator/internal/config/options.go b/operator/internal/config/options.go index 1ce8892fb78d..8aeba0eb53b3 100644 --- a/operator/internal/config/options.go +++ b/operator/internal/config/options.go @@ -21,37 +21,29 @@ func LoadConfig(scheme *runtime.Scheme, configFile string) (*configv1.ProjectCon return &configv1.ProjectConfig{}, options, nil } - ctrlCfg := &configv1.ProjectConfig{} - if configFile != "" { - var err error - options, err = optionsAndFrom(options, File().AtPath(configFile).OfKind(ctrlCfg)) - if err != nil { - return nil, options, fmt.Errorf("failed to parse controller manager config file: %w", err) - } + ctrlCfg, err := loadConfigFile(scheme, configFile) + if err != nil { + return nil, options, fmt.Errorf("failed to parse controller manager config file: %w", err) } + options = mergeOptionsFromFile(options, ctrlCfg) return ctrlCfg, options, nil } -func optionsAndFrom(o manager.Options, loader ControllerManagerConfiguration) (manager.Options, error) { - newObj, err := loader.Complete() - if err != nil { - return o, err - } +func mergeOptionsFromFile(o manager.Options, cfg *configv1.ProjectConfig) manager.Options { + o = setLeaderElectionConfig(o, cfg.ControllerManagerConfigurationSpec) - o = setLeaderElectionConfig(o, newObj) - - if o.MetricsBindAddress == "" && newObj.Metrics.BindAddress != "" { - o.MetricsBindAddress = newObj.Metrics.BindAddress + if o.MetricsBindAddress == "" && cfg.Metrics.BindAddress != "" { + o.MetricsBindAddress = cfg.Metrics.BindAddress } - if o.HealthProbeBindAddress == "" && newObj.Health.HealthProbeBindAddress != "" { - o.HealthProbeBindAddress = newObj.Health.HealthProbeBindAddress + if o.HealthProbeBindAddress == "" && cfg.Health.HealthProbeBindAddress != "" { + o.HealthProbeBindAddress = cfg.Health.HealthProbeBindAddress } //nolint:staticcheck - if o.Port == 0 && newObj.Webhook.Port != nil { - o.Port = *newObj.Webhook.Port + if o.Port == 0 && cfg.Webhook.Port != nil { + o.Port = *cfg.Webhook.Port } //nolint:staticcheck @@ -61,7 +53,7 @@ func optionsAndFrom(o manager.Options, loader ControllerManagerConfiguration) (m }) } - return o, nil + return o } func setLeaderElectionConfig(o manager.Options, obj configv1.ControllerManagerConfigurationSpec) manager.Options { From b24cbdaec1a13db554702450ee05fc8f2a2aeb84 Mon Sep 17 00:00:00 2001 From: Robert Jacob Date: Tue, 16 Jan 2024 19:49:17 +0100 Subject: [PATCH 6/8] Remove previous loader implementation --- operator/internal/config/config.go | 98 ------------------------------ operator/internal/config/loader.go | 9 ++- 2 files changed, 7 insertions(+), 100 deletions(-) delete mode 100644 operator/internal/config/config.go diff --git a/operator/internal/config/config.go b/operator/internal/config/config.go deleted file mode 100644 index 78ca4c29c89e..000000000000 --- a/operator/internal/config/config.go +++ /dev/null @@ -1,98 +0,0 @@ -package config - -import ( - "errors" - "fmt" - "os" - "sync" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - - configv1 "github.com/grafana/loki/operator/apis/config/v1" -) - -var ( - errSchemeNotSupplied = errors.New("scheme not supplied to controller configuration loader") - errConfigFileLoading = errors.New("could not read file at path") - errRuntimeObjectDecoding = errors.New("could not decode file into runtime.Object") -) - -// ControllerManagerConfiguration defines the functions necessary to parse a config file -// and to configure the Options struct for the ctrl.Manager. -type ControllerManagerConfiguration interface { - runtime.Object - - // Complete returns the versioned configuration - Complete() (configv1.ControllerManagerConfigurationSpec, error) -} - -// DeferredFileLoader is used to configure the decoder for loading controller -// runtime component config types. -type DeferredFileLoader struct { - ControllerManagerConfiguration - path string - scheme *runtime.Scheme - once sync.Once - err error -} - -// File will set up the deferred file loader for the configuration -// this will also configure the defaults for the loader if nothing is -// -// Defaults: -// * Path: "./config.yaml" -// * Kind: GenericControllerManagerConfiguration -func File() *DeferredFileLoader { - scheme := runtime.NewScheme() - utilruntime.Must(configv1.AddToScheme(scheme)) - return &DeferredFileLoader{ - path: "./config.yaml", - ControllerManagerConfiguration: &configv1.ControllerManagerConfiguration{}, - scheme: scheme, - } -} - -// Complete will use sync.Once to set the scheme. -func (d *DeferredFileLoader) Complete() (configv1.ControllerManagerConfigurationSpec, error) { - d.once.Do(d.loadFile) - if d.err != nil { - return configv1.ControllerManagerConfigurationSpec{}, d.err - } - return d.ControllerManagerConfiguration.Complete() -} - -// AtPath will set the path to load the file for the decoder. -func (d *DeferredFileLoader) AtPath(path string) *DeferredFileLoader { - d.path = path - return d -} - -// OfKind will set the type to be used for decoding the file into. -func (d *DeferredFileLoader) OfKind(obj ControllerManagerConfiguration) *DeferredFileLoader { - d.ControllerManagerConfiguration = obj - return d -} - -// loadFile is used from the mutex.Once to load the file. -func (d *DeferredFileLoader) loadFile() { - if d.scheme == nil { - d.err = errSchemeNotSupplied - return - } - - content, err := os.ReadFile(d.path) - if err != nil { - d.err = fmt.Errorf("%w %s", errConfigFileLoading, d.path) - return - } - - codecs := serializer.NewCodecFactory(d.scheme) - - // Regardless of if the bytes are of any external version, - // it will be read successfully and converted into the internal version - if err = runtime.DecodeInto(codecs.UniversalDecoder(), content, d.ControllerManagerConfiguration); err != nil { - d.err = errRuntimeObjectDecoding - } -} diff --git a/operator/internal/config/loader.go b/operator/internal/config/loader.go index dc9c8e989d89..b5af090ddb88 100644 --- a/operator/internal/config/loader.go +++ b/operator/internal/config/loader.go @@ -1,13 +1,18 @@ package config import ( + "errors" "fmt" - configv1 "github.com/grafana/loki/operator/apis/config/v1" + "os" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" - "os" + + configv1 "github.com/grafana/loki/operator/apis/config/v1" ) +var errConfigFileLoading = errors.New("could not read file at path") + func loadConfigFile(scheme *runtime.Scheme, configFile string) (*configv1.ProjectConfig, error) { content, err := os.ReadFile(configFile) if err != nil { From 4787af08f4b13a48a3b878f1fcd3bf6ac9c32850 Mon Sep 17 00:00:00 2001 From: Periklis Tsirakidis Date: Thu, 18 Jan 2024 08:51:45 +0100 Subject: [PATCH 7/8] Apply suggestions from code review Co-authored-by: Robert Jacob --- operator/apis/config/v1/doc.go | 4 ---- operator/config/docs/config.json | 6 +++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/operator/apis/config/v1/doc.go b/operator/apis/config/v1/doc.go index 3e963d0531c5..032708a3bd20 100644 --- a/operator/apis/config/v1/doc.go +++ b/operator/apis/config/v1/doc.go @@ -18,7 +18,3 @@ var ( // AddToScheme adds the types in this group-version to the given scheme. AddToScheme = SchemeBuilder.AddToScheme ) - -func init() { - SchemeBuilder.Register(&ControllerManagerConfiguration{}) -} diff --git a/operator/config/docs/config.json b/operator/config/docs/config.json index 2d12922c9df9..fb1b7d8a11b2 100644 --- a/operator/config/docs/config.json +++ b/operator/config/docs/config.json @@ -5,8 +5,12 @@ "hideTypePatterns": [ "ParseError$", "List$", + "ControllerHealth$", "ControllerManagerConfiguration$", - "ControllerManagerConfigurationSpec$" + "ControllerManagerConfigurationSpec$", + "ControllerMetrics$", + "ControllerWebhook$", + "ProjectConfig$" ], "externalPackages": [ { From a92a7873ef92d884a0d93ee382831572eb7872a4 Mon Sep 17 00:00:00 2001 From: Periklis Tsirakidis Date: Thu, 18 Jan 2024 08:56:19 +0100 Subject: [PATCH 8/8] Regenerate docs --- operator/docs/operator/feature-gates.md | 173 ------------------------ 1 file changed, 173 deletions(-) diff --git a/operator/docs/operator/feature-gates.md b/operator/docs/operator/feature-gates.md index a1831c936f9b..1d5c046be775 100644 --- a/operator/docs/operator/feature-gates.md +++ b/operator/docs/operator/feature-gates.md @@ -98,96 +98,7 @@ The refresh is applied to all LokiStack certificates at once.

-## ControllerHealth { #config-loki-grafana-com-v1-ControllerHealth } -
-

ControllerHealth defines the health configs.

-
- - - - - - - - - - - - - -
FieldDescription
-healthProbeBindAddress
- -string - -
-(Optional) -

HealthProbeBindAddress is the TCP address that the controller should bind to -for serving health probes -It can be set to “0” or “” to disable serving the health probe.

-
- -## ControllerMetrics { #config-loki-grafana-com-v1-ControllerMetrics } -
-

ControllerMetrics defines the metrics configs.

-
- - - - - - - - - - - - - -
FieldDescription
-bindAddress
- -string - -
-(Optional) -

BindAddress is the TCP address that the controller should bind to -for serving prometheus metrics. -It can be set to “0” to disable the metrics serving.

-
- -## ControllerWebhook { #config-loki-grafana-com-v1-ControllerWebhook } -
-

ControllerWebhook defines the webhook server for the controller.

-
- - - - - - - - - - - - - -
FieldDescription
-port
- -int - -
-(Optional) -

Port is the port that the webhook server serves at. -It is used to set webhook.Server.Port.

-
- ## FeatureGates { #config-loki-grafana-com-v1-FeatureGates } -

-(Appears on:ProjectConfig) -

FeatureGates is the supported set of all operator feature gates.

@@ -501,90 +412,6 @@ bool -## ProjectConfig { #config-loki-grafana-com-v1-ProjectConfig } -
-

ProjectConfig is the Schema for the projectconfigs API

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-leaderElection
- - -Kubernetes v1alpha1.LeaderElectionConfiguration - - -
-(Optional) -

LeaderElection is the LeaderElection config to be used when configuring -the manager.Manager leader election

-
-metrics
- - -ControllerMetrics - - -
-(Optional) -

Metrics contains the controller metrics configuration

-
-health
- - -ControllerHealth - - -
-(Optional) -

Health contains the controller health configuration

-
-webhook
- - -ControllerWebhook - - -
-(Optional) -

Webhook contains the controllers webhook configuration

-
-featureGates
- - -FeatureGates - - -
-
- ## TLSProfileType { #config-loki-grafana-com-v1-TLSProfileType } (string alias)