Provisioning: Abstract away how we build discovery and dynamic clients (#110662)

Abstract away how we get discovery and dynamic clients
This commit is contained in:
Roberto Jiménez Sánchez
2025-09-05 09:22:30 +00:00
committed by GitHub
parent 6a54340501
commit 1b9e479b68
19 changed files with 216 additions and 159 deletions
@@ -107,7 +107,7 @@ func (f *finalizer) processExistingItems(
errors := 0
for _, item := range items.Items {
res, _, err := clients.ForResource(schema.GroupVersionResource{
res, _, err := clients.ForResource(ctx, schema.GroupVersionResource{
Group: item.Group,
Resource: item.Resource,
})
+1 -1
View File
@@ -99,7 +99,7 @@ func (c *filesConnector) Connect(ctx context.Context, name string, opts runtime.
return
}
folderClient, err := clients.Folder()
folderClient, err := clients.Folder(ctx)
if err != nil {
responder.Error(fmt.Errorf("failed to get folder client: %w", err))
return
@@ -11,7 +11,7 @@ import (
func ExportAll(ctx context.Context, repoName string, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error {
// FIXME: should we sign with grafana user?
folderClient, err := clients.Folder()
folderClient, err := clients.Folder(ctx)
if err != nil {
return err
}
@@ -31,7 +31,7 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions,
}
progress.SetMessage(ctx, fmt.Sprintf("export %s", kind.Resource))
client, _, err := clients.ForResource(kind)
client, _, err := clients.ForResource(ctx, kind)
if err != nil {
return fmt.Errorf("get client for %s: %w", kind.Resource, err)
}
@@ -47,7 +47,7 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions,
// For v2 we need to request the original version
if strings.HasPrefix(storedVersion, "v2alpha1") {
if v2clientAlphaV1 == nil {
v2clientAlphaV1, _, err = clients.ForResource(resources.DashboardResourceV2alpha1)
v2clientAlphaV1, _, err = clients.ForResource(ctx, resources.DashboardResourceV2alpha1)
if err != nil {
return nil, err
}
@@ -57,7 +57,7 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions,
if strings.HasPrefix(storedVersion, "v2beta1") {
if v2clientAlphaV2 == nil {
v2clientAlphaV2, _, err = clients.ForResource(resources.DashboardResourceV2beta1)
v2clientAlphaV2, _, err = clients.ForResource(ctx, resources.DashboardResourceV2beta1)
if err != nil {
return nil, err
}
@@ -98,7 +98,7 @@ func TestExportResources_Dashboards_Success(t *testing.T) {
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
options := resources.WriteOptions{
Path: "grafana",
Ref: "feature/branch",
@@ -124,7 +124,7 @@ func TestExportResources_Dashboards_ClientError(t *testing.T) {
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, fmt.Errorf("didn't work"))
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, fmt.Errorf("didn't work"))
}
err := runExportTest(t, nil, setupProgress, setupResources)
@@ -151,7 +151,7 @@ func TestExportResources_Dashboards_WithErrors(t *testing.T) {
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
options := resources.WriteOptions{
Path: "grafana",
Ref: "feature/branch",
@@ -185,7 +185,7 @@ func TestExportResources_Dashboards_TooManyErrors(t *testing.T) {
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
options := resources.WriteOptions{
Path: "grafana",
Ref: "feature/branch",
@@ -215,7 +215,7 @@ func TestExportResources_Dashboards_IgnoresExisting(t *testing.T) {
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
options := resources.WriteOptions{
Path: "grafana",
Ref: "feature/branch",
@@ -262,7 +262,7 @@ func TestExportResources_Dashboards_SavedVersion(t *testing.T) {
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
options := resources.WriteOptions{
Path: "grafana",
Ref: "feature/branch",
@@ -328,7 +328,7 @@ func TestExportResources_Dashboards_FailedConversionNoStoredVersion(t *testing.T
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
// The value is not saved
}
@@ -366,12 +366,12 @@ func TestExportResources_Dashboards_V2Alpha1(t *testing.T) {
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
// Setup v1 client
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
// Setup v2 client
v2Dashboard := createV2DashboardObject("v2-dashboard", "v2alpha1")
v2Client := &mockDynamicInterface{items: []unstructured.Unstructured{v2Dashboard}}
resourceClients.On("ForResource", resources.DashboardResourceV2alpha1).Return(v2Client, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResourceV2alpha1).Return(v2Client, gvk, nil)
options := resources.WriteOptions{
Path: "grafana",
@@ -427,8 +427,8 @@ func TestExportResources_Dashboards_V2Alpha1_ClientError(t *testing.T) {
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResourceV2alpha1).Return(nil, gvk, fmt.Errorf("v2 client error"))
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResourceV2alpha1).Return(nil, gvk, fmt.Errorf("v2 client error"))
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
}
err := runExportTest(t, mockItems, setupProgress, setupResources)
@@ -465,12 +465,12 @@ func TestExportResources_Dashboards_V2beta1(t *testing.T) {
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
// Setup v1 client
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
// Setup v2 client
v2Dashboard := createV2DashboardObject("v2-dashboard", "v2beta1")
v2Client := &mockDynamicInterface{items: []unstructured.Unstructured{v2Dashboard}}
resourceClients.On("ForResource", resources.DashboardResourceV2beta1).Return(v2Client, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResourceV2beta1).Return(v2Client, gvk, nil)
options := resources.WriteOptions{
Path: "grafana",
@@ -526,8 +526,8 @@ func TestExportResources_Dashboards_V2beta1_ClientError(t *testing.T) {
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResourceV2beta1).Return(nil, gvk, fmt.Errorf("v2 client error"))
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResourceV2beta1).Return(nil, gvk, fmt.Errorf("v2 client error"))
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
}
err := runExportTest(t, mockItems, setupProgress, setupResources)
@@ -560,7 +560,7 @@ func TestExportResources_Dashboards_SkipsManagedResources(t *testing.T) {
}
setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) {
resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil)
resourceClients.On("ForResource", mock.Anything, resources.DashboardResource).Return(mockClient, gvk, nil)
// No WriteResourceFileFromObject call expected since resource should be skipped
}
@@ -33,7 +33,7 @@ func (c *namespaceCleaner) Clean(ctx context.Context, namespace string, progress
for _, kind := range resources.SupportedProvisioningResources {
progress.SetMessage(ctx, fmt.Sprintf("remove unprovisioned %s", kind.Resource))
client, _, err := clients.ForResource(kind)
client, _, err := clients.ForResource(ctx, kind)
if err != nil {
return fmt.Errorf("get resource client: %w", err)
}
@@ -21,8 +21,8 @@ type mockClients struct {
mock.Mock
}
func (m *mockClients) ForResource(gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error) {
args := m.Called(gvr)
func (m *mockClients) ForResource(ctx context.Context, gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error) {
args := m.Called(ctx, gvr)
var ri dynamic.ResourceInterface
if args.Get(0) != nil {
ri = args.Get(0).(dynamic.ResourceInterface)
@@ -30,8 +30,8 @@ func (m *mockClients) ForResource(gvr schema.GroupVersionResource) (dynamic.Reso
return ri, args.Get(1).(schema.GroupVersionKind), args.Error(2)
}
func (m *mockClients) ForKind(gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error) {
args := m.Called(gvk)
func (m *mockClients) ForKind(ctx context.Context, gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error) {
args := m.Called(ctx, gvk)
var ri dynamic.ResourceInterface
if args.Get(0) != nil {
ri = args.Get(0).(dynamic.ResourceInterface)
@@ -39,8 +39,8 @@ func (m *mockClients) ForKind(gvk schema.GroupVersionKind) (dynamic.ResourceInte
return ri, args.Get(1).(schema.GroupVersionResource), args.Error(2)
}
func (m *mockClients) Folder() (dynamic.ResourceInterface, error) {
args := m.Called()
func (m *mockClients) Folder(ctx context.Context) (dynamic.ResourceInterface, error) {
args := m.Called(ctx)
var ri dynamic.ResourceInterface
if args.Get(0) != nil {
ri = args.Get(0).(dynamic.ResourceInterface)
@@ -48,8 +48,8 @@ func (m *mockClients) Folder() (dynamic.ResourceInterface, error) {
return ri, args.Error(1)
}
func (m *mockClients) User() (dynamic.ResourceInterface, error) {
args := m.Called()
func (m *mockClients) User(ctx context.Context) (dynamic.ResourceInterface, error) {
args := m.Called(ctx)
var ri dynamic.ResourceInterface
if args.Get(0) != nil {
ri = args.Get(0).(dynamic.ResourceInterface)
@@ -75,7 +75,7 @@ func TestNamespaceCleaner_Clean(t *testing.T) {
t.Run("should fail when getting resource client fails", func(t *testing.T) {
clients := &mockClients{}
clients.On("ForResource", resources.SupportedProvisioningResources[0]).
clients.On("ForResource", mock.Anything, resources.SupportedProvisioningResources[0]).
Return(nil, schema.GroupVersionKind{}, errors.New("failed to get resource client"))
mockClientFactory := resources.NewMockClientFactory(t)
@@ -113,7 +113,7 @@ func TestNamespaceCleaner_Clean(t *testing.T) {
}
clients := &mockClients{}
clients.On("ForResource", mock.Anything).
clients.On("ForResource", mock.Anything, mock.Anything).
Return(mockDynamicClient, schema.GroupVersionKind{}, nil)
mockClientFactory := resources.NewMockClientFactory(t)
@@ -180,7 +180,7 @@ func TestNamespaceCleaner_Clean(t *testing.T) {
}
clients := &mockClients{}
clients.On("ForResource", mock.Anything).
clients.On("ForResource", mock.Anything, mock.Anything).
Return(mockDynamicClient, schema.GroupVersionKind{}, nil)
mockClientFactory := resources.NewMockClientFactory(t)
@@ -253,7 +253,7 @@ func TestNamespaceCleaner_Clean(t *testing.T) {
}
clients := &mockClients{}
clients.On("ForResource", mock.Anything).
clients.On("ForResource", mock.Anything, mock.Anything).
Return(mockDynamicClient, schema.GroupVersionKind{}, nil)
mockClientFactory := resources.NewMockClientFactory(t)
@@ -82,7 +82,7 @@ func applyChanges(ctx context.Context, changes []ResourceFileChange, clients res
}
// TODO: should we use the clients or the resource manager instead?
client, _, err := clients.ForResource(versionlessGVR)
client, _, err := clients.ForResource(ctx, versionlessGVR)
if err != nil {
result.Error = fmt.Errorf("get client for deleted object: %w", err)
progress.Record(ctx, result)
@@ -408,7 +408,7 @@ func TestFullSync_ApplyChanges(t *testing.T) { //nolint:gocyclo
return true, nil, nil
})
clients.On("ForResource", schema.GroupVersionResource{
clients.On("ForResource", mock.Anything, schema.GroupVersionResource{
Group: "dashboards",
Resource: "Dashboard",
}).Return(fakeDynamicClient.Resource(resources.DashboardResource), schema.GroupVersionKind{
@@ -467,7 +467,7 @@ func TestFullSync_ApplyChanges(t *testing.T) { //nolint:gocyclo
return true, nil, fmt.Errorf("delete failed")
})
clients.On("ForResource", schema.GroupVersionResource{
clients.On("ForResource", mock.Anything, schema.GroupVersionResource{
Group: "dashboards",
Resource: "Dashboard",
}).Return(fakeDynamicClient.Resource(resources.DashboardResource), schema.GroupVersionKind{
@@ -544,7 +544,7 @@ func TestFullSync_ApplyChanges(t *testing.T) { //nolint:gocyclo
setupMocks: func(repo *repository.MockRepository, repoResources *resources.MockRepositoryResources, clients *resources.MockResourceClients, progress *jobs.MockJobProgressRecorder, compareFn *MockCompareFn) {
progress.On("TooManyErrors").Return(nil)
clients.On("ForResource", schema.GroupVersionResource{
clients.On("ForResource", mock.Anything, schema.GroupVersionResource{
Group: "dashboards",
Resource: "Dashboard",
}).Return(nil, schema.GroupVersionKind{}, errors.New("didn't work"))
@@ -599,7 +599,7 @@ func TestFullSync_ApplyChanges(t *testing.T) { //nolint:gocyclo
return true, nil, nil
})
clients.On("ForResource", schema.GroupVersionResource{
clients.On("ForResource", mock.Anything, schema.GroupVersionResource{
Group: "folders",
Resource: "Folder",
}).Return(fakeDynamicClient.Resource(resources.FolderResource), schema.GroupVersionKind{
@@ -658,7 +658,7 @@ func TestFullSync_ApplyChanges(t *testing.T) { //nolint:gocyclo
return true, nil, fmt.Errorf("delete failed")
})
clients.On("ForResource", schema.GroupVersionResource{
clients.On("ForResource", mock.Anything, schema.GroupVersionResource{
Group: "folders",
Resource: "Folder",
}).Return(fakeDynamicClient.Resource(resources.FolderResource), schema.GroupVersionKind{
@@ -41,7 +41,7 @@ type ClientFactory interface {
}
type clientFactory struct {
configProvider apiserver.RestConfigProvider
clientsProvider clientsProvider
}
// TODO: Rename to NamespacedClients
@@ -49,51 +49,92 @@ type clientFactory struct {
//
//go:generate mockery --name ResourceClients --structname MockResourceClients --inpackage --filename clients_mock.go --with-expecter
type ResourceClients interface {
ForKind(gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error)
ForResource(gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error)
ForKind(ctx context.Context, gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error)
ForResource(ctx context.Context, gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error)
Folder(ctx context.Context) (dynamic.ResourceInterface, error)
User(ctx context.Context) (dynamic.ResourceInterface, error)
}
Folder() (dynamic.ResourceInterface, error)
User() (dynamic.ResourceInterface, error)
type clientsProvider interface {
GetClientsForKind(ctx context.Context, gvk schema.GroupVersionKind) (dynamic.Interface, client.DiscoveryClient, error)
GetClientsForResource(ctx context.Context, gvr schema.GroupVersionResource) (dynamic.Interface, client.DiscoveryClient, error)
}
// singleAPIClients provides clients for all registered APIs
// It implements ClientsProvider by creating a dynamic client and discovery client
// for the given rest config provider
type singleAPIClients struct {
configProvider apiserver.RestConfigProvider
once sync.Once
dynamic dynamic.Interface
discovery client.DiscoveryClient
initErr error
}
func newSingleAPIClients(configProvider apiserver.RestConfigProvider) clientsProvider {
return &singleAPIClients{configProvider: configProvider}
}
func (p *singleAPIClients) onlyOnce(ctx context.Context) error {
p.once.Do(func() {
restConfig, e := p.configProvider.GetRestConfig(ctx)
if e != nil {
p.initErr = fmt.Errorf("get rest config: %w", e)
return
}
p.dynamic, e = dynamic.NewForConfig(restConfig)
if e != nil {
p.initErr = fmt.Errorf("create dynamic client: %w", e)
return
}
p.discovery, e = client.NewDiscoveryClient(restConfig)
if e != nil {
p.initErr = fmt.Errorf("create discovery client: %w", e)
return
}
})
return p.initErr
}
func (p *singleAPIClients) GetClientsForKind(ctx context.Context, _ schema.GroupVersionKind) (dynamic.Interface, client.DiscoveryClient, error) {
if err := p.onlyOnce(ctx); err != nil {
return nil, nil, fmt.Errorf("get clients: %w", err)
}
return p.dynamic, p.discovery, nil
}
func (p *singleAPIClients) GetClientsForResource(ctx context.Context, _ schema.GroupVersionResource) (dynamic.Interface, client.DiscoveryClient, error) {
if err := p.onlyOnce(ctx); err != nil {
return nil, nil, fmt.Errorf("get clients: %w", err)
}
return p.dynamic, p.discovery, nil
}
func NewClientFactory(configProvider apiserver.RestConfigProvider) ClientFactory {
return &clientFactory{configProvider}
return &clientFactory{newSingleAPIClients(configProvider)}
}
func (f *clientFactory) Clients(ctx context.Context, namespace string) (ResourceClients, error) {
restConfig, err := f.configProvider.GetRestConfig(ctx)
if err != nil {
return nil, err
}
if namespace == "" {
return nil, fmt.Errorf("missing namespace")
}
discovery, err := client.NewDiscoveryClient(restConfig)
if err != nil {
return nil, err
}
client, err := dynamic.NewForConfig(restConfig)
if err != nil {
return nil, err
}
return &resourceClients{
namespace: namespace,
discovery: discovery,
dynamic: client,
byKind: make(map[schema.GroupVersionKind]*clientInfo),
byResource: make(map[schema.GroupVersionResource]*clientInfo),
namespace: namespace,
clientsProvider: f.clientsProvider,
byKind: make(map[schema.GroupVersionKind]*clientInfo),
byResource: make(map[schema.GroupVersionResource]*clientInfo),
}, nil
}
type resourceClients struct {
namespace string
dynamic dynamic.Interface
discovery client.DiscoveryClient
namespace string
clientsProvider clientsProvider
// ResourceInterface cache for this context + namespace
mutex sync.Mutex
@@ -110,7 +151,7 @@ type clientInfo struct {
// ForKind returns a client for a kind.
// If the kind has a version, it will be used.
// If the kind does not have a version, the preferred version will be used.
func (c *resourceClients) ForKind(gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error) {
func (c *resourceClients) ForKind(ctx context.Context, gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
@@ -119,12 +160,16 @@ func (c *resourceClients) ForKind(gvk schema.GroupVersionKind) (dynamic.Resource
return info.client, info.gvr, nil
}
var err error
dynamic, discovery, err := c.clientsProvider.GetClientsForKind(ctx, gvk)
if err != nil {
return nil, schema.GroupVersionResource{}, fmt.Errorf("get clients for resource %s: %w", gvk.String(), err)
}
var gvr schema.GroupVersionResource
var versionless schema.GroupVersionKind
if gvk.Version == "" {
versionless = gvk
gvr, gvk, err = c.discovery.GetPreferredVersionForKind(schema.GroupKind{
gvr, gvk, err = discovery.GetPreferredVersionForKind(schema.GroupKind{
Group: gvk.Group,
Kind: gvk.Kind,
})
@@ -138,7 +183,7 @@ func (c *resourceClients) ForKind(gvk schema.GroupVersionKind) (dynamic.Resource
return info.client, info.gvr, nil
}
} else {
gvr, err = c.discovery.GetResourceForKind(gvk)
gvr, err = discovery.GetResourceForKind(gvk)
if err != nil {
return nil, schema.GroupVersionResource{}, err
}
@@ -146,7 +191,7 @@ func (c *resourceClients) ForKind(gvk schema.GroupVersionKind) (dynamic.Resource
info = &clientInfo{
gvk: gvk,
gvr: gvr,
client: c.dynamic.Resource(gvr).Namespace(c.namespace),
client: dynamic.Resource(gvr).Namespace(c.namespace),
}
c.byKind[gvk] = info
c.byResource[gvr] = info
@@ -159,7 +204,7 @@ func (c *resourceClients) ForKind(gvk schema.GroupVersionKind) (dynamic.Resource
// ForResource returns a client for a resource.
// If the resource has a version, it will be used.
// If the resource does not have a version, the preferred version will be used.
func (c *resourceClients) ForResource(gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error) {
func (c *resourceClients) ForResource(ctx context.Context, gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
@@ -168,12 +213,16 @@ func (c *resourceClients) ForResource(gvr schema.GroupVersionResource) (dynamic.
return info.client, info.gvk, nil
}
var err error
dynamic, discovery, err := c.clientsProvider.GetClientsForResource(ctx, gvr)
if err != nil {
return nil, schema.GroupVersionKind{}, fmt.Errorf("get clients for kind %s: %w", gvr.String(), err)
}
var gvk schema.GroupVersionKind
var versionless schema.GroupVersionResource
if gvr.Version == "" {
versionless = gvr
gvr, gvk, err = c.discovery.GetPreferredVesion(schema.GroupResource{
gvr, gvk, err = discovery.GetPreferredVesion(schema.GroupResource{
Group: gvr.Group,
Resource: gvr.Resource,
})
@@ -187,7 +236,7 @@ func (c *resourceClients) ForResource(gvr schema.GroupVersionResource) (dynamic.
return info.client, info.gvk, nil
}
} else {
gvk, err = c.discovery.GetKindForResource(gvr)
gvk, err = discovery.GetKindForResource(gvr)
if err != nil {
return nil, schema.GroupVersionKind{}, err
}
@@ -195,7 +244,7 @@ func (c *resourceClients) ForResource(gvr schema.GroupVersionResource) (dynamic.
info = &clientInfo{
gvk: gvk,
gvr: gvr,
client: c.dynamic.Resource(gvr).Namespace(c.namespace),
client: dynamic.Resource(gvr).Namespace(c.namespace),
}
c.byKind[gvk] = info
c.byResource[gvr] = info
@@ -205,13 +254,13 @@ func (c *resourceClients) ForResource(gvr schema.GroupVersionResource) (dynamic.
return info.client, info.gvk, nil
}
func (c *resourceClients) Folder() (dynamic.ResourceInterface, error) {
client, _, err := c.ForResource(FolderResource)
func (c *resourceClients) Folder(ctx context.Context) (dynamic.ResourceInterface, error) {
client, _, err := c.ForResource(ctx, FolderResource)
return client, err
}
func (c *resourceClients) User() (dynamic.ResourceInterface, error) {
v, _, err := c.ForResource(UserResource)
func (c *resourceClients) User(ctx context.Context) (dynamic.ResourceInterface, error) {
v, _, err := c.ForResource(ctx, UserResource)
return v, err
}
@@ -3,6 +3,8 @@
package resources
import (
context "context"
mock "github.com/stretchr/testify/mock"
dynamic "k8s.io/client-go/dynamic"
@@ -22,9 +24,9 @@ func (_m *MockResourceClients) EXPECT() *MockResourceClients_Expecter {
return &MockResourceClients_Expecter{mock: &_m.Mock}
}
// Folder provides a mock function with no fields
func (_m *MockResourceClients) Folder() (dynamic.ResourceInterface, error) {
ret := _m.Called()
// Folder provides a mock function with given fields: ctx
func (_m *MockResourceClients) Folder(ctx context.Context) (dynamic.ResourceInterface, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Folder")
@@ -32,19 +34,19 @@ func (_m *MockResourceClients) Folder() (dynamic.ResourceInterface, error) {
var r0 dynamic.ResourceInterface
var r1 error
if rf, ok := ret.Get(0).(func() (dynamic.ResourceInterface, error)); ok {
return rf()
if rf, ok := ret.Get(0).(func(context.Context) (dynamic.ResourceInterface, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func() dynamic.ResourceInterface); ok {
r0 = rf()
if rf, ok := ret.Get(0).(func(context.Context) dynamic.ResourceInterface); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(dynamic.ResourceInterface)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
@@ -58,13 +60,14 @@ type MockResourceClients_Folder_Call struct {
}
// Folder is a helper method to define mock.On call
func (_e *MockResourceClients_Expecter) Folder() *MockResourceClients_Folder_Call {
return &MockResourceClients_Folder_Call{Call: _e.mock.On("Folder")}
// - ctx context.Context
func (_e *MockResourceClients_Expecter) Folder(ctx interface{}) *MockResourceClients_Folder_Call {
return &MockResourceClients_Folder_Call{Call: _e.mock.On("Folder", ctx)}
}
func (_c *MockResourceClients_Folder_Call) Run(run func()) *MockResourceClients_Folder_Call {
func (_c *MockResourceClients_Folder_Call) Run(run func(ctx context.Context)) *MockResourceClients_Folder_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
run(args[0].(context.Context))
})
return _c
}
@@ -74,14 +77,14 @@ func (_c *MockResourceClients_Folder_Call) Return(_a0 dynamic.ResourceInterface,
return _c
}
func (_c *MockResourceClients_Folder_Call) RunAndReturn(run func() (dynamic.ResourceInterface, error)) *MockResourceClients_Folder_Call {
func (_c *MockResourceClients_Folder_Call) RunAndReturn(run func(context.Context) (dynamic.ResourceInterface, error)) *MockResourceClients_Folder_Call {
_c.Call.Return(run)
return _c
}
// ForKind provides a mock function with given fields: gvk
func (_m *MockResourceClients) ForKind(gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error) {
ret := _m.Called(gvk)
// ForKind provides a mock function with given fields: ctx, gvk
func (_m *MockResourceClients) ForKind(ctx context.Context, gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error) {
ret := _m.Called(ctx, gvk)
if len(ret) == 0 {
panic("no return value specified for ForKind")
@@ -90,25 +93,25 @@ func (_m *MockResourceClients) ForKind(gvk schema.GroupVersionKind) (dynamic.Res
var r0 dynamic.ResourceInterface
var r1 schema.GroupVersionResource
var r2 error
if rf, ok := ret.Get(0).(func(schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error)); ok {
return rf(gvk)
if rf, ok := ret.Get(0).(func(context.Context, schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error)); ok {
return rf(ctx, gvk)
}
if rf, ok := ret.Get(0).(func(schema.GroupVersionKind) dynamic.ResourceInterface); ok {
r0 = rf(gvk)
if rf, ok := ret.Get(0).(func(context.Context, schema.GroupVersionKind) dynamic.ResourceInterface); ok {
r0 = rf(ctx, gvk)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(dynamic.ResourceInterface)
}
}
if rf, ok := ret.Get(1).(func(schema.GroupVersionKind) schema.GroupVersionResource); ok {
r1 = rf(gvk)
if rf, ok := ret.Get(1).(func(context.Context, schema.GroupVersionKind) schema.GroupVersionResource); ok {
r1 = rf(ctx, gvk)
} else {
r1 = ret.Get(1).(schema.GroupVersionResource)
}
if rf, ok := ret.Get(2).(func(schema.GroupVersionKind) error); ok {
r2 = rf(gvk)
if rf, ok := ret.Get(2).(func(context.Context, schema.GroupVersionKind) error); ok {
r2 = rf(ctx, gvk)
} else {
r2 = ret.Error(2)
}
@@ -122,14 +125,15 @@ type MockResourceClients_ForKind_Call struct {
}
// ForKind is a helper method to define mock.On call
// - ctx context.Context
// - gvk schema.GroupVersionKind
func (_e *MockResourceClients_Expecter) ForKind(gvk interface{}) *MockResourceClients_ForKind_Call {
return &MockResourceClients_ForKind_Call{Call: _e.mock.On("ForKind", gvk)}
func (_e *MockResourceClients_Expecter) ForKind(ctx interface{}, gvk interface{}) *MockResourceClients_ForKind_Call {
return &MockResourceClients_ForKind_Call{Call: _e.mock.On("ForKind", ctx, gvk)}
}
func (_c *MockResourceClients_ForKind_Call) Run(run func(gvk schema.GroupVersionKind)) *MockResourceClients_ForKind_Call {
func (_c *MockResourceClients_ForKind_Call) Run(run func(ctx context.Context, gvk schema.GroupVersionKind)) *MockResourceClients_ForKind_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(schema.GroupVersionKind))
run(args[0].(context.Context), args[1].(schema.GroupVersionKind))
})
return _c
}
@@ -139,14 +143,14 @@ func (_c *MockResourceClients_ForKind_Call) Return(_a0 dynamic.ResourceInterface
return _c
}
func (_c *MockResourceClients_ForKind_Call) RunAndReturn(run func(schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error)) *MockResourceClients_ForKind_Call {
func (_c *MockResourceClients_ForKind_Call) RunAndReturn(run func(context.Context, schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error)) *MockResourceClients_ForKind_Call {
_c.Call.Return(run)
return _c
}
// ForResource provides a mock function with given fields: gvr
func (_m *MockResourceClients) ForResource(gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error) {
ret := _m.Called(gvr)
// ForResource provides a mock function with given fields: ctx, gvr
func (_m *MockResourceClients) ForResource(ctx context.Context, gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error) {
ret := _m.Called(ctx, gvr)
if len(ret) == 0 {
panic("no return value specified for ForResource")
@@ -155,25 +159,25 @@ func (_m *MockResourceClients) ForResource(gvr schema.GroupVersionResource) (dyn
var r0 dynamic.ResourceInterface
var r1 schema.GroupVersionKind
var r2 error
if rf, ok := ret.Get(0).(func(schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error)); ok {
return rf(gvr)
if rf, ok := ret.Get(0).(func(context.Context, schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error)); ok {
return rf(ctx, gvr)
}
if rf, ok := ret.Get(0).(func(schema.GroupVersionResource) dynamic.ResourceInterface); ok {
r0 = rf(gvr)
if rf, ok := ret.Get(0).(func(context.Context, schema.GroupVersionResource) dynamic.ResourceInterface); ok {
r0 = rf(ctx, gvr)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(dynamic.ResourceInterface)
}
}
if rf, ok := ret.Get(1).(func(schema.GroupVersionResource) schema.GroupVersionKind); ok {
r1 = rf(gvr)
if rf, ok := ret.Get(1).(func(context.Context, schema.GroupVersionResource) schema.GroupVersionKind); ok {
r1 = rf(ctx, gvr)
} else {
r1 = ret.Get(1).(schema.GroupVersionKind)
}
if rf, ok := ret.Get(2).(func(schema.GroupVersionResource) error); ok {
r2 = rf(gvr)
if rf, ok := ret.Get(2).(func(context.Context, schema.GroupVersionResource) error); ok {
r2 = rf(ctx, gvr)
} else {
r2 = ret.Error(2)
}
@@ -187,14 +191,15 @@ type MockResourceClients_ForResource_Call struct {
}
// ForResource is a helper method to define mock.On call
// - ctx context.Context
// - gvr schema.GroupVersionResource
func (_e *MockResourceClients_Expecter) ForResource(gvr interface{}) *MockResourceClients_ForResource_Call {
return &MockResourceClients_ForResource_Call{Call: _e.mock.On("ForResource", gvr)}
func (_e *MockResourceClients_Expecter) ForResource(ctx interface{}, gvr interface{}) *MockResourceClients_ForResource_Call {
return &MockResourceClients_ForResource_Call{Call: _e.mock.On("ForResource", ctx, gvr)}
}
func (_c *MockResourceClients_ForResource_Call) Run(run func(gvr schema.GroupVersionResource)) *MockResourceClients_ForResource_Call {
func (_c *MockResourceClients_ForResource_Call) Run(run func(ctx context.Context, gvr schema.GroupVersionResource)) *MockResourceClients_ForResource_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(schema.GroupVersionResource))
run(args[0].(context.Context), args[1].(schema.GroupVersionResource))
})
return _c
}
@@ -204,14 +209,14 @@ func (_c *MockResourceClients_ForResource_Call) Return(_a0 dynamic.ResourceInter
return _c
}
func (_c *MockResourceClients_ForResource_Call) RunAndReturn(run func(schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error)) *MockResourceClients_ForResource_Call {
func (_c *MockResourceClients_ForResource_Call) RunAndReturn(run func(context.Context, schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error)) *MockResourceClients_ForResource_Call {
_c.Call.Return(run)
return _c
}
// User provides a mock function with no fields
func (_m *MockResourceClients) User() (dynamic.ResourceInterface, error) {
ret := _m.Called()
// User provides a mock function with given fields: ctx
func (_m *MockResourceClients) User(ctx context.Context) (dynamic.ResourceInterface, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for User")
@@ -219,19 +224,19 @@ func (_m *MockResourceClients) User() (dynamic.ResourceInterface, error) {
var r0 dynamic.ResourceInterface
var r1 error
if rf, ok := ret.Get(0).(func() (dynamic.ResourceInterface, error)); ok {
return rf()
if rf, ok := ret.Get(0).(func(context.Context) (dynamic.ResourceInterface, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func() dynamic.ResourceInterface); ok {
r0 = rf()
if rf, ok := ret.Get(0).(func(context.Context) dynamic.ResourceInterface); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(dynamic.ResourceInterface)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
@@ -245,13 +250,14 @@ type MockResourceClients_User_Call struct {
}
// User is a helper method to define mock.On call
func (_e *MockResourceClients_Expecter) User() *MockResourceClients_User_Call {
return &MockResourceClients_User_Call{Call: _e.mock.On("User")}
// - ctx context.Context
func (_e *MockResourceClients_Expecter) User(ctx interface{}) *MockResourceClients_User_Call {
return &MockResourceClients_User_Call{Call: _e.mock.On("User", ctx)}
}
func (_c *MockResourceClients_User_Call) Run(run func()) *MockResourceClients_User_Call {
func (_c *MockResourceClients_User_Call) Run(run func(ctx context.Context)) *MockResourceClients_User_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
run(args[0].(context.Context))
})
return _c
}
@@ -261,7 +267,7 @@ func (_c *MockResourceClients_User_Call) Return(_a0 dynamic.ResourceInterface, _
return _c
}
func (_c *MockResourceClients_User_Call) RunAndReturn(run func() (dynamic.ResourceInterface, error)) *MockResourceClients_User_Call {
func (_c *MockResourceClients_User_Call) RunAndReturn(run func(context.Context) (dynamic.ResourceInterface, error)) *MockResourceClients_User_Call {
_c.Call.Return(run)
return _c
}
@@ -208,7 +208,7 @@ func (r *parser) Parse(ctx context.Context, info *repository.FileInfo) (parsed *
}
// TODO: catch the not found gvk error to return bad request
parsed.Client, parsed.GVR, err = r.clients.ForKind(parsed.GVK)
parsed.Client, parsed.GVR, err = r.clients.ForKind(ctx, parsed.GVK)
if err != nil {
return nil, fmt.Errorf("get client for kind: %w", err)
}
@@ -5,6 +5,7 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
@@ -16,9 +17,9 @@ import (
func TestParser(t *testing.T) {
clients := NewMockResourceClients(t)
clients.On("ForKind", dashboardV0.DashboardResourceInfo.GroupVersionKind()).
clients.On("ForKind", mock.Anything, dashboardV0.DashboardResourceInfo.GroupVersionKind()).
Return(nil, dashboardV0.DashboardResourceInfo.GroupVersionResource(), nil).Maybe()
clients.On("ForKind", dashboardV1.DashboardResourceInfo.GroupVersionKind()).
clients.On("ForKind", mock.Anything, dashboardV1.DashboardResourceInfo.GroupVersionKind()).
Return(nil, dashboardV1.DashboardResourceInfo.GroupVersionResource(), nil).Maybe()
parser := &parser{
@@ -64,7 +64,7 @@ func (r *repositoryResources) List(ctx context.Context) (*provisioning.ResourceL
// FindResourcePath finds the repository file path for a resource by its name and GroupVersionKind
func (r *repositoryResources) FindResourcePath(ctx context.Context, name string, gvk schema.GroupVersionKind) (string, error) {
// Use ForKind to get the dynamic client for this resource type
client, gvr, err := r.clients.ForKind(gvk)
client, gvr, err := r.clients.ForKind(ctx, gvk)
if err != nil {
return "", fmt.Errorf("get client for kind %s: %w", gvk.Kind, err)
}
@@ -107,7 +107,7 @@ func (r *repositoryResourcesFactory) Client(ctx context.Context, repo repository
return nil, fmt.Errorf("create clients: %w", err)
}
folderClient, err := clients.Folder()
folderClient, err := clients.Folder(ctx)
if err != nil {
return nil, fmt.Errorf("create folder client: %w", err)
}
@@ -313,9 +313,9 @@ func TestRepositoryResources_FindResourcePath(t *testing.T) {
// Mock ForKind call
if tt.forKindError != nil {
mockClients.On("ForKind", tt.gvk).Return(nil, schema.GroupVersionResource{}, tt.forKindError)
mockClients.On("ForKind", mock.Anything, tt.gvk).Return(nil, schema.GroupVersionResource{}, tt.forKindError)
} else {
mockClients.On("ForKind", tt.gvk).Return(mockClient, tt.expectedGVR, nil)
mockClients.On("ForKind", mock.Anything, tt.gvk).Return(mockClient, tt.expectedGVR, nil)
// Mock Get call if ForKind succeeds
if tt.getError != nil {
@@ -263,7 +263,7 @@ func (r *ResourcesManager) RemoveResourceFromFile(ctx context.Context, path stri
return "", schema.GroupVersionKind{}, ErrMissingName
}
client, _, err := r.clients.ForKind(*gvk)
client, _, err := r.clients.ForKind(ctx, *gvk)
if err != nil {
return "", schema.GroupVersionKind{}, fmt.Errorf("unable to get client for deleted object: %w", err)
}
@@ -43,7 +43,7 @@ func (f *signerFactory) New(ctx context.Context, opts SignOptions) (Signer, erro
return nil, fmt.Errorf("get clients: %w", err)
}
userClient, err := clients.User()
userClient, err := clients.User(ctx)
if err != nil {
return nil, fmt.Errorf("get user client: %w", err)
}
@@ -5,6 +5,7 @@ import (
"fmt"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
@@ -37,7 +38,7 @@ func TestSignerFactory_New(t *testing.T) {
setupMocks: func(t *testing.T, clients *resources.MockClientFactory) {
mockResourceClients := resources.NewMockResourceClients(t)
clients.On("Clients", context.Background(), "test-ns").Return(mockResourceClients, nil)
mockResourceClients.On("User").Return(nil, nil)
mockResourceClients.On("User", mock.Anything).Return(nil, nil)
},
expectedType: &loadUsersOnceSigner{},
},
@@ -61,7 +62,7 @@ func TestSignerFactory_New(t *testing.T) {
setupMocks: func(t *testing.T, clients *resources.MockClientFactory) {
mockResourceClients := resources.NewMockResourceClients(t)
clients.On("Clients", context.Background(), "test-ns").Return(mockResourceClients, nil)
mockResourceClients.On("User").Return(nil, fmt.Errorf("user client error"))
mockResourceClients.On("User", mock.Anything).Return(nil, fmt.Errorf("user client error"))
},
expectedError: "get user client: user client error",
},
+3 -3
View File
@@ -24,7 +24,7 @@ func TestIntegrationProvisioning_Client(t *testing.T) {
require.NoError(t, err)
t.Run("dashboard client support", func(t *testing.T) {
_, _, err := clients.ForResource(schema.GroupVersionResource{
_, _, err := clients.ForResource(ctx, schema.GroupVersionResource{
Group: dashboardV1.GROUP,
Version: dashboardV1.VERSION,
Resource: "dashboards",
@@ -32,7 +32,7 @@ func TestIntegrationProvisioning_Client(t *testing.T) {
require.NoError(t, err)
// With empty version, we should get the preferred version (v1beta1)
_, gvk, err := clients.ForResource(schema.GroupVersionResource{
_, gvk, err := clients.ForResource(ctx, schema.GroupVersionResource{
Group: dashboardV1.GROUP,
Resource: "dashboards",
})
@@ -40,7 +40,7 @@ func TestIntegrationProvisioning_Client(t *testing.T) {
require.Equal(t, dashboardV1.VERSION, gvk.Version)
require.Equal(t, "Dashboard", gvk.Kind)
_, _, err = clients.ForKind(schema.GroupVersionKind{
_, _, err = clients.ForKind(ctx, schema.GroupVersionKind{
Group: dashboardV1.GROUP,
Version: dashboardV1.VERSION,
Kind: "Dashboard",