Skip to content

Commit

Permalink
Add module disable command (#2282)
Browse files Browse the repository at this point in the history
  • Loading branch information
pPrecel authored Dec 17, 2024
1 parent 9e68ec3 commit a4d9651
Show file tree
Hide file tree
Showing 10 changed files with 704 additions and 12 deletions.
42 changes: 42 additions & 0 deletions internal/cmd/alpha/module/disable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package module

import (
"github.com/kyma-project/cli.v3/internal/clierror"
"github.com/kyma-project/cli.v3/internal/cmdcommon"
"github.com/kyma-project/cli.v3/internal/modules"
"github.com/spf13/cobra"
)

type disableConfig struct {
*cmdcommon.KymaConfig

module string
}

func newDisableCMD(kymaConfig *cmdcommon.KymaConfig) *cobra.Command {
cfg := disableConfig{
KymaConfig: kymaConfig,
}

cmd := &cobra.Command{
Use: "disable <module>",
Short: "Disable module",
Long: "Use this command to disable module",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg.module = args[0]
clierror.Check(runDisable(&cfg))
},
}

return cmd
}

func runDisable(cfg *disableConfig) clierror.Error {
client, clierr := cfg.GetKubeClientWithClierr()
if clierr != nil {
return clierr
}

return modules.Disable(cfg.Ctx, client, cfg.module)
}
1 change: 1 addition & 0 deletions internal/cmd/alpha/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func NewModuleCMD(kymaConfig *cmdcommon.KymaConfig) *cobra.Command {

cmd.AddCommand(newListCMD(kymaConfig))
cmd.AddCommand(newEnableCMD(kymaConfig))
cmd.AddCommand(newDisableCMD(kymaConfig))

return cmd
}
8 changes: 7 additions & 1 deletion internal/cmdcommon/types/map_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"strings"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -23,7 +24,12 @@ func TestEnvMap(t *testing.T) {
}
require.Equal(t, expectedMap, envMap.Values)
require.Equal(t, expectedNullableMap, envMap.GetNullableMap())
require.Equal(t, "TEST1=1,TEST2=2", envMap.String())

// expect TEST1=1,TEST2=2 or TEST2=2,TEST1=1
stringElems := strings.Split(envMap.String(), ",")
require.Len(t, stringElems, 2)
require.Contains(t, stringElems, "TEST1=1")
require.Contains(t, stringElems, "TEST2=2")
})

t.Run("get type", func(t *testing.T) {
Expand Down
7 changes: 7 additions & 0 deletions internal/kube/fake/rootlessdynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import (
"context"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/watch"
)

type RootlessDynamicClient struct {
// outputs
ReturnErr error
ReturnGetErr error
ReturnRemoveErr error
ReturnWatchErr error
ReturnGetObj unstructured.Unstructured
ReturnListObjs *unstructured.UnstructuredList
ReturnWatcher watch.Interface

// inputs summary
GetObjs []unstructured.Unstructured
Expand Down Expand Up @@ -50,3 +53,7 @@ func (m *RootlessDynamicClient) RemoveMany(_ context.Context, objs []unstructure
m.RemovedObjs = append(m.RemovedObjs, objs...)
return m.ReturnRemoveErr
}

func (m *RootlessDynamicClient) WatchSingleResource(_ context.Context, obj *unstructured.Unstructured) (watch.Interface, error) {
return m.ReturnWatcher, m.ReturnWatchErr
}
37 changes: 36 additions & 1 deletion internal/kube/kyma/kyma.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ const (
type Interface interface {
ListModuleReleaseMeta(context.Context) (*ModuleReleaseMetaList, error)
ListModuleTemplate(context.Context) (*ModuleTemplateList, error)
GetModuleReleaseMetaForModule(context.Context, string) (*ModuleReleaseMeta, error)
GetModuleTemplateForModule(context.Context, string, string) (*ModuleTemplate, error)
GetDefaultKyma(context.Context) (*Kyma, error)
UpdateDefaultKyma(context.Context, *Kyma) error
WaitForModuleState(context.Context, string, ...string) error
GetModuleInfo(context.Context, string) (*KymaModuleInfo, error)
WaitForModuleState(context.Context, string, ...string) error
EnableModule(context.Context, string, string, string) error
DisableModule(context.Context, string) error
}
Expand All @@ -50,6 +52,39 @@ func (c *client) ListModuleTemplate(ctx context.Context) (*ModuleTemplateList, e
return list[ModuleTemplateList](ctx, c.dynamic, GVRModuleTemplate)
}

// GetModuleReleaseMetaForModule returns right ModuleReleaseMeta CR corelated with given module name
func (c *client) GetModuleReleaseMetaForModule(ctx context.Context, moduleName string) (*ModuleReleaseMeta, error) {
list, err := c.ListModuleReleaseMeta(ctx)
if err != nil {
return nil, err
}

for _, releaseMeta := range list.Items {
if releaseMeta.Spec.ModuleName == moduleName {
return &releaseMeta, nil
}
}

return nil, fmt.Errorf("can't find ModuleReleaseMeta CR for module %s", moduleName)
}

// GetModuleTemplateForModule returns ModuleTemplate CR corelated with given module name in right version
func (c *client) GetModuleTemplateForModule(ctx context.Context, moduleName, moduleVersion string) (*ModuleTemplate, error) {
moduleTemplates, err := c.ListModuleTemplate(ctx)
if err != nil {
return nil, err
}

for _, moduleTemplate := range moduleTemplates.Items {
if moduleTemplate.Spec.ModuleName == moduleName &&
moduleTemplate.Spec.Version == moduleVersion {
return &moduleTemplate, nil
}
}

return nil, fmt.Errorf("can't find ModuleTemplate CR for module %s in version %s", moduleName, moduleVersion)
}

// GetDefaultKyma gets the default Kyma CR from the kyma-system namespace and cast it to the Kyma structure
func (c *client) GetDefaultKyma(ctx context.Context) (*Kyma, error) {
u, err := c.dynamic.Resource(GVRKyma).
Expand Down
62 changes: 61 additions & 1 deletion internal/kube/kyma/kyma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ func Test_client_ListModuleReleaseMeta(t *testing.T) {
}

func Test_client_ListModuleTemplate(t *testing.T) {
t.Run("list ModuleReleaseMeta", func(t *testing.T) {
t.Run("list ModuleTemplate", func(t *testing.T) {
scheme := runtime.NewScheme()
scheme.AddKnownTypes(GVRModuleTemplate.GroupVersion())
client := NewClient(dynamic_fake.NewSimpleDynamicClient(scheme,
Expand All @@ -451,6 +451,66 @@ func Test_client_ListModuleTemplate(t *testing.T) {
})
}

func Test_client_GetModuleReleaseMetaForModule(t *testing.T) {
t.Run("get module ModuleReleaseMeta", func(t *testing.T) {
scheme := runtime.NewScheme()
scheme.AddKnownTypes(GVRModuleReleaseMeta.GroupVersion())
client := NewClient(dynamic_fake.NewSimpleDynamicClient(scheme,
fixModuleReleaseMeta("test-1"),
fixModuleReleaseMeta("test-2"),
))

got, err := client.GetModuleReleaseMetaForModule(context.Background(), "test-2")

require.NoError(t, err)
require.Equal(t, fixModuleReleaseMetaStruct("test-2"), *got)
})

t.Run("no ModuleReleaseMeta for module", func(t *testing.T) {
scheme := runtime.NewScheme()
scheme.AddKnownTypes(GVRModuleReleaseMeta.GroupVersion())
client := NewClient(dynamic_fake.NewSimpleDynamicClient(scheme,
fixModuleReleaseMeta("test-1"),
fixModuleReleaseMeta("test-2"),
))

got, err := client.GetModuleReleaseMetaForModule(context.Background(), "test-123")

require.ErrorContains(t, err, "can't find ModuleReleaseMeta CR for module test-123")
require.Nil(t, got)
})
}

func Test_client_GetModuleTemplateForModule(t *testing.T) {
t.Run("get module ModuleTemplate", func(t *testing.T) {
scheme := runtime.NewScheme()
scheme.AddKnownTypes(GVRModuleTemplate.GroupVersion())
client := NewClient(dynamic_fake.NewSimpleDynamicClient(scheme,
fixModuleTemplate("test-1"),
fixModuleTemplate("test-2"),
))

got, err := client.GetModuleTemplateForModule(context.Background(), "test-2", "0.1")

require.NoError(t, err)
require.Equal(t, fixModuleTemplateStruct("test-2"), *got)
})

t.Run("no ModuleReleaseMeta for module", func(t *testing.T) {
scheme := runtime.NewScheme()
scheme.AddKnownTypes(GVRModuleTemplate.GroupVersion())
client := NewClient(dynamic_fake.NewSimpleDynamicClient(scheme,
fixModuleTemplate("test-1"),
fixModuleTemplate("test-2"),
))

got, err := client.GetModuleTemplateForModule(context.Background(), "test-2", "0.2")

require.ErrorContains(t, err, "can't find ModuleTemplate CR for module test-2 in version 0.2")
require.Nil(t, got)
})
}

func Test_client_GetModuleInfo(t *testing.T) {
t.Run("get ModuleInfo", func(t *testing.T) {
scheme := runtime.NewScheme()
Expand Down
65 changes: 56 additions & 9 deletions internal/kube/rootlessdynamic/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
"strings"

"k8s.io/apimachinery/pkg/api/errors"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
)
Expand All @@ -18,10 +18,12 @@ type applyFunc func(context.Context, dynamic.ResourceInterface, *unstructured.Un

type Interface interface {
Get(context.Context, *unstructured.Unstructured) (*unstructured.Unstructured, error)
List(context.Context, *unstructured.Unstructured) (*unstructured.UnstructuredList, error)
Apply(context.Context, *unstructured.Unstructured) error
ApplyMany(context.Context, []unstructured.Unstructured) error
Remove(context.Context, *unstructured.Unstructured) error
RemoveMany(context.Context, []unstructured.Unstructured) error
WatchSingleResource(context.Context, *unstructured.Unstructured) (watch.Interface, error)
}

type client struct {
Expand Down Expand Up @@ -58,11 +60,27 @@ func (c *client) Get(ctx context.Context, resource *unstructured.Unstructured) (
}

if apiResource.Namespaced {
return c.dynamic.Resource(*gvr).Namespace("kyma-system").Get(ctx, resource.GetName(), metav1.GetOptions{})
return c.dynamic.Resource(*gvr).Namespace(resource.GetNamespace()).Get(ctx, resource.GetName(), metav1.GetOptions{})
}
return c.dynamic.Resource(*gvr).Get(ctx, resource.GetName(), metav1.GetOptions{})
}

func (c *client) List(ctx context.Context, resource *unstructured.Unstructured) (*unstructured.UnstructuredList, error) {
group, version := groupVersion(resource.GetAPIVersion())
apiResource, err := c.discoverAPIResource(group, version, resource.GetKind())
if err != nil {
return nil, fmt.Errorf("failed to discover API resource using discovery client: %w", err)
}

gvr := &schema.GroupVersionResource{
Group: group,
Version: version,
Resource: apiResource.Name,
}

return c.dynamic.Resource(*gvr).List(ctx, metav1.ListOptions{})
}

func (c *client) Apply(ctx context.Context, resource *unstructured.Unstructured) error {
group, version := groupVersion(resource.GetAPIVersion())
apiResource, err := c.discoverAPIResource(group, version, resource.GetKind())
Expand All @@ -77,12 +95,7 @@ func (c *client) Apply(ctx context.Context, resource *unstructured.Unstructured)
}

if apiResource.Namespaced {
if resource.GetNamespace() == "" {
// make resource has namespace set
resource.SetNamespace("default")
}

err = c.applyFunc(ctx, c.dynamic.Resource(*gvr).Namespace(resource.GetNamespace()), resource)
err = c.applyFunc(ctx, c.dynamic.Resource(*gvr).Namespace(getResourceNamespace(resource)), resource)
if err != nil {
return fmt.Errorf("failed to apply namespaced resource: %w", err)
}
Expand Down Expand Up @@ -119,7 +132,7 @@ func (c *client) Remove(ctx context.Context, resource *unstructured.Unstructured
}

if apiResource.Namespaced {
err = c.dynamic.Resource(*gvr).Namespace("kyma-system").Delete(ctx, resource.GetName(), metav1.DeleteOptions{})
err = c.dynamic.Resource(*gvr).Namespace(getResourceNamespace(resource)).Delete(ctx, resource.GetName(), metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
return fmt.Errorf("failed to delete namespaced resource %w", err)
}
Expand All @@ -142,6 +155,31 @@ func (c *client) RemoveMany(ctx context.Context, objs []unstructured.Unstructure
return nil
}

func (c *client) WatchSingleResource(ctx context.Context, resource *unstructured.Unstructured) (watch.Interface, error) {
group, version := groupVersion(resource.GetAPIVersion())
apiResource, err := c.discoverAPIResource(group, version, resource.GetKind())
if err != nil {
return nil, fmt.Errorf("failed to discover API resource using discovery client: %w", err)
}

fieldSelector := fmt.Sprintf("metadata.name=%s", resource.GetName())
gvr := &schema.GroupVersionResource{
Group: group,
Version: version,
Resource: apiResource.Name,
}

if apiResource.Namespaced {
return c.dynamic.Resource(*gvr).Namespace(getResourceNamespace(resource)).Watch(ctx, metav1.ListOptions{
FieldSelector: fieldSelector,
})
}

return c.dynamic.Resource(*gvr).Watch(ctx, metav1.ListOptions{
FieldSelector: fieldSelector,
})
}

// applyResource creates or updates given object
func applyResource(ctx context.Context, resourceInterface dynamic.ResourceInterface, resource *unstructured.Unstructured) error {
// this function can't be tested because of dynamic.FakeDynamicClient limitations
Expand Down Expand Up @@ -177,3 +215,12 @@ func groupVersion(version string) (string, string) {
}
return "", split[0]
}

// returns resource namespace or kyma-system if empty
func getResourceNamespace(resource *unstructured.Unstructured) string {
if resource.GetNamespace() != "" {
return resource.GetNamespace()
}

return "kyma-system"
}
Loading

0 comments on commit a4d9651

Please sign in to comment.