nd002test/on-premise/main/rancher_v2.9.1/source/rancher-2.9.1/pkg/features/feature.go

355 lines
9.1 KiB
Go

package features
import (
"fmt"
"strconv"
"strings"
v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
managementv3 "github.com/rancher/rancher/pkg/generated/controllers/management.cattle.io/v3"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
features = make(map[string]*Feature)
// Features, ex.: ClusterRandomName = newFeature("cluster-randomizer", "Randomizes clusters.", false, false)
UnsupportedStorageDrivers = newFeature(
"unsupported-storage-drivers",
"Allows the use of types for storage providers and provisioners that are not enabled by default.",
false,
true,
true)
IstioVirtualServiceUI = newFeature(
"istio-virtual-service-ui",
"Exposes a UI that enables users to create, read, update and delete virtual services and destination rules, which are traffic management features of Istio.",
true,
true,
true)
MCM = newFeature(
"multi-cluster-management",
"Multi-cluster provisioning and management of Kubernetes clusters.",
true,
false,
true)
MCMAgent = newFeature(
"multi-cluster-management-agent",
"Run downstream controllers for multi-cluster management.",
false,
false,
false)
Fleet = newFeature(
"fleet",
"Install Fleet when starting Rancher",
true,
false,
true)
Gitops = newFeature(
"continuous-delivery",
"Gitops components in fleet",
true,
false,
true)
Auth = newFeature(
"auth",
"Enable authentication",
true,
false,
false)
EmbeddedClusterAPI = newFeature(
"embedded-cluster-api",
"Enable a Rancher-managed instance of cluster-api core controller",
true,
false,
false)
RKE2 = newFeature(
"rke2",
"Enable provisioning of RKE2",
true,
false,
true)
Legacy = newFeature(
"legacy",
"Enable legacy features",
false,
true,
true)
ProvisioningV2 = newFeature(
"provisioningv2",
"Enable cluster-api based provisioning framework",
true,
false,
false)
TokenHashing = newFeature(
"token-hashing",
"Enable one way hashing of tokens. Once enabled token hashing can not be disabled",
false,
true,
true)
Harvester = newFeature(
"harvester",
"Enable Harvester integration, with ability to import and manage Harvester clusters",
true,
true,
true)
RKE1CustomNodeCleanup = newFeature(
"rke1-custom-node-cleanup",
"Enable cleanup RKE1 custom cluster nodes when they are deleted",
true,
true,
true)
HarvesterBaremetalContainerWorkload = newFeature(
"harvester-baremetal-container-workload",
"[Experimental]: Deploy container workloads to underlying harvester cluster",
false,
true,
true)
ProvisioningV2FleetWorkspaceBackPopulation = newFeature(
"provisioningv2-fleet-workspace-back-population",
"[Experimental]: Allow Fleet workspace name to be changed on clusters administrated by provisioning v2",
false,
false,
true)
UIExtension = newFeature(
"uiextension",
"Enable UI Extensions when starting Rancher",
true,
false,
true,
)
UISQLCache = newFeature(
"ui-sql-cache",
"[Experimental]: Enable SQLite-backed caching to improve performance and provide additional UI sorting/filtering features.",
false,
false,
true)
RKE1UI = newFeature(
"rke1-ui",
"Enable RKE1 provisioning in the Rancher UI",
true,
true,
true)
)
type Feature struct {
name string
description string
// val is the effective value- it is equal to default until explicitly changed.
// The order of precedence is lockedValue > value > default
val bool
// default value of feature
def bool
// if a feature is not dynamic, then rancher must be restarted when the value is changed
dynamic bool
// Whether we should install this feature or assume something else will install and manage the Feature CR
install bool
}
// InitializeFeatures updates feature default if given valid --features flag and creates/updates necessary features in k8s
func InitializeFeatures(featuresClient managementv3.FeatureClient, featureArgs string) {
// applies any default values assigned in --features flag to feature map
if err := applyArgumentDefaults(featureArgs); err != nil {
logrus.Errorf("failed to apply feature args: %v", err)
}
if featuresClient == nil {
return
}
// external-rules feature flag was removed in 2.9. We need to delete it for users upgrading from 2.8.
err := featuresClient.Delete("external-rules", &metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
logrus.Errorf("unable to delete external-rules feature: %v", err)
}
// creates any features in map that do not exist, updates features with new default value
for key, f := range features {
featureState, err := featuresClient.Get(key, metav1.GetOptions{})
if err != nil {
if !errors.IsNotFound(err) {
logrus.Errorf("unable to retrieve feature %s in initialize features: %v", f.name, err)
}
if f.install {
// value starts off as nil, that way rancher can determine if value has been manually assigned
newFeature := &v3.Feature{
ObjectMeta: metav1.ObjectMeta{
Name: f.name,
},
Spec: v3.FeatureSpec{
Value: nil,
},
Status: v3.FeatureStatus{
Default: f.def,
Dynamic: f.dynamic,
Description: f.description,
},
}
if _, err := featuresClient.Create(newFeature); err != nil {
logrus.Errorf("unable to create feature %s in initialize features: %v", f.name, err)
}
}
} else {
newFeatureState := featureState.DeepCopy()
// checks if default value has changed
if featureState.Status.Default != f.def {
newFeatureState.Status.Default = f.def
}
// checks if developer has changed dynamic value from previous rancher version
if featureState.Status.Dynamic != f.dynamic {
newFeatureState.Status.Dynamic = f.dynamic
}
// checks if developer has changed description value from previous rancher version
if featureState.Status.Description != f.description {
newFeatureState.Status.Description = f.description
}
newFeatureState, err = featuresClient.Update(newFeatureState)
if err != nil {
logrus.Errorf("unable to update feature %s in initialize features: %v", f.name, err)
continue
}
if newFeatureState.Status.LockedValue != nil {
f.Set(*newFeatureState.Status.LockedValue)
continue
}
if featureState.Spec.Value == nil {
continue
}
if *featureState.Spec.Value == f.val {
continue
}
f.Set(*featureState.Spec.Value)
}
}
}
func SetFeature(featuresClient managementv3.FeatureClient, featureName string, value bool) error {
if featuresClient == nil {
return nil
}
featureState, err := featuresClient.Get(featureName, metav1.GetOptions{})
if err != nil {
return err
}
featureState.Spec.Value = &[]bool{value}[0]
if _, err = featuresClient.Update(featureState); err != nil {
return err
}
return nil
}
// applyArgumentDefaults reads the features arguments and uses their values to overwrite
// the corresponding feature default value
func applyArgumentDefaults(featureArgs string) error {
if featureArgs == "" {
return nil
}
formattingError := fmt.Errorf("feature argument [%s] should be of the form \"features=feature1=bool,feature2=bool\"", featureArgs)
args := strings.Split(featureArgs, ",")
applyFeatureDefaults := make(map[string]bool)
for _, feature := range args {
featureSet := strings.Split(feature, "=")
if len(featureSet) != 2 {
return formattingError
}
key := featureSet[0]
if features[key] == nil {
return fmt.Errorf("\"%s\" is not a valid feature", key)
}
value, err := strconv.ParseBool(featureSet[1])
if err != nil {
return formattingError
}
applyFeatureDefaults[key] = value
}
// only want to apply defaults once all args have been parsed and validated
for k, v := range applyFeatureDefaults {
features[k].def = v
features[k].val = v
}
return nil
}
// Enabled returns whether the feature is enabled
func (f *Feature) Enabled() bool {
return f.val
}
// Disable will disable a feature such that regardless of the user's choice it will always be false
func (f *Feature) Disable() {
f.val = false
f.def = false
delete(features, f.name)
}
// Dynamic returns whether the feature is dynamic. Rancher must be restarted when
// a non-dynamic feature's effective value is changed.
func (f *Feature) Dynamic() bool {
return f.dynamic
}
func (f *Feature) Set(val bool) {
f.val = val
}
func (f *Feature) Name() string {
return f.name
}
func GetFeatureByName(name string) *Feature {
return features[name]
}
func IsEnabled(feature *v3.Feature) bool {
if feature == nil {
return false
}
if feature.Status.LockedValue != nil {
return *feature.Status.LockedValue
}
if feature.Spec.Value == nil {
return feature.Status.Default
}
return *feature.Spec.Value
}
// newFeature adds feature to the global feature map
func newFeature(name, description string, def, dynamic, install bool) *Feature {
feature := &Feature{
name: name,
description: description,
def: def,
val: def,
dynamic: dynamic,
install: install,
}
// feature will be stored in feature map, features contained in feature
// map will then be initialized
features[name] = feature
return feature
}