Skip to content

Commit

Permalink
Enforce compatibility attributes
Browse files Browse the repository at this point in the history
* fill out statuses with the new attributes
  for managedclustes,managements,*templates
  resources
* fix a couple of logical errors
* check compatibility attributes in the
  admission controller
* tests
* fix typos

Closes Mirantis#400 Mirantis#354
  • Loading branch information
zerospiel authored and Kshatrix committed Oct 15, 2024
1 parent 835a640 commit 571dc77
Show file tree
Hide file tree
Showing 24 changed files with 521 additions and 184 deletions.
7 changes: 7 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ issues:
- text: "struct-tag: unknown option 'inline' in JSON tag"
linters:
- revive
- text: "Unhandled error in call to function fmt.Print*"
linters:
- revive
linters:
disable-all: true
enable:
Expand Down Expand Up @@ -60,6 +63,10 @@ linters:
- whitespace

linters-settings:
dupl:
# Tokens count to trigger issue.
# Default: 150
threshold: 200
gofmt:
# Apply the rewrite rules to the source before reformatting.
# https://pkg.go.dev/cmd/gofmt
Expand Down
26 changes: 20 additions & 6 deletions api/v1alpha1/clustertemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const ClusterTemplateKind = "ClusterTemplate"
const (
// Denotes the clustertemplate resource Kind.
ClusterTemplateKind = "ClusterTemplate"
// ChartAnnotationKubernetesVersion is an annotation containing the Kubernetes exact version in the SemVer format associated with a ClusterTemplate.
ChartAnnotationKubernetesVersion = "hmc.mirantis.com/k8s-version"
)

// ClusterTemplateSpec defines the desired state of ClusterTemplate
type ClusterTemplateSpec struct {
Expand Down Expand Up @@ -61,6 +66,20 @@ func (t *ClusterTemplate) FillStatusWithProviders(annotations map[string]string)
return fmt.Errorf("failed to parse ClusterTemplate infrastructure providers: %v", err)
}

kversion := annotations[ChartAnnotationKubernetesVersion]
if t.Spec.KubertenesVersion != "" {
kversion = t.Spec.KubertenesVersion
}
if kversion == "" {
return nil
}

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

t.Status.KubertenesVersion = kversion

return nil
}

Expand All @@ -69,11 +88,6 @@ func (t *ClusterTemplate) GetSpecProviders() ProvidersTupled {
return t.Spec.Providers
}

// GetStatusProviders returns .status.providers of the Template.
func (t *ClusterTemplate) GetStatusProviders() ProvidersTupled {
return t.Status.Providers
}

// GetHelmSpec returns .spec.helm of the Template.
func (t *ClusterTemplate) GetHelmSpec() *HelmSpec {
return &t.Spec.Helm
Expand Down
4 changes: 2 additions & 2 deletions api/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type (

// Holds different types of CAPI providers with either
// an exact or constrainted version in the SemVer format. The requirement
// is determined by a consumer this type.
// is determined by a consumer of this type.
ProvidersTupled struct {
// List of CAPI infrastructure providers with either an exact or constrainted version in the SemVer format.
InfrastructureProviders []ProviderTuple `json:"infrastructure,omitempty"`
Expand All @@ -49,7 +49,7 @@ type (
// Name of the provider.
Name string `json:"name,omitempty"`
// Compatibility restriction in the SemVer format (exact or constrainted version)
VersionOrContraint string `json:"versionOrContraint,omitempty"`
VersionOrConstraint string `json:"versionOrConstraint,omitempty"`
}
)

Expand Down
8 changes: 6 additions & 2 deletions api/v1alpha1/managedcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ type ManagedClusterSpec struct {
// +kubebuilder:validation:MinLength=1

// Template is a reference to a Template object located in the same namespace.
Template string `json:"template"`
Template string `json:"template"`
// Name reference to the related Credentials object.
Credential string `json:"credential,omitempty"`
// Services is a list of services created via ServiceTemplates
// that could be installed on the target cluster.
Expand All @@ -101,8 +102,11 @@ type ManagedClusterSpec struct {
// ManagedClusterStatus defines the observed state of ManagedCluster
type ManagedClusterStatus struct {
// Currently compatible K8S version of the cluster. Being set only if
// the corresponding ClusterTemplate provided it in the spec.
// provided by the corresponding ClusterTemplate.
KubertenesVersion string `json:"k8sVersion,omitempty"`
// Providers represent exposed CAPI providers with constrainted compatibility versions set.
// Propagated from the corresponding ClusterTemplate.
Providers ProvidersTupled `json:"providers,omitempty"`
// Conditions contains details for the current state of the ManagedCluster
Conditions []metav1.Condition `json:"conditions,omitempty"`
// ObservedGeneration is the last observed generation.
Expand Down
22 changes: 17 additions & 5 deletions api/v1alpha1/providertemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ChartAnnotationCAPIVersion is an annotation containing the CAPI exact version in the SemVer format associated with a ProviderTemplate.
const ChartAnnotationCAPIVersion = "hmc.mirantis.com/capi-version"

// ProviderTemplateSpec defines the desired state of ProviderTemplate
type ProviderTemplateSpec struct {
Helm HelmSpec `json:"helm"`
Expand Down Expand Up @@ -59,6 +62,20 @@ func (t *ProviderTemplate) FillStatusWithProviders(annotations map[string]string
return fmt.Errorf("failed to parse ProviderTemplate infrastructure providers: %v", err)
}

capiVersion := annotations[ChartAnnotationCAPIVersion]
if t.Spec.CAPIVersion != "" {
capiVersion = t.Spec.CAPIVersion
}
if capiVersion == "" {
return nil
}

if _, err := semver.NewVersion(capiVersion); err != nil {
return fmt.Errorf("failed to parse CAPI version %s: %w", capiVersion, err)
}

t.Status.CAPIVersion = capiVersion

return nil
}

Expand All @@ -67,11 +84,6 @@ func (t *ProviderTemplate) GetSpecProviders() ProvidersTupled {
return t.Spec.Providers
}

// GetStatusProviders returns .status.providers of the Template.
func (t *ProviderTemplate) GetStatusProviders() ProvidersTupled {
return t.Status.Providers
}

// GetHelmSpec returns .spec.helm of the Template.
func (t *ProviderTemplate) GetHelmSpec() *HelmSpec {
return &t.Spec.Helm
Expand Down
50 changes: 37 additions & 13 deletions api/v1alpha1/servicetemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@
package v1alpha1

import (
"fmt"
"strings"

"github.com/Masterminds/semver/v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const ServiceTemplateKind = "ServiceTemplate"
const (
// Denotes the servicetemplate resource Kind.
ServiceTemplateKind = "ServiceTemplate"
// ChartAnnotationKubernetesConstraint is an annotation containing the Kubernetes constrainted version in the SemVer format associated with a ServiceTemplate.
ChartAnnotationKubernetesConstraint = "hmc.mirantis.com/k8s-version-constraint"
)

// ServiceTemplateSpec defines the desired state of ServiceTemplate
type ServiceTemplateSpec struct {
Expand All @@ -43,42 +50,59 @@ type ServiceTemplateStatus struct {

// FillStatusWithProviders sets the status of the template with providers
// either from the spec or from the given annotations.
//
// The return parameter is noop and is always nil.
func (t *ServiceTemplate) FillStatusWithProviders(annotations map[string]string) error {
parseProviders := func(typ providersType) []string {
var (
pspec, pstatus []string
anno string
pspec []string
anno string
)
switch typ {
case bootstrapProvidersType:
pspec, pstatus = t.Spec.Providers.BootstrapProviders, t.Status.Providers.BootstrapProviders
anno = ChartAnnotationBootstrapProviders
pspec, anno = t.Spec.Providers.BootstrapProviders, ChartAnnotationBootstrapProviders
case controlPlaneProvidersType:
pspec, pstatus = t.Spec.Providers.ControlPlaneProviders, t.Status.Providers.ControlPlaneProviders
anno = ChartAnnotationControlPlaneProviders
pspec, anno = t.Spec.Providers.ControlPlaneProviders, ChartAnnotationControlPlaneProviders
case infrastructureProvidersType:
pspec, pstatus = t.Spec.Providers.InfrastructureProviders, t.Status.Providers.InfrastructureProviders
anno = ChartAnnotationInfraProviders
pspec, anno = t.Spec.Providers.InfrastructureProviders, ChartAnnotationInfraProviders
}

if len(pspec) > 0 {
return pstatus
return pspec
}

providers := annotations[anno]
if len(providers) == 0 {
return []string{}
}

return strings.Split(providers, ",")
splitted := strings.Split(providers, ",")
result := make([]string, 0, len(splitted))
for _, v := range splitted {
if c := strings.TrimSpace(v); c != "" {
result = append(result, c)
}
}

return result
}

t.Status.Providers.BootstrapProviders = parseProviders(bootstrapProvidersType)
t.Status.Providers.ControlPlaneProviders = parseProviders(controlPlaneProvidersType)
t.Status.Providers.InfrastructureProviders = parseProviders(infrastructureProvidersType)

kconstraint := annotations[ChartAnnotationKubernetesConstraint]
if t.Spec.KubertenesConstraint != "" {
kconstraint = t.Spec.KubertenesConstraint
}
if kconstraint == "" {
return nil
}

if _, err := semver.NewConstraint(kconstraint); err != nil {
return fmt.Errorf("failed to parse kubernetes constraint %s: %w", kconstraint, err)
}

t.Status.KubertenesConstraint = kconstraint

return nil
}

Expand Down
35 changes: 13 additions & 22 deletions api/v1alpha1/templates_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,10 @@ const (
infrastructureProvidersType
)

func parseProviders[T any](providersGetter interface {
GetSpecProviders() ProvidersTupled
GetStatusProviders() ProvidersTupled
}, typ providersType, annotations map[string]string, validationFn func(string) (T, error),
) ([]ProviderTuple, error) {
pspec, pstatus, anno := getProvidersSpecStatusAnno(providersGetter, typ)
func parseProviders[T any](providersGetter interface{ GetSpecProviders() ProvidersTupled }, typ providersType, annotations map[string]string, validationFn func(string) (T, error)) ([]ProviderTuple, error) {
pspec, anno := getProvidersSpecAnno(providersGetter, typ)
if len(pspec) > 0 {
return pstatus, nil
return pspec, nil
}

providers := annotations[anno]
Expand All @@ -102,13 +98,12 @@ func parseProviders[T any](providersGetter interface {

var (
splitted = strings.Split(providers, ",")
pstatus = make([]ProviderTuple, 0, len(splitted))
merr error
)

pstatus = make([]ProviderTuple, 0, len(splitted))

for _, v := range splitted {
nVerOrC := strings.SplitN(v, " ", 1)
v = strings.TrimSpace(v)
nVerOrC := strings.SplitN(v, " ", 2)
if len(nVerOrC) == 0 { // BCE (bound check elimination)
continue
}
Expand All @@ -121,30 +116,26 @@ func parseProviders[T any](providersGetter interface {

ver := strings.TrimSpace(nVerOrC[1])
if _, err := validationFn(ver); err != nil { // validation
merr = errors.Join(merr, fmt.Errorf("failed to parse version %s in the %s: %v", ver, v, err))
merr = errors.Join(merr, fmt.Errorf("failed to parse %s in the %s: %v", ver, v, err))
continue
}

n.VersionOrContraint = ver
n.VersionOrConstraint = ver
pstatus = append(pstatus, n)
}

return pstatus, merr
}

func getProvidersSpecStatusAnno(providersGetter interface {
GetSpecProviders() ProvidersTupled
GetStatusProviders() ProvidersTupled
}, typ providersType,
) (spec, status []ProviderTuple, annotation string) {
func getProvidersSpecAnno(providersGetter interface{ GetSpecProviders() ProvidersTupled }, typ providersType) (spec []ProviderTuple, annotation string) {
switch typ {
case bootstrapProvidersType:
return providersGetter.GetSpecProviders().BootstrapProviders, providersGetter.GetStatusProviders().BootstrapProviders, ChartAnnotationBootstrapProviders
return providersGetter.GetSpecProviders().BootstrapProviders, ChartAnnotationBootstrapProviders
case controlPlaneProvidersType:
return providersGetter.GetSpecProviders().ControlPlaneProviders, providersGetter.GetStatusProviders().ControlPlaneProviders, ChartAnnotationControlPlaneProviders
return providersGetter.GetSpecProviders().ControlPlaneProviders, ChartAnnotationControlPlaneProviders
case infrastructureProvidersType:
return providersGetter.GetSpecProviders().InfrastructureProviders, providersGetter.GetStatusProviders().InfrastructureProviders, ChartAnnotationInfraProviders
return providersGetter.GetSpecProviders().InfrastructureProviders, ChartAnnotationInfraProviders
default:
return []ProviderTuple{}, []ProviderTuple{}, ""
return []ProviderTuple{}, ""
}
}
1 change: 1 addition & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ require (
github.com/fluxcd/pkg/apis/meta v1.6.1
github.com/fluxcd/pkg/runtime v0.49.1
github.com/fluxcd/source-controller/api v1.4.1
github.com/go-logr/logr v1.4.2
github.com/google/uuid v1.6.0
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/onsi/ginkgo/v2 v2.20.2
Expand Down Expand Up @@ -72,6 +71,7 @@ require (
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
Expand Down
Loading

0 comments on commit 571dc77

Please sign in to comment.