Skip to content
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

Merged
merged 26 commits into from
Oct 1, 2024
Merged

App Logic and Runtime Separation #402

merged 26 commits into from
Oct 1, 2024

Conversation

IfSentient
Copy link
Contributor

@IfSentient IfSentient commented Sep 12, 2024

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 over app.App which translates runtime-specific behaviors into the app.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 to simple.Operator, exists as simple.App (created with simple.NewApp). A user can also write a custom implementation of app.App if they so wish, and it should be compatible with any runner that consumes an app.App/app.Provider.

app.Provider is an interface for usage by runners, which provides an app.Manifest to the runner to get app capabilities, and then allows for creation of an app.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 with simple.App, this gives users the same (and slightly more) functionality that simple.Operator currently does. Planned in a future PR is plugin.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.

@IfSentient IfSentient requested a review from a team as a code owner September 12, 2024 19:41
}

// AppManagedKind is a Kind and associated functionality used by an App.
type AppManagedKind struct {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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).

Copy link
Contributor

@radiohead radiohead left a 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.

app/app.go Outdated Show resolved Hide resolved
app/app.go Outdated Show resolved Hide resolved
simple/app.go Show resolved Hide resolved
simple/app.go Outdated Show resolved Hide resolved
simple/app.go Outdated Show resolved Hide resolved
simple/app.go Outdated Show resolved Hide resolved
simple/app.go Show resolved Hide resolved
simple/app.go Show resolved Hide resolved
app/app.go Outdated Show resolved Hide resolved
simple/app_operator.go Outdated Show resolved Hide resolved
…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
Copy link
Contributor

@ferruvich ferruvich Sep 19, 2024

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.
Copy link
Contributor

@radiohead radiohead left a 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.

simple/app.go Outdated Show resolved Hide resolved
simple/app.go Outdated Show resolved Hide resolved
operator/runner.go Show resolved Hide resolved
app/runner.go Show resolved Hide resolved
simple/app.go Outdated Show resolved Hide resolved
@IfSentient IfSentient merged commit c9ec2d8 into main Oct 1, 2024
9 checks passed
@IfSentient IfSentient deleted the app-interface-and-runner branch October 1, 2024 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow for Runtime and App Separation
4 participants