-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ffc4214
commit 9d398a8
Showing
5 changed files
with
369 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1" | ||
"github.com/kubesaw/ksctl/pkg/client" | ||
"github.com/kubesaw/ksctl/pkg/configuration" | ||
clicontext "github.com/kubesaw/ksctl/pkg/context" | ||
"github.com/kubesaw/ksctl/pkg/ioutils" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/utils/strings/slices" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
func NewEnableFeatureCmd() *cobra.Command { | ||
return &cobra.Command{ | ||
Use: "enable-feature <space-name> <feature-name>", | ||
Short: "Enable a feature for the given Space", | ||
Long: `Enable a feature toggle for the given Space. There are two expected | ||
parameters - the first one is the Space name and the second is the name of the feature toggle that should be enabled for the Space.`, | ||
Args: cobra.ExactArgs(2), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
term := ioutils.NewTerminal(cmd.InOrStdin, cmd.OutOrStdout) | ||
ctx := clicontext.NewCommandContext(term, client.DefaultNewClient) | ||
return EnableFeature(ctx, args[0], args[1]) | ||
}, | ||
} | ||
} | ||
|
||
func EnableFeature(ctx *clicontext.CommandContext, spaceName, featureToggleName string) error { | ||
return client.PatchSpace(ctx, spaceName, func(space *toolchainv1alpha1.Space) (bool, error) { | ||
cfg, err := configuration.LoadClusterConfig(ctx, configuration.HostName) | ||
if err != nil { | ||
return false, err | ||
} | ||
cl, err := ctx.NewClient(cfg.Token, cfg.ServerAPI) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
// get ToolchainConfig to check if the feature toggle is supported or not | ||
config := &toolchainv1alpha1.ToolchainConfig{} | ||
namespacedName := types.NamespacedName{Namespace: cfg.OperatorNamespace, Name: "config"} | ||
if err := cl.Get(ctx, namespacedName, config); err != nil { | ||
return false, fmt.Errorf("unable to get ToolchainConfig: %w", err) | ||
} | ||
// if no feature toggle is supported then return an error | ||
if len(config.Spec.Host.Tiers.FeatureToggles) == 0 { | ||
return false, fmt.Errorf("the feature toggle is not supported - the list of supported toggles is empty") | ||
} | ||
|
||
supportedFeatureToggles := make([]string, len(config.Spec.Host.Tiers.FeatureToggles)) | ||
for i, fToggle := range config.Spec.Host.Tiers.FeatureToggles { | ||
supportedFeatureToggles[i] = fToggle.Name | ||
} | ||
|
||
// if the requested feature is not in the list of supported toggles, then print the list of supported ones and return an error | ||
if !slices.Contains(supportedFeatureToggles, featureToggleName) { | ||
ctx.Printlnf("The feature toggle '%s' is not listed as a supported feature toggle in ToolchainConfig CR.", featureToggleName) | ||
fToggleNamesList := "\n" | ||
for _, fToggleName := range supportedFeatureToggles { | ||
fToggleNamesList += fmt.Sprintf("%s\n", fToggleName) | ||
} | ||
ctx.PrintContextSeparatorWithBodyf(fToggleNamesList, "The supported feature toggles are:") | ||
return false, fmt.Errorf("the feature toggle is not supported") | ||
} | ||
|
||
// get already enabled features for the space | ||
currentFeatures := strings.TrimSpace(space.Annotations[toolchainv1alpha1.FeatureToggleNameAnnotationKey]) | ||
var enabledFeatures []string | ||
if currentFeatures != "" { | ||
enabledFeatures = strings.Split(currentFeatures, ",") | ||
} | ||
if err := ctx.PrintObject(space, "The current Space"); err != nil { | ||
return false, err | ||
} | ||
|
||
// check if it's already enabled or not | ||
if slices.Contains(enabledFeatures, featureToggleName) { | ||
ctx.Println("") | ||
ctx.Println("The space has the feature toggle already enabled. There is nothing to do.") | ||
ctx.Println("") | ||
return false, nil | ||
} | ||
|
||
confirmation := ctx.AskForConfirmation(ioutils.WithMessagef( | ||
"enable the feature toggle '%s' for the Space '%s'? The already enabled feature toggles are '%s'.", | ||
featureToggleName, spaceName, currentFeatures)) | ||
|
||
if confirmation { | ||
enabledFeatures = append(enabledFeatures, featureToggleName) | ||
if space.Annotations == nil { | ||
space.Annotations = map[string]string{} | ||
} | ||
space.Annotations[toolchainv1alpha1.FeatureToggleNameAnnotationKey] = strings.Join(enabledFeatures, ",") | ||
return true, nil | ||
} | ||
return false, nil | ||
}, "Successfully enabled feature toggle for the Space") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
package cmd_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1" | ||
"github.com/codeready-toolchain/toolchain-common/pkg/test" | ||
"github.com/kubesaw/ksctl/pkg/cmd" | ||
clicontext "github.com/kubesaw/ksctl/pkg/context" | ||
. "github.com/kubesaw/ksctl/pkg/test" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestEnableFeatureCmd(t *testing.T) { | ||
// given | ||
SetFileConfig(t, Host()) | ||
config := configWithFeatures([]toolchainv1alpha1.FeatureToggle{ | ||
{ | ||
Name: "feature-x", | ||
}, | ||
}) | ||
|
||
var combinations = []struct { | ||
alreadyEnabled string | ||
afterEnable map[string]string | ||
}{ | ||
{ | ||
alreadyEnabled: "", | ||
afterEnable: map[string]string{ | ||
toolchainv1alpha1.FeatureToggleNameAnnotationKey: "feature-x", | ||
}, | ||
}, | ||
{ | ||
alreadyEnabled: "feature0", | ||
afterEnable: map[string]string{ | ||
toolchainv1alpha1.FeatureToggleNameAnnotationKey: "feature0,feature-x", | ||
}, | ||
}, | ||
{ | ||
alreadyEnabled: "feature1,feature2,feature3", | ||
afterEnable: map[string]string{ | ||
toolchainv1alpha1.FeatureToggleNameAnnotationKey: "feature1,feature2,feature3,feature-x", | ||
}, | ||
}, | ||
} | ||
|
||
for _, data := range combinations { | ||
t.Run("with the already enabled features: "+data.alreadyEnabled, func(t *testing.T) { | ||
// given | ||
space := newSpace() | ||
if data.alreadyEnabled != "" { | ||
space.Annotations = map[string]string{ | ||
toolchainv1alpha1.FeatureToggleNameAnnotationKey: data.alreadyEnabled, | ||
} | ||
} | ||
|
||
for _, answer := range []string{"Y", "n"} { | ||
|
||
t.Run("when answer is "+answer, func(t *testing.T) { | ||
// given | ||
newClient, fakeClient := NewFakeClients(t, space, config) | ||
term := NewFakeTerminalWithResponse(answer) | ||
ctx := clicontext.NewCommandContext(term, newClient) | ||
|
||
// when | ||
err := cmd.EnableFeature(ctx, space.Name, "feature-x") | ||
|
||
// then | ||
require.NoError(t, err) | ||
|
||
output := term.Output() | ||
assert.Contains(t, output, fmt.Sprintf("enable the feature toggle 'feature-x' for the Space 'testspace'? The already enabled feature toggles are '%s'.", data.alreadyEnabled)) | ||
assert.NotContains(t, output, "cool-token") | ||
expectedSpace := newSpace() | ||
|
||
if answer == "Y" { | ||
expectedSpace.Annotations = data.afterEnable | ||
assert.Contains(t, output, "Successfully enabled feature toggle for the Space") | ||
|
||
} else { | ||
expectedSpace.Annotations = space.Annotations | ||
assert.NotContains(t, output, "Successfully enabled feature toggle for the Space") | ||
} | ||
assertSpaceAnnotations(t, fakeClient, expectedSpace) | ||
|
||
}) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestEnableFeatureCmdWhenFeatureIsAlreadyEnabled(t *testing.T) { | ||
// given | ||
SetFileConfig(t, Host()) | ||
config := configWithFeatures([]toolchainv1alpha1.FeatureToggle{ | ||
{ | ||
Name: "feature-x", | ||
}, | ||
}) | ||
|
||
for _, alreadyEnabled := range []string{"feature-x", "feature-x,feature0", "feature1,feature2,feature-x,feature3"} { | ||
t.Run("with the already enabled features: "+alreadyEnabled, func(t *testing.T) { | ||
// given | ||
space := newSpace() | ||
if alreadyEnabled != "" { | ||
space.Annotations = map[string]string{ | ||
toolchainv1alpha1.FeatureToggleNameAnnotationKey: alreadyEnabled, | ||
} | ||
} | ||
// given | ||
newClient, fakeClient := NewFakeClients(t, space, config) | ||
term := NewFakeTerminalWithResponse("Y") | ||
ctx := clicontext.NewCommandContext(term, newClient) | ||
|
||
// when | ||
err := cmd.EnableFeature(ctx, space.Name, "feature-x") | ||
|
||
// then | ||
require.NoError(t, err) | ||
assertSpaceAnnotations(t, fakeClient, space) // no change | ||
|
||
output := term.Output() | ||
assert.Contains(t, output, "The space has the feature toggle already enabled. There is nothing to do.") | ||
assert.NotContains(t, output, "enable the feature toggle 'feature-x' for the Space 'testspace'?") | ||
assert.NotContains(t, output, "Successfully enabled feature toggle for the Space") | ||
assert.NotContains(t, output, "cool-token") | ||
|
||
}) | ||
} | ||
} | ||
|
||
func TestEnableFeatureCmdWhenFeatureIsNotSupported(t *testing.T) { | ||
// given | ||
SetFileConfig(t, Host()) | ||
|
||
var combinations = []struct { | ||
nameList string | ||
supportedFeatures []toolchainv1alpha1.FeatureToggle | ||
}{ | ||
{ | ||
nameList: "", | ||
supportedFeatures: nil, | ||
}, | ||
{ | ||
nameList: "feature-0", | ||
supportedFeatures: []toolchainv1alpha1.FeatureToggle{ | ||
{ | ||
Name: "feature-0", | ||
}, | ||
}, | ||
}, | ||
{ | ||
nameList: "feature1\nfeature2\nfeature3", | ||
supportedFeatures: []toolchainv1alpha1.FeatureToggle{ | ||
{ | ||
Name: "feature1", | ||
}, | ||
{ | ||
Name: "feature2", | ||
}, | ||
{ | ||
Name: "feature3", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, data := range combinations { | ||
t.Run("with the supported features: "+data.nameList, func(t *testing.T) { | ||
// given | ||
space := newSpace() | ||
config := configWithFeatures(data.supportedFeatures) | ||
// given | ||
newClient, fakeClient := NewFakeClients(t, space, config) | ||
term := NewFakeTerminalWithResponse("Y") | ||
ctx := clicontext.NewCommandContext(term, newClient) | ||
|
||
// when | ||
err := cmd.EnableFeature(ctx, space.Name, "feature-x") | ||
|
||
// then | ||
require.Error(t, err) | ||
output := term.Output() | ||
if data.supportedFeatures == nil { | ||
require.EqualError(t, err, "the feature toggle is not supported - the list of supported toggles is empty") | ||
} else { | ||
require.EqualError(t, err, "the feature toggle is not supported") | ||
assert.Contains(t, output, "The feature toggle 'feature-x' is not listed as a supported feature toggle in ToolchainConfig CR.") | ||
assert.Contains(t, output, "The supported feature toggles are:") | ||
assert.Contains(t, output, data.nameList) | ||
} | ||
|
||
assert.NotContains(t, output, "Successfully enabled feature toggle for the Space") | ||
assert.NotContains(t, output, "cool-token") | ||
assertSpaceAnnotations(t, fakeClient, space) // no change | ||
}) | ||
} | ||
} | ||
|
||
func TestEnableFeatureCmdWhenSpaceNotFound(t *testing.T) { | ||
// given | ||
config := configWithFeatures([]toolchainv1alpha1.FeatureToggle{ | ||
{ | ||
Name: "feature-x", | ||
}, | ||
}) | ||
space := newSpace() | ||
newClient, fakeClient := NewFakeClients(t, space, config) | ||
SetFileConfig(t, Host()) | ||
term := NewFakeTerminalWithResponse("Y") | ||
ctx := clicontext.NewCommandContext(term, newClient) | ||
|
||
// when | ||
err := cmd.EnableFeature(ctx, "another", "feature-x") | ||
|
||
// then | ||
require.EqualError(t, err, "spaces.toolchain.dev.openshift.com \"another\" not found") | ||
assertSpaceAnnotations(t, fakeClient, space) // unrelated space should be unchanged | ||
output := term.Output() | ||
assert.NotContains(t, output, "enable the feature toggle 'feature-x' for the Space 'testspace'?") | ||
assert.NotContains(t, output, "Successfully enabled feature toggle for the Space") | ||
assert.NotContains(t, output, "cool-token") | ||
} | ||
|
||
func TestEnableFeatureCmdWhenConfigNotFound(t *testing.T) { | ||
// given | ||
space := newSpace() | ||
newClient, fakeClient := NewFakeClients(t, space) | ||
SetFileConfig(t, Host()) | ||
term := NewFakeTerminalWithResponse("Y") | ||
ctx := clicontext.NewCommandContext(term, newClient) | ||
|
||
// when | ||
err := cmd.EnableFeature(ctx, space.Name, "feature-x") | ||
|
||
// then | ||
require.EqualError(t, err, "unable to get ToolchainConfig: toolchainconfigs.toolchain.dev.openshift.com \"config\" not found") | ||
assertSpaceAnnotations(t, fakeClient, space) // no change | ||
} | ||
|
||
func assertSpaceAnnotations(t *testing.T, fakeClient *test.FakeClient, expectedSpace *toolchainv1alpha1.Space) { | ||
updatedSpace := &toolchainv1alpha1.Space{} | ||
err := fakeClient.Get(context.TODO(), test.NamespacedName(expectedSpace.Namespace, expectedSpace.Name), updatedSpace) | ||
require.NoError(t, err) | ||
assert.Equal(t, expectedSpace.Annotations, updatedSpace.Annotations) | ||
} | ||
|
||
func configWithFeatures(toggles []toolchainv1alpha1.FeatureToggle) *toolchainv1alpha1.ToolchainConfig { | ||
toolchainConfig := &toolchainv1alpha1.ToolchainConfig{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Namespace: test.HostOperatorNs, | ||
Name: "config", | ||
}, | ||
} | ||
toolchainConfig.Spec.Host.Tiers.FeatureToggles = toggles | ||
return toolchainConfig | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters