FeatureToggles: Removed deprecated code (#111750)
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
package feature_toggle_api
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
GROUP = "featuretoggle.grafana.app"
|
||||
VERSION = "v0alpha1"
|
||||
APIVERSION = GROUP + "/" + VERSION
|
||||
)
|
||||
|
||||
// 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"`
|
||||
|
||||
// Expression to determine if the flag is enabled by default -- can only be "true" for toggles that are public preview, generally available, or deprecated
|
||||
Expression string `json:"expression,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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type ResolvedToggleState struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// The user is allowed to edit feature toggles on this system
|
||||
AllowEditing bool `json:"allowEditing,omitempty"`
|
||||
|
||||
// The system has changes that require still require a restart
|
||||
RestartRequired bool `json:"restartRequired,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"`
|
||||
|
||||
// The feature toggle stage
|
||||
Stage string `json:"stage"`
|
||||
|
||||
// Is the flag enabled
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Can this flag be updated
|
||||
Writeable bool `json:"writeable"`
|
||||
|
||||
// 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"`
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -14,10 +13,7 @@ var (
|
||||
)
|
||||
|
||||
type FeatureManager struct {
|
||||
isDevMod bool
|
||||
restartRequired bool
|
||||
|
||||
Settings setting.FeatureMgmtSettings
|
||||
isDevMod bool
|
||||
|
||||
flags map[string]*FeatureFlag
|
||||
enabled map[string]bool // only the "on" values
|
||||
@@ -131,66 +127,6 @@ func (fm *FeatureManager) GetFlags() []FeatureFlag {
|
||||
return v
|
||||
}
|
||||
|
||||
// isFeatureEditingAllowed checks if the backend is properly configured to allow feature toggle changes from the UI
|
||||
func (fm *FeatureManager) IsFeatureEditingAllowed() bool {
|
||||
return fm.Settings.AllowEditing && fm.Settings.UpdateWebhook != ""
|
||||
}
|
||||
|
||||
// indicate if a change has been made (not that accurate, but better than nothing)
|
||||
func (fm *FeatureManager) IsRestartRequired() bool {
|
||||
return fm.restartRequired
|
||||
}
|
||||
|
||||
// Flags that can be edited
|
||||
func (fm *FeatureManager) IsEditableFromAdminPage(key string) bool {
|
||||
flag, ok := fm.flags[key]
|
||||
if !ok ||
|
||||
!fm.IsFeatureEditingAllowed() ||
|
||||
!flag.AllowSelfServe ||
|
||||
flag.Name == FlagFeatureToggleAdminPage {
|
||||
return false
|
||||
}
|
||||
return flag.Stage == FeatureStageGeneralAvailability ||
|
||||
flag.Stage == FeatureStagePublicPreview ||
|
||||
flag.Stage == FeatureStageDeprecated
|
||||
}
|
||||
|
||||
// Flags that should not be shown in the UI (regardless of their state)
|
||||
func (fm *FeatureManager) IsHiddenFromAdminPage(key string, lenient bool) bool {
|
||||
_, hide := fm.Settings.HiddenToggles[key]
|
||||
flag, ok := fm.flags[key]
|
||||
if !ok || flag.HideFromAdminPage || hide {
|
||||
return true // unknown flag (should we show it as a warning!)
|
||||
}
|
||||
|
||||
// Explicitly hidden from configs
|
||||
_, found := fm.Settings.HiddenToggles[key]
|
||||
if found {
|
||||
return true
|
||||
}
|
||||
if lenient {
|
||||
return false
|
||||
}
|
||||
|
||||
return flag.Stage == FeatureStageUnknown ||
|
||||
flag.Stage == FeatureStageExperimental ||
|
||||
flag.Stage == FeatureStagePrivatePreview
|
||||
}
|
||||
|
||||
// Get the flags that were explicitly set on startup
|
||||
func (fm *FeatureManager) GetStartupFlags() map[string]bool {
|
||||
return fm.startup
|
||||
}
|
||||
|
||||
// Perhaps expose the flag warnings
|
||||
func (fm *FeatureManager) GetWarning() map[string]string {
|
||||
return fm.warnings
|
||||
}
|
||||
|
||||
func (fm *FeatureManager) SetRestartRequired() {
|
||||
fm.restartRequired = true
|
||||
}
|
||||
|
||||
// ############# Test Functions #############
|
||||
|
||||
func WithFeatures(spec ...any) FeatureToggles {
|
||||
@@ -223,33 +159,3 @@ func WithManager(spec ...any) *FeatureManager {
|
||||
|
||||
return &FeatureManager{enabled: enabled, flags: features, startup: enabled, warnings: map[string]string{}}
|
||||
}
|
||||
|
||||
// WithFeatureManager is used to define feature toggle manager for testing.
|
||||
// It should be used when your test feature toggles require metadata beyond `Name` and `Enabled`.
|
||||
// You should provide a feature toggle Name at a minimum.
|
||||
func WithFeatureManager(cfg setting.FeatureMgmtSettings, flags []*FeatureFlag, disabled ...string) *FeatureManager {
|
||||
count := len(flags)
|
||||
features := make(map[string]*FeatureFlag, count)
|
||||
enabled := make(map[string]bool, count)
|
||||
|
||||
dis := make(map[string]bool)
|
||||
for _, v := range disabled {
|
||||
dis[v] = true
|
||||
}
|
||||
|
||||
for _, f := range flags {
|
||||
if f.Name == "" {
|
||||
continue
|
||||
}
|
||||
features[f.Name] = f
|
||||
enabled[f.Name] = !dis[f.Name]
|
||||
}
|
||||
|
||||
return &FeatureManager{
|
||||
Settings: cfg,
|
||||
enabled: enabled,
|
||||
flags: features,
|
||||
startup: enabled,
|
||||
warnings: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
|
||||
featuretoggle "github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1"
|
||||
featuretoggleapi "github.com/grafana/grafana/pkg/services/featuremgmt/feature_toggle_api"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -332,15 +332,6 @@ var (
|
||||
RequiresRestart: true,
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "featureToggleAdminPage",
|
||||
Description: "Enable admin page for managing feature toggles from the Grafana front-end. Grafana Cloud only.",
|
||||
Stage: FeatureStageExperimental,
|
||||
FrontendOnly: false,
|
||||
Owner: grafanaBackendServicesSquad,
|
||||
RequiresRestart: true,
|
||||
HideFromDocs: true,
|
||||
},
|
||||
{
|
||||
Name: "awsAsyncQueryCaching",
|
||||
Description: "Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled",
|
||||
@@ -2073,8 +2064,8 @@ var (
|
||||
var f embed.FS
|
||||
|
||||
// Get the cached feature list (exposed as a k8s resource)
|
||||
func GetEmbeddedFeatureList() (featuretoggle.FeatureList, error) {
|
||||
features := featuretoggle.FeatureList{}
|
||||
func GetEmbeddedFeatureList() (featuretoggleapi.FeatureList, error) {
|
||||
features := featuretoggleapi.FeatureList{}
|
||||
body, err := f.ReadFile("toggles_gen.json")
|
||||
if err == nil {
|
||||
err = json.Unmarshal(body, &features)
|
||||
|
||||
@@ -27,7 +27,6 @@ func ProvideManagerService(cfg *setting.Cfg) (*FeatureManager, error) {
|
||||
enabled: make(map[string]bool),
|
||||
startup: make(map[string]bool),
|
||||
warnings: make(map[string]string),
|
||||
Settings: cfg.FeatureManagement,
|
||||
log: log.New("featuremgmt"),
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ datasourceAPIServers,experimental,@grafana/grafana-app-platform-squad,false,true
|
||||
grafanaAPIServerWithExperimentalAPIs,experimental,@grafana/grafana-app-platform-squad,true,true,false
|
||||
provisioning,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
grafanaAPIServerEnsureKubectlAccess,experimental,@grafana/grafana-app-platform-squad,true,true,false
|
||||
featureToggleAdminPage,experimental,@grafana/grafana-backend-services-squad,false,true,false
|
||||
awsAsyncQueryCaching,GA,@grafana/aws-datasources,false,false,false
|
||||
queryCacheRequestDeduplication,experimental,@grafana/grafana-operator-experience-squad,false,false,false
|
||||
permissionsFilterRemoveSubquery,experimental,@grafana/search-and-storage,false,false,false
|
||||
|
||||
|
@@ -179,10 +179,6 @@ const (
|
||||
// Start an additional https handler and write kubectl options
|
||||
FlagGrafanaAPIServerEnsureKubectlAccess = "grafanaAPIServerEnsureKubectlAccess"
|
||||
|
||||
// FlagFeatureToggleAdminPage
|
||||
// Enable admin page for managing feature toggles from the Grafana front-end. Grafana Cloud only.
|
||||
FlagFeatureToggleAdminPage = "featureToggleAdminPage"
|
||||
|
||||
// FlagAwsAsyncQueryCaching
|
||||
// Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled
|
||||
FlagAwsAsyncQueryCaching = "awsAsyncQueryCaching"
|
||||
|
||||
@@ -1577,6 +1577,7 @@
|
||||
"name": "featureToggleAdminPage",
|
||||
"resourceVersion": "1758022099771",
|
||||
"creationTimestamp": "2023-07-18T20:43:32Z",
|
||||
"deletionTimestamp": "2025-09-29T13:36:16Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-09-16 11:28:19.771156 +0000 UTC"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
featuretoggleapi "github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1"
|
||||
featuretoggleapi "github.com/grafana/grafana/pkg/services/featuremgmt/feature_toggle_api"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt/strcase"
|
||||
)
|
||||
|
||||
|
||||
@@ -44,15 +44,6 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
Text: "Organizations", SubTitle: "Isolated instances of Grafana running on the same server", Id: "global-orgs", Url: s.cfg.AppSubURL + "/admin/orgs", Icon: "building",
|
||||
})
|
||||
}
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagFeatureToggleAdminPage) && hasAccess(ac.EvalPermission(ac.ActionFeatureManagementRead)) {
|
||||
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
|
||||
Text: "Feature toggles",
|
||||
SubTitle: "View and edit feature toggles",
|
||||
Id: "feature-toggles",
|
||||
Url: s.cfg.AppSubURL + "/admin/featuretoggles",
|
||||
Icon: "toggle-on",
|
||||
})
|
||||
}
|
||||
if hasAccess(cloudmigration.MigrationAssistantAccess) && s.features.IsEnabled(ctx, featuremgmt.FlagOnPremToCloudMigrations) {
|
||||
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
|
||||
Text: "Migrate to Grafana Cloud",
|
||||
|
||||
Reference in New Issue
Block a user