mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Playlists: convert to use reconcilers instead (#98075)
This commit is contained in:
parent
c172bbba50
commit
24bf337c56
@ -7,6 +7,7 @@ init_cmds = [
|
|||||||
watch_all = true
|
watch_all = true
|
||||||
follow_symlinks = true
|
follow_symlinks = true
|
||||||
watch_dirs = [
|
watch_dirs = [
|
||||||
|
"$WORKDIR/apps",
|
||||||
"$WORKDIR/pkg",
|
"$WORKDIR/pkg",
|
||||||
"$WORKDIR/public/views",
|
"$WORKDIR/public/views",
|
||||||
"$WORKDIR/conf",
|
"$WORKDIR/conf",
|
||||||
|
@ -5,6 +5,7 @@ go 1.23.1
|
|||||||
require (
|
require (
|
||||||
github.com/grafana/grafana-app-sdk v0.23.1
|
github.com/grafana/grafana-app-sdk v0.23.1
|
||||||
k8s.io/apimachinery v0.31.3
|
k8s.io/apimachinery v0.31.3
|
||||||
|
k8s.io/client-go v0.31.3
|
||||||
k8s.io/klog/v2 v2.130.1
|
k8s.io/klog/v2 v2.130.1
|
||||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340
|
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340
|
||||||
)
|
)
|
||||||
@ -75,7 +76,6 @@ require (
|
|||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/api v0.31.3 // indirect
|
k8s.io/api v0.31.3 // indirect
|
||||||
k8s.io/apiextensions-apiserver v0.31.3 // indirect
|
k8s.io/apiextensions-apiserver v0.31.3 // indirect
|
||||||
k8s.io/client-go v0.31.3 // indirect
|
|
||||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
|
@ -2,34 +2,48 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/app"
|
"github.com/grafana/grafana-app-sdk/app"
|
||||||
|
"github.com/grafana/grafana-app-sdk/k8s"
|
||||||
"github.com/grafana/grafana-app-sdk/operator"
|
"github.com/grafana/grafana-app-sdk/operator"
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
"github.com/grafana/grafana-app-sdk/resource"
|
||||||
"github.com/grafana/grafana-app-sdk/simple"
|
"github.com/grafana/grafana-app-sdk/simple"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/apps/playlist/pkg/reconcilers"
|
||||||
|
|
||||||
playlistv0alpha1 "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1"
|
playlistv0alpha1 "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1"
|
||||||
"github.com/grafana/grafana/apps/playlist/pkg/watchers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlaylistConfig struct {
|
type PlaylistConfig struct {
|
||||||
EnableWatchers bool
|
EnableReconcilers bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPatchClient(restConfig rest.Config, playlistKind resource.Kind) (operator.PatchClient, error) {
|
||||||
|
clientGenerator := k8s.NewClientRegistry(restConfig, k8s.ClientConfig{})
|
||||||
|
return clientGenerator.ClientFor(playlistKind)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg app.Config) (app.App, error) {
|
func New(cfg app.Config) (app.App, error) {
|
||||||
var (
|
var (
|
||||||
playlistWatcher operator.ResourceWatcher
|
playlistReconciler operator.Reconciler
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
playlistConfig, ok := cfg.SpecificConfig.(*PlaylistConfig)
|
playlistConfig, ok := cfg.SpecificConfig.(*PlaylistConfig)
|
||||||
if ok && playlistConfig.EnableWatchers {
|
if ok && playlistConfig.EnableReconcilers {
|
||||||
playlistWatcher, err = watchers.NewPlaylistWatcher()
|
patchClient, err := getPatchClient(cfg.KubeConfig, playlistv0alpha1.PlaylistKind())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create PlaylistWatcher: %w", err)
|
klog.ErrorS(err, "Error getting patch client for use with opinionated reconciler")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
playlistReconciler, err = reconcilers.NewPlaylistReconciler(patchClient)
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "Error creating playlist reconciler")
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +58,7 @@ func New(cfg app.Config) (app.App, error) {
|
|||||||
ManagedKinds: []simple.AppManagedKind{
|
ManagedKinds: []simple.AppManagedKind{
|
||||||
{
|
{
|
||||||
Kind: playlistv0alpha1.PlaylistKind(),
|
Kind: playlistv0alpha1.PlaylistKind(),
|
||||||
Watcher: playlistWatcher,
|
Reconciler: playlistReconciler,
|
||||||
Mutator: &simple.Mutator{
|
Mutator: &simple.Mutator{
|
||||||
MutateFunc: func(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) {
|
MutateFunc: func(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) {
|
||||||
// modify req.Object if needed
|
// modify req.Object if needed
|
||||||
|
46
apps/playlist/pkg/reconcilers/reconciler_playlist.go
Normal file
46
apps/playlist/pkg/reconcilers/reconciler_playlist.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package reconcilers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-app-sdk/operator"
|
||||||
|
playlist "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPlaylistReconciler(patchClient operator.PatchClient) (operator.Reconciler, error) {
|
||||||
|
inner := operator.TypedReconciler[*playlist.Playlist]{}
|
||||||
|
|
||||||
|
inner.ReconcileFunc = func(ctx context.Context, request operator.TypedReconcileRequest[*playlist.Playlist]) (operator.ReconcileResult, error) {
|
||||||
|
switch request.Action {
|
||||||
|
case operator.ReconcileActionCreated:
|
||||||
|
klog.InfoS("Added resource", "name", request.Object.GetStaticMetadata().Identifier().Name)
|
||||||
|
return operator.ReconcileResult{}, nil
|
||||||
|
case operator.ReconcileActionUpdated:
|
||||||
|
klog.InfoS("Updated resource", "name", request.Object.GetStaticMetadata().Identifier().Name)
|
||||||
|
return operator.ReconcileResult{}, nil
|
||||||
|
case operator.ReconcileActionDeleted:
|
||||||
|
klog.InfoS("Deleted resource", "name", request.Object.GetStaticMetadata().Identifier().Name)
|
||||||
|
return operator.ReconcileResult{}, nil
|
||||||
|
case operator.ReconcileActionResynced:
|
||||||
|
klog.InfoS("Possibly updated resource", "name", request.Object.GetStaticMetadata().Identifier().Name)
|
||||||
|
return operator.ReconcileResult{}, nil
|
||||||
|
case operator.ReconcileActionUnknown:
|
||||||
|
klog.InfoS("error reconciling unknown action for Playlist", "action", request.Action, "object", request.Object)
|
||||||
|
return operator.ReconcileResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.InfoS("error reconciling invalid action for Playlist", "action", request.Action, "object", request.Object)
|
||||||
|
return operator.ReconcileResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefixing the finalizer with <group>-<kind> similar to how OpinionatedWatcher does
|
||||||
|
reconciler, err := operator.NewOpinionatedReconciler(patchClient, "playlist-playlists-finalizer")
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "Error creating opinionated reconciler for playlists")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reconciler.Reconciler = &inner
|
||||||
|
return reconciler, nil
|
||||||
|
}
|
@ -1,75 +0,0 @@
|
|||||||
package watchers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/operator"
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
playlist "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ operator.ResourceWatcher = &PlaylistWatcher{}
|
|
||||||
|
|
||||||
type PlaylistWatcher struct{}
|
|
||||||
|
|
||||||
func NewPlaylistWatcher() (*PlaylistWatcher, error) {
|
|
||||||
return &PlaylistWatcher{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add handles add events for playlist.Playlist resources.
|
|
||||||
func (s *PlaylistWatcher) Add(ctx context.Context, rObj resource.Object) error {
|
|
||||||
object, ok := rObj.(*playlist.Playlist)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("provided object is not of type *playlist.Playlist (name=%s, namespace=%s, kind=%s)",
|
|
||||||
rObj.GetStaticMetadata().Name, rObj.GetStaticMetadata().Namespace, rObj.GetStaticMetadata().Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.InfoS("Added resource", "name", object.GetStaticMetadata().Identifier().Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update handles update events for playlist.Playlist resources.
|
|
||||||
func (s *PlaylistWatcher) Update(ctx context.Context, rOld resource.Object, rNew resource.Object) error {
|
|
||||||
oldObject, ok := rOld.(*playlist.Playlist)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("provided object is not of type *playlist.Playlist (name=%s, namespace=%s, kind=%s)",
|
|
||||||
rOld.GetStaticMetadata().Name, rOld.GetStaticMetadata().Namespace, rOld.GetStaticMetadata().Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok = rNew.(*playlist.Playlist)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("provided object is not of type *playlist.Playlist (name=%s, namespace=%s, kind=%s)",
|
|
||||||
rNew.GetStaticMetadata().Name, rNew.GetStaticMetadata().Namespace, rNew.GetStaticMetadata().Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.InfoS("Updated resource", "name", oldObject.GetStaticMetadata().Identifier().Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete handles delete events for playlist.Playlist resources.
|
|
||||||
func (s *PlaylistWatcher) Delete(ctx context.Context, rObj resource.Object) error {
|
|
||||||
object, ok := rObj.(*playlist.Playlist)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("provided object is not of type *playlist.Playlist (name=%s, namespace=%s, kind=%s)",
|
|
||||||
rObj.GetStaticMetadata().Name, rObj.GetStaticMetadata().Namespace, rObj.GetStaticMetadata().Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.InfoS("Deleted resource", "name", object.GetStaticMetadata().Identifier().Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync is not a standard resource.Watcher function, but is used when wrapping this watcher in an operator.OpinionatedWatcher.
|
|
||||||
// It handles resources which MAY have been updated during an outage period where the watcher was not able to consume events.
|
|
||||||
func (s *PlaylistWatcher) Sync(ctx context.Context, rObj resource.Object) error {
|
|
||||||
object, ok := rObj.(*playlist.Playlist)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("provided object is not of type *playlist.Playlist (name=%s, namespace=%s, kind=%s)",
|
|
||||||
rObj.GetStaticMetadata().Name, rObj.GetStaticMetadata().Namespace, rObj.GetStaticMetadata().Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.InfoS("Possible resource update", "name", object.GetStaticMetadata().Identifier().Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -220,7 +220,7 @@ Experimental features might be changed or removed without prior notice.
|
|||||||
| `timeRangeProvider` | Enables time pickers sync |
|
| `timeRangeProvider` | Enables time pickers sync |
|
||||||
| `prometheusUsesCombobox` | Use new combobox component for Prometheus query editor |
|
| `prometheusUsesCombobox` | Use new combobox component for Prometheus query editor |
|
||||||
| `userStorageAPI` | Enables the user storage API |
|
| `userStorageAPI` | Enables the user storage API |
|
||||||
| `playlistsWatcher` | Enables experimental watcher for playlists |
|
| `playlistsReconciler` | Enables experimental reconciler for playlists |
|
||||||
| `prometheusSpecialCharsInLabelValues` | Adds support for quotes and special characters in label values for Prometheus queries |
|
| `prometheusSpecialCharsInLabelValues` | Adds support for quotes and special characters in label values for Prometheus queries |
|
||||||
| `enableExtensionsAdminPage` | Enables the extension admin page regardless of development mode |
|
| `enableExtensionsAdminPage` | Enables the extension admin page regardless of development mode |
|
||||||
| `enableSCIM` | Enables SCIM support for user and group management |
|
| `enableSCIM` | Enables SCIM support for user and group management |
|
||||||
|
@ -228,7 +228,7 @@ export interface FeatureToggles {
|
|||||||
userStorageAPI?: boolean;
|
userStorageAPI?: boolean;
|
||||||
azureMonitorDisableLogLimit?: boolean;
|
azureMonitorDisableLogLimit?: boolean;
|
||||||
preinstallAutoUpdate?: boolean;
|
preinstallAutoUpdate?: boolean;
|
||||||
playlistsWatcher?: boolean;
|
playlistsReconciler?: boolean;
|
||||||
passwordlessMagicLinkAuthentication?: boolean;
|
passwordlessMagicLinkAuthentication?: boolean;
|
||||||
exploreMetricsRelatedLogs?: boolean;
|
exploreMetricsRelatedLogs?: boolean;
|
||||||
prometheusSpecialCharsInLabelValues?: boolean;
|
prometheusSpecialCharsInLabelValues?: boolean;
|
||||||
|
@ -41,7 +41,7 @@ func RegisterApp(
|
|||||||
LegacyStorageGetter: provider.legacyStorageGetter,
|
LegacyStorageGetter: provider.legacyStorageGetter,
|
||||||
ManagedKinds: playlistapp.GetKinds(),
|
ManagedKinds: playlistapp.GetKinds(),
|
||||||
CustomConfig: any(&playlistapp.PlaylistConfig{
|
CustomConfig: any(&playlistapp.PlaylistConfig{
|
||||||
EnableWatchers: features.IsEnabledGlobally(featuremgmt.FlagPlaylistsWatcher),
|
EnableReconcilers: features.IsEnabledGlobally(featuremgmt.FlagPlaylistsReconciler),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
provider.Provider = simple.NewAppProvider(apis.LocalManifest(), appCfg, playlistapp.New)
|
provider.Provider = simple.NewAppProvider(apis.LocalManifest(), appCfg, playlistapp.New)
|
||||||
|
@ -1578,8 +1578,8 @@ var (
|
|||||||
Expression: "true", // enabled by default
|
Expression: "true", // enabled by default
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "playlistsWatcher",
|
Name: "playlistsReconciler",
|
||||||
Description: "Enables experimental watcher for playlists",
|
Description: "Enables experimental reconciler for playlists",
|
||||||
Stage: FeatureStageExperimental,
|
Stage: FeatureStageExperimental,
|
||||||
Owner: grafanaAppPlatformSquad,
|
Owner: grafanaAppPlatformSquad,
|
||||||
RequiresRestart: true,
|
RequiresRestart: true,
|
||||||
|
@ -209,7 +209,7 @@ prometheusUsesCombobox,experimental,@grafana/observability-metrics,false,false,f
|
|||||||
userStorageAPI,experimental,@grafana/plugins-platform-backend,false,false,false
|
userStorageAPI,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||||
azureMonitorDisableLogLimit,GA,@grafana/partner-datasources,false,false,false
|
azureMonitorDisableLogLimit,GA,@grafana/partner-datasources,false,false,false
|
||||||
preinstallAutoUpdate,GA,@grafana/plugins-platform-backend,false,false,false
|
preinstallAutoUpdate,GA,@grafana/plugins-platform-backend,false,false,false
|
||||||
playlistsWatcher,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
playlistsReconciler,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||||
passwordlessMagicLinkAuthentication,experimental,@grafana/identity-access-team,false,false,false
|
passwordlessMagicLinkAuthentication,experimental,@grafana/identity-access-team,false,false,false
|
||||||
exploreMetricsRelatedLogs,experimental,@grafana/observability-metrics,false,false,true
|
exploreMetricsRelatedLogs,experimental,@grafana/observability-metrics,false,false,true
|
||||||
prometheusSpecialCharsInLabelValues,experimental,@grafana/observability-metrics,false,false,true
|
prometheusSpecialCharsInLabelValues,experimental,@grafana/observability-metrics,false,false,true
|
||||||
|
|
@ -847,9 +847,9 @@ const (
|
|||||||
// Enables automatic updates for pre-installed plugins
|
// Enables automatic updates for pre-installed plugins
|
||||||
FlagPreinstallAutoUpdate = "preinstallAutoUpdate"
|
FlagPreinstallAutoUpdate = "preinstallAutoUpdate"
|
||||||
|
|
||||||
// FlagPlaylistsWatcher
|
// FlagPlaylistsReconciler
|
||||||
// Enables experimental watcher for playlists
|
// Enables experimental reconciler for playlists
|
||||||
FlagPlaylistsWatcher = "playlistsWatcher"
|
FlagPlaylistsReconciler = "playlistsReconciler"
|
||||||
|
|
||||||
// FlagPasswordlessMagicLinkAuthentication
|
// FlagPasswordlessMagicLinkAuthentication
|
||||||
// Enable passwordless login via magic link authentication
|
// Enable passwordless login via magic link authentication
|
||||||
|
@ -2670,12 +2670,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "playlistsWatcher",
|
"name": "playlistsReconciler",
|
||||||
"resourceVersion": "1730462910506",
|
"resourceVersion": "1734463170112",
|
||||||
"creationTimestamp": "2024-11-01T12:08:30Z"
|
"creationTimestamp": "2024-11-01T12:08:30Z",
|
||||||
|
"deletionTimestamp": "2024-12-19T19:17:00Z",
|
||||||
|
"annotations": {
|
||||||
|
"grafana.app/updatedTimestamp": "2024-12-17 19:19:30.112629 +0000 UTC"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
"description": "Enables experimental watcher for playlists",
|
"description": "Enables experimental reconciler for playlists",
|
||||||
"stage": "experimental",
|
"stage": "experimental",
|
||||||
"codeowner": "@grafana/grafana-app-platform-squad",
|
"codeowner": "@grafana/grafana-app-platform-squad",
|
||||||
"requiresRestart": true
|
"requiresRestart": true
|
||||||
|
Loading…
Reference in New Issue
Block a user