Skip to content

Commit

Permalink
feat: Add Version to Kyma.Spec.Modules (#1694)
Browse files Browse the repository at this point in the history
* add version to kyma.spec.modules list

* add missing manifest update base on moduletemplate generation change.

* remove parameter for NewCachedDescriptorProvider

* fix dead link

* adjust unit test coverage

* fix flaky test

* chore: Refactor NewCachedDescriptorProvider (#1695)

* remove parameter for NewCachedDescriptorProvider

* fix dead link

* adjust unit test coverage

* fix flaky test

* refactor FilterTemplate

* add integration test

* Update tests/integration/controller/kyma/kyma_module_channel_test.go

Co-authored-by: Tomasz Smelcerz <tomasz.smelcerz@sap.com>

* Update tests/integration/controller/kyma/kyma_module_version_test.go

Co-authored-by: Tomasz Smelcerz <tomasz.smelcerz@sap.com>

* fix existing test

---------

Co-authored-by: Tomasz Smelcerz <tomasz.smelcerz@sap.com>
  • Loading branch information
ruanxin and Tomasz-Smelcerz-SAP authored Jul 29, 2024
1 parent 2a4eb7a commit d2b4f3f
Show file tree
Hide file tree
Showing 21 changed files with 434 additions and 100 deletions.
8 changes: 8 additions & 0 deletions api/shared/channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package shared

type Channel string

const (
// NoneChannel when this value is defined for the ModuleTemplate, it means that the ModuleTemplate is not assigned to any channel.
NoneChannel Channel = "none"
)
45 changes: 32 additions & 13 deletions api/v1beta2/kyma_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,19 @@ type Module struct {

// Channel is the desired channel of the Module. If this changes or is set, it will be used to resolve a new
// ModuleTemplate based on the new resolved resources.
// The Version and Channel are mutually exclusive options.
// +kubebuilder:validation:Pattern:=^[a-z]+$
// +kubebuilder:validation:MaxLength:=32
// +kubebuilder:validation:MinLength:=3
Channel string `json:"channel,omitempty"`

// Version is the desired version of the Module. If this changes or is set, it will be used to resolve a new
// ModuleTemplate based on this specific version.
// The Version and Channel are mutually exclusive options.
// The regular expression come from here: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
// +kubebuilder:validation:Pattern:=`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`
Version string `json:"version,omitempty"`

// RemoteModuleTemplateRef is deprecated and will no longer have any functionality.
// It will be removed in the upcoming API version.
RemoteModuleTemplateRef string `json:"remoteModuleTemplateRef,omitempty"`
Expand Down Expand Up @@ -208,14 +216,6 @@ type PartialMeta struct {

const DefaultChannel = "regular"

func PartialMetaFromObject(object apimetav1.Object) PartialMeta {
return PartialMeta{
Name: object.GetName(),
Namespace: object.GetNamespace(),
Generation: object.GetGeneration(),
}
}

func (m PartialMeta) GetName() string {
return m.Name
}
Expand Down Expand Up @@ -388,32 +388,51 @@ func (kyma *Kyma) IsBeta() bool {
type AvailableModule struct {
Module
Enabled bool
Valid bool
}

func (kyma *Kyma) GetAvailableModules() []AvailableModule {
moduleMap := make(map[string]bool)
modules := make([]AvailableModule, 0)
for _, module := range kyma.Spec.Modules {
moduleMap[module.Name] = true
modules = append(modules, AvailableModule{Module: module, Enabled: true})
if strings.ToLower(module.Channel) == string(shared.NoneChannel) {
modules = append(modules, AvailableModule{Module: module, Enabled: true, Valid: false})
continue
}
if module.Version != "" && module.Channel != "" {
modules = append(modules, AvailableModule{Module: module, Enabled: true, Valid: false})
continue
}
modules = append(modules, AvailableModule{Module: module, Enabled: true, Valid: true})
}

for _, module := range kyma.Status.Modules {
_, exist := moduleMap[module.Name]
for _, moduleInStatus := range kyma.Status.Modules {
_, exist := moduleMap[moduleInStatus.Name]
if exist {
continue
}

modules = append(modules, AvailableModule{
Module: Module{
Name: module.Name,
Channel: module.Channel,
Name: moduleInStatus.Name,
Channel: moduleInStatus.Channel,
Version: moduleInStatus.Version,
},
Enabled: false,
Valid: determineModuleValidity(moduleInStatus),
})
}
return modules
}

func determineModuleValidity(moduleStatus ModuleStatus) bool {
if moduleStatus.Template == nil {
return false
}
return true
}

func (kyma *Kyma) EnsureLabelsAndFinalizers() bool {
if controllerutil.ContainsFinalizer(kyma, "foregroundDeletion") {
return false
Expand Down
152 changes: 152 additions & 0 deletions api/v1beta2/kyma_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package v1beta2

import (
"reflect"
"testing"

apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func Test_GetAvailableModules_When_ModuleInSpecOnly(t *testing.T) {
tests := []struct {
name string
KymaSpec KymaSpec
want []AvailableModule
}{
{
name: "When Channel and Version are both set, then the module is invalid",
KymaSpec: KymaSpec{
Modules: []Module{
{Name: "Module1", Channel: "regular", Version: "v1.0"},
},
},
want: []AvailableModule{
{Module: Module{Name: "Module1", Channel: "regular", Version: "v1.0"}, Enabled: true, Valid: false},
},
},
{
name: "When Channel is set, then the module is valid",
KymaSpec: KymaSpec{
Modules: []Module{
{Name: "Module1", Channel: "regular"},
},
},
want: []AvailableModule{
{Module: Module{Name: "Module1", Channel: "regular"}, Enabled: true, Valid: true},
},
},
{
name: "When Version is set, then the module is valid",
KymaSpec: KymaSpec{
Modules: []Module{
{Name: "Module1", Version: "v1.0"},
},
},
want: []AvailableModule{
{Module: Module{Name: "Module1", Version: "v1.0"}, Enabled: true, Valid: true},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
kyma := &Kyma{
Spec: tt.KymaSpec,
}
if got := kyma.GetAvailableModules(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAvailableModules() = %v, want %v", got, tt.want)
}
})
}
}

func Test_GetAvailableModules_When_ModuleInStatusOnly(t *testing.T) {
tests := []struct {
name string
KymaStatus KymaStatus
want []AvailableModule
}{
{
name: "When Template exists, then the module is valid",
KymaStatus: KymaStatus{
Modules: []ModuleStatus{
{
Name: "Module1",
Channel: "regular",
Version: "v1.0",
Template: &TrackingObject{TypeMeta: apimetav1.TypeMeta{Kind: "ModuleTemplate"}},
},
},
},
want: []AvailableModule{
{Module: Module{Name: "Module1", Channel: "regular", Version: "v1.0"}, Enabled: false, Valid: true},
},
},
{
name: "When Template not exists,then the module is invalid",
KymaStatus: KymaStatus{
Modules: []ModuleStatus{
{
Name: "Module1",
Channel: "regular",
Version: "v1.0",
Template: nil,
},
},
},
want: []AvailableModule{
{Module: Module{Name: "Module1", Channel: "regular", Version: "v1.0"}, Enabled: false, Valid: false},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
kyma := &Kyma{
Status: tt.KymaStatus,
}
if got := kyma.GetAvailableModules(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAvailableModules() = %v, want %v", got, tt.want)
}
})
}
}

func Test_GetAvailableModules_When_ModuleExistsInSpecAndStatus(t *testing.T) {
tests := []struct {
name string
KymaSpec KymaSpec
KymaStatus KymaStatus
want []AvailableModule
}{
{
name: "When Module have different version between Spec and Status, the output should be based on Spec",
KymaSpec: KymaSpec{
Modules: []Module{
{Name: "Module1", Version: "v1.1"},
},
},
KymaStatus: KymaStatus{
Modules: []ModuleStatus{
{
Name: "Module1",
Version: "v1.0",
},
},
},
want: []AvailableModule{
{Module: Module{Name: "Module1", Version: "v1.1"}, Enabled: true, Valid: true},
},
},
}
for _, tt := range tests {
test := tt
t.Run(test.name, func(t *testing.T) {
kyma := &Kyma{
Spec: test.KymaSpec,
Status: test.KymaStatus,
}
if got := kyma.GetAvailableModules(); !reflect.DeepEqual(got, test.want) {
t.Errorf("GetAvailableModules() = %v, want %v", got, test.want)
}
})
}
}
19 changes: 19 additions & 0 deletions api/v1beta2/moduletemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ limitations under the License.
package v1beta2

import (
"errors"
"fmt"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc"
apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -183,6 +186,22 @@ func (m *ModuleTemplate) IsInternal() bool {
return false
}

var ErrInvalidVersion = errors.New("can't find valid semantic version")

func (m *ModuleTemplate) GetVersion() (*semver.Version, error) {
if m.Annotations != nil {
moduleVersion, found := m.Annotations[shared.ModuleVersionAnnotation]
if found {
version, err := semver.NewVersion(moduleVersion)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrInvalidVersion, err.Error())
}
return version, nil
}
}
return nil, ErrInvalidVersion
}

func (m *ModuleTemplate) IsBeta() bool {
if isBeta, found := m.Labels[shared.BetaLabel]; found {
return strings.ToLower(isBeta) == shared.EnableLabelValue
Expand Down
18 changes: 18 additions & 0 deletions config/crd/bases/operator.kyma-project.io_kymas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ spec:
description: |-
Channel is the desired channel of the Module. If this changes or is set, it will be used to resolve a new
ModuleTemplate based on the new resolved resources.
The Version and Channel are mutually exclusive options.
maxLength: 32
minLength: 3
pattern: ^[a-z]+$
Expand Down Expand Up @@ -97,6 +98,14 @@ spec:
RemoteModuleTemplateRef is deprecated and will no longer have any functionality.
It will be removed in the upcoming API version.
type: string
version:
description: |-
Version is the desired version of the Module. If this changes or is set, it will be used to resolve a new
ModuleTemplate based on this specific version.
The Version and Channel are mutually exclusive options.
The regular expression come from here: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
type: string
required:
- name
type: object
Expand Down Expand Up @@ -497,6 +506,7 @@ spec:
description: |-
Channel is the desired channel of the Module. If this changes or is set, it will be used to resolve a new
ModuleTemplate based on the new resolved resources.
The Version and Channel are mutually exclusive options.
maxLength: 32
minLength: 3
pattern: ^[a-z]+$
Expand Down Expand Up @@ -530,6 +540,14 @@ spec:
RemoteModuleTemplateRef is deprecated and will no longer have any functionality.
It will be removed in the upcoming API version.
type: string
version:
description: |-
Version is the desired version of the Module. If this changes or is set, it will be used to resolve a new
ModuleTemplate based on this specific version.
The Version and Channel are mutually exclusive options.
The regular expression come from here: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
type: string
required:
- name
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ spec:
pattern: ^([a-z]{3,}(-[a-z]{3,})*)?$
type: string
version:
description: Version identifies the version of the Module. It can
be empty, or a semantic version.
description: Version identifies the version of the Module. Can be
empty, or a semantic version.
maxLength: 32
pattern: ^((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[a-zA-Z-][0-9a-zA-Z-]*)?)?$
type: string
Expand Down
Loading

0 comments on commit d2b4f3f

Please sign in to comment.