This PR introduces the app package, which contains a few important concepts:
App, an interface which describes a unit which is run for an app-platform application. This has a "default" implementation in simple, but can be implemented by a user as they see fit. The interface describes actions which can be taken based on kinds (validate, mutate, convert, etc.) and main loop logic to be run by the wrapper/runner.
Provider, an interface for a wrapper/runner to use to get app data and instantiate an app with config based on its (the runner's) runtime.
Manifest, an early implementation of the App Manifest, which will likely be iterated on, but is required for the runners to work properly, as they need a way of describing app capability.
There is also a "default" app.App implementation as simple.App, and two "runners,": simple.StandaloneOperator, which functions just like a simple.Operator, and plugin.App, which wraps the app in the plugin gRPC runtime, translating the plugin gRPC calls to app ones (this runner is still slightly WIP as there is not a mechanism at the moment for it to get a kube config on initialization).
CUE codegen now also generates a file-based AppManifest (as a CR), and an in-code manifest to use if a AppManifest CRD is not available in the environment. Admission and conversion capabilities can be specified in the apiResource section of the kind's definition like so:
An update to the tutorial project's cmd/operator/main.go using the new app setup would look like:
const EnvCfgKey = "environment_config"
func main() {
// Configure the default logger to use slog
logging.DefaultLogger = logging.NewSLogLogger(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
//Load the config from the environment
cfg, err := LoadConfigFromEnv()
if err != nil {
logging.DefaultLogger.With("error", err).Error("Unable to load config from environment")
panic(err)
}
// Load the kube config
kubeConfig, err := LoadInClusterConfig()
if err != nil {
logging.DefaultLogger.With("error", err).Error("Unable to load kubernetes configuration")
panic(err)
}
// Set up the operator configuration (kubeconfig, metrics, tracing, etc.)
// This is the same as the config for an operator in the old way of doing things, but contains the additional AppConfig to pass on when initializing the App
operatorConfig := simple.OperatorAppConfig{
AppConfig: app.AppConfig{
Kubeconfig: kubeConfig.RestConfig,
ExtraConfig: map[string]any{
EnvCfgKey: cfg, // Add the config loaded from the env to the app config just in case we need it
},
},
MetricsConfig: simple.MetricsConfig{
Enabled: true,
},
TracingConfig: simple.TracingConfig{
Enabled: true,
OpenTelemetryConfig: simple.OpenTelemetryConfig{
Host: cfg.OTelConfig.Host,
Port: cfg.OTelConfig.Port,
ConnType: simple.OTelConnType(cfg.OTelConfig.ConnType),
ServiceName: cfg.OTelConfig.ServiceName,
},
},
WebhookConfig: simple.OperatorWebhookConfig{
Port: cfg.WebhookServer.Port,
TLSConfig: k8s.TLSConfig{
CertPath: cfg.WebhookServer.TLSCertPath,
KeyPath: cfg.WebhookServer.TLSKeyPath,
},
},
}
operator, err := simple.NewStandaloneOperator(simple.NewAppProvider(generated.LocalManifest(), newApp))
if err != nil {
logging.DefaultLogger.With("error", err).Error("Unable to create operator")
panic(err)
}
stopCh := make(chan struct{})
// Signal channel
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
stopCh <- struct{}{}
}()
err = operator.Run(operatorConfig, stopCh)
if err != nil {
logging.DefaultLogger.With("error", err).Error("Operator run error")
panic(err)
}
logging.DefaultLogger.Info("Normal operator exit")
}
// newApp is a function which creates a new app.App from an app.AppConfig
func newApp(cfg app.AppConfig) (app.App, error) {
watcher, err := watchers.NewIssueWatcher()
if err != nil {
return nil, err
}
return simple.NewApp(simple.AppConfig{
Kubeconfig: cfg.Kubeconfig,
ManagedKinds: []simple.AppManagedKind{{
Kind: issue.Kind(),
Watcher: watcher,
Validator: validators.NewIssueValidator(),
Mutator: mutators.NewIssueMutator(),
}},
})
}
This PR is being split into two separate non-draft PR's. This Draft will stay open to show forward context when reviewing other PR's
First PR: PR for Initial App Manifest Implementation
What This PR Does
This PR implements https://github.com/grafana/grafana-app-sdk/issues/385
This PR introduces the
app
package, which contains a few important concepts:App
, an interface which describes a unit which is run for an app-platform application. This has a "default" implementation insimple
, but can be implemented by a user as they see fit. The interface describes actions which can be taken based on kinds (validate, mutate, convert, etc.) and main loop logic to be run by the wrapper/runner.Provider
, an interface for a wrapper/runner to use to get app data and instantiate an app with config based on its (the runner's) runtime.Manifest
, an early implementation of the App Manifest, which will likely be iterated on, but is required for the runners to work properly, as they need a way of describing app capability.There is also a "default"
app.App
implementation assimple.App
, and two "runners,":simple.StandaloneOperator
, which functions just like asimple.Operator
, andplugin.App
, which wraps the app in the plugin gRPC runtime, translating the plugin gRPC calls to app ones (this runner is still slightly WIP as there is not a mechanism at the moment for it to get a kube config on initialization).CUE codegen now also generates a file-based AppManifest (as a CR), and an in-code manifest to use if a AppManifest CRD is not available in the environment. Admission and conversion capabilities can be specified in the
apiResource
section of the kind's definition like so:An update to the tutorial project's
cmd/operator/main.go
using the new app setup would look like: