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
  • Loading branch information
zerospiel committed Oct 4, 2024
1 parent 3c63ec6 commit fcadb9e
Show file tree
Hide file tree
Showing 24 changed files with 536 additions and 213 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
8 changes: 4 additions & 4 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 Expand Up @@ -132,11 +132,11 @@ func (c ProvidersTupled) BootstrapProvidersNames() []string {
}

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

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

func (c ProvidersTupled) names(typ providersType) []string {
Expand Down
36 changes: 19 additions & 17 deletions api/v1alpha1/managedcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,24 +64,23 @@ const (
ProgressingReason string = "Progressing"
)

// ManagedClusterServiceSpec represents a Service within ManagedCluster
type ManagedClusterServiceSpec struct {
// Template is a reference to a Template object located in the same namespace.
// ManagedClusterService represents a Service within ManagedCluster
type ManagedClusterService struct {
// Values is the helm values to be passed to the template.
Values *apiextensionsv1.JSON `json:"values,omitempty"`
// +kubebuilder:validation:MinLength=1

// Template is a reference to a ServiceTemplate object located in the same namespace.
Template string `json:"template"`
// Disable can be set to disable handling of this service.
// +optional
Disable bool `json:"disable"`
// Name is the chart release.
// +kubebuilder:validation:MinLength=1

// Name is the chart release.
Name string `json:"name"`
// Namespace is the namespace the release will be installed in.
// It will default to Name if not provided.
// +optional
Namespace string `json:"namespace"`
// Values is the helm values to be passed to the template.
// +optional
Values *apiextensionsv1.JSON `json:"values,omitempty"`
Namespace string `json:"namespace,omitempty"`
// Disable can be set to disable handling of this service.
Disable bool `json:"disable,omitempty"`
}

// ManagedClusterSpec defines the desired state of ManagedCluster
Expand All @@ -95,20 +94,23 @@ type ManagedClusterSpec struct {

// Template is a reference to a Template object located in the same namespace.
Template string `json:"template"`
// DryRun specifies whether the template should be applied after validation or only validated.
DryRun bool `json:"dryRun,omitempty"`
// 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.
// +optional
Services []ManagedClusterServiceSpec `json:"services,omitempty"`
Services []ManagedClusterService `json:"services,omitempty"`
// DryRun specifies whether the template should be applied after validation or only validated.
DryRun bool `json:"dryRun,omitempty"`
}

// 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
40 changes: 28 additions & 12 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,28 +50,23 @@ 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]
Expand All @@ -79,6 +81,20 @@ func (t *ServiceTemplate) FillStatusWithProviders(annotations map[string]string)
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
34 changes: 12 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,11 @@ 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)
nVerOrC := strings.SplitN(v, " ", 2)
if len(nVerOrC) == 0 { // BCE (bound check elimination)
continue
}
Expand All @@ -121,30 +115,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{}, ""
}
}
11 changes: 6 additions & 5 deletions api/v1alpha1/zz_generated.deepcopy.go

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

Loading

0 comments on commit fcadb9e

Please sign in to comment.