Skip to content

feat: enable optional ServiceMonitor creation for Backstage CR - #1374

Merged
gazarenkov merged 46 commits into
redhat-developer:mainfrom
Fortune-Ndlovu:enable-optional-service-monitor
Aug 6, 2025
Merged

feat: enable optional ServiceMonitor creation for Backstage CR#1374
gazarenkov merged 46 commits into
redhat-developer:mainfrom
Fortune-Ndlovu:enable-optional-service-monitor

Conversation

@Fortune-Ndlovu

@Fortune-Ndlovu Fortune-Ndlovu commented Jul 15, 2025

Copy link
Copy Markdown
Member

Description

This PR adds optional ServiceMonitor support for Backstage.
A new spec.monitoring.enabled field is introduced in the Backstage CR. When set to true, the operator will automatically create and manage a Prometheus ServiceMonitor for the Backstage instance. If disabled or omitted, no ServiceMonitor is created.

Which issue(s) does this PR fix or relate to

https://issues.redhat.com/browse/RHIDP-5780

PR acceptance criteria

  • Tests
  • Documentation

How to test changes / Special notes to the reviewer

Summary by Sourcery

Add optional Prometheus ServiceMonitor support for Backstage by introducing a spec.monitoring.enabled flag, registering the monitoring API, and implementing controller logic to create and manage ServiceMonitor resources when enabled.

New Features:

  • Introduce a Monitoring struct with an Enabled flag in BackstageSpec to toggle ServiceMonitor creation
  • Implement applyServiceMonitor in the controller to create or update a ServiceMonitor resource via server-side apply when monitoring is enabled

Build:

  • Add the Prometheus operator monitoring API dependency in go.mod and register it to the controller-runtime scheme
  • Update various indirect dependency versions in go.mod

Summary by Sourcery

Enable optional ServiceMonitor support for Backstage CR by introducing a new spec.monitoring.enabled flag and implementing controller logic to manage Prometheus ServiceMonitor resources accordingly

New Features:

  • Add Monitoring struct with Enabled flag to BackstageSpec and extend CRD manifests to include the new monitoring configuration
  • Implement applyServiceMonitor in BackstageReconciler to create, update, or delete a ServiceMonitor via server-side apply based on the monitoring flag
  • Register the monitoring.coreos.com/v1 ServiceMonitor API in the scheme and extend operator RBAC rules to manage ServiceMonitor and CRD resources

Enhancements:

  • Enhance MockClient to support Delete and Patch operations and simulate CRD-not-found errors for ServiceMonitor tests
  • Adjust integration tests to assert spec.Replicas values instead of status.ReadyReplicas and include the monitoring API scheme

Build:

  • Add prometheus-operator monitoring API dependency to go.mod and register it in main.go and test suite for scheme setup

Tests:

  • Add unit tests in monitor_test.go to cover applyServiceMonitor scenarios for disabled monitoring, missing CRD, creation, and update flows
  • Update integration tests to include monitoringv1 in the scheme and external CRD path

Signed-off-by: Fortune-Ndlovu <fndlovu@redhat.com>
@sourcery-ai

sourcery-ai Bot commented Jul 15, 2025

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Add optional ServiceMonitor support for Backstage CR by introducing a spec.monitoring.enabled flag, extending API types and CRD schemas, updating controller logic and RBAC, registering the Prometheus ServiceMonitor API, and adding corresponding tests and mock client support.

Sequence diagram for ServiceMonitor reconciliation when monitoring is enabled

sequenceDiagram
    participant Operator
    participant K8sAPI as Kubernetes API
    participant Prometheus
    Operator->>K8sAPI: Read Backstage CR (spec.monitoring.enabled)
    alt monitoring enabled
        Operator->>K8sAPI: Patch ServiceMonitor (create/update)
        K8sAPI-->>Operator: ServiceMonitor applied
        Prometheus->>K8sAPI: Discover ServiceMonitor
        K8sAPI-->>Prometheus: ServiceMonitor details
    else monitoring disabled
        Operator->>K8sAPI: Delete ServiceMonitor if exists
        K8sAPI-->>Operator: ServiceMonitor deleted
    end
Loading

ER diagram for Backstage CRD with new Monitoring field

erDiagram
    BACKSTAGE_SPEC {
        string name
        Database database
        Monitoring monitoring
    }
    MONITORING {
        bool enabled
    }
    BACKSTAGE_SPEC ||--o{ MONITORING : has
Loading

Class diagram for updated BackstageSpec and Monitoring types

classDiagram
    class BackstageSpec {
        Database* database
        Monitoring monitoring
        +IsMonitoringEnabled() bool
    }
    class Monitoring {
        bool enabled
    }
    BackstageSpec --> Monitoring : monitoring
    BackstageSpec --> Database : database
Loading

File-Level Changes

Change Details Files
Introduce API field and CRD schema for optional monitoring
  • Add Monitoring struct with Enabled flag and default to BackstageSpec
  • Implement IsMonitoringEnabled helper method
  • Update deepcopy functions to include Monitoring
  • Extend CRD and CSV schemas to include spec.monitoring with default false
api/v1alpha4/backstage_types.go
api/v1alpha4/zz_generated.deepcopy.go
dist/backstage.io/install.yaml
dist/rhdh/install.yaml
bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml
bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml
bundle/backstage.io/manifests/rhdh.redhat.com_backstages.yaml
config/crd/bases/rhdh.redhat.com_backstages.yaml
Implement ServiceMonitor reconciliation in the operator
  • Add applyServiceMonitor method to create/update/delete ServiceMonitor via server-side apply
  • Invoke applyServiceMonitor in Reconcile before core resource reconciliation
  • Add RBAC markers for ServiceMonitor and apiextensions CRDs
internal/controller/backstage_controller.go
internal/controller/monitor.go
config/rbac/role.yaml
Register monitoring API and update dependencies
  • Add prometheus-operator monitoring API dependency in go.mod
  • Register monitoringv1 scheme in cmd/main.go and test suite
  • Include external CRD directory path for monitoring in integration_tests
go.mod
cmd/main.go
integration_tests/suite_test.go
Enhance mock client to support ServiceMonitor operations
  • Implement Delete to properly remove objects and return NotFound errors
  • Implement Patch to simulate CRD discovery failure and apply JSON patch
internal/controller/mock_client.go
Add tests covering monitoring scenarios
  • Add monitor_test.go unit tests for disabled, missing CRD, creation and update paths
  • Adjust CR compatibility tests to assert spec.Replicas instead of Status.ReadyReplicas
internal/controller/monitor_test.go
integration_tests/cr-compatibility_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@openshift-ci
openshift-ci Bot requested review from rm3l and zdrapela July 15, 2025 10:28

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey @Fortune-Ndlovu - I've reviewed your changes - here's some feedback:

  • The operator’s RBAC needs to be updated to grant permissions for monitoring.coreos.com ServiceMonitor resources so it can create/patch them.
  • Disabling spec.monitoring.enabled currently just skips creation and leaves existing ServiceMonitors behind—consider adding logic to delete the ServiceMonitor when monitoring is turned off.
  • You may want to expose the metrics port name (and scrape interval) in the CR rather than hardcoding "metrics" to make the ServiceMonitor more configurable.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The operator’s RBAC needs to be updated to grant permissions for monitoring.coreos.com ServiceMonitor resources so it can create/patch them.
- Disabling spec.monitoring.enabled currently just skips creation and leaves existing ServiceMonitors behind—consider adding logic to delete the ServiceMonitor when monitoring is turned off.
- You may want to expose the metrics port name (and scrape interval) in the CR rather than hardcoding "metrics" to make the ServiceMonitor more configurable.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Co-authored-by: Fortune-Ndlovu <Fortune-Ndlovu@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Files changed in bundle and installer generation!

Those changes to the operator bundle/installer manifests should have been pushed automatically to your PR branch.

NOTE: If the PR checks are stuck after this additional commit, manually close the PR and immediately reopen it to trigger the checks again.

@github-actions

Copy link
Copy Markdown
Contributor

Signed-off-by: Fortune-Ndlovu <fndlovu@redhat.com>
Co-authored-by: Fortune-Ndlovu <Fortune-Ndlovu@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Files changed in bundle and installer generation!

Those changes to the operator bundle/installer manifests should have been pushed automatically to your PR branch.

NOTE: If the PR checks are stuck after this additional commit, manually close the PR and immediately reopen it to trigger the checks again.

@Fortune-Ndlovu Fortune-Ndlovu changed the title feat: enable optional ServiceMonitor creation for Backstage CR WIP feat: enable optional ServiceMonitor creation for Backstage CR Jul 16, 2025
…a make manifests. Implemented applyServiceMonitor logic in monitor.go. Added RBAC for ServiceMonitor and regenerated manager-role.

Signed-off-by: Fortune-Ndlovu <fndlovu@redhat.com>
Co-authored-by: Fortune-Ndlovu <Fortune-Ndlovu@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Files changed in bundle and installer generation!

Those changes to the operator bundle/installer manifests should have been pushed automatically to your PR branch.

NOTE: If the PR checks are stuck after this additional commit, manually close the PR and immediately reopen it to trigger the checks again.

@github-actions

github-actions Bot commented Aug 1, 2025

Copy link
Copy Markdown
Contributor

Comment thread cmd/main.go Outdated

@Fortune-Ndlovu Fortune-Ndlovu left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

minor suggestions, @gazarenkov wdyt?

Comment thread internal/controller/monitor.go
Comment thread internal/controller/monitor.go Outdated
Comment thread internal/controller/monitor.go
Comment thread internal/controller/monitor_test.go Outdated
Comment thread internal/controller/monitor_test.go
Comment thread internal/controller/monitor_test.go

@Fortune-Ndlovu Fortune-Ndlovu left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

make monitor.go consistent with the rest of the codebase while maintaining the same logging functionality

Comment thread internal/controller/monitor.go
Comment thread internal/controller/backstage_controller.go Outdated
Signed-off-by: Fortune-Ndlovu <fndlovu@redhat.com>
@github-actions

github-actions Bot commented Aug 4, 2025

Copy link
Copy Markdown
Contributor

Signed-off-by: Fortune-Ndlovu <fndlovu@redhat.com>
@github-actions

github-actions Bot commented Aug 4, 2025

Copy link
Copy Markdown
Contributor

@Fortune-Ndlovu

Copy link
Copy Markdown
Member Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey @Fortune-Ndlovu - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `internal/controller/mock_client.go:105` </location>
<code_context>

-func (m MockClient) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.PatchOption) error {
-	panic(implementMe)
+func (m MockClient) Patch(_ context.Context, obj client.Object, patch client.Patch, _ ...client.PatchOption) error {
+	if obj.GetName() == "" {
+		return fmt.Errorf("patch: object Name should not be empty")
</code_context>

<issue_to_address>
Patch method does not simulate patch logic, only replaces the object.

Currently, the Patch method replaces the entire object instead of merging changes, which may cause tests to pass incorrectly. Please update the implementation to better simulate patch behavior or clearly document this limitation.

Suggested implementation:

```golang
func (m MockClient) Patch(_ context.Context, obj client.Object, patch client.Patch, _ ...client.PatchOption) error {
	// NOTE: This mock Patch implementation does NOT perform a true merge/patch operation.
	// It only replaces the object in the store with the provided object.
	// This may cause tests to pass incorrectly if they rely on patch semantics.
	// If you need more accurate patch simulation, extend this method to merge fields from the patch into the existing object.

	if obj.GetName() == "" {
		return fmt.Errorf("patch: object Name should not be empty")
	}

	// Simulate CRD not found for ServiceMonitor when CRD is not registered
	objKind := kind(obj)
	if objKind == "ServiceMonitor" {
		// Check if ServiceMonitor CRD exists in our mock store
		crdKey := NameKind{Name: "servicemonitors.monitoring.coreos.com", Kind: "CustomResourceDefinition"}
		if _, exists := m.objects[crdKey]; !exists {
			return fmt.Errorf(`no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1"`)
		}

```

If you want to simulate a minimal merge for `client.MergeFrom`, you could:
- Retrieve the existing object from `m.objects`
- Use reflection or type assertion to copy only the fields that are set in the patch source to the target object
- Update the object in the store

However, this can be complex and is not shown here. The above change at least documents the limitation clearly, as requested.
</issue_to_address>

### Comment 2
<location> `api/v1alpha4/backstage_types.go:348` </location>
<code_context>
+// IsMonitoringEnabled checks if monitoring is explicitly enabled in the BackstageSpec.
+// Returns false if the Monitoring field is nil (not configured) or explicitly disabled.
+// Returns true only when spec.monitoring.enabled is set to true in the CR
+func (s *BackstageSpec) IsMonitoringEnabled() bool {
+	return s.Monitoring.Enabled
+}
</code_context>

<issue_to_address>
IsMonitoringEnabled does not distinguish between unset and explicitly disabled.

Since Monitoring is not a pointer, IsMonitoringEnabled cannot differentiate between Monitoring being unset and Enabled being false. To address this, consider making Monitoring a pointer.

Suggested implementation:

```golang
 // IsMonitoringEnabled checks if monitoring is explicitly enabled in the BackstageSpec.
 // Returns false if the Monitoring field is nil (not configured) or explicitly disabled.
 // Returns true only when spec.monitoring.enabled is set to true in the CR
 func (s *BackstageSpec) IsMonitoringEnabled() bool {
 	return s.Monitoring != nil && s.Monitoring.Enabled
 }

```

```golang
type Monitoring struct {
	// Enable ServiceMonitor for Prometheus scraping
	// +optional
	// +kubebuilder:default=false
	Enabled bool `json:"enabled,omitempty"`
}

// In BackstageSpec struct definition (not shown in the provided code), change:
// Monitoring Monitoring `json:"monitoring,omitempty"`
// to:
// Monitoring *Monitoring `json:"monitoring,omitempty"`

```

You must update the `BackstageSpec` struct definition (not shown in the provided code) to change the type of the `Monitoring` field from `Monitoring` to `*Monitoring`:

From:
```go
Monitoring Monitoring `json:"monitoring,omitempty"`
```
To:
```go
Monitoring *Monitoring `json:"monitoring,omitempty"`
```

You should also check any code that instantiates or accesses `BackstageSpec` to ensure it correctly handles the pointer type for `Monitoring`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread internal/controller/mock_client.go
Comment thread api/v1alpha4/backstage_types.go
Signed-off-by: Fortune-Ndlovu <fndlovu@redhat.com>
@github-actions

github-actions Bot commented Aug 4, 2025

Copy link
Copy Markdown
Contributor

Comment thread internal/controller/backstage_controller.go Outdated
Fortune-Ndlovu and others added 2 commits August 5, 2025 14:31
…egenerate bundle manifests

Signed-off-by: Fortune-Ndlovu <fndlovu@redhat.com>
Co-authored-by: Fortune-Ndlovu <Fortune-Ndlovu@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 5, 2025

Copy link
Copy Markdown
Contributor

⚠️ Files changed in bundle and installer generation!

Those changes to the operator bundle/installer manifests should have been pushed automatically to your PR branch.

NOTE: If the PR checks are stuck after this additional commit, manually close the PR and immediately reopen it to trigger the checks again.

@github-actions

github-actions Bot commented Aug 5, 2025

Copy link
Copy Markdown
Contributor

Signed-off-by: Fortune-Ndlovu <fndlovu@redhat.com>
@github-actions

github-actions Bot commented Aug 5, 2025

Copy link
Copy Markdown
Contributor

Signed-off-by: Fortune-Ndlovu <fndlovu@redhat.com>
@github-actions

github-actions Bot commented Aug 6, 2025

Copy link
Copy Markdown
Contributor

@openshift-ci

openshift-ci Bot commented Aug 6, 2025

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: gazarenkov

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

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants