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 }