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

API Server Poc #231

Draft
wants to merge 50 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9f5baef
Refactor of the Object interface to be a superset of the kubernetes i…
IfSentient Feb 9, 2024
76ee059
Implement New resource.Kind and resource.Object in Other Packages (#207)
IfSentient Feb 14, 2024
0c382e4
[Kind/Object Refactor] Codegen Updates (#211)
IfSentient Feb 21, 2024
b74f174
Update tutorial based on changes to Object interface and codegen. (#214)
IfSentient Feb 22, 2024
f574161
[Kind/Object Refactor] Misc Final Fixes (#216)
IfSentient Feb 23, 2024
653c4c9
[bugfix] thema codec: set the APIVersion and Kind correctly.
IfSentient Feb 23, 2024
8668730
Merge branch 'main' into kind-object-refactor
IfSentient Feb 23, 2024
3c3be6e
Merge branch 'main' into kind-object-refactor
radiohead Feb 27, 2024
3c7814a
add wip poc
toddtreece Mar 1, 2024
5d0cb2a
Some POC work for an apiserver package, based on work in the simple p…
IfSentient Mar 6, 2024
49a5553
Initial working POC of k8s openAPI codegen using kube-openapi in a Je…
IfSentient Mar 7, 2024
16d9766
Bump the all group with 1 update (#222)
dependabot[bot] Feb 29, 2024
4ca88b3
Fix calls to local scripts (#225)
mem Mar 4, 2024
c7f326a
Fix generated files' permissions (#224)
mem Mar 5, 2024
96cdf4b
simple version is almost working
toddtreece Mar 13, 2024
bcd0d30
Initial working POC of k8s openAPI codegen using kube-openapi in a Je…
IfSentient Mar 7, 2024
557c64e
working apiserver codegen
toddtreece Mar 14, 2024
c9e00bb
add example
toddtreece Mar 14, 2024
af7b038
add basic apiserver command
toddtreece Mar 14, 2024
07f5104
Update codegen to allow for prefixing of all go types with the kind n…
IfSentient Mar 14, 2024
39cae77
Minor clean-up of storage to dynamically decide on GetAttrs func for …
IfSentient Mar 14, 2024
8baeb00
Some more jenny updates to handle mutliple kinds in the same package,…
IfSentient Mar 14, 2024
062727b
Initial work on subresource routes.
IfSentient Mar 15, 2024
edade39
Small example updates.
IfSentient Mar 15, 2024
5ec9bee
Some code re-organization.
IfSentient Mar 15, 2024
8af0aab
In-progress apiserver.ResourceGroup work.
IfSentient Mar 18, 2024
af93241
add storage for subresources
toddtreece Mar 19, 2024
34edb3e
update codegen
toddtreece Mar 19, 2024
9f7148e
Added a second version to the ExternalName kind to demonstrate and te…
IfSentient Mar 20, 2024
61c0839
Removed unused examples/apiserver/apis/resource directory
IfSentient Mar 20, 2024
38069d8
Update codegen to better handle grouping, have codecs prefixed by kin…
IfSentient Mar 20, 2024
19ed13a
Moved APIServerOptions and NewCommandStartAPIServer from into , to g…
IfSentient Mar 20, 2024
5fd31a9
Tiny update to README and comments to main.
IfSentient Mar 21, 2024
46055b0
add admission support
toddtreece Mar 26, 2024
08f8b02
progress on admission hooks
toddtreece Mar 27, 2024
260d1c0
fix admission bug related to multiple versions
toddtreece Apr 8, 2024
6ab35c4
Merge branch 'main' into apiserver-poc
IfSentient Apr 10, 2024
5d764a2
Fix jennies/generators for tests.
IfSentient Apr 10, 2024
38e110a
downgrade client_go dependency
toddtreece Apr 11, 2024
28a1b0a
Set prometheus/common version to v0.46.0 to avoid test compile error.
IfSentient Apr 11, 2024
340cddf
Updated openapi jenny to correctly by-package codegen openAPI (instea…
IfSentient Apr 15, 2024
ac838a1
Merge branch 'main' of https://github.com/grafana/grafana-app-sdk int…
toddtreece Apr 16, 2024
e259e1d
Update apiserver example go.mod and parent go.work file so the exampl…
IfSentient Apr 17, 2024
dc7a517
Introduced APIGroupProvider interface and updated apiserver.ResourceG…
IfSentient Apr 19, 2024
334d964
Change apiserver.Resource.Reconciler field to a function that instant…
IfSentient Apr 19, 2024
da5b5ab
Remove accidental generated code from main branch CLI.
IfSentient Apr 19, 2024
41156de
Added convenience functions for GVK and GVR to resource.Kind
IfSentient May 1, 2024
f9c721d
Setup post-start hooks in apiserver.Config.Complete().
IfSentient May 1, 2024
bb9ae54
Updated resource conversion to be in the apiserver.Resource type inst…
IfSentient May 24, 2024
ed1af17
initial work on an app-centric workflow and ability to use a plugin a…
IfSentient Jul 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions apiserver/connector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package apiserver

import (
"context"
"fmt"
"net/http"
"net/url"

"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/validation/spec"
)

type ResourceCallOptions struct {
metav1.TypeMeta

// Path is the URL path to use for the current proxy request
Path string
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceCallOptions) DeepCopyInto(out *ResourceCallOptions) {
*out = *in
out.TypeMeta = in.TypeMeta
return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceCallOptions.
func (in *ResourceCallOptions) DeepCopy() *ResourceCallOptions {
if in == nil {
return nil
}
out := new(ResourceCallOptions)
in.DeepCopyInto(out)
return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ResourceCallOptions) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

var _ = rest.Connecter(&SubresourceConnector{})

// TODO: customize rather than hard-code?
var methods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}

type SubresourceConnector struct {
Routes []SubresourceRoute
}

func (r *SubresourceConnector) New() runtime.Object {
return &ResourceCallOptions{}
}

func (r *SubresourceConnector) Destroy() {
}

func (r *SubresourceConnector) ConnectMethods() []string {
return methods
}

func (r *SubresourceConnector) NewConnectOptions() (runtime.Object, bool, string) {
return &ResourceCallOptions{}, true, "path"
}

func (r *SubresourceConnector) Connect(ctx context.Context, id string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
resourceCallOpts, ok := opts.(*ResourceCallOptions)
if !ok {
return nil, fmt.Errorf("invalid options object: %#v", opts)
}
fmt.Println("ResourceCallREST.Connect() called with id:", id, "and opts:", resourceCallOpts)
// TODO: map instead?
info, ok := request.RequestInfoFrom(ctx)
if !ok {
return nil, fmt.Errorf("unable to retrieve request info from context")
}
identifier := resource.Identifier{
Name: info.Name,
Namespace: info.Namespace,
}
for _, route := range r.Routes {
if route.Path == info.Subresource {
return &handlerWrapper{
id: identifier,
handler: route.Handler,
}, nil
}
}
return nil, fmt.Errorf("no matching route for id '%s'", id)
}

type handlerWrapper struct {
id resource.Identifier
handler AdditionalRouteHandler
}

func (h *handlerWrapper) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.handler(w, req, h.id)
}

// CovertURLValuesToResourceCallOptions reads from the input *url.Values and assigns fields in the out *ResourceCallOptions
func CovertURLValuesToResourceCallOptions(in *url.Values, out *ResourceCallOptions, s conversion.Scope) error {
if values, ok := map[string][]string(*in)["path"]; ok && len(values) > 0 {
if err := runtime.Convert_Slice_string_To_string(&values, &out.Path, s); err != nil {
return err
}
} else {
out.Path = ""
}
return nil
}

func GetResourceCallOptionsOpenAPIDefinition() map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
"github.com/grafana/grafana-app-sdk/apiserver.ResourceCallOptions": common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ExternalNameFoo defines model for ExternalNameFoo.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
"path": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"foo"},
},
},
},
}
}
128 changes: 128 additions & 0 deletions apiserver/conversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package apiserver

import (
"bytes"
"fmt"

"github.com/grafana/grafana-app-sdk/resource"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// ResourceConverter is an interface which describes a type which can convert an object to and from its internal representation.
// Kubernetes API Server kind version conversion will always convert to and from the internal version when converting between versions,
// so converting from version v1 to v2 will go v1 -> internal -> v2, calling
// ToInternal(v1Obj, internalObj) then FromInternal(internalObj, v2Obj)
type ResourceConverter interface {
FromInternal(src any, dst any) error
ToInternal(src any, dst any) error
}

// TypedResourceConverter is an implementation of ResourceConverter which allows the user to work with typed inputs
type TypedResourceConverter[InternalType, ResourceType resource.Object] struct {
FromInternalFunc func(src InternalType, dst ResourceType) error
ToInternalFunc func(src ResourceType, dst InternalType) error
}

// FromInternal attempts to convert src into InternalType and dst into ResourceType (returning an error if either of
// these operations fail), and then calls FromInternalFunc using the converted types.
// If FromInternalFunc is nil, it will call DefaultConversionFunc instead.
func (t *TypedResourceConverter[InternalType, ResourceType]) FromInternal(src, dst any) error {
s, ok := src.(InternalType)
if !ok {
return fmt.Errorf("src is not of type InternalType")
}
d, ok := dst.(ResourceType)
if !ok {
return fmt.Errorf("dst is not of type ResourceType")
}
if t.FromInternalFunc == nil {
return DefaultConversionFunc(s, d)
}
return t.FromInternalFunc(s, d)
}

// ToInternal attempts to convert src into ResourceType and dst into InternalType (returning an error if either of
// these operations fail), and then calls ToInternalFunc using the converted types.
// If ToInternalFunc is nil, it will call DefaultConversionFunc instead.
func (t *TypedResourceConverter[InternalType, ResourceType]) ToInternal(src, dst any) error {
s, ok := src.(ResourceType)
if !ok {
return fmt.Errorf("src is not of type ResourceType")
}
d, ok := dst.(InternalType)
if !ok {
return fmt.Errorf("dst is not of type InternalType")
}
if t.FromInternalFunc == nil {
return DefaultConversionFunc(s, d)
}
return t.ToInternalFunc(s, d)
}

var codec = resource.NewJSONCodec()

// DefaultConversionFunc is a default conversion function which translates src into dst by using a resource.JSONCodec
// to write src to JSON, then read the JSON bytes into dst.
func DefaultConversionFunc(src, dst resource.Object) error {
buf := bytes.Buffer{}
err := codec.Write(&buf, src)
if err != nil {
return err
}
err = codec.Read(bytes.NewReader(buf.Bytes()), dst)
if err != nil {
return err
}
return nil
}

// GenericObjectConverter implements ResourceConverter and calls DefaultConversionFunc for its conversion process.
// It uses the GVK value to set GroupVersionKind when converting.
type GenericObjectConverter struct {
GVK schema.GroupVersionKind
}

// FromInternal attempts to convert src and dst into resource.Object, returning an error if this fails,
// then calls DefaultConversionFunc on the converted src and dst. It sets the GroupVersionKind on dst
// to the value of GenericObjectConverter.GVK.
func (g *GenericObjectConverter) FromInternal(src, dst any) error {
a, ok := src.(resource.Object)
if !ok {
return fmt.Errorf("src does not implement resource.Object")
}
b, ok := dst.(resource.Object)
if !ok {
return fmt.Errorf("dst does not implement resource.Object")
}
err := DefaultConversionFunc(a, b)
if err != nil {
return err
}
b.SetGroupVersionKind(g.GVK)
return nil
}

// ToInternal attempts to convert src and dst into resource.Object, returning an error if this fails,
// then calls DefaultConversionFunc on the converted src and dst. It sets the Group and Kind on dst
// to the value of GenericObjectConverter.GVK, and Version to runtime.APIVersionInternal.
func (g *GenericObjectConverter) ToInternal(src, dst any) error {
a, ok := src.(resource.Object)
if !ok {
return fmt.Errorf("src does not implement resource.Object")
}
b, ok := dst.(resource.Object)
if !ok {
return fmt.Errorf("dst does not implement resource.Object")
}
err := DefaultConversionFunc(a, b)
if err != nil {
return err
}
b.SetGroupVersionKind(schema.GroupVersionKind{
Group: g.GVK.Group,
Version: runtime.APIVersionInternal,
Kind: g.GVK.Kind,
})
return nil
}
Loading
Loading