diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index f817d2041e..bf7ce2dfe3 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -6,6 +6,7 @@ package main import ( "context" + "crypto/tls" "os" goruntime "runtime" "strconv" @@ -145,7 +146,7 @@ func main() { log.Info("upgrade checking enabled") // get the URL for the check for upgrades endpoint if set in the env assertNoError(upgradecheck.ManagedScheduler(mgr, - isOpenshift(ctx, mgr.GetConfig()), os.Getenv("CHECK_FOR_UPGRADES_URL"), versionString, nil)) + false, os.Getenv("CHECK_FOR_UPGRADES_URL"), versionString, nil)) } assertNoError(mgr.Start(ctx)) @@ -157,13 +158,16 @@ func main() { func addControllersToManager(ctx context.Context, mgr manager.Manager) error { os.Setenv("REGISTRATION_REQUIRED", "false") + platform := detectPlatform(ctx, mgr.GetConfig()) + openShift := platform == "openshift" + r := &postgrescluster.Reconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Owner: postgrescluster.ControllerName, Recorder: mgr.GetEventRecorderFor(postgrescluster.ControllerName), Tracer: otel.Tracer(postgrescluster.ControllerName), - IsOpenShift: isOpenshift(ctx, mgr.GetConfig()), + IsOpenShift: openShift, CertManagerCtrlFunc: certmanager.NewController, RestConfig: mgr.GetConfig(), } @@ -201,10 +205,10 @@ func addControllersToManager(ctx context.Context, mgr manager.Manager) error { Owner: pgcluster.PGClusterControllerName, Recorder: mgr.GetEventRecorderFor(pgcluster.PGClusterControllerName), Tracer: otel.Tracer(pgcluster.PGClusterControllerName), - Platform: detectPlatform(ctx, mgr.GetConfig()), + Platform: platform, KubeVersion: getServerVersion(ctx, mgr.GetConfig()), CrunchyController: cm.Controller(), - IsOpenShift: isOpenshift(ctx, mgr.GetConfig()), + IsOpenShift: openShift, Cron: pgcluster.NewCronRegistry(), ExternalChan: externalEvents, StopExternalWatchers: stopChan, @@ -280,7 +284,7 @@ func addControllersToManager(ctx context.Context, mgr manager.Manager) error { Client: mgr.GetClient(), Owner: "pgadmin-controller", Recorder: mgr.GetEventRecorderFor(naming.ControllerPGAdmin), - IsOpenShift: isOpenshift(ctx, mgr.GetConfig()), + IsOpenShift: openShift, } if err := pgAdminReconciler.SetupWithManager(mgr); err != nil { @@ -366,83 +370,99 @@ func initManager(ctx context.Context) (runtime.Options, error) { return options, nil } -func isGKE(ctx context.Context, cfg *rest.Config) bool { +// hasAPIGroup returns true if the cluster exposes the given API group name. +func hasAPIGroup(ctx context.Context, cfg *rest.Config, groupName string) bool { log := logging.FromContext(ctx) + dc, err := discovery.NewDiscoveryClientForConfig(cfg) + if err != nil { + log.V(1).Info("platform detection: could not create discovery client", "error", err.Error()) + return false + } + ok, err := k8s.GroupExists(dc, groupName) + if err != nil { + log.V(1).Info("platform detection: could not list API groups", "error", err.Error()) + return false + } + return ok +} - const groupName, kind = "cloud.google.com", "BackendConfig" - - client, err := discovery.NewDiscoveryClientForConfig(cfg) - assertNoError(err) +type platformProbe struct { + name string + label string + apiGroups []string + hosts []string + custom func(ctx context.Context, cfg *rest.Config) bool +} - groups, err := client.ServerGroups() - if err != nil { - assertNoError(err) +func (p platformProbe) detect(ctx context.Context, cfg *rest.Config) bool { + if p.custom != nil { + return p.custom(ctx, cfg) } - for _, g := range groups.Groups { - if g.Name != groupName { - continue + for _, g := range p.apiGroups { + if hasAPIGroup(ctx, cfg, g) { + return true } - for _, v := range g.Versions { - resourceList, err := client.ServerResourcesForGroupVersion(v.GroupVersion) - if err != nil { - assertNoError(err) - } - for _, r := range resourceList.APIResources { - if r.Kind == kind { - log.Info("detected GKE environment") - return true - } - } + } + for _, h := range p.hosts { + if strings.Contains(cfg.Host, h) { + return true } } - return false } -func isEKS(ctx context.Context, cfg *rest.Config) bool { - log := logging.FromContext(ctx) - - const groupName, kind = "vpcresources.k8s.aws", "SecurityGroupPolicy" - - client, err := discovery.NewDiscoveryClientForConfig(cfg) - assertNoError(err) +var platformProbes = []platformProbe{ + {name: "openshift", label: "Openshift", apiGroups: []string{"security.openshift.io"}}, + {name: "gke", label: "GKE", apiGroups: []string{"networking.gke.io"}}, + // crd.k8s.amazonaws.com (VPC CNI) and metrics.eks.amazonaws.com are independent EKS signals. + {name: "eks", label: "EKS", apiGroups: []string{"crd.k8s.amazonaws.com", "metrics.eks.amazonaws.com"}}, + // AKS exposes no unique API groups; inspect the API server TLS cert SAN instead. + {name: "aks", label: "AKS", custom: detectAKS}, + {name: "doks", label: "DOKS", apiGroups: []string{"dataplane-operator.doks.digitalocean.com"}}, + {name: "oke", label: "OKE", apiGroups: []string{"oci.oraclecloud.com"}, hosts: []string{".oraclecloud.com"}}, + {name: "ack", label: "ACK", apiGroups: []string{"alibabacloud.com"}, hosts: []string{".aliyuncs.com"}}, + // kommander.mesosphere.io is the legacy D2iQ/Konvoy group name for NKP. + {name: "nkp", label: "NKP", apiGroups: []string{"nkp.nutanix.com", "kommander.mesosphere.io"}}, + {name: "platform9", label: "Platform9", hosts: []string{".platform9.io", ".platform9.net"}}, + {name: "tanzu", label: "Tanzu", apiGroups: []string{"run.tanzu.vmware.com"}}, + {name: "rancher", label: "Rancher", apiGroups: []string{"management.cattle.io"}}, +} - groups, err := client.ServerGroups() +func detectAKS(ctx context.Context, cfg *rest.Config) bool { + tlsCfg, err := rest.TLSConfigFor(cfg) if err != nil { - assertNoError(err) - } - for _, g := range groups.Groups { - if g.Name != groupName { - continue - } - for _, v := range g.Versions { - resourceList, err := client.ServerResourcesForGroupVersion(v.GroupVersion) - if err != nil { - assertNoError(err) - } - for _, r := range resourceList.APIResources { - if r.Kind == kind { - log.Info("detected EKS environment") - return true - } + logging.FromContext(ctx).V(1).Info("platform detection: could not build TLS config", "error", err.Error()) + return false + } + host := strings.TrimPrefix(cfg.Host, "https://") + host = strings.TrimPrefix(host, "http://") + dialCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + netConn, err := (&tls.Dialer{Config: tlsCfg}).DialContext(dialCtx, "tcp", host) + if err != nil { + logging.FromContext(ctx).V(1).Info("platform detection: could not dial API server", "error", err.Error()) + return false + } + defer netConn.Close() + conn := netConn.(*tls.Conn) + for _, cert := range conn.ConnectionState().PeerCertificates { + for _, san := range cert.DNSNames { + if strings.HasSuffix(san, ".azmk8s.io") { + return true } } } - return false } func detectPlatform(ctx context.Context, cfg *rest.Config) string { - switch { - case isOpenshift(ctx, cfg): - return "openshift" - case isGKE(ctx, cfg): - return "gke" - case isEKS(ctx, cfg): - return "eks" - default: - return "unknown" + for _, probe := range platformProbes { + if probe.detect(ctx, cfg) { + logging.FromContext(ctx).Info("detected " + probe.label + " environment") + return probe.name + } } + return "unknown" } // getServerVersion returns the stringified server version (i.e., the same info `kubectl version` @@ -503,38 +523,6 @@ func getLogLevel() zapcore.LevelEnabler { } } -func isOpenshift(ctx context.Context, cfg *rest.Config) bool { - log := logging.FromContext(ctx) - - const sccGroupName, sccKind = "security.openshift.io", "SecurityContextConstraints" - - client, err := discovery.NewDiscoveryClientForConfig(cfg) - assertNoError(err) - - groups, err := client.ServerGroups() - if err != nil { - assertNoError(err) - } - for _, g := range groups.Groups { - if g.Name != sccGroupName { - continue - } - for _, v := range g.Versions { - resourceList, err := client.ServerResourcesForGroupVersion(v.GroupVersion) - if err != nil { - assertNoError(err) - } - for _, r := range resourceList.APIResources { - if r.Kind == sccKind { - log.Info("detected Openshift environment") - return true - } - } - } - } - - return false -} type envConfig struct { LeaderElection bool `default:"true" envconfig:"PGO_CONTROLLER_LEADER_ELECTION_ENABLED"` diff --git a/percona/k8s/util.go b/percona/k8s/util.go index 89e5547624..8c6d0d2ffc 100644 --- a/percona/k8s/util.go +++ b/percona/k8s/util.go @@ -214,3 +214,26 @@ func GroupVersionKindExists(dc *discovery.DiscoveryClient, groupVersion, kind st return false, nil } + +// GroupExists checks whether a given API group exists in the Kubernetes API Server. +func GroupExists(dc *discovery.DiscoveryClient, group string) (bool, error) { + if dc == nil { + return false, errors.New("discovery client is nil") + } + if group == "" { + return false, errors.New("group must not be empty") + } + + groups, err := dc.ServerGroups() + if err != nil { + return false, errors.Wrap(err, "get server groups") + } + + for _, g := range groups.Groups { + if g.Name == group { + return true, nil + } + } + + return false, nil +}