-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
App Logic and Runtime Separation #402
Conversation
…capabilities via CUE and generating the App Manifest file and go type. Initial Go implementation of the manifest with the ability for the manifest data to be located in various places, starting with embedding manifest data.
…gs for admission operations.
…dalone operator app runner.
} | ||
|
||
// AppManagedKind is a Kind and associated functionality used by an App. | ||
type AppManagedKind struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any distinction between a kind that the app manipulates vs one that it has a read-only relationship with? Is that distinction useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the valuable distinction would be between kinds managed by the app (i.e. in the app manifest/CRDs the app author deploys) and kinds that you may want to watch/reconcile which are not managed by the app. In this case, there is no way to introduce kinds that are not managed by the app to watch/reconcile, but this is probably a good feature to introduce to simple.App
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've introduced simple.AppUnmanagedKind
and simple.App.WatchKind(simple.AppUnmanagedKind)
for establishing watchers/reconcilers on kinds which the app does not manage (this can also still be done via the AddRunnable
method, but it's more cumbersome).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! A few questions / suggestions.
…le purposes to a simple.App.
…ontext as well in StandaloneOperator.Run. Introduce app.MultiRunner for a centralized place for dealing with a Runnable that runs multiple Runnables under the hood (analogous to operator.Operator).
…onfig, which is app-specific config that a provider will pass to the runner to include in app.Config when calling NewApp. Added app.SingletonRunner, which allows for multiple Run() calls without running the wrapped Runnable multiple times. Use app.SingletonRunner for running the webserver components of simple.StandaloneOperator, and invert the provider/config logic so that config is provided in NewStandaloneOperator, and Run now is passed an app.Provider.
… AppManagedKind or AppUnmanagedKind has both a Watcher and Reconciler.
…er about the functionality. CHange the signature to return an object, rather than using an http.ResponseWriter for responses, and introduce a sentinel error for a not-found route.
…d(s) when registering custom resource routes.
app/app.go
Outdated
// should be capable of being run by an app wrapper. | ||
type App interface { | ||
// Validate validates the incoming request, and returns an error if validation fails | ||
Validate(ctx context.Context, request *resource.AdmissionRequest) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very nit: since we have AdmissionRequest
and MutatingResponse
re-declared above, should we use them here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should be using them here, I completely forgot to do so 🤦 . Good catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, implementing was a bit more work than I anticipated, but Validate
and Mutate
now use the app.AdmissionRequest
and app.MutatingResponse
. Had to introduce some new interfaces and types in simple
(simple.KindValidator
and its simple implementation simple.Validator
, and simple.KindMutator
and its implementation simple.Mutator
), and then a translation in operator.Runner
because k8s.WebhookServer
wants the resource
variants. Probably future work to make this whole chain more efficient, but for now, the API for app.App
is correct, which is what matters more.
…AdmissionRequest and app.MutatingResponse types, instead of the resource.* ones. Updated simple.App to use this and introduce interfaces analogous to resource.ValidatingAdmissionController and resource.MutatingAdmissionController (simple.KindValidator and simple.KindMutator respectively), in addition to simple implementations of them analogous to resource.SimpleValidatingAdmissionController and resource.SimpleMutatingAdmissionController (simple.Validator and simple.Mutator respectively). Updated operator.Runner to cast between the resource.* and app.* variants of these types, as k8s.WebhookServer uses the resource ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Just a few last bits.
What This PR Does / Why We Need It
This PR resolves #385 by introducing a new way to write and run apps focused around app logic implementing the interface
app.App
, and runtime logic (such as a standalone operator) existing as a layer overapp.App
which translates runtime-specific behaviors into theapp.App
method calls.App Logic
App logic is now handled as implementation of the new
app.App
interface, which describes all possible app behaviors. A default implementation, akin tosimple.Operator
, exists assimple.App
(created withsimple.NewApp
). A user can also write a custom implementation ofapp.App
if they so wish, and it should be compatible with any runner that consumes anapp.App
/app.Provider
.app.Provider
is an interface for usage by runners, which provides anapp.Manifest
to the runner to get app capabilities, and then allows for creation of anapp.App
with configuration sourced from the runner (such as KubeConfig, which may have varying methods by which it is loaded depending on the runner).Runtime Logic
This PR introduces only one runner,
simple.StandaloneOperator
, which runs an app as a standalone operator. Combined withsimple.App
, this gives users the same (and slightly more) functionality thatsimple.Operator
currently does. Planned in a future PR isplugin.App
(or a similar name), which will run the app as a plugin and translate gRPC admission and CallResource calls into the app method calls.Codegen & Documentation
Codegen for
grafana-app-sdk component add
will be introduced in a future PR to keep this one lighter, alongside documentation and examples for writing an app in this new manner (and existing documentation will be updated to working with apps this way). This PR is focused on introducing the new functionality, and as the "old" (current) way of writing apps is still perfectly valid, documentation will be added and updated in a docs-focused PR.Other Changes
This PR also contains a drive-by fix for app manifest go codegen, as it currently improperly duplicates the
Validation
field for mutation when enabled.