Skip to content
176 changes: 82 additions & 94 deletions cmd/postgres-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package main

import (
"context"
"crypto/tls"
"os"
goruntime "runtime"
"strconv"
Expand Down Expand Up @@ -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))
Comment thread
hors marked this conversation as resolved.
}

assertNoError(mgr.Start(ctx))
Expand All @@ -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(),
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Comment thread
hors marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a helper utility GroupVersionKindExists here -#1655

I'd suggest we move this to a GroupVersionExists in the same package for consistency

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Comment on lines +373 to +384
}
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"}},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if rancher run on AWS, what's the api group returned?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a more eks specific signal?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The most reliable truly-EKS signal without extra RBAC requires both groups

  return hasAPIGroup(ctx, cfg, "crd.k8s.amazonaws.com") &&
          hasAPIGroup(ctx, cfg, "metrics.eks.amazonaws.com")
}},

The problem is that clusters without the metrics addon enabled would fall back to unknown :(

// 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 {
Comment thread
hors marked this conversation as resolved.
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
}
}
Comment on lines +459 to +464
return "unknown"
}

// getServerVersion returns the stringified server version (i.e., the same info `kubectl version`
Expand Down Expand Up @@ -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
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[gofmt] reported by reviewdog 🐶

Suggested change

type envConfig struct {
LeaderElection bool `default:"true" envconfig:"PGO_CONTROLLER_LEADER_ELECTION_ENABLED"`
Expand Down
23 changes: 23 additions & 0 deletions percona/k8s/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading