Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ COLLECT_PROFILES_CMD := $(addprefix bin/, collect-profiles)
OPM := $(addprefix bin/, opm)
OLM_CMDS := $(shell go list -mod=vendor $(OLM_PKG)/cmd/...)
PSM_CMD := $(addprefix bin/, psm)
LIFECYCLE_CONTROLLER_CMD := $(addprefix bin/, lifecycle-controller)
LIFECYCLE_SERVER_CMD := $(addprefix bin/, lifecycle-server)
REGISTRY_CMDS := $(addprefix bin/, $(shell ls staging/operator-registry/cmd | grep -v opm))

# Default image tag for build/olm-container and build/registry-container
Expand Down Expand Up @@ -77,7 +79,7 @@ build/registry:
$(MAKE) $(REGISTRY_CMDS) $(OPM)

build/olm:
$(MAKE) $(PSM_CMD) $(OLM_CMDS) $(COLLECT_PROFILES_CMD) bin/copy-content
$(MAKE) $(PSM_CMD) $(OLM_CMDS) $(COLLECT_PROFILES_CMD) bin/copy-content $(LIFECYCLE_CONTROLLER_CMD) $(LIFECYCLE_SERVER_CMD)

$(OPM): version_flags=-ldflags "-X '$(REGISTRY_PKG)/cmd/opm/version.gitCommit=$(GIT_COMMIT)' -X '$(REGISTRY_PKG)/cmd/opm/version.opmVersion=$(OPM_VERSION)' -X '$(REGISTRY_PKG)/cmd/opm/version.buildDate=$(BUILD_DATE)'"
$(OPM):
Expand All @@ -97,6 +99,12 @@ $(PSM_CMD): FORCE
$(COLLECT_PROFILES_CMD): FORCE
go build $(GO_BUILD_OPTS) $(GO_BUILD_TAGS) -o $(COLLECT_PROFILES_CMD) $(ROOT_PKG)/cmd/collect-profiles

$(LIFECYCLE_CONTROLLER_CMD): FORCE
go build $(GO_BUILD_OPTS) $(GO_BUILD_TAGS) -o $(LIFECYCLE_CONTROLLER_CMD) $(ROOT_PKG)/cmd/lifecycle-controller

$(LIFECYCLE_SERVER_CMD): FORCE
go build $(GO_BUILD_OPTS) $(GO_BUILD_TAGS) -o $(LIFECYCLE_SERVER_CMD) $(ROOT_PKG)/cmd/lifecycle-server

.PHONY: cross
cross: version_flags=-X '$(REGISTRY_PKG)/cmd/opm/version.gitCommit=$(GIT_COMMIT)' -X '$(REGISTRY_PKG)/cmd/opm/version.opmVersion=$(OPM_VERSION)' -X '$(REGISTRY_PKG)/cmd/opm/version.buildDate=$(BUILD_DATE)'
cross:
Expand Down
23 changes: 23 additions & 0 deletions cmd/lifecycle-controller/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"fmt"
"os"

"github.com/spf13/cobra"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)

func main() {
rootCmd := &cobra.Command{
Use: "lifecycle-controller",
Short: "Lifecycle Metadata Controller for OLM",
}

rootCmd.AddCommand(newStartCmd())

if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "error running lifecycle-controller: %v\n", err)
os.Exit(1)
}
}
232 changes: 232 additions & 0 deletions cmd/lifecycle-controller/start.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package main

import (
"context"
"crypto/tls"
"fmt"
"net/http"
"os"
"time"

configv1 "github.com/openshift/api/config/v1"
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/spf13/cobra"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
metricsfilters "sigs.k8s.io/controller-runtime/pkg/metrics/filters"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/source"

controllers "github.com/openshift/operator-framework-olm/pkg/lifecycle-controller"
)

const (
defaultMetricsAddr = ":8443"
defaultHealthCheckAddr = ":8081"
leaderElectionID = "lifecycle-controller-lock"

// Leader election defaults per OpenShift conventions
// https://github.com/openshift/enhancements/blob/master/CONVENTIONS.md#high-availability
defaultLeaseDuration = 137 * time.Second
defaultRenewDeadline = 107 * time.Second
defaultRetryPeriod = 26 * time.Second
)

var (
disableLeaderElection bool
healthCheckAddr string
metricsAddr string
catalogSourceLabelSelector string
catalogSourceFieldSelector string
)

func newStartCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Start the Lifecycle Controller",
SilenceUsage: true,
RunE: run,
}

cmd.Flags().StringVar(&healthCheckAddr, "health", defaultHealthCheckAddr, "health check address")
cmd.Flags().StringVar(&metricsAddr, "metrics", defaultMetricsAddr, "metrics address")
cmd.Flags().BoolVar(&disableLeaderElection, "disable-leader-election", false, "disable leader election")
cmd.Flags().StringVar(&catalogSourceLabelSelector, "catalog-source-label-selector", "", "label selector for catalog sources to manage (empty means all)")
cmd.Flags().StringVar(&catalogSourceFieldSelector, "catalog-source-field-selector", "", "field selector for catalog sources to manage (empty means all)")

return cmd
}

func run(_ *cobra.Command, _ []string) error {
serverImage := os.Getenv("LIFECYCLE_SERVER_IMAGE")
if serverImage == "" {
return fmt.Errorf("LIFECYCLE_SERVER_IMAGE environment variable is required")
}

namespace := os.Getenv("NAMESPACE")
if !disableLeaderElection && namespace == "" {
return fmt.Errorf("NAMESPACE environment variable is required when leader election is enabled")
}

ctrl.SetLogger(klog.NewKlogr())
setupLog := ctrl.Log.WithName("setup")

version := os.Getenv("RELEASE_VERSION")
if version == "" {
version = "unknown"
}
setupLog.Info("starting lifecycle-controller", "version", version)

// Parse the catalog source label selector
labelSelector, err := labels.Parse(catalogSourceLabelSelector)
if err != nil {
setupLog.Error(err, "failed to parse catalog-source-label-selector", "selector", catalogSourceLabelSelector)
return fmt.Errorf("invalid catalog-source-label-selector %q: %w", catalogSourceLabelSelector, err)
}
setupLog.Info("using catalog source label selector", "selector", labelSelector.String())

// Parse the catalog source field selector
fieldSelector, err := fields.ParseSelector(catalogSourceFieldSelector)
if err != nil {
setupLog.Error(err, "failed to parse catalog-source-field-selector", "selector", catalogSourceFieldSelector)
return fmt.Errorf("invalid catalog-source-field-selector %q: %w", catalogSourceFieldSelector, err)
}
setupLog.Info("using catalog source field selector", "selector", fieldSelector.String())

restConfig := ctrl.GetConfigOrDie()
scheme := setupScheme()

// Create a temporary client to read initial TLS configuration
tempClient, err := client.New(restConfig, client.Options{Scheme: scheme})
if err != nil {
setupLog.Error(err, "failed to create temporary client for TLS config")
return err
}

// Get initial TLS configuration from APIServer "cluster"
ctx := context.Background()
initialTLSConfig := controllers.GetClusterTLSConfig(ctx, tempClient, setupLog)

// Create a TLS config provider for dynamic updates
tlsProvider := controllers.NewTLSConfigProvider(initialTLSConfig)

// Leader election timing defaults
leaseDuration := defaultLeaseDuration
renewDeadline := defaultRenewDeadline
retryPeriod := defaultRetryPeriod

mgr, err := ctrl.NewManager(restConfig, manager.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: true,
FilterProvider: metricsfilters.WithAuthenticationAndAuthorization,
TLSOpts: []func(*tls.Config){
func(cfg *tls.Config) {
// Use GetConfigForClient for dynamic TLS configuration
cfg.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
cfg := tlsProvider.Get()
return cfg.Clone(), nil
}
},
},
},
LeaderElection: !disableLeaderElection,
LeaderElectionNamespace: namespace,
LeaderElectionID: leaderElectionID,
LeaseDuration: &leaseDuration,
RenewDeadline: &renewDeadline,
RetryPeriod: &retryPeriod,
HealthProbeBindAddress: healthCheckAddr,
LeaderElectionReleaseOnCancel: true,
Cache: cache.Options{
ByObject: map[client.Object]cache.ByObject{
&operatorsv1alpha1.CatalogSource{}: {},
&corev1.Pod{}: {
Label: catalogPodLabelSelector(),
},
&appsv1.Deployment{}: {
Label: controllers.LifecycleServerLabelSelector(),
},
&configv1.APIServer{}: {},
},
},
})
if err != nil {
setupLog.Error(err, "failed to setup manager instance")
return err
}

// Create channel for TLS config change notifications
// The apiServerWatcher sends events to this channel after updating the TLS provider
tlsChangeChan := make(chan event.GenericEvent)
tlsChangeSource := source.Channel(tlsChangeChan, &handler.EnqueueRequestForObject{})

tlsProfileLog := ctrl.Log.WithName("controllers").WithName("tlsprofile-controller")
tlsProfileReconciler := controllers.ClusterTLSProfileReconciler{
Client: mgr.GetClient(),
Log: tlsProfileLog,
TLSProvider: tlsProvider,
OnChange: func(prev, cur *tls.Config) {
// Trigger reconciliation of all CatalogSources to update lifecycle-server deployments
var catalogSources operatorsv1alpha1.CatalogSourceList
if err := mgr.GetClient().List(ctx, &catalogSources); err != nil {
tlsProfileLog.Error(err, "failed to list CatalogSources to requeue for TLS reconfiguration; CatalogSources will not receive new TLS configuration until their next reconciliation")
return
}

tlsProfileLog.Info("requeueing CatalogSources TLS reconfiguration", "count", len(catalogSources.Items))

// Send events to trigger reconciliation
for i := range catalogSources.Items {
cs := &catalogSources.Items[i]
tlsChangeChan <- event.GenericEvent{Object: cs}
}
},
}
// Set up TLSProfileReconciler to reconcile TLS profile changes.
if err := tlsProfileReconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "failed to setup TLSProfile watcher")
return err
}

reconciler := &controllers.LifecycleControllerReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("lifecycle-controller"),
Scheme: mgr.GetScheme(),
ServerImage: serverImage,
CatalogSourceLabelSelector: labelSelector,
CatalogSourceFieldSelector: fieldSelector,
TLSConfigProvider: tlsProvider,
}

if err := reconciler.SetupWithManager(mgr, tlsChangeSource); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "lifecycle-controller")
return err
}

// Add health check endpoint (used for both liveness and readiness probes)
if err := mgr.AddHealthzCheck("healthz", func(req *http.Request) error {
return nil
}); err != nil {
setupLog.Error(err, "unable to set up health check")
return err
}

setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
return err
}

return nil
}
34 changes: 34 additions & 0 deletions cmd/lifecycle-controller/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"fmt"

"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/selection"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"

configv1 "github.com/openshift/api/config/v1"
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
)

func setupScheme() *runtime.Scheme {
scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(operatorsv1alpha1.AddToScheme(scheme))
utilruntime.Must(configv1.AddToScheme(scheme))

return scheme
}

// catalogPodLabelSelector returns a label selector matching pods with olm.catalogSource label
func catalogPodLabelSelector() labels.Selector {
// This call cannot fail: the label key is valid and selection.Exists requires no values.
req, err := labels.NewRequirement("olm.catalogSource", selection.Exists, nil)
if err != nil {
// Panic on impossible error to satisfy static analysis and catch programming errors
panic(fmt.Sprintf("BUG: failed to create label requirement: %v", err))
}
return labels.NewSelector().Add(*req)
}
22 changes: 22 additions & 0 deletions cmd/lifecycle-server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

func main() {
rootCmd := &cobra.Command{
Use: "lifecycle-server",
Short: "Lifecycle Metadata Server for OLM",
}

rootCmd.AddCommand(newStartCmd())

if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "error running lifecycle-server: %v\n", err)
os.Exit(1)
}
}
Loading