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

Compatibility: unify providers and base on CAPI contract versions #525

Merged
merged 3 commits into from
Oct 24, 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: 18 additions & 18 deletions api/v1alpha1/clustertemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,44 +30,39 @@ const (

// ClusterTemplateSpec defines the desired state of ClusterTemplate
type ClusterTemplateSpec struct {
Helm HelmSpec `json:"helm"`
Helm HelmSpec `json:"helm"`
CAPIContracts CompatibilityContracts `json:"capiContracts,omitempty"`
// Kubernetes exact version in the SemVer format provided by this ClusterTemplate.
KubernetesVersion string `json:"k8sVersion,omitempty"`
// Providers represent required CAPI providers with constrained compatibility versions set.
// Providers represent required CAPI providers with supported contract versions.
// Should be set if not present in the Helm chart metadata.
// Compatibility attributes are optional to be defined.
Providers ProvidersTupled `json:"providers,omitempty"`
Providers Providers `json:"providers,omitempty"`
}

// ClusterTemplateStatus defines the observed state of ClusterTemplate
type ClusterTemplateStatus struct {
CAPIContracts CompatibilityContracts `json:"capiContracts,omitempty"`
// Kubernetes exact version in the SemVer format provided by this ClusterTemplate.
KubernetesVersion string `json:"k8sVersion,omitempty"`
// Providers represent required CAPI providers with constrained compatibility versions set
// Providers represent required CAPI providers with supported contract versions
// if the latter has been given.
Providers ProvidersTupled `json:"providers,omitempty"`
Providers Providers `json:"providers,omitempty"`

TemplateStatusCommon `json:",inline"`
}

// FillStatusWithProviders sets the status of the template with providers
// either from the spec or from the given annotations.
func (t *ClusterTemplate) FillStatusWithProviders(annotations map[string]string) error {
var err error
t.Status.Providers.BootstrapProviders, err = parseProviders(t, bootstrapProvidersType, annotations, semver.NewConstraint)
if err != nil {
return fmt.Errorf("failed to parse ClusterTemplate bootstrap providers: %v", err)
}
t.Status.Providers = getProvidersList(t, annotations)

t.Status.Providers.ControlPlaneProviders, err = parseProviders(t, controlPlaneProvidersType, annotations, semver.NewConstraint)
contractsStatus, err := getCAPIContracts(t, annotations)
if err != nil {
return fmt.Errorf("failed to parse ClusterTemplate controlPlane providers: %v", err)
return fmt.Errorf("failed to get CAPI contract versions for ClusterTemplate %s/%s: %v", t.GetNamespace(), t.GetName(), err)
}

t.Status.Providers.InfrastructureProviders, err = parseProviders(t, infrastructureProvidersType, annotations, semver.NewConstraint)
if err != nil {
return fmt.Errorf("failed to parse ClusterTemplate infrastructure providers: %v", err)
}
t.Status.CAPIContracts = contractsStatus

kversion := annotations[ChartAnnotationKubernetesVersion]
if t.Spec.KubernetesVersion != "" {
Expand All @@ -78,16 +73,21 @@ func (t *ClusterTemplate) FillStatusWithProviders(annotations map[string]string)
}

if _, err := semver.NewVersion(kversion); err != nil {
return fmt.Errorf("failed to parse kubernetes version %s: %w", kversion, err)
return fmt.Errorf("failed to parse kubernetes version %s for ClusterTemplate %s/%s: %w", kversion, t.GetNamespace(), t.GetName(), err)
}

t.Status.KubernetesVersion = kversion

return nil
}

// GetContracts returns .spec.capiContracts of the Template.
func (t *ClusterTemplate) GetContracts() CompatibilityContracts {
return t.Spec.CAPIContracts
}

// GetSpecProviders returns .spec.providers of the Template.
func (t *ClusterTemplate) GetSpecProviders() ProvidersTupled {
func (t *ClusterTemplate) GetSpecProviders() Providers {
return t.Spec.Providers
}

Expand Down
76 changes: 10 additions & 66 deletions api/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,16 @@ const (
)

type (
// Providers hold different types of CAPI providers.
Providers struct {
// InfrastructureProviders is the list of CAPI infrastructure providers
InfrastructureProviders []string `json:"infrastructure,omitempty"`
// BootstrapProviders is the list of CAPI bootstrap providers
BootstrapProviders []string `json:"bootstrap,omitempty"`
// ControlPlaneProviders is the list of CAPI control plane providers
ControlPlaneProviders []string `json:"controlPlane,omitempty"`
}

// Holds different types of CAPI providers with either
// an exact or constrained version in the SemVer format. The requirement
// is determined by a consumer of this type.
ProvidersTupled struct {
// List of CAPI infrastructure providers with either an exact or constrained version in the SemVer format.
// Compatibility attributes are optional to be defined.
InfrastructureProviders []ProviderTuple `json:"infrastructure,omitempty"`
// List of CAPI bootstrap providers with either an exact or constrained version in the SemVer format.
// Compatibility attributes are optional to be defined.
BootstrapProviders []ProviderTuple `json:"bootstrap,omitempty"`
// List of CAPI control plane providers with either an exact or constrained version in the SemVer format.
// Compatibility attributes are optional to be defined.
ControlPlaneProviders []ProviderTuple `json:"controlPlane,omitempty"`
}

// Represents name of the provider with either an exact or constrained version in the SemVer format.
ProviderTuple struct {
// Name of the provider.
Name string `json:"name,omitempty"`
// Compatibility restriction in the SemVer format (exact or constrained version).
// Optional to be defined.
VersionOrConstraint string `json:"versionOrConstraint,omitempty"`
}
// Holds different types of CAPI providers.
Providers []string

// Holds key-value pairs with compatibility [contract versions],
// where the key is the core CAPI contract version,
// and the value is an underscore-delimited (_) list of provider contract versions
// supported by the core CAPI.
//
// [contract versions]: https://cluster-api.sigs.k8s.io/developer/providers/contracts
CompatibilityContracts map[string]string
)

const (
Expand Down Expand Up @@ -144,36 +121,3 @@ func ExtractServiceTemplateName(rawObj client.Object) []string {

return templates
}

func (c ProvidersTupled) BootstrapProvidersNames() []string {
return c.names(bootstrapProvidersType)
}

func (c ProvidersTupled) ControlPlaneProvidersNames() []string {
return c.names(controlPlaneProvidersType)
}

func (c ProvidersTupled) InfrastructureProvidersNames() []string {
return c.names(infrastructureProvidersType)
}

func (c ProvidersTupled) names(typ providersType) []string {
f := func(nn []ProviderTuple) []string {
res := make([]string, len(nn))
for i, v := range nn {
res[i] = v.Name
}
return res
}

switch typ {
case bootstrapProvidersType:
return f(c.BootstrapProviders)
case controlPlaneProvidersType:
return f(c.ControlPlaneProviders)
case infrastructureProvidersType:
return f(c.InfrastructureProviders)
default:
return []string{}
}
}
74 changes: 74 additions & 0 deletions api/v1alpha1/compatibility_contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2024
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1alpha1

import (
"strconv"
"strings"
)

// isCAPIContractVersion determines whether a given string
// represents a version in the CAPI contract version format (e.g. v1_v1beta1_v1alpha1, etc.).
func isCAPIContractVersion(version string) bool {
for _, v := range strings.Split(version, "_") {
if !isCAPIContractSingleVersion(v) {
return false
}
}

return true
}

// isCAPIContractSingleVersion determines whether a given string
// represents a single version in the CAPI contract version format (e.g. v1, v1beta1, v1alpha1, etc.).
func isCAPIContractSingleVersion(version string) bool {
if !strings.HasPrefix(version, "v") {
return false
}

parts := strings.Split(version, "v")
if len(parts) != 2 || parts[0] != "" || strings.IndexByte(version, '_') != -1 { // skip v1_v1beta1 list of versions
return false
}

const (
alphaPrefix, betaPrefix = "alpha", "beta"
)

versionNumber := parts[1]
alphaIndex := strings.Index(versionNumber, alphaPrefix)
betaIndex := strings.Index(versionNumber, betaPrefix)

if alphaIndex != -1 {
return isNonMajor(versionNumber, alphaPrefix, alphaIndex)
} else if betaIndex != -1 {
return isNonMajor(versionNumber, betaPrefix, betaIndex)
}

_, err := strconv.Atoi(strings.TrimSpace(versionNumber))
return err == nil
}

func isNonMajor(version, prefix string, prefixIdx int) bool {
majorVer := version[:prefixIdx]
prefixedVer := version[prefixIdx+len(prefix):]

if _, err := strconv.Atoi(majorVer); err != nil {
return false
}

_, err := strconv.Atoi(prefixedVer)
return err == nil
}
72 changes: 72 additions & 0 deletions api/v1alpha1/compatibility_contract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2024
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1alpha1

import "testing"

func Test_isCAPIContractVersion(t *testing.T) {
tests := []struct {
version string
isValid bool
}{
{"v1", true},
{"v1alpha1", true},
{"v1beta1", true},
{"v2", true},
{"v3alpha2", true},
{"v33beta22", true},
{"v1alpha1_v1beta1", true},
{"v1alpha1v1alha2_v1beta1", false},
{"v4beta1", true},
{"invalid", false},
{"v1alpha", false},
{"v1beta", false},
{"v1alpha1beta1", false},
}

for _, test := range tests {
result := isCAPIContractVersion(test.version)
if result != test.isValid {
t.Errorf("isValidVersion(%q) = %v, want %v", test.version, result, test.isValid)
}
}
}

func Test_isCAPIContractSingleVersion(t *testing.T) {
tests := []struct {
version string
isValid bool
}{
{"v1", true},
{"v1alpha1", true},
{"v1beta1", true},
{"v2", true},
{"v3alpha2", true},
{"v33beta22", true},
{"v4beta1", true},
{"invalid", false},
{"v1alpha", false},
{"v1beta", false},
{"v1alpha1beta1", false},
{"v1alpha1_v1beta1", false},
}

for _, test := range tests {
result := isCAPIContractSingleVersion(test.version)
if result != test.isValid {
t.Errorf("isValidVersion(%q) = %v, want %v", test.version, result, test.isValid)
}
}
}
11 changes: 9 additions & 2 deletions api/v1alpha1/management_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,20 @@ func GetDefaultProviders() []Provider {

// ManagementStatus defines the observed state of Management
type ManagementStatus struct {
// For each CAPI provider name holds its compatibility [contract versions]
// in a key-value pairs, where the key is the core CAPI contract version,
// and the value is an underscore-delimited (_) list of provider contract versions
// supported by the core CAPI.
//
// [contract versions]: https://cluster-api.sigs.k8s.io/developer/providers/contracts
CAPIContracts map[string]CompatibilityContracts `json:"capiContracts,omitempty"`
// Components indicates the status of installed HMC components and CAPI providers.
Components map[string]ComponentStatus `json:"components,omitempty"`
// Release indicates the current Release object.
Release string `json:"release,omitempty"`
// AvailableProviders holds all CAPI providers available along with
// their exact compatibility versions if specified in ProviderTemplates on the Management cluster.
AvailableProviders ProvidersTupled `json:"availableProviders,omitempty"`
// their supported contract versions, if specified in ProviderTemplates, on the Management cluster.
AvailableProviders Providers `json:"availableProviders,omitempty"`
// ObservedGeneration is the last observed generation.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}
Expand Down
Loading