Skip to content
This repository has been archived by the owner on Oct 10, 2023. It is now read-only.

Commit

Permalink
Update feature plugin, client, and tests to work with new Feature(Gat…
Browse files Browse the repository at this point in the history
…e) v1alpha2 APIs

- update feature plugin and client code to work with core.tanzu.vmware.com APIs
- update unit tests for both plugin and client

Signed-off-by: F. Gold <fgold@vmware.com>
  • Loading branch information
codegold79 committed Dec 1, 2022
1 parent b13e57a commit 0495a94
Show file tree
Hide file tree
Showing 12 changed files with 1,287 additions and 437 deletions.
31 changes: 15 additions & 16 deletions cmd/cli/plugin/feature/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# feature
# Feature

Feature plugin lets you operate on Features and FeatureGates
Feature plugin gives access to features using featuregates.

## Usage

Feature plugin has 3 commands
Feature plugin has three commands:

1. list - allows to list the features that are gated by a particular
FeatureGate.
2. activate - allows to activate a feature.
3. deactivate - allows to deactivate a feature.

By default, Feature plugin operates on Features that are gated by `tkg-system`
FeatureGate, but that can be changed by specifying `featuregate` flag.
Feature plugin is able to list all discoverable features on the cluster.
Optionally, a FeatureGate may be specified by using the `featuregate` flag.

Ex:
Example:

```sh
# list Features associated with tkg-system FeatureGate
Expand Down Expand Up @@ -45,26 +45,25 @@ Use "tanzu feature [command] --help" for more information about a command.

```sh
>>> tanzu feature list --help
List Features
List features

Usage:
tanzu feature list [flags]

Examples:

# List a clusters Features
# List feature(s) in the cluster.
tanzu feature list --activated
tanzu feature list --unavailable
tanzu feature list --deactivated

Flags:
-a, --activated List only activated Features
-d, --deactivated List only deactivated Features
-e, --extended Include extended output. Higher latency as it requires more API calls.
-f, --featuregate string List Features gated by a particular FeatureGate (default "tkg-system")
-h, --help help for list
-o, --output string Output format (yaml|json|table)
-u, --unavailable List only Features specified in the gate but missing from cluster
-a, --activated List only activated features
-d, --deactivated List only deactivated features
-e, --extended Include extended output
-f, --featuregate string List features gated by a particular FeatureGate
-h, --help Help for list
-x, --include-experimental Allows displaying experimental features
-o, --output string Output format (yaml|json|table)
```

### activate command
Expand Down
112 changes: 104 additions & 8 deletions cmd/cli/plugin/feature/activate.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
// Copyright 2021 VMware, Inc. All Rights Reserved.
// Copyright 2022 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"context"
"fmt"
"strings"

"github.com/spf13/cobra"

corev1alpha2 "github.com/vmware-tanzu/tanzu-framework/apis/core/v1alpha2"
"github.com/vmware-tanzu/tanzu-framework/featuregates/client/pkg/featuregateclient"
)

var (
userAllowsVoidingWarranty bool
)

// FeatureActivateCmd is for activating Features
var FeatureActivateCmd = &cobra.Command{
Use: "activate <feature>",
Short: "Activate Features",
Short: "Activate features",
Args: cobra.ExactArgs(1),
Example: `
# Activate a cluster Feature
Expand All @@ -24,22 +30,112 @@ var FeatureActivateCmd = &cobra.Command{
}

func init() {
FeatureActivateCmd.Flags().StringVarP(&featuregate, "featuregate", "f", "tkg-system", "Activate a Feature gated by a particular FeatureGate")
FeatureActivateCmd.Flags().BoolVar(&userAllowsVoidingWarranty, "permanentlyVoidAllSupportGuarantees", false, "Allow for the permanent voiding of all support guarantees for this environment. For some features, e.g. experimental features, if a user sets the activation status to one that does not match the default activation, all support guarantees for this environment will be permanently voided.")
}

func featureActivate(cmd *cobra.Command, args []string) error {
featureName := args[0]
featureGateClient, err := featuregateclient.NewFeatureGateClient()

fgClient, err := featuregateclient.NewFeatureGateClient()
if err != nil {
return fmt.Errorf("couldn't get featureGateRunner: %w", err)
return fmt.Errorf("could not get FeatureGate client: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
defer cancel()

if err := featureGateClient.ActivateFeature(ctx, featureName, featuregate); err != nil {
return fmt.Errorf("couldn't activate feature %s: %w", featureName, err)
feature, err := fgClient.GetFeature(ctx, featureName)
if err != nil {
return fmt.Errorf("could not get Feature %s: %w", featureName, err)
}

gates, err := fgClient.GetFeatureGateList(ctx)
if err != nil {
return fmt.Errorf("could not get FeatureGate List: %w", err)
}

gateName, featRef := featuregateclient.FeatureRefFromGateList(gates, featureName)

var proceedWithVoidingWarranty bool
if willWarrantyBeVoided(featRef, feature) {
// The warranty will be voided with the request, so check that user allows it.
var userAllows *bool
if cmd.Flags().Changed("permanentlyVoidAllSupportGuarantees") {
userAllows = &userAllowsVoidingWarranty
}
proceedWithVoidingWarranty, err = userGivesPermissionToVoidWarranty(feature, userAllows)
if err != nil {
return fmt.Errorf("could not get user permission to void warranty for Feature %s: %w", featureName, err)
}
}
cmd.Printf("Feature %s Activated", featureName)

err = fgClient.ActivateFeature(ctx, featureName, proceedWithVoidingWarranty)
if err != nil {
return fmt.Errorf("could not activate Feature %s gated by FeatureGate %s: %w", featureName, gateName, err)
}

displayActivationWarnings(feature)

cmd.Printf("Feature %s gated by FeatureGate %s is activated.\n", featureName, gateName)
return nil
}

// displayActivationWarnings warns the user that technical preview features are
// unstable and lack support.
func displayActivationWarnings(feature *corev1alpha2.Feature) {
if feature.Spec.Stability == corev1alpha2.TechnicalPreview {
fmt.Printf("Warning: Technical preview features are not ready, but are not believed to be dangerous. The feature itself is unsupported, but activating technical preview features does not affect the support status of the environment. Use at your own risk.\n\n")
}
}

// willWarrantyBeVoided checks that activating the Feature will cause warranty to be voided.
// Warranty will be voided only if all the following conditions are met:
// - The stability policy's VoidsWarranty field value is true. This means changing the activation
// set point away from default activation (in this case, default activate will have to be false)
// will void the warranty.
// - The warranty is not already void. If it's void, then it cannot be voided as it's already void.
// - The current Feature activation set point in the FeatureGate reference is false. If the opposite is
// true--that the Feature is already set to be activated--then there is nothing to change and
// the warranty will not be voided.
// - Activating the Feature will set it different than its default activation setting. This means the
// default activation of a Feature must be false for the warranty to be voided. On the other hand,
// if the default activation is true, then activating the Feature will not void the warranty.
//
// If all of the above are true, then the warranty will be voided and the function returns true.
// Otherwise, if any are false, the warranty will not be voided and the function returns false.
func willWarrantyBeVoided(ref corev1alpha2.FeatureReference, feature *corev1alpha2.Feature) bool {
stability := feature.Spec.Stability
policy := corev1alpha2.GetPolicyForStabilityLevel(stability)
return policy.VoidsWarranty && !ref.PermanentlyVoidAllSupportGuarantees && !ref.Activate && !policy.DefaultActivation
}

func userGivesPermissionToVoidWarranty(feature *corev1alpha2.Feature, userAllowsByFlag *bool) (bool, error) {
if userAllowsByFlag == nil {
// Request user permission interactively to void the warranty if not already set by flag.
fmt.Printf("Warning: activating %q, a %s Feature will irrevocably void all support guarantees for this environment. You will need to recreate the environment to return to a supported state.\nWould you like to continue [y/N]?", feature.Name, feature.Spec.Stability)
return userAllowsByInteractiveCLI()
}
// If the user did pass a value to the flag, use the user's input. No interactive user input is needed.
return *userAllowsByFlag, nil
}

func userAllowsByInteractiveCLI() (bool, error) {
var userAnswer string
if n, err := fmt.Scanln(&userAnswer); err != nil {
if n == 0 {
return false, nil
}
return false, fmt.Errorf("could not read user input: %w", err)
}

userAnswer = strings.TrimSpace(strings.ToLower(userAnswer))
switch {
case userAnswer == "yes" || userAnswer == "y":
return true, nil
case userAnswer == "" || userAnswer == "no" || userAnswer == "n":
return false, nil
default:
fmt.Printf("Please respond with %q or %q\n", "y", "n")
return userAllowsByInteractiveCLI()
}
}
17 changes: 8 additions & 9 deletions cmd/cli/plugin/feature/deactivate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,21 @@ var FeatureDeactivateCmd = &cobra.Command{
tanzu feature deactivate myfeature`,
RunE: func(cmd *cobra.Command, args []string) error {
featureName := args[0]
featureGateClient, err := featuregateclient.NewFeatureGateClient()

fgClient, err := featuregateclient.NewFeatureGateClient()
if err != nil {
return fmt.Errorf("couldn't get a featureGateRunner: %w", err)
return fmt.Errorf("could not get FeatureGate client: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
defer cancel()

if err := featureGateClient.DeactivateFeature(ctx, featureName, featuregate); err != nil {
return fmt.Errorf("couldn't deactivate feature %s: %w", featureName, err)
gateName, err := fgClient.DeactivateFeature(ctx, featureName)
if err != nil {
return fmt.Errorf("could not deactivate Feature %s gated by FeatureGate %s: %w", featureName, gateName, err)
}
cmd.Printf("Feature %s Deactivated", featureName)

cmd.Printf("Feature %s gated by FeatureGate %s is deactivated.\n", featureName, gateName)
return nil
},
}

func init() {
FeatureDeactivateCmd.Flags().StringVarP(&featuregate, "featuregate", "f", "tkg-system", "Deactivate Feature gated by a particular FeatureGate")
}
4 changes: 2 additions & 2 deletions cmd/cli/plugin/feature/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ replace (
require (
github.com/aunum/log v0.0.0-20200821225356-38d2e2c8b489
github.com/spf13/cobra v1.5.0
github.com/vmware-tanzu/tanzu-framework/apis/config v0.0.0-00010101000000-000000000000
github.com/vmware-tanzu/tanzu-framework/apis/core v0.0.0-00010101000000-000000000000
github.com/vmware-tanzu/tanzu-framework/cli/runtime v0.0.0-00010101000000-000000000000
github.com/vmware-tanzu/tanzu-framework/featuregates/client v0.0.0-00010101000000-000000000000
k8s.io/apimachinery v0.24.4
k8s.io/client-go v0.24.4
sigs.k8s.io/controller-runtime v0.12.3
)
Expand Down Expand Up @@ -88,6 +87,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.24.4 // indirect
k8s.io/apiextensions-apiserver v0.24.4 // indirect
k8s.io/apimachinery v0.24.4 // indirect
k8s.io/component-base v0.24.4 // indirect
k8s.io/klog/v2 v2.80.0 // indirect
k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea // indirect
Expand Down
Loading

0 comments on commit 0495a94

Please # to comment.