K8s/FeatureFlags: Add an apiserver to manage feature flags (dev only) (#80501)

* add deployment registry API cloud only

* update versions

* add feature flag endpoints

* use helpers

* merge main

* update AllowSelfServie and re-run code gen

* fix package name

* add allowselfserve flag to payload

* remove config

* update list api to return the full registry including states

* change enabled check

* fix compile error

* add feature toggle and split path in frontend

* changes

* with status

* add more status/state

* add back config thing

* add back config thing

* merge main

* merge main

* now on the /current api endpoint

* now on the /current api endpoint

* drop frontend changes

* change group name to featuretoggle (singular)

* use the same settings

* now with patch

* more common refs

* more common refs

* WIP actually do the webhook

* fix comment

* fewer imports

* registe standalone

* one less file

* fix singular name

---------

Co-authored-by: Michael Mandrus <michael.mandrus@grafana.com>
This commit is contained in:
Ryan McKinley
2024-01-17 21:32:44 -08:00
committed by GitHub
co-authored by Michael Mandrus
parent cbc84a802d
commit 41e523bde7
25 changed files with 4058 additions and 120 deletions
+5
View File
@@ -0,0 +1,5 @@
// +k8s:deepcopy-gen=package
// +k8s:openapi-gen=true
// +groupName=featuretoggle.grafana.app
package v0alpha1 // import "github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1"
@@ -0,0 +1,33 @@
package v0alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
)
const (
GROUP = "featuretoggle.grafana.app"
VERSION = "v0alpha1"
APIVERSION = GROUP + "/" + VERSION
)
// FeatureResourceInfo represents each feature that may have a toggle
var FeatureResourceInfo = common.NewResourceInfo(GROUP, VERSION,
"features", "feature", "Feature",
func() runtime.Object { return &Feature{} },
func() runtime.Object { return &FeatureList{} },
)
// TogglesResourceInfo represents the actual configuration
var TogglesResourceInfo = common.NewResourceInfo(GROUP, VERSION,
"featuretoggles", "featuretoggle", "FeatureToggles",
func() runtime.Object { return &FeatureToggles{} },
func() runtime.Object { return &FeatureTogglesList{} },
)
var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION}
)
+111
View File
@@ -0,0 +1,111 @@
package v0alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
)
// Feature represents a feature in development and information about that feature
// It does *not* know the status, only defines properties about the feature itself
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type Feature struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec FeatureSpec `json:"spec,omitempty"`
}
type FeatureSpec struct {
// The feature description
Description string `json:"description"`
// Indicates the features level of stability
Stage string `json:"stage"`
// The team who owns this feature development
Owner string `json:"codeowner,omitempty"`
// Enabled by default for version >=
EnabledVersion string `json:"enabledVersion,omitempty"`
// Must be run using in development mode (early dev)
RequiresDevMode bool `json:"requiresDevMode,omitempty"`
// The flab behavior only effects frontend -- it is not used in the backend
FrontendOnly bool `json:"frontend,omitempty"`
// The flag is used at startup, so any change requires a restart
RequiresRestart bool `json:"requiresRestart,omitempty"`
// Allow cloud users to set the values in UI
AllowSelfServe bool `json:"allowSelfServe,omitempty"`
// Do not show the value in the UI
HideFromAdminPage bool `json:"hideFromAdminPage,omitempty"`
// Do not show the value in docs
HideFromDocs bool `json:"hideFromDocs,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type FeatureList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Feature `json:"items,omitempty"`
}
// FeatureToggles define the feature state
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type FeatureToggles struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// The configured toggles. Note this may include unknown fields
Spec map[string]bool `json:"spec"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type FeatureTogglesList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []FeatureToggles `json:"items,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ResolvedToggleState struct {
metav1.TypeMeta `json:",inline"`
// Can any flag be updated
Writeable bool `json:"writeable,omitempty"`
// The currently enabled flags
Enabled map[string]bool `json:"enabled,omitempty"`
// Details on the current status
Toggles []ToggleStatus `json:"toggles,omitempty"`
}
type ToggleStatus struct {
// The feature toggle name
Name string `json:"name"`
// The flag description
Description string `json:"description,omitempty"`
// Is the flag enabled
Enabled bool `json:"enabled"`
// Can this flag be updated
Writeable bool `json:"writeable,omitempty"`
// Where was the value configured
// eg: startup | tenant|org | user | browser
// missing means default
Source *common.ObjectReference `json:"source,omitempty"`
// eg: unknown flag
Warning string `json:"warning,omitempty"`
}
@@ -0,0 +1,215 @@
//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 (
commonv0alpha1 "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
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 *Feature) DeepCopyInto(out *Feature) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Feature.
func (in *Feature) DeepCopy() *Feature {
if in == nil {
return nil
}
out := new(Feature)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Feature) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FeatureList) DeepCopyInto(out *FeatureList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Feature, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureList.
func (in *FeatureList) DeepCopy() *FeatureList {
if in == nil {
return nil
}
out := new(FeatureList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FeatureList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FeatureSpec) DeepCopyInto(out *FeatureSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureSpec.
func (in *FeatureSpec) DeepCopy() *FeatureSpec {
if in == nil {
return nil
}
out := new(FeatureSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FeatureToggles) DeepCopyInto(out *FeatureToggles) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
if in.Spec != nil {
in, out := &in.Spec, &out.Spec
*out = make(map[string]bool, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureToggles.
func (in *FeatureToggles) DeepCopy() *FeatureToggles {
if in == nil {
return nil
}
out := new(FeatureToggles)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FeatureToggles) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FeatureTogglesList) DeepCopyInto(out *FeatureTogglesList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]FeatureToggles, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureTogglesList.
func (in *FeatureTogglesList) DeepCopy() *FeatureTogglesList {
if in == nil {
return nil
}
out := new(FeatureTogglesList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FeatureTogglesList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResolvedToggleState) DeepCopyInto(out *ResolvedToggleState) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Enabled != nil {
in, out := &in.Enabled, &out.Enabled
*out = make(map[string]bool, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Toggles != nil {
in, out := &in.Toggles, &out.Toggles
*out = make([]ToggleStatus, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedToggleState.
func (in *ResolvedToggleState) DeepCopy() *ResolvedToggleState {
if in == nil {
return nil
}
out := new(ResolvedToggleState)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ResolvedToggleState) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ToggleStatus) DeepCopyInto(out *ToggleStatus) {
*out = *in
if in.Source != nil {
in, out := &in.Source, &out.Source
*out = new(commonv0alpha1.ObjectReference)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ToggleStatus.
func (in *ToggleStatus) DeepCopy() *ToggleStatus {
if in == nil {
return nil
}
out := new(ToggleStatus)
in.DeepCopyInto(out)
return out
}
@@ -0,0 +1,19 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by defaulter-gen. DO NOT EDIT.
package v0alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}
File diff suppressed because it is too large Load Diff