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:
co-authored by
Michael Mandrus
parent
cbc84a802d
commit
41e523bde7
@@ -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}
|
||||
)
|
||||
@@ -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
Reference in New Issue
Block a user