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

Rework modules list #2343

Merged
merged 5 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 4 additions & 4 deletions internal/cmd/alpha/module/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ func newListCMD(kymaConfig *cmdcommon.KymaConfig) *cobra.Command {

cmd := &cobra.Command{
Use: "list",
Short: "List modules.",
Long: `List either installed, managed or available Kyma modules.`,
Short: "List installed modules.",
Long: `List installed Kyma modules.`,
Run: func(_ *cobra.Command, _ []string) {
clierror.Check(listModules(&cfg))
},
Expand All @@ -34,9 +34,9 @@ func listModules(cfg *modulesConfig) clierror.Error {
return clierr
}

modulesList, err := modules.List(cfg.Ctx, client)
modulesList, err := modules.ListInstalled(cfg.Ctx, client)
if err != nil {
return clierror.Wrap(err, clierror.New("failed to list available modules from the cluster"))
return clierror.Wrap(err, clierror.New("failed to list installed modules from the cluster"))
}

modules.Render(modulesList, modules.ModulesTableInfo)
Expand Down
4 changes: 4 additions & 0 deletions internal/kube/fake/kyma.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ func (c *KymaClient) GetModuleReleaseMetaForModule(_ context.Context, _ string)
return &c.ReturnModuleReleaseMeta, c.ReturnErr
}

func (c *KymaClient) GetModuleTemplate(ctx context.Context, name, namespace string) (*kyma.ModuleTemplate, error) {
return &c.ReturnModuleTemplate, c.ReturnGetModuleTemplateErr
}

func (c *KymaClient) GetModuleTemplateForModule(_ context.Context, _, _ string) (*kyma.ModuleTemplate, error) {
return &c.ReturnModuleTemplate, c.ReturnGetModuleTemplateErr
}
Expand Down
15 changes: 15 additions & 0 deletions internal/kube/kyma/kyma.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Interface interface {
ListModuleReleaseMeta(context.Context) (*ModuleReleaseMetaList, error)
ListModuleTemplate(context.Context) (*ModuleTemplateList, error)
GetModuleReleaseMetaForModule(context.Context, string) (*ModuleReleaseMeta, error)
GetModuleTemplate(context.Context, string, string) (*ModuleTemplate, error)
GetModuleTemplateForModule(context.Context, string, string) (*ModuleTemplate, error)
GetDefaultKyma(context.Context) (*Kyma, error)
UpdateDefaultKyma(context.Context, *Kyma) error
Expand Down Expand Up @@ -73,6 +74,20 @@ func (c *client) GetModuleReleaseMetaForModule(ctx context.Context, moduleName s
return nil, fmt.Errorf("can't find ModuleReleaseMeta CR for module %s", moduleName)
}

func (c *client) GetModuleTemplate(ctx context.Context, namespace, name string) (*ModuleTemplate, error) {
u, err := c.dynamic.Resource(GVRModuleTemplate).
Namespace(namespace).
Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}

moduleTemplate := &ModuleTemplate{}
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, moduleTemplate)

return moduleTemplate, err
}

// GetModuleTemplateForModule returns ModuleTemplate CR corelated with given module name in right version
func (c *client) GetModuleTemplateForModule(ctx context.Context, moduleName, moduleChannel string) (*ModuleTemplate, error) {
moduleTemplates, err := c.ListModuleTemplate(ctx)
Expand Down
9 changes: 5 additions & 4 deletions internal/kube/kyma/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ type KymaStatus struct {
}

type ModuleStatus struct {
Name string `json:"name"`
Channel string `json:"channel,omitempty"`
Version string `json:"version,omitempty"`
State string `json:"state,omitempty"`
Name string `json:"name"`
Channel string `json:"channel,omitempty"`
Version string `json:"version,omitempty"`
State string `json:"state,omitempty"`
Template unstructured.Unstructured `json:"template,omitempty"`
}

type KymaModuleInfo struct {
Expand Down
191 changes: 73 additions & 118 deletions internal/modules/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ const (
)

type ModuleInstallDetails struct {
Version string
Channel string
Managed Managed
Version string
Channel string
Managed Managed
CustomResourcePolicy string
// Possible states: https://github.com/kyma-project/lifecycle-manager/blob/main/api/shared/state.go
State string
}
Expand All @@ -41,65 +42,33 @@ type ModuleVersion struct {

type ModulesList []Module

// List returns list of available module on a cluster
// collects info about modules based on ModuleTemplates, ModuleReleaseMetas and the KymaCR
// TODO: base this func of the Kyma CR only
func List(ctx context.Context, client kube.Client) (ModulesList, error) {
moduleTemplates, err := client.Kyma().ListModuleTemplate(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to list all ModuleTemplate CRs from the cluster")
}

modulereleasemetas, err := client.Kyma().ListModuleReleaseMeta(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to list all ModuleReleaseMeta CRs from the cluster")
}

// ListInstalled returns list of installed module on a cluster
// collects info about modules based on the KymaCR
func ListInstalled(ctx context.Context, client kube.Client) (ModulesList, error) {
defaultKyma, err := client.Kyma().GetDefaultKyma(ctx)
if err != nil && !apierrors.IsNotFound(err) {
return nil, errors.Wrap(err, "failed to get default Kyma CR from the cluster")
}

modulesList := ModulesList{}
for _, moduleTemplate := range moduleTemplates.Items {
moduleName := moduleTemplate.Spec.ModuleName
if moduleName == "" {
// ignore incompatible/corrupted ModuleTemplates
continue
}
version := ModuleVersion{
Version: moduleTemplate.Spec.Version,
Repository: moduleTemplate.Spec.Info.Repository,
Channel: getAssignedChannel(
*modulereleasemetas,
moduleName,
moduleTemplate.Spec.Version,
),
}

moduleInstalled := isModuleInstalled(defaultKyma, moduleName)
for _, moduleStatus := range defaultKyma.Status.Modules {
moduleSpec := getKymaModuleSpec(defaultKyma, moduleStatus.Name)

state := ""
if moduleInstalled {
// only get state of installed modules
state, err = getModuleState(ctx, client, moduleTemplate, defaultKyma)
if err != nil {
return nil, errors.Wrapf(err, "failed to get module state from the %s ModuleTemplate", moduleTemplate.GetName())
}
}
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 a new record in the list
modulesList = append(modulesList, Module{
Name: moduleName,
InstallDetails: getInstallDetails(defaultKyma, *modulereleasemetas, moduleName, state),
Versions: []ModuleVersion{
version,
},
})
state, err := getModuleState(ctx, client, moduleStatus, moduleSpec)
if err != nil {
return nil, errors.Wrapf(err, "failed to get module state for module %s", moduleStatus.Name)
}

modulesList = append(modulesList, Module{
Name: moduleStatus.Name,
InstallDetails: ModuleInstallDetails{
Channel: moduleStatus.Channel,
Managed: getManaged(moduleSpec),
CustomResourcePolicy: getCustomResourcePolicy(moduleSpec),
Version: moduleStatus.Version,
State: state,
},
})
}

return modulesList, nil
Expand All @@ -113,7 +82,7 @@ func ListCatalog(ctx context.Context, client kube.Client) (ModulesList, error) {
return nil, errors.Wrap(err, "failed to list all ModuleTemplate CRs from the cluster")
}

modulereleasemetas, err := client.Kyma().ListModuleReleaseMeta(ctx)
moduleReleaseMetas, err := client.Kyma().ListModuleReleaseMeta(ctx)
if err != nil {
moduleList := listOldModulesCatalog(moduleTemplates)
if len(moduleList) != 0 {
Expand All @@ -133,7 +102,7 @@ func ListCatalog(ctx context.Context, client kube.Client) (ModulesList, error) {
Version: moduleTemplate.Spec.Version,
Repository: moduleTemplate.Spec.Info.Repository,
Channel: getAssignedChannel(
*modulereleasemetas,
*moduleReleaseMetas,
moduleName,
moduleTemplate.Spec.Version,
),
Expand All @@ -156,32 +125,65 @@ func ListCatalog(ctx context.Context, client kube.Client) (ModulesList, error) {
return modulesList, nil
}

func getModuleState(ctx context.Context, client kube.Client, moduleTemplate kyma.ModuleTemplate, kymaCR *kyma.Kyma) (string, error) {
// get state from Kyma CR if it exists
if state := getStateFromKymaCR(moduleTemplate, kymaCR); state != "" {
return state, nil
func getManaged(moduleSpec *kyma.Module) Managed {
if moduleSpec != nil && moduleSpec.Managed != nil {
return Managed(strconv.FormatBool(*moduleSpec.Managed))
}

// default value
return "true"
}

func getCustomResourcePolicy(moduleSpec *kyma.Module) string {
if moduleSpec != nil && moduleSpec.CustomResourcePolicy != "" {
return moduleSpec.CustomResourcePolicy
}

// default value
return "CreateAndDelete"
}

func getModuleState(ctx context.Context, client kube.Client, moduleStatus kyma.ModuleStatus, moduleSpec *kyma.Module) (string, error) {
if moduleSpec == nil {
// module is under deletion
return moduleStatus.State, nil
}

if moduleSpec.CustomResourcePolicy == "CreateAndDelete" {
// module CR is managed by klm
return moduleStatus.State, nil
}

if moduleSpec.Managed != nil && !*moduleSpec.Managed {
// module is unmanaged
return moduleStatus.State, nil
}

// TODO: replace with right namespace
// https://github.com/kyma-project/lifecycle-manager/issues/2232
moduleTemplate, err := client.Kyma().GetModuleTemplate(ctx, "kyma-system", moduleStatus.Template.GetName())
if err != nil {
return "", errors.Wrapf(err, "failed to get ModuleTemplate %s/%s", "kyma-system", moduleStatus.Template.GetName())
}

// get state from moduleTemplate.Spec.Data if it exists
state, err := getStateFromData(ctx, client, moduleTemplate.Spec.Data)
if err != nil || state != "" {
// get state from moduleTemplate.Spec.Data (module CR) if it exists
return state, err
}

// get state from resource described in moduleTemplate.Spec.Manager if it exists
state, err = getResourceState(ctx, client, moduleTemplate.Spec.Manager)
return state, err
// get state from resource described in moduleTemplate.Spec.Manager (module operator) if it exists
return getResourceState(ctx, client, moduleTemplate.Spec.Manager)
}

func getStateFromKymaCR(moduleTemplate kyma.ModuleTemplate, kymaCR *kyma.Kyma) string {
if kymaCR != nil {
for _, module := range kymaCR.Status.Modules {
if module.Name == moduleTemplate.Spec.ModuleName && module.State != "" {
return module.State
}
func getKymaModuleSpec(kymaCR *kyma.Kyma, moduleName string) *kyma.Module {
for _, module := range kymaCR.Spec.Modules {
if module.Name == moduleName {
return &module
}
}
return ""

return nil
}

func getStateFromData(ctx context.Context, client kube.Client, data unstructured.Unstructured) (string, error) {
Expand Down Expand Up @@ -301,53 +303,6 @@ func getStateFromConditions(conditions []interface{}) string {
return ""
}

func isModuleInstalled(kyma *kyma.Kyma, moduleName string) bool {
if kyma != nil {
for _, module := range kyma.Status.Modules {
if module.Name == moduleName {
return true
}
}
}

// module is not installed
return false
}

func getInstallDetails(kyma *kyma.Kyma, releaseMetas kyma.ModuleReleaseMetaList, moduleName, state 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,
State: state,
}
}
}
}

// 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 {
if module.Managed != nil {
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
Loading
Loading