K8s: Add example api service (#75911)

This commit is contained in:
Ryan McKinley
2023-10-06 11:55:22 -07:00
committed by GitHub
parent 0a50ca7231
commit 717a9dd616
19 changed files with 702 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
// +k8s:deepcopy-gen=package
// +k8s:openapi-gen=true
// +groupName=example.grafana.com
// The testing api is a dependency free service that we can use to experiment with
// api aggregation across multiple deployment models. Specifically:
// - standalone: running as part of the standard grafana build
// - aggregated: running as the target
package v0alpha1 // import "github.com/grafana/grafana/pkg/apis/example/v0alpha1"

View File

@@ -0,0 +1,89 @@
package v0alpha1
import (
common "k8s.io/kube-openapi/pkg/common"
spec "k8s.io/kube-openapi/pkg/validation/spec"
)
// NOTE: this must match the golang fully qualified name!
const kindKey = "github.com/grafana/grafana/pkg/apis/example/v0alpha1.RuntimeInfo"
func getOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
kindKey: schema_pkg_apis_example_v0alpha1_RuntimeInfo(ref),
}
}
func schema_pkg_apis_example_v0alpha1_RuntimeInfo(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"startupTime": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int64",
},
},
"buildVersion": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"buildCommit": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"enterpriseBuildCommit": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"buildBranch": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"buildStamp": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int64",
},
},
"enterprise": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
"packaging": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}

View File

@@ -0,0 +1,195 @@
package v0alpha1
import (
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
common "k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// GroupName is the group name for this API.
const GroupName = "example.grafana.com"
const VersionID = "v0alpha1" //
const APIVersion = GroupName + "/" + VersionID
var _ grafanaapiserver.APIGroupBuilder = (*TestingAPIBuilder)(nil)
// This is used just so wire has something unique to return
type TestingAPIBuilder struct {
codecs serializer.CodecFactory
}
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *TestingAPIBuilder {
if !features.IsEnabled(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
return nil // skip registration unless opting into experimental apis
}
builder := &TestingAPIBuilder{}
apiregistration.RegisterAPI(builder)
return builder
}
func (b *TestingAPIBuilder) GetGroupVersion() schema.GroupVersion {
return SchemeGroupVersion
}
func (b *TestingAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
err := AddToScheme(scheme)
if err != nil {
return err
}
return scheme.SetVersionPriority(SchemeGroupVersion)
}
func (b *TestingAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
optsGetter generic.RESTOptionsGetter,
) (*genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(GroupName, scheme, metav1.ParameterCodec, codecs)
storage := map[string]rest.Storage{}
storage["runtime"] = newDeploymentInfoStorage()
apiGroupInfo.VersionedResourcesStorageMap[VersionID] = storage
b.codecs = codecs
return &apiGroupInfo, nil
}
func (b *TestingAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
return getOpenAPIDefinitions
}
// Register additional routes with the server
func (b *TestingAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
return &grafanaapiserver.APIRoutes{
Root: []grafanaapiserver.APIRouteHandler{
{
Path: "/aaa",
Spec: &spec3.PathProps{
Summary: "an example at the root level",
Description: "longer description here?",
Get: &spec3.Operation{
OperationProps: spec3.OperationProps{
Parameters: []*spec3.Parameter{
{ParameterProps: spec3.ParameterProps{
Name: "a",
}},
},
Responses: &spec3.Responses{
ResponsesProps: spec3.ResponsesProps{
StatusCodeResponses: map[int]*spec3.Response{
200: {
ResponseProps: spec3.ResponseProps{
Description: "OK",
Content: map[string]*spec3.MediaType{
"text/plain": {
MediaTypeProps: spec3.MediaTypeProps{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
},
},
},
},
},
},
},
},
},
},
},
Handler: func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("Root level handler (aaa)"))
},
},
{
Path: "/bbb",
Spec: &spec3.PathProps{
Summary: "an example at the root level",
Description: "longer description here?",
Get: &spec3.Operation{
OperationProps: spec3.OperationProps{
Parameters: []*spec3.Parameter{
{ParameterProps: spec3.ParameterProps{
Name: "b",
}},
},
},
},
},
Handler: func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("Root level handler (bbb)"))
},
},
},
Namespace: []grafanaapiserver.APIRouteHandler{
{
Path: "/ccc",
Spec: &spec3.PathProps{
Summary: "an example at the root level",
Description: "longer description here?",
Get: &spec3.Operation{
OperationProps: spec3.OperationProps{
Parameters: []*spec3.Parameter{
{ParameterProps: spec3.ParameterProps{
Name: "a",
}},
},
},
},
},
Handler: func(w http.ResponseWriter, r *http.Request) {
info, ok := request.RequestInfoFrom(r.Context())
if !ok {
responsewriters.ErrorNegotiated(
apierrors.NewInternalError(fmt.Errorf("no RequestInfo found in the context")),
b.codecs, schema.GroupVersion{}, w, r,
)
return
}
_, _ = w.Write([]byte("Custom namespace route ccc: " + info.Namespace))
},
},
},
}
}
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: VersionID}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
// SchemeBuilder points to a list of functions added to Scheme.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
localSchemeBuilder = &SchemeBuilder
// AddToScheme is a common registration function for mapping packaged scoped group & version keys to a scheme.
AddToScheme = localSchemeBuilder.AddToScheme
)
// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&RuntimeInfo{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@@ -0,0 +1,69 @@
package v0alpha1
import (
"context"
"time"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/setting"
)
var (
_ rest.Scoper = (*staticStorage)(nil)
_ rest.SingularNameProvider = (*staticStorage)(nil)
_ rest.Lister = (*staticStorage)(nil)
_ rest.Storage = (*staticStorage)(nil)
)
type staticStorage struct {
info RuntimeInfo
}
func newDeploymentInfoStorage() *staticStorage {
return &staticStorage{
info: RuntimeInfo{
TypeMeta: metav1.TypeMeta{
APIVersion: APIVersion,
Kind: "DeploymentInfo",
},
BuildVersion: setting.BuildVersion,
BuildCommit: setting.BuildCommit,
BuildBranch: setting.BuildBranch,
EnterpriseBuildCommit: setting.EnterpriseBuildCommit,
BuildStamp: setting.BuildStamp,
IsEnterprise: setting.IsEnterprise,
Packaging: setting.Packaging,
StartupTime: time.Now().UnixMilli(),
},
}
}
func (s *staticStorage) New() runtime.Object {
return &RuntimeInfo{}
}
func (s *staticStorage) Destroy() {}
func (s *staticStorage) NamespaceScoped() bool {
return false
}
func (s *staticStorage) GetSingularName() string {
return "runtime"
}
func (s *staticStorage) NewList() runtime.Object {
return &RuntimeInfo{}
}
func (s *staticStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
return rest.NewDefaultTableConvertor(Resource("runtime")).ConvertToTable(ctx, object, tableOptions)
}
func (s *staticStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
return &s.info, nil
}

View File

@@ -0,0 +1,20 @@
package v0alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type RuntimeInfo struct {
metav1.TypeMeta `json:",inline"`
StartupTime int64 `json:"startupTime,omitempty"`
BuildVersion string `json:"buildVersion,omitempty"`
BuildCommit string `json:"buildCommit,omitempty"`
EnterpriseBuildCommit string `json:"enterpriseBuildCommit,omitempty"`
BuildBranch string `json:"buildBranch,omitempty"`
BuildStamp int64 `json:"buildStamp,omitempty"`
IsEnterprise bool `json:"enterprise,omitempty"`
Packaging string `json:"packaging,omitempty"`
}

View File

@@ -0,0 +1,37 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by deepcopy-gen. DO NOT EDIT.
package v0alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RuntimeInfo) DeepCopyInto(out *RuntimeInfo) {
*out = *in
out.TypeMeta = in.TypeMeta
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuntimeInfo.
func (in *RuntimeInfo) DeepCopy() *RuntimeInfo {
if in == nil {
return nil
}
out := new(RuntimeInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RuntimeInfo) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@@ -9,7 +9,6 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
common "k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
@@ -36,6 +35,10 @@ func RegisterAPIService(p playlist.Service, apiregistration grafanaapiserver.API
return builder
}
func (b *PlaylistAPIBuilder) GetGroupVersion() schema.GroupVersion {
return SchemeGroupVersion
}
func (b *PlaylistAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
err := AddToScheme(scheme)
if err != nil {
@@ -72,9 +75,8 @@ func (b *PlaylistAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinition
return getOpenAPIDefinitions
}
// Register additional routes with the server
func (b *PlaylistAPIBuilder) GetOpenAPIPostProcessor() func(*spec3.OpenAPI) (*spec3.OpenAPI, error) {
return nil
func (b *PlaylistAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
return nil // no custom API routes
}
// SchemeGroupVersion is group version used to register these objects

View File

@@ -3,9 +3,13 @@ package apis
import (
"github.com/google/wire"
examplev0alpha1 "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
playlistsv0alpha1 "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
)
// WireSet is the list of all services
// NOTE: you must also register the service in: pkg/registry/apis/apis.go
var WireSet = wire.NewSet(
playlistsv0alpha1.RegisterAPIService,
examplev0alpha1.RegisterAPIService,
)