github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/package-server/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"os"
     9  	"time"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  	"github.com/spf13/cobra"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/util/wait"
    15  	genericfeatures "k8s.io/apiserver/pkg/features"
    16  	genericserver "k8s.io/apiserver/pkg/server"
    17  	genericoptions "k8s.io/apiserver/pkg/server/options"
    18  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    19  	"k8s.io/client-go/informers"
    20  	"k8s.io/client-go/kubernetes"
    21  	"k8s.io/client-go/rest"
    22  	"k8s.io/client-go/tools/clientcmd"
    23  	"k8s.io/client-go/util/workqueue"
    24  
    25  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    26  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client"
    27  	olminformers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions"
    28  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
    29  	"github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver"
    30  	genericpackageserver "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver/generic"
    31  	"github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/provider"
    32  )
    33  
    34  const DefaultWakeupInterval = 12 * time.Hour
    35  
    36  type Operator struct {
    37  	queueinformer.Operator
    38  	olmConfigQueue workqueue.TypedRateLimitingInterface[any]
    39  	options        *PackageServerOptions
    40  }
    41  
    42  // NewCommandStartPackageServer provides a CLI handler for 'start master' command
    43  // with a default PackageServerOptions.
    44  func NewCommandStartPackageServer(ctx context.Context, defaults *PackageServerOptions) *cobra.Command {
    45  	cmd := &cobra.Command{
    46  		Short: "Launch a package API server",
    47  		Long:  "Launch a package API server",
    48  		RunE: func(c *cobra.Command, args []string) error {
    49  			if err := defaults.Run(ctx); err != nil {
    50  				return err
    51  			}
    52  			return nil
    53  		},
    54  	}
    55  
    56  	flags := cmd.Flags()
    57  	flags.DurationVar(&defaults.DefaultSyncInterval, "interval", defaults.DefaultSyncInterval, "default interval at which to re-sync CatalogSources")
    58  	flags.StringVar(&defaults.GlobalNamespace, "global-namespace", defaults.GlobalNamespace, "Name of the namespace where the global CatalogSources are located")
    59  	flags.StringVar(&defaults.Kubeconfig, "kubeconfig", defaults.Kubeconfig, "path to the kubeconfig used to connect to the Kubernetes API server and the Kubelets (defaults to in-cluster config)")
    60  	flags.BoolVar(&defaults.Debug, "debug", defaults.Debug, "use debug log level")
    61  
    62  	defaults.SecureServing.AddFlags(flags)
    63  	defaults.Authentication.AddFlags(flags)
    64  	defaults.Authorization.AddFlags(flags)
    65  	defaults.Features.AddFlags(flags)
    66  
    67  	return cmd
    68  }
    69  
    70  type PackageServerOptions struct {
    71  	SecureServing  *genericoptions.SecureServingOptionsWithLoopback
    72  	Authentication *genericoptions.DelegatingAuthenticationOptions
    73  	Authorization  *genericoptions.DelegatingAuthorizationOptions
    74  	Features       *genericoptions.FeatureOptions
    75  
    76  	GlobalNamespace     string
    77  	DefaultSyncInterval time.Duration
    78  	CurrentSyncInterval time.Duration
    79  
    80  	Kubeconfig   string
    81  	RegistryAddr string
    82  
    83  	// Only to be used to for testing
    84  	DisableAuthForTesting bool
    85  
    86  	// Enable debug log level
    87  	Debug bool
    88  
    89  	SharedInformerFactory informers.SharedInformerFactory
    90  	StdOut                io.Writer
    91  	StdErr                io.Writer
    92  }
    93  
    94  func NewPackageServerOptions(out, errOut io.Writer) *PackageServerOptions {
    95  	o := &PackageServerOptions{
    96  		SecureServing:  genericoptions.NewSecureServingOptions().WithLoopback(),
    97  		Authentication: genericoptions.NewDelegatingAuthenticationOptions(),
    98  		Authorization:  genericoptions.NewDelegatingAuthorizationOptions(),
    99  		Features:       genericoptions.NewFeatureOptions(),
   100  
   101  		DefaultSyncInterval: DefaultWakeupInterval,
   102  		CurrentSyncInterval: DefaultWakeupInterval,
   103  
   104  		DisableAuthForTesting: false,
   105  		Debug:                 false,
   106  
   107  		StdOut: out,
   108  		StdErr: errOut,
   109  	}
   110  
   111  	return o
   112  }
   113  
   114  // Config returns config for the PackageServerOptions.
   115  func (o *PackageServerOptions) Config(ctx context.Context) (*apiserver.Config, error) {
   116  	if err := o.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", nil, []net.IP{net.ParseIP("127.0.0.1")}); err != nil {
   117  		return nil, fmt.Errorf("error creating self-signed certificates: %v", err)
   118  	}
   119  
   120  	config := genericserver.NewConfig(genericpackageserver.Codecs)
   121  	if err := o.SecureServing.ApplyTo(&config.SecureServing, &config.LoopbackClientConfig); err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	serverConfig := &apiserver.Config{
   126  		GenericConfig:  config,
   127  		ProviderConfig: genericpackageserver.ProviderConfig{},
   128  	}
   129  
   130  	if o.DisableAuthForTesting {
   131  		return serverConfig, nil
   132  	}
   133  
   134  	// See https://github.com/openshift/library-go/blob/7a65fdb398e28782ee1650959a5e0419121e97ae/pkg/config/serving/server.go#L61-L63 for details on
   135  	// the following auth/z config
   136  	pollCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
   137  	defer cancel()
   138  
   139  	authenticationOptions := genericoptions.NewDelegatingAuthenticationOptions()
   140  	authenticationOptions.RemoteKubeConfigFile = o.Kubeconfig
   141  
   142  	// The platform generally uses 30s for /metrics scraping, avoid API request for every other /metrics request to the component
   143  	authenticationOptions.CacheTTL = 35 * time.Second
   144  
   145  	// In some cases the API server can return connection refused when getting the "extension-apiserver-authentication" config map
   146  	var lastApplyErr error
   147  	err := wait.PollUntilContextCancel(pollCtx, 1*time.Second, true, func(_ context.Context) (done bool, err error) {
   148  		lastApplyErr := authenticationOptions.ApplyTo(&config.Authentication, config.SecureServing, config.OpenAPIConfig)
   149  		if lastApplyErr != nil {
   150  			log.WithError(lastApplyErr).Warn("Error initializing delegating authentication (will retry)")
   151  			return false, nil
   152  		}
   153  		return true, nil
   154  	})
   155  
   156  	if err != nil {
   157  		return nil, lastApplyErr
   158  	}
   159  
   160  	if err := o.Authentication.ApplyTo(&config.Authentication, config.SecureServing, nil); err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	authorizationOptions := genericoptions.NewDelegatingAuthorizationOptions().
   165  		WithAlwaysAllowPaths("/healthz", "/readyz", "/livez"). // This allows the kubelet to always get health and readiness without causing an access check
   166  		WithAlwaysAllowGroups("system:masters")                // in a kube cluster, system:masters can take any action, so there is no need to ask for an authz check
   167  	authenticationOptions.RemoteKubeConfigFile = o.Kubeconfig
   168  
   169  	// The platform generally uses 30s for /metrics scraping, avoid API request for every other /metrics request to the component
   170  	authorizationOptions.AllowCacheTTL = 35 * time.Second
   171  
   172  	// In some cases the API server can return connection refused when getting the "extension-apiserver-authentication" config map
   173  	err = wait.PollUntilContextCancel(ctx, 1*time.Second, true, func(_ context.Context) (done bool, err error) {
   174  		lastApplyErr = authorizationOptions.ApplyTo(&config.Authorization)
   175  		if lastApplyErr != nil {
   176  			log.WithError(lastApplyErr).Warn("Error initializing delegating authorization (will retry)")
   177  			return false, nil
   178  		}
   179  		return true, nil
   180  	})
   181  
   182  	if err != nil {
   183  		return nil, lastApplyErr
   184  	}
   185  
   186  	if err := o.Authorization.ApplyTo(&config.Authorization); err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	return serverConfig, nil
   191  }
   192  
   193  // Run starts a new packageserver for the PackageServerOptions.
   194  func (o *PackageServerOptions) Run(ctx context.Context) error {
   195  	if o.Debug {
   196  		log.SetLevel(log.DebugLevel)
   197  	}
   198  
   199  	// Enables http2 DOS mitigations for unauthenticated clients.
   200  	utilfeature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{
   201  		string(genericfeatures.UnauthenticatedHTTP2DOSMitigation): true,
   202  	})
   203  
   204  	// Grab the config for the API server
   205  	config, err := o.Config(ctx)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	config.GenericConfig.EnableMetrics = true
   210  
   211  	// Set up the client config
   212  	var clientConfig *rest.Config
   213  	if len(o.Kubeconfig) > 0 {
   214  		loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: o.Kubeconfig}
   215  		loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
   216  
   217  		clientConfig, err = loader.ClientConfig()
   218  	} else {
   219  		clientConfig, err = rest.InClusterConfig()
   220  	}
   221  	if err != nil {
   222  		return fmt.Errorf("unable to construct lister client config: %v", err)
   223  	}
   224  
   225  	kubeClient, err := kubernetes.NewForConfig(clientConfig)
   226  	if err != nil {
   227  		return fmt.Errorf("unable to construct lister client: %v", err)
   228  	}
   229  
   230  	crClient, err := client.NewClient(o.Kubeconfig)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	queueOperator, err := queueinformer.NewOperator(crClient.Discovery())
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	op := &Operator{
   241  		Operator: queueOperator,
   242  		olmConfigQueue: workqueue.NewTypedRateLimitingQueueWithConfig[any](
   243  			workqueue.DefaultTypedControllerRateLimiter[any](),
   244  			workqueue.TypedRateLimitingQueueConfig[any]{
   245  				Name: "olmConfig",
   246  			}),
   247  		options: o,
   248  	}
   249  
   250  	olmConfigInformer := olminformers.NewSharedInformerFactoryWithOptions(crClient, 0).Operators().V1().OLMConfigs()
   251  	olmConfigQueueInformer, err := queueinformer.NewQueueInformer(
   252  		ctx,
   253  		queueinformer.WithInformer(olmConfigInformer.Informer()),
   254  		queueinformer.WithQueue(op.olmConfigQueue),
   255  		queueinformer.WithIndexer(olmConfigInformer.Informer().GetIndexer()),
   256  		queueinformer.WithSyncer(queueinformer.LegacySyncHandler(op.syncOLMConfig).ToSyncer()),
   257  	)
   258  	if err != nil {
   259  		return err
   260  	}
   261  	if err := op.RegisterQueueInformer(olmConfigQueueInformer); err != nil {
   262  		return err
   263  	}
   264  
   265  	// Use the interval from the CLI as default
   266  	if o.CurrentSyncInterval != o.DefaultSyncInterval {
   267  		log.Infof("CLI argument changed default from '%v' to '%v'", o.CurrentSyncInterval, o.DefaultSyncInterval)
   268  		o.CurrentSyncInterval = o.DefaultSyncInterval
   269  	}
   270  	// Use the interval from the OLMConfig
   271  	cfg, err := crClient.OperatorsV1().OLMConfigs().Get(ctx, "cluster", metav1.GetOptions{})
   272  	if err != nil {
   273  		log.Warnf("Error retrieving Interval from OLMConfig: '%v'", err)
   274  	} else {
   275  		if cfg.Spec.Features != nil && cfg.Spec.Features.PackageServerSyncInterval != nil {
   276  			o.CurrentSyncInterval = cfg.Spec.Features.PackageServerSyncInterval.Duration
   277  			log.Infof("Retrieved Interval from OLMConfig: '%v'", o.CurrentSyncInterval.String())
   278  		} else {
   279  			log.Infof("Defaulting Interval to '%v'", o.DefaultSyncInterval)
   280  		}
   281  	}
   282  
   283  	sourceProvider, err := provider.NewRegistryProvider(ctx, crClient, queueOperator, o.CurrentSyncInterval, o.GlobalNamespace)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	config.ProviderConfig.Provider = sourceProvider
   288  
   289  	// We should never need to resync, since we're not worried about missing events,
   290  	// and resync is actually for regular interval-based reconciliation these days,
   291  	// so set the default resync interval to 0
   292  	informerFactory := informers.NewSharedInformerFactory(kubeClient, 0)
   293  
   294  	server, err := config.Complete(informerFactory).New()
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	sourceProvider.Run(ctx)
   300  	<-sourceProvider.Ready()
   301  
   302  	err = server.GenericAPIServer.PrepareRun().RunWithContext(ctx)
   303  	<-sourceProvider.Done()
   304  
   305  	return err
   306  }
   307  
   308  func (op *Operator) syncOLMConfig(obj interface{}) error {
   309  	olmConfig, ok := obj.(*operatorsv1.OLMConfig)
   310  	if !ok {
   311  		return fmt.Errorf("casting OLMConfig failed")
   312  	}
   313  	// restart the pod on change
   314  	if olmConfig.Spec.Features == nil || olmConfig.Spec.Features.PackageServerSyncInterval == nil {
   315  		if op.options.CurrentSyncInterval != op.options.DefaultSyncInterval {
   316  			log.Warnf("Change to olmConfig: '%v' != default '%v'", op.options.CurrentSyncInterval, op.options.DefaultSyncInterval)
   317  			os.Exit(0)
   318  		}
   319  	} else {
   320  		if op.options.CurrentSyncInterval != olmConfig.Spec.Features.PackageServerSyncInterval.Duration {
   321  			log.Warnf("Change to olmConfig: old '%v' != new '%v'", op.options.CurrentSyncInterval, olmConfig.Spec.Features.PackageServerSyncInterval.Duration)
   322  			os.Exit(0)
   323  		}
   324  	}
   325  
   326  	return nil
   327  }