Skip to content

Conversation

@joelanford
Copy link
Member

Add controller that watches CatalogSources and manages lifecycle-server deployments. The server serves FBC catalog content over HTTP.

Includes build targets, container image updates, and RBAC manifests.

Add controller that watches CatalogSources and manages lifecycle-server
deployments. The server serves FBC catalog content over HTTP.

Includes build targets, container image updates, and RBAC manifests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jan 30, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jan 30, 2026
@openshift-ci-robot
Copy link

openshift-ci-robot commented Jan 30, 2026

@joelanford: This pull request references OPRUN-4445 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Add controller that watches CatalogSources and manages lifecycle-server deployments. The server serves FBC catalog content over HTTP.

Includes build targets, container image updates, and RBAC manifests.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jan 30, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jan 30, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: joelanford

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jan 30, 2026
joelanford and others added 2 commits January 30, 2026 15:37
- Add TLSConfigProvider for thread-safe dynamic TLS configuration updates
- Use tls.Config.GetConfigForClient for metrics server to dynamically
  pick up TLS profile changes without requiring a controller restart
- Watch APIServer "cluster" resource and trigger CatalogSource
  reconciliation when TLS security profile changes
- Use channel source to ensure TLS provider is updated before
  reconcile requests are processed (avoids race condition)
- Add NetworkPolicy for lifecycle-controller (manifest) and
  lifecycle-server (created dynamically per CatalogSource)
- Add RBAC permissions for apiservers and networkpolicies
- Add WebhookRetryBackoff to lifecycle-server authn/authz config
- Change health endpoints from localhost to all interfaces for
  Kubernetes probe compatibility
- Rename labels: olm.lifecycle-server=true -> app=olm-lifecycle-server,
  app=lifecycle-controller -> app=olm-lifecycle-controller
- Add olm.lifecycle-server/catalog-name label constant

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@joelanford
Copy link
Member Author

/test all

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a lifecycle-controller and lifecycle-server to manage and serve FBC (File-Based Catalog) lifecycle data over HTTP. The controller watches CatalogSources and dynamically creates lifecycle-server deployments per catalog, while the server provides an authenticated HTTP API for accessing lifecycle information.

Changes:

  • Added lifecycle-server package that loads and serves FBC lifecycle data via HTTP with authentication/authorization
  • Added lifecycle-controller that watches CatalogSources and manages lifecycle-server deployments, services, and network policies
  • Integrated TLS configuration dynamically from OpenShift APIServer resource
  • Added build targets, container image updates, and comprehensive RBAC manifests

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pkg/lifecycle-server/server.go HTTP handler for serving lifecycle data with version/package lookup
pkg/lifecycle-server/fbc.go FBC data loader with regex-based schema matching
pkg/lifecycle-controller/controller.go Main controller reconciling CatalogSources and managing lifecycle-server resources
cmd/lifecycle-server/main.go, start.go, util.go CLI for lifecycle-server with TLS, authn/authz, and health endpoints
cmd/lifecycle-controller/main.go, start.go, util.go CLI for lifecycle-controller with leader election and TLS config watching
Makefile Build targets for new lifecycle-controller and lifecycle-server binaries
operator-lifecycle-manager.Dockerfile Container image updates to include new binaries
manifests/0000_50_olm_09-lifecycle-server.rbac.yaml ClusterRole for lifecycle-server (tokenreviews/subjectaccessreviews)
manifests/0000_50_olm_08-lifecycle-controller.rbac.yaml RBAC for lifecycle-controller including cluster-wide resource management
manifests/0000_50_olm_08-lifecycle-controller.networkpolicy.yaml NetworkPolicy restricting lifecycle-controller ingress/egress
manifests/0000_50_olm_08-lifecycle-controller.deployment.yaml Deployment manifest for lifecycle-controller with security constraints

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

catalogDir := "/configs" // default for standard catalog images
if cs.Spec.GrpcPodConfig != nil && cs.Spec.GrpcPodConfig.ExtractContent != nil && cs.Spec.GrpcPodConfig.ExtractContent.CatalogDir != "" {
catalogDir = cs.Spec.GrpcPodConfig.ExtractContent.CatalogDir
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The fbcPath construction concatenates catalogMountPath and catalogDir without ensuring proper path formatting. If catalogDir starts with a slash (e.g., "/configs"), the result would be "/catalog/configs" which is correct, but if it doesn't start with a slash, or if it has a trailing slash, it could lead to malformed paths. Consider using path.Join or ensuring consistent path formatting to avoid potential path construction issues.

Suggested change
}
}
// Ensure catalogDir has a leading slash so concatenation produces a valid path.
if !strings.HasPrefix(catalogDir, "/") {
catalogDir = "/" + catalogDir
}

Copilot uses AI. Check for mistakes.
func (p *TLSConfigProvider) GetCipherSuites() []string {
p.mu.RLock()
defer p.mu.RUnlock()
return p.config.cipherSuiteStrings
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The GetCipherSuites method returns a slice that shares the underlying array with the config. If this slice is modified by the caller, it could lead to unexpected behavior or race conditions. Consider returning a defensive copy of the slice to ensure thread safety.

Suggested change
return p.config.cipherSuiteStrings
if p.config == nil || p.config.cipherSuiteStrings == nil {
return nil
}
ciphers := make([]string, len(p.config.cipherSuiteStrings))
copy(ciphers, p.config.cipherSuiteStrings)
return ciphers

Copilot uses AI. Check for mistakes.
log.Error(err, "failed to delete serviceaccount")
return err
}

Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The cleanupResources function does not delete the NetworkPolicy that is created in ensureResources. This will result in orphaned NetworkPolicy resources when a CatalogSource is deleted or no longer matches the selectors. The NetworkPolicy should be deleted along with the Deployment, Service, and ServiceAccount to ensure complete cleanup.

Suggested change
// Delete NetworkPolicy
np := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: csNamespace,
},
}
if err := r.Delete(ctx, np); err != nil && !errors.IsNotFound(err) {
log.Error(err, "failed to delete networkpolicy")
return err
}

Copilot uses AI. Check for mistakes.
name = strings.ReplaceAll(name, "_", "-")
if len(name) > 63 {
name = name[:63]
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The resourceName function truncates names to 63 characters but doesn't handle the case where truncation results in a trailing hyphen, which is invalid for Kubernetes resource names. Consider trimming trailing hyphens after truncation to ensure the generated name is always valid.

Suggested change
}
}
name = strings.TrimRight(name, "-")

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +86
// LoadLifecycleData loads lifecycle blobs from FBC files at the given path
func LoadLifecycleData(fbcPath string) (LifecycleIndex, error) {
result := make(LifecycleIndex)
var mu sync.Mutex

// Check if path exists
if _, err := os.Stat(fbcPath); os.IsNotExist(err) {
return result, nil
}

root := os.DirFS(fbcPath)
err := declcfg.WalkMetasFS(context.Background(), root, func(path string, meta *declcfg.Meta, err error) error {
if err != nil {
return nil // Skip errors, continue walking
}
if meta == nil {
return nil
}

// Check if schema matches our pattern
matches := schemaVersionRegex.FindStringSubmatch(meta.Schema)
if matches == nil {
return nil
}
schemaVersion := matches[1] // e.g., "v1alpha1"

if meta.Package == "" {
return nil
}

// Store in index (thread-safe)
mu.Lock()
if result[schemaVersion] == nil {
result[schemaVersion] = make(map[string]json.RawMessage)
}
result[schemaVersion][meta.Package] = meta.Blob
mu.Unlock()

return nil
})

if err != nil {
return nil, err
}

return result, nil
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The LoadLifecycleData function has several code paths that should be covered by tests: successful loading, non-existent path, schema matching, and concurrent access. The repository has test coverage for pkg/package-server-manager (e.g., pkg/package-server-manager/controller_test.go), so tests should be added for this new functionality as well.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +66
// NewHandler creates a new HTTP handler for the lifecycle API
func NewHandler(data LifecycleIndex, log logr.Logger) http.Handler {
mux := http.NewServeMux()

// GET /api/{version}/lifecycles/{package}
mux.HandleFunc("GET /api/{version}/lifecycles/{package}", func(w http.ResponseWriter, r *http.Request) {
version := r.PathValue("version")
pkg := r.PathValue("package")

// If no lifecycle data is available, return 503 Service Unavailable
if len(data) == 0 {
log.V(1).Info("no lifecycle data available, returning 503")
http.Error(w, "No lifecycle data available", http.StatusServiceUnavailable)
return
}

// Look up version in index
versionData, ok := data[version]
if !ok {
log.V(1).Info("version not found", "version", version, "package", pkg)
http.NotFound(w, r)
return
}

// Look up package in version
rawJSON, ok := versionData[pkg]
if !ok {
log.V(1).Info("package not found", "version", version, "package", pkg)
http.NotFound(w, r)
return
}

log.V(1).Info("returning lifecycle data", "version", version, "package", pkg)

w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(rawJSON); err != nil {
log.V(1).Error(err, "failed to write response")
}
})

return mux
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The HTTP handler has multiple code paths (no data, version not found, package not found, successful response) that should be covered by tests. The repository has test coverage for pkg/package-server-manager (e.g., pkg/package-server-manager/controller_test.go), so tests should be added for this new functionality as well.

Copilot uses AI. Check for mistakes.
Comment on lines +320 to +329
// resourceName generates a DNS-compatible name for lifecycle-server resources
func resourceName(csName string) string {
name := fmt.Sprintf("%s-%s", csName, resourceBaseName)
name = strings.ReplaceAll(name, ".", "-")
name = strings.ReplaceAll(name, "_", "-")
if len(name) > 63 {
name = name[:63]
}
return strings.ToLower(name)
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The resourceName function has several edge cases (names with dots, underscores, long names, truncation) that should be covered by tests to ensure it always generates valid Kubernetes resource names. The repository has test coverage for pkg/package-server-manager (e.g., pkg/package-server-manager/controller_test.go), so tests should be added for this new functionality as well.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +61
- name: GOMEMLIMIT
value: "5MiB"
resources:
requests:
cpu: 10m
memory: 10Mi
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The GOMEMLIMIT is set to "5MiB" but the memory request is set to "10Mi". These should be aligned for optimal performance. Typically, GOMEMLIMIT should be set to 90% of the memory limit, or if no limit is set, slightly below the memory request to account for non-Go memory usage. Consider either increasing GOMEMLIMIT closer to 10Mi or adjusting the memory request.

Copilot uses AI. Check for mistakes.
@joelanford
Copy link
Member Author

/test all

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jan 31, 2026

@joelanford: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/verify decf7eb link true /test verify
ci/prow/e2e-aws-olmv0-ext decf7eb link true /test e2e-aws-olmv0-ext

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants