355 lines
9.1 KiB
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
|
|
}
|