From f0331047dd5476f597ce838158a46830dcbf90bc Mon Sep 17 00:00:00 2001 From: Nick Hale Date: Thu, 16 Sep 2021 09:29:14 -0400 Subject: [PATCH 1/2] refactor(catalog): factor out main command Factor the content of main() into an exported function in a separate package so that it can be invoked externally. --- cmd/catalog/main.go | 155 +------------------------------ pkg/cmd/catalog/catalog.go | 183 +++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 153 deletions(-) create mode 100644 pkg/cmd/catalog/catalog.go diff --git a/cmd/catalog/main.go b/cmd/catalog/main.go index b2072b6894..48ebdf6e10 100644 --- a/cmd/catalog/main.go +++ b/cmd/catalog/main.go @@ -1,160 +1,9 @@ package main import ( - "context" - "flag" - "fmt" - "net/http" - "os" - "time" - - configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" - log "github.com/sirupsen/logrus" - utilclock "k8s.io/apimachinery/pkg/util/clock" - k8sscheme "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/tools/clientcmd" - - "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalogtemplate" - "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" - "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorstatus" - "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/server" - "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/signals" - "github.com/operator-framework/operator-lifecycle-manager/pkg/metrics" - olmversion "github.com/operator-framework/operator-lifecycle-manager/pkg/version" + "github.com/operator-framework/operator-lifecycle-manager/pkg/cmd/catalog" ) -const ( - catalogNamespaceEnvVarName = "GLOBAL_CATALOG_NAMESPACE" - defaultWakeupInterval = 15 * time.Minute - defaultCatalogNamespace = "openshift-operator-lifecycle-manager" - defaultConfigMapServerImage = "quay.io/operator-framework/configmap-operator-registry:latest" - defaultOPMImage = "quay.io/operator-framework/upstream-opm-builder:latest" - defaultUtilImage = "quay.io/operator-framework/olm:latest" - defaultOperatorName = "" -) - -// config flags defined globally so that they appear on the test binary as well -var ( - kubeConfigPath = flag.String( - "kubeconfig", "", "absolute path to the kubeconfig file") - - wakeupInterval = flag.Duration( - "interval", defaultWakeupInterval, "wakeup interval") - - catalogNamespace = flag.String( - "namespace", defaultCatalogNamespace, "namespace where catalog will run and install catalog resources") - - configmapServerImage = flag.String( - "configmapServerImage", defaultConfigMapServerImage, "the image to use for serving the operator registry api for a configmap") - - opmImage = flag.String( - "opmImage", defaultOPMImage, "the image to use for unpacking bundle content with opm") - - utilImage = flag.String( - "util-image", defaultUtilImage, "an image containing custom olm utilities") - - writeStatusName = flag.String( - "writeStatusName", defaultOperatorName, "ClusterOperator name in which to write status, set to \"\" to disable.") - - debug = flag.Bool( - "debug", false, "use debug log level") - - version = flag.Bool("version", false, "displays olm version") - - tlsKeyPath = flag.String( - "tls-key", "", "Path to use for private key (requires tls-cert)") - - tlsCertPath = flag.String( - "tls-cert", "", "Path to use for certificate key (requires tls-key)") - - profiling = flag.Bool("profiling", false, "deprecated") - - clientCAPath = flag.String("client-ca", "", "path to watch for client ca bundle") - - installPlanTimeout = flag.Duration("install-plan-retry-timeout", 1*time.Minute, "time since first attempt at which plan execution errors are considered fatal") - bundleUnpackTimeout = flag.Duration("bundle-unpack-timeout", 10*time.Minute, "The time limit for bundle unpacking, after which InstallPlan execution is considered to have failed. 0 is considered as having no timeout.") -) - -func init() { - metrics.RegisterCatalog() -} - func main() { - // Get exit signal context - ctx, cancel := context.WithCancel(signals.Context()) - defer cancel() - - // Parse the command-line flags. - flag.Parse() - - // Check if version flag was set - if *version { - fmt.Print(olmversion.String()) - - // Exit early - os.Exit(0) - } - - logger := log.New() - if *debug { - logger.SetLevel(log.DebugLevel) - } - logger.Infof("log level %s", logger.Level) - - // If the catalogNamespaceEnvVarName environment variable is set, then update the value of catalogNamespace. - if catalogNamespaceEnvVarValue := os.Getenv(catalogNamespaceEnvVarName); catalogNamespaceEnvVarValue != "" { - logger.Infof("%s environment variable is set. Updating Global Catalog Namespace to %s", catalogNamespaceEnvVarName, catalogNamespaceEnvVarValue) - *catalogNamespace = catalogNamespaceEnvVarValue - } - - listenAndServe, err := server.GetListenAndServeFunc(logger, tlsCertPath, tlsKeyPath, clientCAPath) - if err != nil { - logger.Fatal("Error setting up health/metric/pprof service: %v", err) - } - - go func() { - if err := listenAndServe(); err != nil && err != http.ErrServerClosed { - logger.Error(err) - } - }() - - // create a config client for operator status - config, err := clientcmd.BuildConfigFromFlags("", *kubeConfigPath) - if err != nil { - log.Fatalf("error configuring client: %s", err.Error()) - } - configClient, err := configv1client.NewForConfig(config) - if err != nil { - log.Fatalf("error configuring client: %s", err.Error()) - } - opClient := operatorclient.NewClientFromConfig(*kubeConfigPath, logger) - crClient, err := client.NewClient(*kubeConfigPath) - if err != nil { - log.Fatalf("error configuring client: %s", err.Error()) - } - - // Create a new instance of the operator. - op, err := catalog.NewOperator(ctx, *kubeConfigPath, utilclock.RealClock{}, logger, *wakeupInterval, *configmapServerImage, *opmImage, *utilImage, *catalogNamespace, k8sscheme.Scheme, *installPlanTimeout, *bundleUnpackTimeout) - if err != nil { - log.Panicf("error configuring catalog operator: %s", err.Error()) - } - - opCatalogTemplate, err := catalogtemplate.NewOperator(ctx, *kubeConfigPath, logger, *wakeupInterval, *catalogNamespace) - if err != nil { - log.Panicf("error configuring catalog template operator: %s", err.Error()) - } - - op.Run(ctx) - <-op.Ready() - - opCatalogTemplate.Run(ctx) - <-opCatalogTemplate.Ready() - - if *writeStatusName != "" { - operatorstatus.MonitorClusterStatus(*writeStatusName, op.AtLevel(), op.Done(), opClient, configClient, crClient) - } - - <-op.Done() + catalog.Exec() } diff --git a/pkg/cmd/catalog/catalog.go b/pkg/cmd/catalog/catalog.go new file mode 100644 index 0000000000..86c7fe8ff7 --- /dev/null +++ b/pkg/cmd/catalog/catalog.go @@ -0,0 +1,183 @@ +package catalog + +import ( + "context" + "flag" + "fmt" + "net/http" + "os" + "sync" + "time" + + configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" + log "github.com/sirupsen/logrus" + utilclock "k8s.io/apimachinery/pkg/util/clock" + k8sscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/clientcmd" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalogtemplate" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorstatus" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/server" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/signals" + "github.com/operator-framework/operator-lifecycle-manager/pkg/metrics" + olmversion "github.com/operator-framework/operator-lifecycle-manager/pkg/version" +) + +const ( + catalogNamespaceEnvVarName = "GLOBAL_CATALOG_NAMESPACE" + defaultWakeupInterval = 15 * time.Minute + defaultCatalogNamespace = "openshift-operator-lifecycle-manager" + defaultConfigMapServerImage = "quay.io/operator-framework/configmap-operator-registry:latest" + defaultOPMImage = "quay.io/operator-framework/upstream-opm-builder:latest" + defaultUtilImage = "quay.io/operator-framework/olm:latest" + defaultOperatorName = "" +) + +// config flags defined globally so that they appear on the test binary as well +var ( + kubeConfigPath = flag.String( + "kubeconfig", "", "absolute path to the kubeconfig file") + + wakeupInterval = flag.Duration( + "interval", defaultWakeupInterval, "wakeup interval") + + catalogNamespace = flag.String( + "namespace", defaultCatalogNamespace, "namespace where catalog will run and install catalog resources") + + configmapServerImage = flag.String( + "configmapServerImage", defaultConfigMapServerImage, "the image to use for serving the operator registry api for a configmap") + + opmImage = flag.String( + "opmImage", defaultOPMImage, "the image to use for unpacking bundle content with opm") + + utilImage = flag.String( + "util-image", defaultUtilImage, "an image containing custom olm utilities") + + writeStatusName = flag.String( + "writeStatusName", defaultOperatorName, "ClusterOperator name in which to write status, set to \"\" to disable.") + + debug = flag.Bool( + "debug", false, "use debug log level") + + version = flag.Bool("version", false, "displays olm version") + + tlsKeyPath = flag.String( + "tls-key", "", "Path to use for private key (requires tls-cert)") + + tlsCertPath = flag.String( + "tls-cert", "", "Path to use for certificate key (requires tls-key)") + + profiling = flag.Bool("profiling", false, "deprecated") + + clientCAPath = flag.String("client-ca", "", "path to watch for client ca bundle") + + installPlanTimeout = flag.Duration("install-plan-retry-timeout", 1*time.Minute, "time since first attempt at which plan execution errors are considered fatal") + bundleUnpackTimeout = flag.Duration("bundle-unpack-timeout", 10*time.Minute, "The time limit for bundle unpacking, after which InstallPlan execution is considered to have failed. 0 is considered as having no timeout.") +) + +func init() { + metrics.RegisterCatalog() +} + +type config struct { +} + +type Option func(*config) + +func apply(options []Option, to *config) *config { + for _, opt := range options { + opt(to) + } + + return to +} + +var once sync.Once + +func Exec(options ...Option) { + once.Do(func() { + // The command should never be executed more than once + exec(apply(options, &config{})) + }) +} + +func exec(cfg *config) { + // Get exit signal context + ctx, cancel := context.WithCancel(signals.Context()) + defer cancel() + + // Parse the command-line flags. + flag.Parse() + + // Check if version flag was set + if *version { + fmt.Print(olmversion.String()) + + // Exit early + os.Exit(0) + } + + logger := log.New() + if *debug { + logger.SetLevel(log.DebugLevel) + } + logger.Infof("log level %s", logger.Level) + + // If the catalogNamespaceEnvVarName environment variable is set, then update the value of catalogNamespace. + if catalogNamespaceEnvVarValue := os.Getenv(catalogNamespaceEnvVarName); catalogNamespaceEnvVarValue != "" { + logger.Infof("%s environment variable is set. Updating Global Catalog Namespace to %s", catalogNamespaceEnvVarName, catalogNamespaceEnvVarValue) + *catalogNamespace = catalogNamespaceEnvVarValue + } + + listenAndServe, err := server.GetListenAndServeFunc(logger, tlsCertPath, tlsKeyPath, clientCAPath) + if err != nil { + logger.Fatal("Error setting up health/metric/pprof service: %v", err) + } + + go func() { + if err := listenAndServe(); err != nil && err != http.ErrServerClosed { + logger.Error(err) + } + }() + + // create a config client for operator status + config, err := clientcmd.BuildConfigFromFlags("", *kubeConfigPath) + if err != nil { + log.Fatalf("error configuring client: %s", err.Error()) + } + configClient, err := configv1client.NewForConfig(config) + if err != nil { + log.Fatalf("error configuring client: %s", err.Error()) + } + opClient := operatorclient.NewClientFromConfig(*kubeConfigPath, logger) + crClient, err := client.NewClient(*kubeConfigPath) + if err != nil { + log.Fatalf("error configuring client: %s", err.Error()) + } + + // Create a new instance of the operator. + op, err := catalog.NewOperator(ctx, *kubeConfigPath, utilclock.RealClock{}, logger, *wakeupInterval, *configmapServerImage, *opmImage, *utilImage, *catalogNamespace, k8sscheme.Scheme, *installPlanTimeout, *bundleUnpackTimeout) + if err != nil { + log.Panicf("error configuring catalog operator: %s", err.Error()) + } + + opCatalogTemplate, err := catalogtemplate.NewOperator(ctx, *kubeConfigPath, logger, *wakeupInterval, *catalogNamespace) + if err != nil { + log.Panicf("error configuring catalog template operator: %s", err.Error()) + } + + op.Run(ctx) + <-op.Ready() + + opCatalogTemplate.Run(ctx) + <-opCatalogTemplate.Ready() + + if *writeStatusName != "" { + operatorstatus.MonitorClusterStatus(*writeStatusName, op.AtLevel(), op.Done(), opClient, configClient, crClient) + } + + <-op.Done() +} From 384760884b03ac9a35a7ddd78f2d4ca86eff035a Mon Sep 17 00:00:00 2001 From: Nick Hale Date: Thu, 16 Sep 2021 09:32:21 -0400 Subject: [PATCH 2/2] refactor(solver): add system-wide constraint registration Add an exported function that allows system-wide constraints -- i.e constraints used by all solvers -- to be injected. --- .../registry/resolver/installabletypes.go | 113 +++++++++++++++--- 1 file changed, 96 insertions(+), 17 deletions(-) diff --git a/pkg/controller/registry/resolver/installabletypes.go b/pkg/controller/registry/resolver/installabletypes.go index 695c2b07ab..fa685b4af4 100644 --- a/pkg/controller/registry/resolver/installabletypes.go +++ b/pkg/controller/registry/resolver/installabletypes.go @@ -3,6 +3,7 @@ package resolver import ( "fmt" "strings" + "sync" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver" @@ -55,32 +56,110 @@ func bundleId(bundle, channel string, catalog cache.SourceKey) solver.Identifier return solver.IdentifierFromString(fmt.Sprintf("%s/%s/%s", catalog.String(), channel, bundle)) } -func NewBundleInstallableFromOperator(o *cache.Operator) (BundleInstallable, error) { - if o.SourceInfo == nil { - return BundleInstallable{}, fmt.Errorf("unable to resolve the source of bundle %s", o.Name) - } - id := bundleId(o.Name, o.Channel(), o.SourceInfo.Catalog) +// ConstraintProvder knows how to provide solver constraints for a given cache entry. +type ConstraintProvider interface { + // Constraints returns a set of solver constraints for a cache entry. + Constraints(o *cache.Operator) ([]solver.Constraint, error) +} + +// ConstraintProviderFunc allows a function to implement the ConstraintProvider interface. +type ConstraintProviderFunc func(o *cache.Operator) ([]solver.Constraint, error) + +func (c ConstraintProviderFunc) Constraints(o *cache.Operator) ([]solver.Constraint, error) { + return c(o) +} + +// constraintProviderList provides aggregate constraints from a list of ConstraintProviders. +type constraintProviderList struct { + mu sync.RWMutex + providers []ConstraintProvider +} + +// add appends the given ConstraintProviders to the list aggregated over by the constraintProviderList. +// add is threadsafe. +func (c *constraintProviderList) add(providers ...ConstraintProvider) { + c.mu.Lock() + defer c.mu.Unlock() + + c.providers = append(c.providers, providers...) +} + +func (c *constraintProviderList) Constraints(o *cache.Operator) ([]solver.Constraint, error) { + c.mu.RLock() + defer c.mu.RUnlock() + var constraints []solver.Constraint - if o.SourceInfo.Catalog.Virtual() && o.SourceInfo.Subscription == nil { + for _, provider := range c.providers { + cons, err := provider.Constraints(o) + if err != nil { + return nil, err + } + + constraints = append(constraints, cons...) + } + + return constraints, nil +} + +var ( + // systemConstraintProviders is the list of constraint providers used by all solvers. + systemConstraintProviders = constraintProviderList{ + providers: []ConstraintProvider{ + freestandingCSVConstraint(), + deprecatedConstraint(), + }, + } +) + +func freestandingCSVConstraint() ConstraintProviderFunc { + return func(o *cache.Operator) ([]solver.Constraint, error) { + if !(o.SourceInfo.Catalog.Virtual() && o.SourceInfo.Subscription == nil) { + return nil, nil + } + // CSVs already associated with a Subscription // may be replaced, but freestanding CSVs must // appear in any solution. - constraints = append(constraints, PrettyConstraint( + return []solver.Constraint{PrettyConstraint( solver.Mandatory(), fmt.Sprintf("clusterserviceversion %s exists and is not referenced by a subscription", o.Name), - )) - } - for _, p := range o.Properties { - if p.GetType() == operatorregistry.DeprecatedType { - constraints = append(constraints, PrettyConstraint( - solver.Prohibited(), - fmt.Sprintf("bundle %s is deprecated", id), - )) - break + )}, nil + } +} + +func deprecatedConstraint() ConstraintProviderFunc { + return func(o *cache.Operator) ([]solver.Constraint, error) { + id := bundleId(o.Name, o.Channel(), o.SourceInfo.Catalog) + for _, p := range o.Properties { + if p.GetType() == operatorregistry.DeprecatedType { + return []solver.Constraint{PrettyConstraint( + solver.Prohibited(), + fmt.Sprintf("bundle %s is deprecated", id), + )}, nil + } } + + return nil, nil } +} + +// AddSystemConstraintProviders adds providers to the list of providers used system-wide, across all solvers. +func AddSystemConstraintProviders(providers ...ConstraintProvider) { + systemConstraintProviders.add(providers...) +} + +func NewBundleInstallableFromOperator(o *cache.Operator) (BundleInstallable, error) { + if o.SourceInfo == nil { + return BundleInstallable{}, fmt.Errorf("unable to resolve the source of bundle %s", o.Name) + } + + constraints, err := systemConstraintProviders.Constraints(o) + if err != nil { + return BundleInstallable{}, err + } + return BundleInstallable{ - identifier: id, + identifier: bundleId(o.Name, o.Channel(), o.SourceInfo.Catalog), constraints: constraints, }, nil }