Skip to content

Commit

Permalink
feat: Explicit version for module template (#1699)
Browse files Browse the repository at this point in the history
chore: Sync main to feature branch (#1718)

chore: Refactor NewCachedDescriptorProvider (#1695)

docs: Update KLM Local Test Setup Guide (#1680)

chore: Configure different requeue intervals for Manifest reconciliation (#1690)

feat: Drop multiple ways to reference modules in Kyma CR (#1672)

chore: Bump k8s deps (#1703)

fix: Manifest CR should update by moduletemplate generation changes (#1702)

feat: Add Version to Kyma.Spec.Modules (#1694)

chore: Refactor NewCachedDescriptorProvider (#1695)

chore: Update branch to main (#1753)

feat: Module catalog improvements implementation (#1748)

feat: Remove kyma.spec.modules[].version from api (#1837)

---------

Co-authored-by: Christoph Schwägerl <[email protected]>
Co-authored-by: Nesma Badr <[email protected]>
Co-authored-by: Benjamin Lindner <[email protected]>
Co-authored-by: Tomasz Smelcerz <[email protected]>
  • Loading branch information
4 people committed Sep 26, 2024
1 parent 1f2e30d commit e374157
Show file tree
Hide file tree
Showing 45 changed files with 1,729 additions and 177 deletions.
8 changes: 8 additions & 0 deletions .github/actions/deploy-template-operator/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ runs:
run: |
kubectl apply -f tests/moduletemplates/moduletemplate_template_operator_v2_fast.yaml
kubectl apply -f tests/moduletemplates/moduletemplate_template_operator_v1_regular.yaml
- name: Create Template Operator Module for installation by version
working-directory: lifecycle-manager
if: ${{ matrix.e2e-test == 'module-install-by-version' }}
shell: bash
run: |
kubectl apply -f tests/moduletemplates/moduletemplate_template_operator_v2_fast.yaml
kubectl apply -f tests/moduletemplates/moduletemplate_template_operator_v1_regular.yaml
kubectl apply -f tests/moduletemplates/moduletemplate_template_operator_v2_direct_version.yaml
- name: Create Template Operator Module as Mandatory Module
working-directory: lifecycle-manager
if: ${{ matrix.e2e-test == 'mandatory-module' ||
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:
- module-upgrade-channel-switch
- module-upgrade-new-version
- unmanage-module
- module-install-by-version
- skip-manifest-reconciliation
- ca-certificate-rotation
- self-signed-certificate-rotation
Expand Down
2 changes: 2 additions & 0 deletions api-version-compatibility-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ operator.kyma-project.io_moduletemplates.yaml:
- .spec.properties.target
- .spec.required[]|select(.=="target")
v1beta2:
- .spec.properties.version
- .spec.properties.moduleName
- .spec.properties.customStateCheck.description
14 changes: 14 additions & 0 deletions api/shared/channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package shared

import "strings"

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"
)

func (c Channel) Equals(value string) bool {
return string(c) == strings.ToLower(value)
}
46 changes: 8 additions & 38 deletions api/v1beta2/kyma_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ type Module struct {
// +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
// json:"-" to disable installation of specific versions until decided to roll this out
// see https://github.com/kyma-project/lifecycle-manager/issues/1847
Version string `json:"-"`

// 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 @@ -211,14 +219,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 @@ -389,36 +389,6 @@ func (kyma *Kyma) IsBeta() bool {
return found && shared.IsEnabled(beta)
}

type AvailableModule struct {
Module
Enabled bool
Unmanaged 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, Unmanaged: !module.Managed})
}

for _, module := range kyma.Status.Modules {
_, exist := moduleMap[module.Name]
if exist {
continue
}
modules = append(modules, AvailableModule{
Module: Module{
Name: module.Name,
Channel: module.Channel,
},
Enabled: false,
})
}
return modules
}

func (kyma *Kyma) EnsureLabelsAndFinalizers() bool {
if controllerutil.ContainsFinalizer(kyma, "foregroundDeletion") {
return false
Expand Down
51 changes: 51 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 @@ -78,6 +81,18 @@ type ModuleTemplateSpec struct {
// +kubebuilder:validation:MinLength:=3
Channel string `json:"channel"`

// Version identifies the version of the Module. Can be empty, or a semantic version.
// +optional
// +kubebuilder:validation:Pattern:=`^((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[a-zA-Z-][0-9a-zA-Z-]*)?)?$`
// +kubebuilder:validation:MaxLength:=32
Version string `json:"version"`

// ModuleName is the name of the Module. Can be empty.
// +optional
// +kubebuilder:validation:Pattern:=`^([a-z]{3,}(-[a-z]{3,})*)?$`
// +kubebuilder:validation:MaxLength:=64
ModuleName string `json:"moduleName"`

// Mandatory indicates whether the module is mandatory. It is used to enforce the installation of the module with
// its configuration in all runtime clusters.
// +optional
Expand Down Expand Up @@ -172,6 +187,42 @@ func (m *ModuleTemplate) IsInternal() bool {
return false
}

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

// getVersionLegacy() returns the version of the ModuleTemplate from the annotation on the object.
// Remove once shared.ModuleVersionAnnotation is removed
func (m *ModuleTemplate) getVersionLegacy() (string, error) {
if m.Annotations != nil {
moduleVersion, found := m.Annotations[shared.ModuleVersionAnnotation]
if found {
return moduleVersion, nil
}
}
return "", ErrInvalidVersion
}

// GetVersion returns the declared version of the ModuleTemplate from it's Spec.
func (m *ModuleTemplate) GetVersion() (*semver.Version, error) {
var versionValue string
var err error

if m.Spec.Version == "" {
versionValue, err = m.getVersionLegacy()
if err != nil {
return nil, err
}
} else {
versionValue = m.Spec.Version

}

version, err := semver.NewVersion(versionValue)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrInvalidVersion, err.Error())
}
return version, nil
}

func (m *ModuleTemplate) IsBeta() bool {
if isBeta, found := m.Labels[shared.BetaLabel]; found {
return strings.ToLower(isBeta) == shared.EnableLabelValue
Expand Down
105 changes: 105 additions & 0 deletions api/v1beta2/moduletemplate_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package v1beta2

import (
"strings"
"testing"

"github.com/kyma-project/lifecycle-manager/api/shared"
apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func Test_GetVersion(t *testing.T) {
const testVersion = "1.0.1"
const otherVersion = "0.0.1"
tests := []struct {
name string
m *ModuleTemplate
expectedVersion string
expectedErr string
}{
{
name: "Test GetVersion() by annotation (legacy)",
m: &ModuleTemplate{
ObjectMeta: apimetav1.ObjectMeta{
Annotations: map[string]string{
shared.ModuleVersionAnnotation: testVersion,
},
},
},
expectedVersion: testVersion,
},
{
name: "Test GetVersion() by explicit version in Spec",
m: &ModuleTemplate{
Spec: ModuleTemplateSpec{
Version: testVersion,
},
},
expectedVersion: testVersion,
},
{
name: "Test GetVersion() with both version in Spec and annotation",
m: &ModuleTemplate{
ObjectMeta: apimetav1.ObjectMeta{
Annotations: map[string]string{
shared.ModuleVersionAnnotation: otherVersion,
},
},
Spec: ModuleTemplateSpec{
Version: testVersion,
},
},
expectedVersion: testVersion,
},
{
name: "Test GetVersion without any version info",
m: &ModuleTemplate{
ObjectMeta: apimetav1.ObjectMeta{
Annotations: map[string]string{},
},
Spec: ModuleTemplateSpec{},
},
expectedErr: ErrInvalidVersion.Error(),
},
{
name: "Test GetVersion with invalid version",
m: &ModuleTemplate{
Spec: ModuleTemplateSpec{
Version: "invalid",
},
},
expectedErr: "Invalid Semantic Version",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
tt := tt
actualVersion, err := tt.m.GetVersion()
if err != nil {
if actualVersion != nil {
t.Errorf("GetVersion(): Returned version should be nil when error is not nil")
}
if tt.expectedErr == "" {
t.Errorf("GetVersion(): Unexpected error: %v", err)
}
if !strings.Contains(err.Error(), tt.expectedErr) {
t.Errorf("GetVersion(): Actual error = %v, expected error: %v", err, tt.expectedErr)
}
return
}

if actualVersion == nil {
t.Errorf("GetVersion(): Returned version should not be nil when error is nil")
}

if tt.expectedVersion == "" {
t.Errorf("GetVersion(): Expected version is empty but non-nil version is returned")
}

if actualVersion.String() != tt.expectedVersion {
t.Errorf("GetVersion(): actual version = %v, expected version: %v", actualVersion.String(), tt.expectedVersion)
}
})
}
}
16 changes: 0 additions & 16 deletions api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions config/crd/bases/operator.kyma-project.io_moduletemplates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,17 @@ spec:
Mandatory indicates whether the module is mandatory. It is used to enforce the installation of the module with
its configuration in all runtime clusters.
type: boolean
moduleName:
description: ModuleName is the name of the Module. Can be empty.
maxLength: 64
pattern: ^([a-z]{3,}(-[a-z]{3,})*)?$
type: string
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
required:
- channel
- descriptor
Expand Down
Loading

0 comments on commit e374157

Please sign in to comment.