diff --git a/go.mod b/go.mod index 5ec48bbd..13bf308d 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.23.4 require ( github.com/Masterminds/sprig/v3 v3.3.0 + github.com/Netflix/go-env v0.1.2 github.com/blang/semver v3.5.1+incompatible github.com/giantswarm/apiextensions-application v0.6.2 github.com/go-logr/logr v1.4.2 diff --git a/go.sum b/go.sum index 98add1da..3d1313c3 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+ github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Netflix/go-env v0.1.2 h1:0DRoLR9lECQ9Zqvkswuebm3jJ/2enaDX6Ei8/Z+EnK0= +github.com/Netflix/go-env v0.1.2/go.mod h1:WlIhYi++8FlKNJtrop1mjXYAJMzv1f43K4MqCoh0yGE= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= diff --git a/internal/controller/cluster_monitoring_controller.go b/internal/controller/cluster_monitoring_controller.go index d53555d5..376261cf 100644 --- a/internal/controller/cluster_monitoring_controller.go +++ b/internal/controller/cluster_monitoring_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "fmt" "time" "github.com/blang/semver" @@ -29,11 +30,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/giantswarm/observability-operator/pkg/bundle" "github.com/giantswarm/observability-operator/pkg/common" commonmonitoring "github.com/giantswarm/observability-operator/pkg/common/monitoring" + "github.com/giantswarm/observability-operator/pkg/common/organization" + "github.com/giantswarm/observability-operator/pkg/common/password" + "github.com/giantswarm/observability-operator/pkg/config" "github.com/giantswarm/observability-operator/pkg/monitoring" "github.com/giantswarm/observability-operator/pkg/monitoring/alloy" "github.com/giantswarm/observability-operator/pkg/monitoring/heartbeat" @@ -64,6 +69,61 @@ type ClusterMonitoringReconciler struct { MonitoringConfig monitoring.Config } +func SetupClusterMonitoringReconciler(mgr manager.Manager, conf config.Config) error { + managerClient := mgr.GetClient() + + if conf.Environment.OpsgenieApiKey == "" { + return fmt.Errorf("OpsgenieApiKey not set: %q", conf.Environment.OpsgenieApiKey) + } + + heartbeatRepository, err := heartbeat.NewOpsgenieHeartbeatRepository(conf.Environment.OpsgenieApiKey, conf.ManagementCluster) + if err != nil { + return fmt.Errorf("unable to create heartbeat repository: %w", err) + } + + organizationRepository := organization.NewNamespaceRepository(managerClient) + + prometheusAgentService := prometheusagent.PrometheusAgentService{ + Client: managerClient, + OrganizationRepository: organizationRepository, + PasswordManager: password.SimpleManager{}, + ManagementCluster: conf.ManagementCluster, + MonitoringConfig: conf.Monitoring, + } + + alloyService := alloy.Service{ + Client: managerClient, + OrganizationRepository: organizationRepository, + PasswordManager: password.SimpleManager{}, + ManagementCluster: conf.ManagementCluster, + MonitoringConfig: conf.Monitoring, + } + + mimirService := mimir.MimirService{ + Client: managerClient, + PasswordManager: password.SimpleManager{}, + ManagementCluster: conf.ManagementCluster, + } + + r := &ClusterMonitoringReconciler{ + Client: managerClient, + ManagementCluster: conf.ManagementCluster, + HeartbeatRepository: heartbeatRepository, + PrometheusAgentService: prometheusAgentService, + AlloyService: alloyService, + MimirService: mimirService, + MonitoringConfig: conf.Monitoring, + BundleConfigurationService: bundle.NewBundleConfigurationService(managerClient, conf.Monitoring), + } + + err = r.SetupWithManager(mgr) + if err != nil { + return err + } + + return nil +} + // SetupWithManager sets up the controller with the Manager. func (r *ClusterMonitoringReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/internal/controller/grafanaorganization_controller.go b/internal/controller/grafanaorganization_controller.go index 05a9e4bc..c04c1d80 100644 --- a/internal/controller/grafanaorganization_controller.go +++ b/internal/controller/grafanaorganization_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "fmt" "slices" "strings" @@ -34,8 +35,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/giantswarm/observability-operator/pkg/config" + grafanaclient "github.com/giantswarm/observability-operator/pkg/grafana/client" + "github.com/giantswarm/observability-operator/api/v1alpha1" "github.com/giantswarm/observability-operator/internal/controller/predicates" "github.com/giantswarm/observability-operator/pkg/grafana" @@ -49,6 +54,43 @@ type GrafanaOrganizationReconciler struct { GrafanaAPI *grafanaAPI.GrafanaHTTPAPI } +func SetupGrafanaOrganizationReconciler(mgr manager.Manager, environment config.Environment) error { + // Generate Grafana client + // Get grafana admin-password and admin-user + grafanaAdminCredentials := grafanaclient.AdminCredentials{ + Username: environment.GrafanaAdminUsername, + Password: environment.GrafanaAdminPassword, + } + if grafanaAdminCredentials.Username == "" { + return fmt.Errorf("GrafanaAdminUsername not set: %q", environment.GrafanaAdminUsername) + } + if grafanaAdminCredentials.Password == "" { + return fmt.Errorf("GrafanaAdminPassword not set: %q", environment.GrafanaAdminPassword) + } + + grafanaTLSConfig := grafanaclient.TLSConfig{ + Cert: environment.GrafanaTLSCertFile, + Key: environment.GrafanaTLSKeyFile, + } + grafanaAPI, err := grafanaclient.GenerateGrafanaClient(grafanaAdminCredentials, grafanaTLSConfig) + if err != nil { + return fmt.Errorf("unable to create grafana client: %w", err) + } + + r := &GrafanaOrganizationReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + GrafanaAPI: grafanaAPI, + } + + err = r.SetupWithManager(mgr) + if err != nil { + return err + } + + return nil +} + //+kubebuilder:rbac:groups=observability.giantswarm.io,resources=grafanaorganizations,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=observability.giantswarm.io,resources=grafanaorganizations/status,verbs=get;update;patch //+kubebuilder:rbac:groups=observability.giantswarm.io,resources=grafanaorganizations/finalizers,verbs=update diff --git a/main.go b/main.go index 82832bd0..0b6524c5 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + "github.com/Netflix/go-env" appv1 "github.com/giantswarm/apiextensions-application/api/v1alpha1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -41,16 +42,8 @@ import ( observabilityv1alpha1 "github.com/giantswarm/observability-operator/api/v1alpha1" "github.com/giantswarm/observability-operator/internal/controller" - "github.com/giantswarm/observability-operator/pkg/bundle" commonmonitoring "github.com/giantswarm/observability-operator/pkg/common/monitoring" - "github.com/giantswarm/observability-operator/pkg/common/organization" - "github.com/giantswarm/observability-operator/pkg/common/password" "github.com/giantswarm/observability-operator/pkg/config" - "github.com/giantswarm/observability-operator/pkg/grafana/client" - "github.com/giantswarm/observability-operator/pkg/monitoring/alloy" - "github.com/giantswarm/observability-operator/pkg/monitoring/heartbeat" - "github.com/giantswarm/observability-operator/pkg/monitoring/mimir" - "github.com/giantswarm/observability-operator/pkg/monitoring/prometheusagent" //+kubebuilder:scaffold:imports ) @@ -61,15 +54,6 @@ var ( setupLog = ctrl.Log.WithName("setup") ) -const ( - grafanaAdminUsernameEnvVar = "GRAFANA_ADMIN_USERNAME" // #nosec G101 - grafanaAdminPasswordEnvVar = "GRAFANA_ADMIN_PASSWORD" // #nosec G101 - grafanaTLSCertFileEnvVar = "GRAFANA_TLS_CERT_FILE" // #nosec G101 - grafanaTLSKeyFileEnvVar = "GRAFANA_TLS_KEY_FILE" // #nosec G101 - - opsgenieApiKeyEnvVar = "OPSGENIE_API_KEY" // #nosec G101 -) - func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(clusterv1.AddToScheme(scheme)) @@ -90,6 +74,8 @@ func main() { "If set the metrics endpoint is served securely") flag.BoolVar(&conf.EnableHTTP2, "enable-http2", false, "If set, HTTP/2 will be enabled for the metrics and webhook servers") + + // Management cluster configuration flags. flag.StringVar(&conf.ManagementCluster.BaseDomain, "management-cluster-base-domain", "", "The base domain of the management cluster.") flag.StringVar(&conf.ManagementCluster.Customer, "management-cluster-customer", "", @@ -102,6 +88,7 @@ func main() { "The pipeline of the management cluster.") flag.StringVar(&conf.ManagementCluster.Region, "management-cluster-region", "", "The region of the management cluster.") + // Monitoring configuration flags. flag.StringVar(&conf.Monitoring.MonitoringAgent, "monitoring-agent", commonmonitoring.MonitoringAgentAlloy, fmt.Sprintf("select monitoring agent to use (%s or %s)", commonmonitoring.MonitoringAgentPrometheus, commonmonitoring.MonitoringAgentAlloy)) @@ -118,9 +105,17 @@ func main() { opts := zap.Options{ Development: false, } + opts.BindFlags(flag.CommandLine) flag.Parse() + // Load environment variables. + _, err := env.UnmarshalFromEnviron(&conf.Environment) + if err != nil { + setupLog.Error(err, "failed to unmarshal environment variables") + os.Exit(1) + } + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) // if the enable-http2 flag is false (the default), http/2 should be disabled @@ -174,86 +169,17 @@ func main() { // Initialize event recorder. record.InitFromRecorder(mgr.GetEventRecorderFor("observability-operator")) - var opsgenieApiKey = os.Getenv(opsgenieApiKeyEnvVar) - if opsgenieApiKey == "" { - setupLog.Error(nil, fmt.Sprintf("environment variable %s not set", opsgenieApiKeyEnvVar)) - os.Exit(1) - } - - heartbeatRepository, err := heartbeat.NewOpsgenieHeartbeatRepository(opsgenieApiKey, conf.ManagementCluster) + // Setup controller for the Cluster resource. + err = controller.SetupClusterMonitoringReconciler(mgr, conf) if err != nil { - setupLog.Error(err, "unable to create heartbeat repository") - os.Exit(1) - } - - organizationRepository := organization.NewNamespaceRepository(mgr.GetClient()) - - prometheusAgentService := prometheusagent.PrometheusAgentService{ - Client: mgr.GetClient(), - OrganizationRepository: organizationRepository, - PasswordManager: password.SimpleManager{}, - ManagementCluster: conf.ManagementCluster, - MonitoringConfig: conf.Monitoring, - } - - alloyService := alloy.Service{ - Client: mgr.GetClient(), - OrganizationRepository: organizationRepository, - PasswordManager: password.SimpleManager{}, - ManagementCluster: conf.ManagementCluster, - MonitoringConfig: conf.Monitoring, - } - - mimirService := mimir.MimirService{ - Client: mgr.GetClient(), - PasswordManager: password.SimpleManager{}, - ManagementCluster: conf.ManagementCluster, - } - - if err = (&controller.ClusterMonitoringReconciler{ - Client: mgr.GetClient(), - ManagementCluster: conf.ManagementCluster, - HeartbeatRepository: heartbeatRepository, - PrometheusAgentService: prometheusAgentService, - AlloyService: alloyService, - MimirService: mimirService, - MonitoringConfig: conf.Monitoring, - BundleConfigurationService: bundle.NewBundleConfigurationService(mgr.GetClient(), conf.Monitoring), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Cluster") + setupLog.Error(err, "unable to create controller", "controller", "ClusterMonitoringReconciler") os.Exit(1) } - // Generate Grafana client - // Get grafana admin-password and admin-user - grafanaAdminCredentials := client.AdminCredentials{ - Username: os.Getenv(grafanaAdminUsernameEnvVar), - Password: os.Getenv(grafanaAdminPasswordEnvVar), - } - if grafanaAdminCredentials.Username == "" { - setupLog.Error(nil, fmt.Sprintf("environment variable %s not set", grafanaAdminUsernameEnvVar)) - os.Exit(1) - } - if grafanaAdminCredentials.Password == "" { - setupLog.Error(nil, fmt.Sprintf("environment variable %s not set", grafanaAdminPasswordEnvVar)) - os.Exit(1) - } - grafanaTLSConfig := client.TLSConfig{ - Cert: os.Getenv(grafanaTLSCertFileEnvVar), - Key: os.Getenv(grafanaTLSKeyFileEnvVar), - } - grafanaAPI, err := client.GenerateGrafanaClient(grafanaAdminCredentials, grafanaTLSConfig) + // Setup controller for the GrafanaOrganization resource. + err = controller.SetupGrafanaOrganizationReconciler(mgr, conf.Environment) if err != nil { - setupLog.Error(err, "unable to create grafana client") - os.Exit(1) - } - - if err = (&controller.GrafanaOrganizationReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - GrafanaAPI: grafanaAPI, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "GrafanaOrganization") + setupLog.Error(err, "unable to setup controller", "controller", "GrafanaOrganizationReconciler") os.Exit(1) } //+kubebuilder:scaffold:builder diff --git a/pkg/config/config.go b/pkg/config/config.go index 143484ed..03702834 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -15,4 +15,15 @@ type Config struct { ManagementCluster common.ManagementCluster Monitoring monitoring.Config + + Environment Environment +} + +type Environment struct { + GrafanaAdminUsername string `env:"GRAFANA_ADMIN_USERNAME,required=true"` + GrafanaAdminPassword string `env:"GRAFANA_ADMIN_PASSWORD,required=true"` + GrafanaTLSCertFile string `env:"GRAFANA_TLS_CERT_FILE,required=true"` + GrafanaTLSKeyFile string `env:"GRAFANA_TLS_KEY_FILE,required=true"` + + OpsgenieApiKey string `env:"OPSGENIE_API_KEY,required=true"` }