* [API Server] Add Example App for reference use. * Remove Printlns. * Upgrade app-sdk to v0.46.0, update apps to handle breaking changes. * Only start the reconciler for the example app if the v1alpha1 API version is enabled. * Some comment doc updates. * Run make update-workspace * Set codeowner for /apps/example * Run make gofmt and make update-workspace * Run prettier on apps/example/README.md * Add COPY apps/example to Dockerfile * Add an authorizer to the example app. * Fix import ordering. * Update apps/example/kinds/manifest.cue Co-authored-by: Owen Diehl <ow.diehl@gmail.com> * Run make update-workspace * Re-run make gen-go for enterprise import updates * Run make update-workspace --------- Co-authored-by: Owen Diehl <ow.diehl@gmail.com>
144 lines
5.8 KiB
Go
144 lines
5.8 KiB
Go
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()},
|
|
}
|
|
}
|