Files
grafana/pkg/registry/apis/provisioning/controller/mocks/StatusPatcher.go
T
Roberto Jiménez Sánchez b863acab05 Provisioning: Fix race condition causing unhealthy repository message to be lost (#115150)
* Fix race condition causing unhealthy repository message to be lost

This commit fixes a race condition in the provisioning repository controller
where the "Repository is unhealthy" message in the sync status could be lost
due to status updates being based on stale repository objects.

## Problem

The issue occurred in the `process` function when:
1. Repository object was fetched from cache with old status
2. `RefreshHealth` immediately patched the health status to "unhealthy"
3. `determineSyncStatusOps` used the stale object to check if unhealthy
   message was already set
4. A second patch operation based on stale data would overwrite the
   health status update

## Solution

Introduced `RefreshHealthWithPatchOps` method that returns patch operations
instead of immediately applying them. This allows batching all status updates
(health + sync) into a single atomic patch operation, eliminating the race
condition.

## Changes

- Added `HealthCheckerInterface` for better testability
- Added `RefreshHealthWithPatchOps` method to return patch ops without applying
- Updated `process` function to batch health and sync status updates
- Added comprehensive unit tests for the fix

Fixes the issue where unhealthy repositories don't show the "Repository is
unhealthy" message in their sync status.

* Fix staticcheck lint error: remove unnecessary nil check for slice
2025-12-12 13:24:58 +02:00

56 lines
1.5 KiB
Go

// Code generated by mockery v2.53.4. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// StatusPatcher is an autogenerated mock type for the StatusPatcher type
type StatusPatcher struct {
mock.Mock
}
// Patch provides a mock function with given fields: ctx, repo, patchOperations
func (_m *StatusPatcher) Patch(ctx context.Context, repo *v0alpha1.Repository, patchOperations ...map[string]interface{}) error {
_va := make([]interface{}, len(patchOperations))
for _i := range patchOperations {
_va[_i] = patchOperations[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, repo)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for Patch")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Repository, ...map[string]interface{}) error); ok {
r0 = rf(ctx, repo, patchOperations...)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewStatusPatcher creates a new instance of StatusPatcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewStatusPatcher(t interface {
mock.TestingT
Cleanup(func())
}) *StatusPatcher {
mock := &StatusPatcher{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}