The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/apps/example/pkg/app/app.go

143 lines
5.8 KiB

package app
import (
"fmt"
"log/slog"
"os"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/k8s"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
"k8s.io/apimachinery/pkg/runtime/schema"
examplev0alpha1 "github.com/grafana/grafana/apps/example/pkg/apis/example/v0alpha1"
examplev1alpha1 "github.com/grafana/grafana/apps/example/pkg/apis/example/v1alpha1"
)
// New creates a new instance of the Example App. It gets called after the app's APIs have been registered,
// and is used for routing non-storage API requests, admission control, conversion, and can run
// reconcilers on kinds.
func New(cfg app.Config) (app.App, error) {
// APIPath needs to be set to `/apis`, as it defaults to empty
cfg.KubeConfig.APIPath = "/apis"
// We create a client to work with our Example kind in our reconciler
client, err := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()).ClientFor(examplev1alpha1.ExampleKind())
if err != nil {
return nil, fmt.Errorf("unable to create example client: %w", err)
}
var reconciler operator.Reconciler
exampleConfig, ok := cfg.SpecificConfig.(*ExampleConfig)
if ok && exampleConfig.EnableReconciler {
reconciler = NewExampleReconciler(client)
// Set the default logger if the reconciler is enabled--this should be done in grafana's API server handling instead,
// and will be corrected in a future PR
logging.DefaultLogger = logging.NewSLogLogger(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // Temporarily hardcoded to debug for the example
}))
}
// This is the configuration for our App.
simpleConfig := simple.AppConfig{
Name: "example",
KubeConfig: cfg.KubeConfig,
// ManagedKinds is the list of all kinds our app manages (the kinds owned by our app).
// Here, a Kind is defined as a distinct Group, Version, and Kind combination,
// so for each version of our Example kind, we need to add it to this list.
// Each kind can also have admission control attached to it--different versions can have different admission control attached.
// Handlers for custom routes defined in the manifest for the kind go here--this is where they actuall get routed,
// they are only defined in the manifest.
// Reconcilers and/or Watchers are also attached here, though they should only be attached to a single version per kind.
ManagedKinds: []simple.AppManagedKind{
{
Kind: examplev0alpha1.ExampleKind(),
// Validator is run on ingress and is it returns an error the request is rejected
Validator: NewValidator(),
// Mutator is run on ingress and makes changes to the input object
Mutator: NewMutator(),
},
{
Kind: examplev1alpha1.ExampleKind(),
// We only want the reconciler on one version of our kind, and it's usually best to use the latest
// We'll receive events for every example object, regardless of version used in the API,
// it will convert them to the version used for the reconciler.
Reconciler: reconciler,
// By default, reconcilers for ManagedKinds are wrapped in
ReconcileOptions: simple.BasicReconcileOptions{
// Namespace is the namespace your reconciler will watch.
// It defaults to all, so this isn't necessary to specify the way we do here.
Namespace: resource.NamespaceAll,
// We can optionally filter our reconciler to only get events for Example resources which
// satisfy the following label filters
// LabelFilters: []string{"foo=bar"},
// By default, reconcilers for ManagedKinds are wrapped in the app-sdk's OpinionatedReconciler.
// To turn this functionality off, you can set UsePlain to false
// UsePlain: true,
},
// Validator is run on ingress and is it returns an error the request is rejected
Validator: NewValidator(),
// Mutator is run on ingress and makes changes to the input object
Mutator: NewMutator(),
// We defined this route in our CUE, but we need to actually define the HTTP handler for it.
CustomRoutes: simple.AppCustomRouteHandlers{
{
Path: "foo",
Method: "GET",
}: ExampleGetFooHandler,
},
},
},
// Conversion for kinds is defined for all versions of a kind at once.
// This interface may change in the future, see https://github.com/grafana/grafana-app-sdk/issues/617
Converters: map[schema.GroupKind]simple.Converter{
{
Group: cfg.ManifestData.Group,
Kind: examplev0alpha1.ExampleKind().Kind(),
}: NewExampleConverter(),
},
// VersionedCustomRoutes are the custom route handlers for routes defined at the version level of the manifest
// instead of for a specific kind. This are sometimes referred to as "resource routes"
// (as opposed to "subresource routes" which are attached to kinds).
VersionedCustomRoutes: map[string]simple.AppVersionRouteHandlers{
"v1alpha1": {
{
Namespaced: true,
Path: "something",
Method: "GET",
}: GetSomethingHandler,
{
Namespaced: false,
Path: "other",
Method: "GET",
}: GetOtherHandler,
},
},
}
a, err := simple.NewApp(simpleConfig)
if err != nil {
return nil, err
}
// This makes it easier to catch problems at startup, rather than when something doesn't behave as expected.
// ValidateManifest will ensure that the capabilities you define in your simple.AppConfig
// match the capabilities described in the AppManifest.
err = a.ValidateManifest(cfg.ManifestData)
if err != nil {
return nil, err
}
return a, nil
}
func GetKinds() map[schema.GroupVersion][]resource.Kind {
gv := schema.GroupVersion{
Group: examplev1alpha1.ExampleKind().Group(),
Version: examplev1alpha1.ExampleKind().Version(),
}
return map[schema.GroupVersion][]resource.Kind{
gv: {examplev1alpha1.ExampleKind()},
}
}