K8s: Add generic support for status (#92378)

This commit is contained in:
Todd Treece 2024-08-27 20:45:04 -04:00 committed by GitHub
parent 39fda067cf
commit 85ef26a85d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 233 additions and 34 deletions

View File

@ -92,6 +92,10 @@ type GrafanaMetaAccessor interface {
GetOriginTimestamp() (*time.Time, error)
GetSpec() (any, error)
SetSpec(any) error
GetStatus() (any, error)
SetStatus(any) error
// Find a title in the object
// This will reflect the object and try to get:
@ -504,6 +508,36 @@ func (m *grafanaMetaAccessor) GetSpec() (spec any, err error) {
return
}
func (m *grafanaMetaAccessor) SetSpec(s any) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("error setting spec")
}
}()
m.r.FieldByName("Spec").Set(reflect.ValueOf(s))
return
}
func (m *grafanaMetaAccessor) GetStatus() (status any, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("error reading status")
}
}()
status = m.r.FieldByName("Status").Interface()
return
}
func (m *grafanaMetaAccessor) SetStatus(s any) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("error setting status")
}
}()
m.r.FieldByName("Status").Set(reflect.ValueOf(s))
return
}
func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string {
// look for Spec.Title or Spec.Name
spec := m.r.FieldByName("Spec")

View File

@ -14,18 +14,22 @@ require (
k8s.io/component-base v0.31.0
k8s.io/klog/v2 v2.130.1
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/structured-merge-diff/v4 v4.4.1
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.4 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
@ -37,6 +41,7 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@ -56,8 +61,14 @@ require (
go.etcd.io/etcd/api/v3 v3.5.14 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect
go.etcd.io/etcd/client/v3 v3.5.14 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.28.0 // indirect
@ -78,7 +89,7 @@ require (
k8s.io/api v0.31.0 // indirect
k8s.io/client-go v0.31.0 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

View File

@ -35,6 +35,7 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=

View File

@ -0,0 +1,57 @@
package generic
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// NewStatusREST makes a RESTStorage for status that has more limited options.
// It is based on the original REST so that we can share the same underlying store
func NewStatusREST(store *genericregistry.Store, strategy rest.UpdateResetFieldsStrategy) *StatusREST {
statusStore := *store
statusStore.CreateStrategy = nil
statusStore.DeleteStrategy = nil
statusStore.UpdateStrategy = strategy
statusStore.ResetFieldsStrategy = strategy
return &StatusREST{store: &statusStore}
}
// StatusREST implements the REST endpoint for changing the status of an DataPlaneService.
type StatusREST struct {
store *genericregistry.Store
}
var _ = rest.Patcher(&StatusREST{})
// New creates a new DataPlaneService object.
func (r *StatusREST) New() runtime.Object {
return r.store.NewFunc()
}
// Destroy cleans up resources on shutdown.
func (r *StatusREST) Destroy() {
// Given that underlying store is shared with REST,
// we don't destroy it here explicitly.
}
// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return r.store.Get(ctx, name, options)
}
// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -3,57 +3,155 @@ package generic
import (
"context"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
type genericStrategy struct {
runtime.ObjectTyper
names.NameGenerator
gv schema.GroupVersion
}
// NewStrategy creates and returns a genericStrategy instance.
func NewStrategy(typer runtime.ObjectTyper) genericStrategy {
return genericStrategy{typer, names.SimpleNameGenerator}
func NewStrategy(typer runtime.ObjectTyper, gv schema.GroupVersion) *genericStrategy {
return &genericStrategy{typer, names.SimpleNameGenerator, gv}
}
// NamespaceScoped returns true because all Generic resources must be within a namespace.
func (genericStrategy) NamespaceScoped() bool {
func (g *genericStrategy) NamespaceScoped() bool {
return true
}
func (genericStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {}
func (g *genericStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
fieldpath.APIVersion(g.gv.String()): fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
func (genericStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {}
func (g *genericStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {}
func (genericStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
func (g *genericStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
oldMeta, err := utils.MetaAccessor(old)
if err != nil {
return
}
status, err := oldMeta.GetStatus()
if err != nil {
return
}
newMeta, err := utils.MetaAccessor(obj)
if err != nil {
return
}
_ = newMeta.SetStatus(status)
}
func (g *genericStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
return field.ErrorList{}
}
// WarningsOnCreate returns warnings for the creation of the given object.
func (genericStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil }
func (g *genericStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
return nil
}
func (genericStrategy) AllowCreateOnUpdate() bool {
func (g *genericStrategy) AllowCreateOnUpdate() bool {
return true
}
func (genericStrategy) AllowUnconditionalUpdate() bool {
func (g *genericStrategy) AllowUnconditionalUpdate() bool {
return true
}
func (genericStrategy) Canonicalize(obj runtime.Object) {}
func (g *genericStrategy) Canonicalize(obj runtime.Object) {}
func (genericStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
func (g *genericStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return field.ErrorList{}
}
// WarningsOnUpdate returns warnings for the given update.
func (genericStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
func (g *genericStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nil
}
type genericStatusStrategy struct {
runtime.ObjectTyper
names.NameGenerator
gv schema.GroupVersion
}
// NewStatusStrategy creates a new genericStatusStrategy.
func NewStatusStrategy(typer runtime.ObjectTyper, gv schema.GroupVersion) *genericStatusStrategy {
return &genericStatusStrategy{typer, names.SimpleNameGenerator, gv}
}
func (g *genericStatusStrategy) NamespaceScoped() bool {
return true
}
func (g *genericStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
fieldpath.APIVersion(g.gv.String()): fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("metadata"),
),
}
return fields
}
func (g *genericStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
oldMeta, err := utils.MetaAccessor(old)
if err != nil {
return
}
newMeta, err := utils.MetaAccessor(obj)
if err != nil {
return
}
newMeta.SetAnnotations(oldMeta.GetAnnotations())
newMeta.SetLabels(oldMeta.GetLabels())
newMeta.SetFinalizers(oldMeta.GetFinalizers())
newMeta.SetOwnerReferences(oldMeta.GetOwnerReferences())
}
func (g *genericStatusStrategy) AllowCreateOnUpdate() bool {
return false
}
func (g *genericStatusStrategy) AllowUnconditionalUpdate() bool {
return false
}
// Canonicalize normalizes the object after validation.
func (g *genericStatusStrategy) Canonicalize(obj runtime.Object) {
}
// ValidateUpdate validates an update of genericStatusStrategy.
func (g *genericStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return field.ErrorList{}
}
// WarningsOnUpdate returns warnings for the given update.
func (g *genericStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nil
}

View File

@ -35,7 +35,7 @@ func NewStorage(
tableConverter: resourceInfo.TableConverter(),
}
if optsGetter != nil && dualWriteBuilder != nil {
strategy := grafanaregistry.NewStrategy(scheme)
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
s := &genericregistry.Store{
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,

View File

@ -41,7 +41,7 @@ func NewStorage(
tableConverter: resourceInfo.TableConverter(),
}
if optsGetter != nil && dualWriteBuilder != nil {
strategy := grafanaregistry.NewStrategy(scheme)
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
s := &genericregistry.Store{
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,

View File

@ -47,7 +47,7 @@ func (s *dashboardStorage) newStore(scheme *runtime.Scheme, defaultOptsGetter ge
defaultOpts.StorageConfig.Config,
)
strategy := grafanaregistry.NewStrategy(scheme)
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
store := &genericregistry.Store{
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,

View File

@ -16,8 +16,8 @@ type storage struct {
}
func newStorage(scheme *runtime.Scheme) (*storage, error) {
strategy := grafanaregistry.NewStrategy(scheme)
resourceInfo := dashboard.DashboardResourceInfo
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
store := &genericregistry.Store{
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,

View File

@ -5,7 +5,6 @@ import (
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
)
@ -17,16 +16,15 @@ type storage struct {
}
func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter, legacy *legacyStorage) (*storage, error) {
strategy := grafanaregistry.NewStrategy(scheme)
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
resource := v0alpha1.FolderResourceInfo
store := &genericregistry.Store{
NewFunc: resource.NewFunc,
NewListFunc: resource.NewListFunc,
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
PredicateFunc: grafanaregistry.Matcher,
DefaultQualifiedResource: resource.GroupResource(),
DefaultQualifiedResource: resourceInfo.GroupResource(),
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
TableConvertor: legacy.tableConverter,

View File

@ -17,9 +17,9 @@ type storage struct {
}
func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*storage, error) {
strategy := grafanaregistry.NewStrategy(scheme)
resourceInfo := peakq.QueryTemplateResourceInfo
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
store := &genericregistry.Store{
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,

View File

@ -17,7 +17,7 @@ type storage struct {
}
func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter, legacy *legacyStorage) (*storage, error) {
strategy := grafanaregistry.NewStrategy(scheme)
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
resource := playlist.PlaylistResourceInfo
store := &genericregistry.Store{

View File

@ -22,9 +22,8 @@ type storage struct {
}
func newScopeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*storage, error) {
strategy := grafanaregistry.NewStrategy(scheme)
resourceInfo := scope.ScopeResourceInfo
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
store := &genericregistry.Store{
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,
@ -46,9 +45,9 @@ func newScopeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGette
}
func newScopeDashboardBindingStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*storage, error) {
strategy := grafanaregistry.NewStrategy(scheme)
resourceInfo := scope.ScopeDashboardBindingResourceInfo
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
store := &genericregistry.Store{
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,
@ -66,13 +65,14 @@ func newScopeDashboardBindingStorage(scheme *runtime.Scheme, optsGetter generic.
if err := store.CompleteWithOptions(options); err != nil {
return nil, err
}
return &storage{Store: store}, nil
}
func newScopeNodeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*storage, error) {
strategy := grafanaregistry.NewStrategy(scheme)
resourceInfo := scope.ScopeNodeResourceInfo
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
store := &genericregistry.Store{
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,

View File

@ -17,9 +17,9 @@ type storage struct {
}
func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*storage, error) {
strategy := grafanaregistry.NewStrategy(scheme)
resourceInfo := service.ExternalNameResourceInfo
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
store := &genericregistry.Store{
NewFunc: resourceInfo.NewFunc,
NewListFunc: resourceInfo.NewListFunc,