Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement INSTALLED and MANAGED headers for table view when listing modules #2263

Merged
merged 2 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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