Skip to content

Commit

Permalink
Implement INSTALLED and MANAGED headers for table view when listi…
Browse files Browse the repository at this point in the history
…ng modules (#2263)
  • Loading branch information
pPrecel authored Nov 28, 2024
1 parent 13b2856 commit 957c22b
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 80 deletions.
36 changes: 19 additions & 17 deletions internal/kube/kyma/kyma.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
)

const (
defaultKymaName = "default"
defaultKymaNamespace = "kyma-system"
DefaultKymaName = "default"
DefaultKymaNamespace = "kyma-system"
)

type Interface interface {
Expand All @@ -35,31 +35,21 @@ func NewClient(dynamic dynamic.Interface) Interface {
}
}

// ListModuleReleaseMeta lists ModuleReleaseMeta resources from across the whole cluster
func (c *client) ListModuleReleaseMeta(ctx context.Context) (*ModuleReleaseMetaList, error) {
return list[ModuleReleaseMetaList](ctx, c.dynamic, GVRModuleReleaseMeta)
}

// ListModuleTemplate lists ModuleTemplate resources from across the whole cluster
func (c *client) ListModuleTemplate(ctx context.Context) (*ModuleTemplateList, error) {
return list[ModuleTemplateList](ctx, c.dynamic, GVRModuleTemplate)
}

func list[T any](ctx context.Context, client dynamic.Interface, gvr schema.GroupVersionResource) (*T, error) {
list, err := client.Resource(gvr).
List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}

structuredList := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(list.UnstructuredContent(), structuredList)
return structuredList, err
}

// 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).
Namespace(defaultKymaNamespace).
Get(ctx, defaultKymaName, metav1.GetOptions{})
Namespace(DefaultKymaNamespace).
Get(ctx, DefaultKymaName, metav1.GetOptions{})
if err != nil {
return nil, err
}
Expand All @@ -78,7 +68,7 @@ func (c *client) UpdateDefaultKyma(ctx context.Context, obj *Kyma) error {
}

_, err = c.dynamic.Resource(GVRKyma).
Namespace(defaultKymaNamespace).
Namespace(DefaultKymaNamespace).
Update(ctx, &unstructured.Unstructured{Object: u}, metav1.UpdateOptions{})

return err
Expand Down Expand Up @@ -133,3 +123,15 @@ func disableModule(kymaCR *Kyma, moduleName string) *Kyma {

return kymaCR
}

func list[T any](ctx context.Context, client dynamic.Interface, gvr schema.GroupVersionResource) (*T, error) {
list, err := client.Resource(gvr).
List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}

structuredList := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(list.UnstructuredContent(), structuredList)
return structuredList, err
}
2 changes: 2 additions & 0 deletions internal/kube/kyma/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ type Module struct {
ControllerName string `json:"controller,omitempty"`
Channel string `json:"channel,omitempty"`
CustomResourcePolicy string `json:"customResourcePolicy,omitempty"`
Managed bool `json:"managed,omitempty"`
}

// KymaStatus defines the observed state of Kyma
Expand All @@ -125,6 +126,7 @@ type ModuleStatus struct {
Name string `json:"name"`
Channel string `json:"channel,omitempty"`
Version string `json:"version,omitempty"`
State string `json:"state,omitempty"`
}

// ModuleFromInterface converts a map retrieved from the Unstructured kyma CR to a Module struct.
Expand Down
64 changes: 59 additions & 5 deletions internal/modules/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,29 @@ package modules

import (
"context"
"strconv"

"github.com/kyma-project/cli.v3/internal/kube/kyma"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)

type Module struct {
Name string
Versions []ModuleVersion
Name string
Versions []ModuleVersion
InstallDetails ModuleInstallDetails
}

type Managed string

const (
ManagedTrue Managed = "true"
ManagedFalse Managed = "false"
)

type ModuleInstallDetails struct {
Version string
Channel string
Managed Managed
}

type ModuleVersion struct {
Expand All @@ -30,25 +46,32 @@ func List(ctx context.Context, client kyma.Interface) (ModulesList, error) {
return nil, err
}

defaultKyma, err := client.GetDefaultKyma(ctx)
if err != nil && !apierrors.IsNotFound(err) {
return nil, err
}

modulesList := ModulesList{}
for _, moduleTemplate := range moduleTemplates.Items {
moduleName := moduleTemplate.Spec.ModuleName
version := ModuleVersion{
Version: moduleTemplate.Spec.Version,
Repository: moduleTemplate.Spec.Info.Repository,
Channel: getAssignedChannel(
*modulereleasemetas,
moduleTemplate.Spec.ModuleName,
moduleName,
moduleTemplate.Spec.Version,
),
}

if i := getModuleIndex(modulesList, moduleTemplate.Spec.ModuleName); i != -1 {
if i := getModuleIndex(modulesList, moduleName); i != -1 {
// append version if module with same name is in the list
modulesList[i].Versions = append(modulesList[i].Versions, version)
} else {
// otherwise create anew record in the list
modulesList = append(modulesList, Module{
Name: moduleTemplate.Spec.ModuleName,
Name: moduleName,
InstallDetails: getInstallDetails(defaultKyma, *modulereleasemetas, moduleName),
Versions: []ModuleVersion{
version,
},
Expand All @@ -59,6 +82,37 @@ func List(ctx context.Context, client kyma.Interface) (ModulesList, error) {
return modulesList, nil
}

func getInstallDetails(kyma *kyma.Kyma, releaseMetas kyma.ModuleReleaseMetaList, moduleName string) ModuleInstallDetails {
if kyma != nil {
for _, module := range kyma.Status.Modules {
if module.Name == moduleName {
moduleVersion := module.Version
return ModuleInstallDetails{
Channel: getAssignedChannel(releaseMetas, module.Name, moduleVersion),
Managed: getManaged(kyma.Spec.Modules, moduleName),
Version: moduleVersion,
}
}
}
}

// TODO: support community modules

// return empty struct because module is not installed
return ModuleInstallDetails{}
}

// look for value of managed for specific moduleName
func getManaged(specModules []kyma.Module, moduleName string) Managed {
for _, module := range specModules {
if module.Name == moduleName {
return Managed(strconv.FormatBool(module.Managed))
}
}

return ""
}

// look for channel assigned to version with specified moduleName
func getAssignedChannel(releaseMetas kyma.ModuleReleaseMetaList, moduleName, version string) string {
for _, releaseMeta := range releaseMetas.Items {
Expand Down
140 changes: 118 additions & 22 deletions internal/modules/modules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,31 +124,85 @@ var (
},
},
}
)

func TestList(t *testing.T) {
t.Run("list modules from cluster", func(t *testing.T) {
scheme := runtime.NewScheme()
scheme.AddKnownTypes(kyma.GVRModuleTemplate.GroupVersion())
scheme.AddKnownTypes(kyma.GVRModuleReleaseMeta.GroupVersion())
dynamicClient := dynamic_fake.NewSimpleDynamicClient(scheme,
&testModuleTemplate1,
&testModuleTemplate2,
&testModuleTemplate3,
&testModuleTemplate4,
&testReleaseMeta1,
&testReleaseMeta2,
)

modules, err := List(context.Background(), kyma.NewClient(dynamicClient))
testKymaCR = unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "operator.kyma-project.io/v1beta2",
"kind": "Kyma",
"metadata": map[string]interface{}{
"name": kyma.DefaultKymaName,
"namespace": kyma.DefaultKymaNamespace,
},
"spec": map[string]interface{}{
"channel": "fast",
"modules": []interface{}{
map[string]interface{}{
"name": "serverless",
"managed": false,
},
map[string]interface{}{
"name": "keda",
"managed": true,
},
},
},
"status": map[string]interface{}{
"modules": []interface{}{
map[string]interface{}{
"name": "serverless",
"version": "0.0.1",
},
map[string]interface{}{
"name": "keda",
"version": "0.2",
},
},
},
},
}

require.NoError(t, err)
require.Equal(t, ModulesList(fixModuleList()), modules)
})
}
testManagedModuleList = []Module{
{
Name: "keda",
InstallDetails: ModuleInstallDetails{
Managed: ManagedTrue,
Channel: "fast",
Version: "0.2",
},
Versions: []ModuleVersion{
{
Repository: "url-3",
Version: "0.1",
Channel: "regular",
},
{
Version: "0.2",
Channel: "fast",
},
},
},
{
Name: "serverless",
InstallDetails: ModuleInstallDetails{
Managed: ManagedFalse,
Channel: "fast",
Version: "0.0.1",
},
Versions: []ModuleVersion{
{
Repository: "url-1",
Version: "0.0.1",
Channel: "fast",
},
{
Repository: "url-2",
Version: "0.0.2",
},
},
},
}

func fixModuleList() []Module {
return []Module{
testModuleList = []Module{
{
Name: "keda",
Versions: []ModuleVersion{
Expand Down Expand Up @@ -178,4 +232,46 @@ func fixModuleList() []Module {
},
},
}
)

func TestList(t *testing.T) {
t.Run("list modules from cluster without Kyma CR", func(t *testing.T) {
scheme := runtime.NewScheme()
scheme.AddKnownTypes(kyma.GVRModuleTemplate.GroupVersion())
scheme.AddKnownTypes(kyma.GVRModuleReleaseMeta.GroupVersion())
dynamicClient := dynamic_fake.NewSimpleDynamicClient(scheme,
&testModuleTemplate1,
&testModuleTemplate2,
&testModuleTemplate3,
&testModuleTemplate4,
&testReleaseMeta1,
&testReleaseMeta2,
)

modules, err := List(context.Background(), kyma.NewClient(dynamicClient))

require.NoError(t, err)
require.Equal(t, ModulesList(testModuleList), modules)
})

t.Run("list managed modules from cluster", func(t *testing.T) {
scheme := runtime.NewScheme()
scheme.AddKnownTypes(kyma.GVRModuleTemplate.GroupVersion())
scheme.AddKnownTypes(kyma.GVRModuleReleaseMeta.GroupVersion())
scheme.AddKnownTypes(kyma.GVRKyma.GroupVersion())
dynamicClient := dynamic_fake.NewSimpleDynamicClient(scheme,
&testModuleTemplate1,
&testModuleTemplate2,
&testModuleTemplate3,
&testModuleTemplate4,
&testReleaseMeta1,
&testReleaseMeta2,
&testKymaCR,
)

modules, err := List(context.Background(), kyma.NewClient(dynamicClient))

require.NoError(t, err)
require.Equal(t, ModulesList(testManagedModuleList), modules)
})
}
Loading

0 comments on commit 957c22b

Please sign in to comment.