diff --git a/cmd/clusterctl/api/v1alpha3/metadata_type.go b/cmd/clusterctl/api/v1alpha3/metadata_type.go index fbe715e636c1..e7b6cf199f1f 100644 --- a/cmd/clusterctl/api/v1alpha3/metadata_type.go +++ b/cmd/clusterctl/api/v1alpha3/metadata_type.go @@ -32,12 +32,12 @@ type Metadata struct { // +optional metav1.ObjectMeta `json:"metadata,omitempty"` - // releaseSeries maps a provider release series (major/minor) with an API Version of Cluster API (contract). + // releaseSeries maps a provider release series (major/minor) with a Cluster API contract version. // +optional ReleaseSeries []ReleaseSeries `json:"releaseSeries"` } -// ReleaseSeries maps a provider release series (major/minor) with a API Version of Cluster API (contract). +// ReleaseSeries maps a provider release series (major/minor) with a Cluster API contract version. type ReleaseSeries struct { // major version of the release series // +optional diff --git a/cmd/clusterctl/client/client.go b/cmd/clusterctl/client/client.go index 515f17ced497..c3886dd6958e 100644 --- a/cmd/clusterctl/client/client.go +++ b/cmd/clusterctl/client/client.go @@ -19,6 +19,8 @@ package client import ( "context" + "k8s.io/apimachinery/pkg/util/sets" + clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/alpha" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" @@ -106,10 +108,12 @@ type YamlPrinter interface { // clusterctlClient implements Client. type clusterctlClient struct { - configClient config.Client - repositoryClientFactory RepositoryClientFactory - clusterClientFactory ClusterClientFactory - alphaClient alpha.Client + configClient config.Client + repositoryClientFactory RepositoryClientFactory + clusterClientFactory ClusterClientFactory + alphaClient alpha.Client + currentContractVersion string + getCompatibleContractVersions func(string) sets.Set[string] } // RepositoryClientFactoryInput represents the inputs required by the factory. @@ -159,13 +163,24 @@ func InjectClusterClientFactory(factory ClusterClientFactory) Option { } } +// InjectCurrentContractVersion allows you to override the currentContractVersion that +// cluster client uses. This option is intended for internal tests only. +func InjectCurrentContractVersion(currentContractVersion string) Option { + return func(c *clusterctlClient) { + c.currentContractVersion = currentContractVersion + } +} + // New returns a configClient. func New(ctx context.Context, path string, options ...Option) (Client, error) { return newClusterctlClient(ctx, path, options...) } func newClusterctlClient(ctx context.Context, path string, options ...Option) (*clusterctlClient, error) { - client := &clusterctlClient{} + client := &clusterctlClient{ + currentContractVersion: cluster.CurrentContractVersion, + getCompatibleContractVersions: cluster.GetCompatibleContractVersions, + } for _, o := range options { o(client) } @@ -187,7 +202,7 @@ func newClusterctlClient(ctx context.Context, path string, options ...Option) (* // if there is an injected ClusterFactory, use it, otherwise use a default one. if client.clusterClientFactory == nil { - client.clusterClientFactory = defaultClusterFactory(client.configClient) + client.clusterClientFactory = defaultClusterFactory(client.configClient, client.currentContractVersion, client.getCompatibleContractVersions) } // if there is an injected alphaClient, use it, otherwise use a default one. @@ -212,13 +227,15 @@ func defaultRepositoryFactory(configClient config.Client) RepositoryClientFactor } // defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library. -func defaultClusterFactory(configClient config.Client) ClusterClientFactory { +func defaultClusterFactory(configClient config.Client, currentContractVersion string, getCompatibleContractVersions func(string) sets.Set[string]) ClusterClientFactory { return func(input ClusterClientFactoryInput) (cluster.Client, error) { return cluster.New( // Kubeconfig is a type alias to cluster.Kubeconfig cluster.Kubeconfig(input.Kubeconfig), configClient, cluster.InjectYamlProcessor(input.Processor), + cluster.InjectCurrentContractVersion(currentContractVersion), + cluster.InjectGetCompatibleContractVersionsFunc(getCompatibleContractVersions), ), nil } } diff --git a/cmd/clusterctl/client/client_test.go b/cmd/clusterctl/client/client_test.go index 36b3d61fd1fe..38abc334addb 100644 --- a/cmd/clusterctl/client/client_test.go +++ b/cmd/clusterctl/client/client_test.go @@ -23,6 +23,8 @@ import ( "time" "github.com/pkg/errors" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/wait" @@ -182,6 +184,7 @@ func newFakeClient(ctx context.Context, configClient config.Client) *fakeClient } return fake.repositories[input.Provider.ManifestLabel()], nil }), + InjectCurrentContractVersion(currentContractVersion), ) return fake @@ -225,6 +228,8 @@ func newFakeCluster(kubeconfig cluster.Kubeconfig, configClient config.Client) * } return fake.repositories[provider.Name()], nil }), + cluster.InjectCurrentContractVersion(currentContractVersion), + cluster.InjectGetCompatibleContractVersionsFunc(getCompatibleContractVersions), ) return fake } @@ -604,3 +609,19 @@ func (f *fakeComponentClient) getRawBytes(ctx context.Context, options *reposito return f.fakeRepository.GetFile(ctx, options.Version, path) } + +func fakeCAPISetupObjects() []client.Object { + return []client.Object{ + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "clusters.cluster.x-k8s.io"}, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: currentContractVersion, + Storage: true, + }, + }, + }, + }, + } +} diff --git a/cmd/clusterctl/client/cluster/client.go b/cmd/clusterctl/client/cluster/client.go index 367a3955733d..a4250902f6d3 100644 --- a/cmd/clusterctl/client/cluster/client.go +++ b/cmd/clusterctl/client/cluster/client.go @@ -21,14 +21,34 @@ import ( "time" "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository" yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor" logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log" ) +var ( + // CurrentContractVersion is the contract version supported by this Cluster API version. + // Note: Each Cluster API version supports one contract version, and by convention the contract version matches the current API version. + CurrentContractVersion = clusterv1.GroupVersion.Version +) + +// GetCompatibleContractVersions return the list of contract version compatible with a given contract version. +// NOTE: A contract version might be temporarily compatible with older contract versions e.g. to allow users time to transition to the new API. +// NOTE: The return value must include also the contract version received in input. +func GetCompatibleContractVersions(contract string) sets.Set[string] { + compatibleContracts := sets.New(contract) + // v1beta2 contract is temporarily be compatible with v1beta1 (until v1beta1 is EOL). + if contract == "v1beta2" { + compatibleContracts.Insert("v1beta1") + } + return compatibleContracts +} + // Kubeconfig is a type that specifies inputs related to the actual // kubeconfig. type Kubeconfig struct { @@ -89,12 +109,14 @@ type PollImmediateWaiter func(ctx context.Context, interval, timeout time.Durati // clusterClient implements Client. type clusterClient struct { - configClient config.Client - kubeconfig Kubeconfig - proxy Proxy - repositoryClientFactory RepositoryClientFactory - pollImmediateWaiter PollImmediateWaiter - processor yaml.Processor + configClient config.Client + kubeconfig Kubeconfig + proxy Proxy + repositoryClientFactory RepositoryClientFactory + pollImmediateWaiter PollImmediateWaiter + processor yaml.Processor + currentContractVersion string + getCompatibleContractVersions func(string) sets.Set[string] } // RepositoryClientFactory defines a function that returns a new repository.Client. @@ -120,11 +142,11 @@ func (c *clusterClient) ProviderComponents() ComponentsClient { } func (c *clusterClient) ProviderInventory() InventoryClient { - return newInventoryClient(c.proxy, c.pollImmediateWaiter) + return newInventoryClient(c.proxy, c.pollImmediateWaiter, c.currentContractVersion) } func (c *clusterClient) ProviderInstaller() ProviderInstaller { - return newProviderInstaller(c.configClient, c.repositoryClientFactory, c.proxy, c.ProviderInventory(), c.ProviderComponents()) + return newProviderInstaller(c.configClient, c.repositoryClientFactory, c.proxy, c.ProviderInventory(), c.ProviderComponents(), c.currentContractVersion, c.getCompatibleContractVersions) } func (c *clusterClient) ObjectMover() ObjectMover { @@ -132,7 +154,7 @@ func (c *clusterClient) ObjectMover() ObjectMover { } func (c *clusterClient) ProviderUpgrader() ProviderUpgrader { - return newProviderUpgrader(c.configClient, c.proxy, c.repositoryClientFactory, c.ProviderInventory(), c.ProviderComponents()) + return newProviderUpgrader(c.configClient, c.proxy, c.repositoryClientFactory, c.ProviderInventory(), c.ProviderComponents(), c.currentContractVersion, c.getCompatibleContractVersions) } func (c *clusterClient) Template() TemplateClient { @@ -183,6 +205,24 @@ func InjectYamlProcessor(p yaml.Processor) Option { } } +// InjectGetCompatibleContractVersionsFunc allows you to override the getCompatibleContractVersions function that +// cluster client uses. This option is intended for internal tests only. +func InjectGetCompatibleContractVersionsFunc(getCompatibleContractVersions func(string) sets.Set[string]) Option { + return func(c *clusterClient) { + if getCompatibleContractVersions != nil { + c.getCompatibleContractVersions = getCompatibleContractVersions + } + } +} + +// InjectCurrentContractVersion allows you to override the currentContractVersion that +// cluster client uses. This option is intended for internal tests only. +func InjectCurrentContractVersion(currentContractVersion string) Option { + return func(c *clusterClient) { + c.currentContractVersion = currentContractVersion + } +} + // New returns a cluster.Client. func New(kubeconfig Kubeconfig, configClient config.Client, options ...Option) Client { return newClusterClient(kubeconfig, configClient, options...) @@ -190,9 +230,11 @@ func New(kubeconfig Kubeconfig, configClient config.Client, options ...Option) C func newClusterClient(kubeconfig Kubeconfig, configClient config.Client, options ...Option) *clusterClient { client := &clusterClient{ - configClient: configClient, - kubeconfig: kubeconfig, - processor: yaml.NewSimpleProcessor(), + configClient: configClient, + kubeconfig: kubeconfig, + processor: yaml.NewSimpleProcessor(), + currentContractVersion: CurrentContractVersion, + getCompatibleContractVersions: GetCompatibleContractVersions, } for _, o := range options { o(client) diff --git a/cmd/clusterctl/client/cluster/components.go b/cmd/clusterctl/client/cluster/components.go index a33cd8294bce..b0c8ee3c593c 100644 --- a/cmd/clusterctl/client/cluster/components.go +++ b/cmd/clusterctl/client/cluster/components.go @@ -66,10 +66,6 @@ type ComponentsClient interface { // and for the deletion of the provider's CRDs. Delete(ctx context.Context, options DeleteOptions) error - // DeleteWebhookNamespace deletes the core provider webhook namespace (eg. capi-webhook-system). - // This is required when upgrading to v1alpha4 where webhooks are included in the controller itself. - DeleteWebhookNamespace(ctx context.Context) error - // ValidateNoObjectsExist checks if custom resources of the custom resource definitions exist and returns an error if so. ValidateNoObjectsExist(ctx context.Context, provider clusterctlv1.Provider) error } diff --git a/cmd/clusterctl/client/cluster/installer.go b/cmd/clusterctl/client/cluster/installer.go index 6e428e64777f..09aee84afa6e 100644 --- a/cmd/clusterctl/client/cluster/installer.go +++ b/cmd/clusterctl/client/cluster/installer.go @@ -34,7 +34,6 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository" @@ -57,7 +56,7 @@ type ProviderInstaller interface { // Validate performs steps to validate a management cluster by looking at the current state and the providers in the queue. // The following checks are performed in order to ensure a fully operational cluster: // - There must be only one instance of the same provider - // - All the providers in must support the same API Version of Cluster API (contract) + // - All providers must support the contract version or one of its compatible versions // - All provider CRDs that are referenced in core Cluster API CRDs must comply with the CRD naming scheme, // otherwise a warning is logged. Validate(context.Context) error @@ -74,12 +73,14 @@ type InstallOptions struct { // providerInstaller implements ProviderInstaller. type providerInstaller struct { - configClient config.Client - repositoryClientFactory RepositoryClientFactory - proxy Proxy - providerComponents ComponentsClient - providerInventory InventoryClient - installQueue []repository.Components + configClient config.Client + repositoryClientFactory RepositoryClientFactory + proxy Proxy + providerComponents ComponentsClient + providerInventory InventoryClient + installQueue []repository.Components + currentContractVersion string + getCompatibleContractVersions func(string) sets.Set[string] } var _ ProviderInstaller = &providerInstaller{} @@ -188,13 +189,13 @@ func (i *providerInstaller) Validate(ctx context.Context) error { } } - // Gets the API Version of Cluster API (contract) all the providers in the management cluster must support, - // which is the same of the core provider. + // Gets the contract version that all the providers in the management cluster must support, + // which is the same of the core provider (or a compatible one). providerInstanceContracts := map[string]string{} coreProviders := providerList.FilterCore() if len(coreProviders) != 1 { - return errors.Errorf("invalid management cluster: there should a core provider, found %d", len(coreProviders)) + return errors.Errorf("invalid management cluster: there must be one core provider, found %d", len(coreProviders)) } coreProvider := coreProviders[0] @@ -202,18 +203,19 @@ func (i *providerInstaller) Validate(ctx context.Context) error { if err != nil { return err } + compatibleContracts := i.getCompatibleContractVersions(managementClusterContract) - // Checks if all the providers supports the same API Version of Cluster API (contract). + // Checks if all the providers supports the same Cluster API contract or compatible contracts. for _, components := range i.installQueue { provider := components.InventoryObject() - // Gets the API Version of Cluster API (contract) the provider support and compare it with the management cluster contract. + // Gets the contract version supported by the provider and compare it with the contract versions the management cluster is compatible with. providerContract, err := i.getProviderContract(ctx, providerInstanceContracts, provider) if err != nil { return err } - if providerContract != managementClusterContract { - return errors.Errorf("installing provider %q can lead to a non functioning management cluster: the target version for the provider supports the %s API Version of Cluster API (contract), while the management cluster is using %s", components.ManifestLabel(), providerContract, managementClusterContract) + if !compatibleContracts.Has(providerContract) { + return errors.Errorf("installing provider %q could lead to a non functioning management cluster: the target version for the provider implements the %s contract version, while the core provider is compatible with %s contract versions", components.ManifestLabel(), providerContract, strings.Join(compatibleContracts.UnsortedList(), ",")) } } @@ -285,7 +287,7 @@ func validateCRDName(obj unstructured.Unstructured, gk *schema.GroupKind) error "CRDs. If not, this warning can be hidden by setting the %q' annotation.", obj.GetName(), correctCRDName, clusterctlv1.SkipCRDNamePreflightCheckAnnotation) } -// getProviderContract returns the API Version of Cluster API (contract) for a provider instance. +// getProviderContract returns the contract versions supported by a provider instance. func (i *providerInstaller) getProviderContract(ctx context.Context, providerInstanceContracts map[string]string, provider clusterctlv1.Provider) (string, error) { // If the contract for the provider instance is already known, return it. if contract, ok := providerInstanceContracts[provider.InstanceName()]; ok { @@ -321,8 +323,9 @@ func (i *providerInstaller) getProviderContract(ctx context.Context, providerIns return "", errors.Errorf("invalid provider metadata: version %s for the provider %s does not match any release series", provider.Version, provider.InstanceName()) } - if releaseSeries.Contract != clusterv1.GroupVersion.Version { - return "", errors.Errorf("current version of clusterctl is only compatible with %s providers, detected %s for provider %s", clusterv1.GroupVersion.Version, releaseSeries.Contract, provider.ManifestLabel()) + compatibleContracts := i.getCompatibleContractVersions(i.currentContractVersion) + if !compatibleContracts.Has(releaseSeries.Contract) { + return "", errors.Errorf("current version of clusterctl is only compatible with providers implementing the %s contract versions, detected contract version %s for provider %s", strings.Join(compatibleContracts.UnsortedList(), ", "), releaseSeries.Contract, provider.ManifestLabel()) } providerInstanceContracts[provider.InstanceName()] = releaseSeries.Contract @@ -357,12 +360,14 @@ func (i *providerInstaller) Images() []string { return sets.List(ret) } -func newProviderInstaller(configClient config.Client, repositoryClientFactory RepositoryClientFactory, proxy Proxy, providerMetadata InventoryClient, providerComponents ComponentsClient) *providerInstaller { +func newProviderInstaller(configClient config.Client, repositoryClientFactory RepositoryClientFactory, proxy Proxy, providerMetadata InventoryClient, providerComponents ComponentsClient, currentContractVersion string, getCompatibleContractVersions func(string) sets.Set[string]) *providerInstaller { return &providerInstaller{ - configClient: configClient, - repositoryClientFactory: repositoryClientFactory, - proxy: proxy, - providerComponents: providerComponents, - providerInventory: providerMetadata, + configClient: configClient, + repositoryClientFactory: repositoryClientFactory, + proxy: proxy, + providerComponents: providerComponents, + providerInventory: providerMetadata, + currentContractVersion: currentContractVersion, + getCompatibleContractVersions: getCompatibleContractVersions, } } diff --git a/cmd/clusterctl/client/cluster/installer_test.go b/cmd/clusterctl/client/cluster/installer_test.go index f2d61368f797..292ba80b7024 100644 --- a/cmd/clusterctl/client/cluster/installer_test.go +++ b/cmd/clusterctl/client/cluster/installer_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" @@ -30,69 +31,112 @@ import ( "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" ) +var ( + // oldContractVersionNotSupportedAnymore define the previous Cluster API contract, not supported by this release of clusterctl. + // (it has been removed by version of CAPI older that the version in use). + oldContractVersionNotSupportedAnymore = "v1alpha4" + + // oldContractVersionStillSupported define an old Cluster API contract still supported. + oldContractVersionStillSupported = "v1beta1" + + // currentContractVersion define the current Cluster API contract. + currentContractVersion = "v1beta2" + + // nextContractVersionNotSupportedYet define the next Cluster API contract, not supported by this release of clusterctl. + // (it has been introduced by version of CAPI newer that the version in use). + nextContractVersionNotSupportedYet = "v99" + + getCompatibleContractVersions = func(contract string) sets.Set[string] { + compatibleContracts := sets.New(contract) + if contract == currentContractVersion { + compatibleContracts.Insert(oldContractVersionStillSupported) + } + return compatibleContracts + } +) + func Test_providerInstaller_Validate(t *testing.T) { fakeReader := test.NewFakeReader(). WithProvider("cluster-api", clusterctlv1.CoreProviderType, "https://somewhere.com"). WithProvider("infra1", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"). - WithProvider("infra2", clusterctlv1.InfrastructureProviderType, "https://somewhere.com") + WithProvider("infra2", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"). + WithProvider("infra3", clusterctlv1.InfrastructureProviderType, "https://somewhere.com") repositoryMap := map[string]repository.Repository{ "cluster-api": repository.NewMemoryRepository(). WithVersions("v0.9.0", "v1.0.0", "v1.0.1", "v2.0.0"). WithMetadata("v0.9.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupportedAnymore}, }, }). WithMetadata("v1.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), "infrastructure-infra1": repository.NewMemoryRepository(). WithVersions("v0.9.0", "v1.0.0", "v1.0.1", "v2.0.0"). WithMetadata("v0.9.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupportedAnymore}, }, }). WithMetadata("v1.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), "infrastructure-infra2": repository.NewMemoryRepository(). WithVersions("v0.9.0", "v1.0.0", "v1.0.1", "v2.0.0"). WithMetadata("v0.9.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupportedAnymore}, + }, + }). + WithMetadata("v1.0.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 1, Minor: 0, Contract: currentContractVersion}, + }, + }). + WithMetadata("v2.0.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, + }, + }), + "infrastructure-infra3": repository.NewMemoryRepository(). + WithVersions("v0.9.0", "v1.0.0", "v1.0.1", "v2.0.0"). + WithMetadata("v0.9.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupportedAnymore}, }, }). WithMetadata("v1.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionStillSupported}, }, }). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 0, Contract: oldContractVersionStillSupported}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), } @@ -117,6 +161,17 @@ func Test_providerInstaller_Validate(t *testing.T) { }, wantErr: false, }, + { + name: "install core/current contract + infra3/compatible contract on an empty cluster", + fields: fields{ + proxy: test.NewFakeProxy(), // empty cluster + installQueue: []repository.Components{ + newFakeComponents("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), + newFakeComponents("infra3", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra3-system"), + }, + }, + wantErr: false, + }, { name: "install infra2/current contract on a cluster already initialized with core/current contract + infra1/current contract", fields: fields{ @@ -130,16 +185,16 @@ func Test_providerInstaller_Validate(t *testing.T) { wantErr: false, }, { - name: "install another instance of infra1/current contract on a cluster already initialized with core/current contract + infra1/current contract", + name: "install infra3/compatible contract on a cluster already initialized with core/current contract + infra1/current contract", fields: fields{ proxy: test.NewFakeProxy(). WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"). - WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "ns1"), + WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra1-system"), installQueue: []repository.Components{ - newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "ns2"), + newFakeComponents("infra3", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra3-system"), }, }, - wantErr: true, + wantErr: false, }, { name: "install another instance of infra1/current contract on a cluster already initialized with core/current contract + infra1/current contract, same namespace of the existing infra1", @@ -166,7 +221,7 @@ func Test_providerInstaller_Validate(t *testing.T) { wantErr: true, }, { - name: "install core/previous contract + infra1/previous contract on an empty cluster (not supported)", + name: "install core/previous contract (not supported) + infra1/previous contract (not supported) on an empty cluster", fields: fields{ proxy: test.NewFakeProxy(), // empty cluster installQueue: []repository.Components{ @@ -177,7 +232,7 @@ func Test_providerInstaller_Validate(t *testing.T) { wantErr: true, }, { - name: "install core/previous contract + infra1/current contract on an empty cluster (not supported)", + name: "install core/previous contract (not supported) + infra1/current contract on an empty cluster", fields: fields{ proxy: test.NewFakeProxy(), // empty cluster installQueue: []repository.Components{ @@ -199,7 +254,7 @@ func Test_providerInstaller_Validate(t *testing.T) { wantErr: true, }, { - name: "install core/next contract + infra1/next contract on an empty cluster (not supported)", + name: "install core/next contract (not supported) + infra1/next contract (not supported) on an empty cluster", fields: fields{ proxy: test.NewFakeProxy(), // empty cluster installQueue: []repository.Components{ @@ -210,7 +265,7 @@ func Test_providerInstaller_Validate(t *testing.T) { wantErr: true, }, { - name: "install core/current contract + infra1/next contract on an empty cluster (not supported)", + name: "install core/current contract + infra1/next contract (not supported) on an empty cluster", fields: fields{ proxy: test.NewFakeProxy(), // empty cluster installQueue: []repository.Components{ @@ -244,11 +299,13 @@ func Test_providerInstaller_Validate(t *testing.T) { i := &providerInstaller{ configClient: configClient, proxy: tt.fields.proxy, - providerInventory: newInventoryClient(tt.fields.proxy, nil), + providerInventory: newInventoryClient(tt.fields.proxy, nil, currentContractVersion), repositoryClientFactory: func(ctx context.Context, provider config.Provider, configClient config.Client, _ ...repository.Option) (repository.Client, error) { return repository.New(ctx, provider, configClient, repository.InjectRepository(repositoryMap[provider.ManifestLabel()])) }, - installQueue: tt.fields.installQueue, + installQueue: tt.fields.installQueue, + currentContractVersion: currentContractVersion, + getCompatibleContractVersions: getCompatibleContractVersions, } err := i.Validate(ctx) diff --git a/cmd/clusterctl/client/cluster/inventory.go b/cmd/clusterctl/client/cluster/inventory.go index a318a2f273f3..5315205e19f9 100644 --- a/cmd/clusterctl/client/cluster/inventory.go +++ b/cmd/clusterctl/client/cluster/inventory.go @@ -126,18 +126,20 @@ type InventoryClient interface { // inventoryClient implements InventoryClient. type inventoryClient struct { - proxy Proxy - pollImmediateWaiter PollImmediateWaiter + proxy Proxy + pollImmediateWaiter PollImmediateWaiter + currentContractVersion string } // ensure inventoryClient implements InventoryClient. var _ InventoryClient = &inventoryClient{} // newInventoryClient returns a inventoryClient. -func newInventoryClient(proxy Proxy, pollImmediateWaiter PollImmediateWaiter) *inventoryClient { +func newInventoryClient(proxy Proxy, pollImmediateWaiter PollImmediateWaiter, currentContractVersion string) *inventoryClient { return &inventoryClient{ - proxy: proxy, - pollImmediateWaiter: pollImmediateWaiter, + proxy: proxy, + pollImmediateWaiter: pollImmediateWaiter, + currentContractVersion: currentContractVersion, } } @@ -418,7 +420,7 @@ func (p *inventoryClient) CheckCAPIContract(ctx context.Context, options ...Chec for _, version := range crd.Spec.Versions { if version.Storage { - if version.Name == clusterv1.GroupVersion.Version { + if version.Name == p.currentContractVersion { return nil } for _, allowedContract := range opt.AllowCAPIContracts { @@ -426,7 +428,7 @@ func (p *inventoryClient) CheckCAPIContract(ctx context.Context, options ...Chec return nil } } - return errors.Errorf("this version of clusterctl could be used only with %q management clusters, %q detected", clusterv1.GroupVersion.Version, version.Name) + return errors.Errorf("this version of clusterctl could be used only with %q management clusters, %q detected", p.currentContractVersion, version.Name) } } return errors.Errorf("failed to check Cluster API version") @@ -468,7 +470,7 @@ func (p *inventoryClient) CheckSingleProviderInstance(ctx context.Context) error if len(errs) > 0 { return errors.Wrap(kerrors.NewAggregate(errs), "detected multiple instances of the same provider, "+ - "but clusterctl does not support this use case. See https://cluster-api.sigs.k8s.io/developer/architecture/controllers/support-multiple-instances.html for more details") + "but clusterctl does not support this use case. See https://cluster-api.sigs.k8s.io/developer/core/support-multiple-instances for more details") } return nil diff --git a/cmd/clusterctl/client/cluster/inventory_test.go b/cmd/clusterctl/client/cluster/inventory_test.go index 9eaa6f7e29d1..aa8b26d6a99d 100644 --- a/cmd/clusterctl/client/cluster/inventory_test.go +++ b/cmd/clusterctl/client/cluster/inventory_test.go @@ -69,7 +69,7 @@ func Test_inventoryClient_CheckInventoryCRDs(t *testing.T) { ctx := context.Background() proxy := test.NewFakeProxy() - p := newInventoryClient(proxy, fakePollImmediateWaiter) + p := newInventoryClient(proxy, fakePollImmediateWaiter, currentContractVersion) if tt.fields.alreadyHasCRD { // forcing creation of metadata before test g.Expect(p.EnsureCustomResourceDefinitions(ctx)).To(Succeed()) @@ -116,7 +116,7 @@ func Test_inventoryClient_List(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - p := newInventoryClient(test.NewFakeProxy().WithObjs(tt.fields.initObjs...), fakePollImmediateWaiter) + p := newInventoryClient(test.NewFakeProxy().WithObjs(tt.fields.initObjs...), fakePollImmediateWaiter, currentContractVersion) got, err := p.List(context.Background()) if tt.wantErr { g.Expect(err).To(HaveOccurred()) @@ -249,10 +249,10 @@ func Test_CheckCAPIContract(t *testing.T) { Spec: apiextensionsv1.CustomResourceDefinitionSpec{ Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ { - Name: test.PreviousCAPIContractNotSupported, + Name: oldContractVersionNotSupportedAnymore, }, { - Name: test.CurrentCAPIContract, + Name: currentContractVersion, Storage: true, }, }, @@ -270,11 +270,11 @@ func Test_CheckCAPIContract(t *testing.T) { Spec: apiextensionsv1.CustomResourceDefinitionSpec{ Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ { - Name: test.PreviousCAPIContractNotSupported, + Name: oldContractVersionNotSupportedAnymore, Storage: true, }, { - Name: test.CurrentCAPIContract, + Name: currentContractVersion, }, }, }, @@ -291,18 +291,18 @@ func Test_CheckCAPIContract(t *testing.T) { Spec: apiextensionsv1.CustomResourceDefinitionSpec{ Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ { - Name: test.PreviousCAPIContractNotSupported, + Name: oldContractVersionNotSupportedAnymore, Storage: true, }, { - Name: test.CurrentCAPIContract, + Name: currentContractVersion, }, }, }, }), }, args: args{ - options: []CheckCAPIContractOption{AllowCAPIContract{Contract: v1alpha4Contract}, AllowCAPIContract{Contract: test.PreviousCAPIContractNotSupported}}, + options: []CheckCAPIContractOption{AllowCAPIContract{Contract: v1alpha4Contract}, AllowCAPIContract{Contract: oldContractVersionNotSupportedAnymore}}, }, wantErr: false, }, @@ -314,10 +314,10 @@ func Test_CheckCAPIContract(t *testing.T) { Spec: apiextensionsv1.CustomResourceDefinitionSpec{ Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ { - Name: test.CurrentCAPIContract, + Name: currentContractVersion, }, { - Name: test.NextCAPIContractNotSupported, + Name: nextContractVersionNotSupportedYet, Storage: true, }, }, @@ -333,7 +333,8 @@ func Test_CheckCAPIContract(t *testing.T) { g := NewWithT(t) p := &inventoryClient{ - proxy: tt.fields.proxy, + proxy: tt.fields.proxy, + currentContractVersion: currentContractVersion, } err := p.CheckCAPIContract(context.Background(), tt.args.options...) if tt.wantErr { @@ -381,7 +382,7 @@ func Test_inventoryClient_CheckSingleProviderInstance(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - p := newInventoryClient(test.NewFakeProxy().WithObjs(tt.fields.initObjs...), fakePollImmediateWaiter) + p := newInventoryClient(test.NewFakeProxy().WithObjs(tt.fields.initObjs...), fakePollImmediateWaiter, currentContractVersion) err := p.CheckSingleProviderInstance(context.Background()) if tt.wantErr { g.Expect(err).To(HaveOccurred()) diff --git a/cmd/clusterctl/client/cluster/mover_test.go b/cmd/clusterctl/client/cluster/mover_test.go index 3df5b791d311..381c03696b34 100644 --- a/cmd/clusterctl/client/cluster/mover_test.go +++ b/cmd/clusterctl/client/cluster/mover_test.go @@ -1758,9 +1758,9 @@ func Test_objectsMoverService_checkTargetProviders(t *testing.T) { ctx := context.Background() o := &objectMover{ - fromProviderInventory: newInventoryClient(tt.fields.fromProxy, nil), + fromProviderInventory: newInventoryClient(tt.fields.fromProxy, nil, currentContractVersion), } - err := o.checkTargetProviders(ctx, newInventoryClient(tt.args.toProxy, nil)) + err := o.checkTargetProviders(ctx, newInventoryClient(tt.args.toProxy, nil, currentContractVersion)) if tt.wantErr { g.Expect(err).To(HaveOccurred()) } else { diff --git a/cmd/clusterctl/client/cluster/objectgraph_test.go b/cmd/clusterctl/client/cluster/objectgraph_test.go index 9338548607b9..8d4a4d4f883e 100644 --- a/cmd/clusterctl/client/cluster/objectgraph_test.go +++ b/cmd/clusterctl/client/cluster/objectgraph_test.go @@ -1760,7 +1760,7 @@ func getObjectGraphWithObjs(objs []client.Object) *objectGraph { } fromProxy.WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.2.3", "infra1-system") - inventory := newInventoryClient(fromProxy, fakePollImmediateWaiter) + inventory := newInventoryClient(fromProxy, fakePollImmediateWaiter, currentContractVersion) return newObjectGraph(fromProxy, inventory) } @@ -1770,7 +1770,7 @@ func getObjectGraph() *objectGraph { fromProxy := getFakeProxyWithCRDs() fromProxy.WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.2.3", "infra1-system") - inventory := newInventoryClient(fromProxy, fakePollImmediateWaiter) + inventory := newInventoryClient(fromProxy, fakePollImmediateWaiter, currentContractVersion) return newObjectGraph(fromProxy, inventory) } diff --git a/cmd/clusterctl/client/cluster/ownergraph.go b/cmd/clusterctl/client/cluster/ownergraph.go index 4870c4ce8bd1..908cc5fd9d17 100644 --- a/cmd/clusterctl/client/cluster/ownergraph.go +++ b/cmd/clusterctl/client/cluster/ownergraph.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" ) @@ -69,7 +70,8 @@ func FilterClusterObjectsWithNameFilter(s string) func(u unstructured.Unstructur // a custom implementation of this function, or the OwnerGraph it returns. func GetOwnerGraph(ctx context.Context, namespace, kubeconfigPath string, filterFn GetOwnerGraphFilterFunction) (OwnerGraph, error) { p := NewProxy(Kubeconfig{Path: kubeconfigPath, Context: ""}) - invClient := newInventoryClient(p, nil) + // NOTE: Each Cluster API version supports one contract version, and by convention the contract version matches the current API version. + invClient := newInventoryClient(p, nil, clusterv1.GroupVersion.Version) graph := newObjectGraph(p, invClient) diff --git a/cmd/clusterctl/client/cluster/topology_test.go b/cmd/clusterctl/client/cluster/topology_test.go index f9994ccad051..f4618030e272 100644 --- a/cmd/clusterctl/client/cluster/topology_test.go +++ b/cmd/clusterctl/client/cluster/topology_test.go @@ -25,6 +25,8 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/types" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" @@ -339,8 +341,8 @@ func Test_topologyClient_Plan(t *testing.T) { for _, o := range tt.existingObjects { existingObjects = append(existingObjects, o) } - proxy := test.NewFakeProxy().WithClusterAvailable(true).WithFakeCAPISetup().WithObjs(existingObjects...) - inventoryClient := newInventoryClient(proxy, nil) + proxy := test.NewFakeProxy().WithClusterAvailable(true).WithObjs(fakeCAPISetupObjects()...).WithObjs(existingObjects...) + inventoryClient := newInventoryClient(proxy, nil, currentContractVersion) tc := newTopologyClient( proxy, inventoryClient, @@ -396,6 +398,22 @@ func Test_topologyClient_Plan(t *testing.T) { } } +func fakeCAPISetupObjects() []client.Object { + return []client.Object{ + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "clusters.cluster.x-k8s.io"}, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: currentContractVersion, + Storage: true, + }, + }, + }, + }, + } +} + func MatchTopologyPlanOutputItem(kind, namespace, namePrefix string) types.GomegaMatcher { return &topologyPlanOutputItemMatcher{kind, namespace, namePrefix} } diff --git a/cmd/clusterctl/client/cluster/upgrader.go b/cmd/clusterctl/client/cluster/upgrader.go index aff76e8bfa43..b2dd53409bb1 100644 --- a/cmd/clusterctl/client/cluster/upgrader.go +++ b/cmd/clusterctl/client/cluster/upgrader.go @@ -19,6 +19,7 @@ package cluster import ( "context" "sort" + "strings" "time" "github.com/blang/semver/v4" @@ -44,7 +45,7 @@ type ProviderUpgrader interface { Plan(ctx context.Context) ([]UpgradePlan, error) // ApplyPlan executes an upgrade following an UpgradePlan generated by clusterctl. - ApplyPlan(ctx context.Context, opts UpgradeOptions, clusterAPIVersion string) error + ApplyPlan(ctx context.Context, opts UpgradeOptions, contract string) error // ApplyCustomPlan plan executes an upgrade using the UpgradeItems provided by the user. ApplyCustomPlan(ctx context.Context, opts UpgradeOptions, providersToUpgrade ...UpgradeItem) error @@ -85,11 +86,13 @@ func (u *UpgradeItem) UpgradeRef() string { } type providerUpgrader struct { - configClient config.Client - proxy Proxy - repositoryClientFactory RepositoryClientFactory - providerInventory InventoryClient - providerComponents ComponentsClient + configClient config.Client + proxy Proxy + repositoryClientFactory RepositoryClientFactory + providerInventory InventoryClient + providerComponents ComponentsClient + currentContractVersion string + getCompatibleContractVersions func(string) sets.Set[string] } var _ ProviderUpgrader = &providerUpgrader{} @@ -104,7 +107,7 @@ func (u *providerUpgrader) Plan(ctx context.Context) ([]UpgradePlan, error) { } // The core provider is driving all the plan logic for entire management cluster, because all the providers - // are expected to support the same API Version of Cluster API (contract). + // are expected to support the same contract version or compatible onew. // e.g if the core provider supports v1alpha4, all the providers in the same management cluster should support v1alpha4 as well; // all the providers in the management cluster can upgrade to the latest release supporting v1alpha4, or if available, // all the providers can upgrade to the latest release supporting v1alpha5 (not supported in current clusterctl release, @@ -113,7 +116,7 @@ func (u *providerUpgrader) Plan(ctx context.Context) ([]UpgradePlan, error) { // Gets the upgrade info for the core provider. coreProviders := providerList.FilterCore() if len(coreProviders) != 1 { - return nil, errors.Errorf("invalid management cluster: there should a core provider, found %d", len(coreProviders)) + return nil, errors.Errorf("invalid management cluster: there must be one core provider, found %d", len(coreProviders)) } coreProvider := coreProviders[0] @@ -122,15 +125,15 @@ func (u *providerUpgrader) Plan(ctx context.Context) ([]UpgradePlan, error) { return nil, err } - // Identifies the API Version of Cluster API (contract) that we should consider for the management cluster update (Nb. the core provider is driving the entire management cluster). + // Identifies the contract version that we should consider for the management cluster update (Nb. the core provider is driving the entire management cluster). // This includes the current contract and the new ones available, if any. contractsForUpgrade := coreUpgradeInfo.getContractsForUpgrade() if len(contractsForUpgrade) == 0 { - return nil, errors.Wrapf(err, "invalid metadata: unable to find the API Version of Cluster API (contract) supported by the %s provider", coreProvider.InstanceName()) + return nil, errors.Wrapf(err, "invalid metadata: unable to find the contract version implemented by the %s provider", coreProvider.InstanceName()) } - // Creates an UpgradePlan for each contract considered for upgrades; each upgrade plans contains - // an UpgradeItem for each provider defining the next available version with the target contract, if available. + // Creates an UpgradePlan for each contract version considered for upgrades; each upgrade plans contains + // an UpgradeItem for each provider defining the next available version with the target contract versions or a compatible contract version, if available. // e.g. v1alpha4, cluster-api --> v0.4.1, kubeadm bootstrap --> v0.4.1, aws --> v0.X.2 // e.g. v1alpha4, cluster-api --> v0.5.1, kubeadm bootstrap --> v0.5.1, aws --> v0.Y.4 (not supported in current clusterctl release, but upgrade plan should report these options). ret := make([]UpgradePlan, 0) @@ -154,14 +157,14 @@ func (u *providerUpgrader) Plan(ctx context.Context) ([]UpgradePlan, error) { } func (u *providerUpgrader) ApplyPlan(ctx context.Context, opts UpgradeOptions, contract string) error { - if contract != clusterv1.GroupVersion.Version { - return errors.Errorf("current version of clusterctl could only upgrade to %s contract, requested %s", clusterv1.GroupVersion.Version, contract) + if contract != u.currentContractVersion { + return errors.Errorf("current version of clusterctl could only upgrade to %s contract, requested %s", u.currentContractVersion, contract) } log := logf.Log log.Info("Performing upgrade...") - // Gets the upgrade plan for the selected API Version of Cluster API (contract). + // Gets the upgrade plan for the selected contract version. providerList, err := u.providerInventory.List(ctx) if err != nil { return err @@ -181,7 +184,7 @@ func (u *providerUpgrader) ApplyCustomPlan(ctx context.Context, opts UpgradeOpti log.Info("Performing upgrade...") // Create a custom upgrade plan from the upgrade items, taking care of ensuring all the providers in a management - // cluster are consistent with the API Version of Cluster API (contract). + // cluster are consistent with the contract version of the core provider (or compatible ones). upgradePlan, err := u.createCustomPlan(ctx, upgradeItems) if err != nil { return err @@ -194,6 +197,8 @@ func (u *providerUpgrader) ApplyCustomPlan(ctx context.Context, opts UpgradeOpti // getUpgradePlan returns the upgrade plan for a specific set of providers/contract // NB. this function is used both for upgrade plan and upgrade apply. func (u *providerUpgrader) getUpgradePlan(ctx context.Context, providers []clusterctlv1.Provider, contract string) (*UpgradePlan, error) { + compatibleContracts := u.getCompatibleContractVersions(contract) + upgradeItems := []UpgradeItem{} for _, provider := range providers { // Gets the upgrade info for the provider. @@ -202,8 +207,8 @@ func (u *providerUpgrader) getUpgradePlan(ctx context.Context, providers []clust return nil, err } - // Identifies the next available version with the target contract for the provider, if available. - nextVersion := providerUpgradeInfo.getLatestNextVersion(contract) + // Identifies the next available version for the provider with target contract versions or a compatible contract version, if available. + nextVersion := providerUpgradeInfo.getLatestNextVersion(compatibleContracts) // Append the upgrade item for the provider/with the target contract. upgradeItems = append(upgradeItems, UpgradeItem{ @@ -219,9 +224,9 @@ func (u *providerUpgrader) getUpgradePlan(ctx context.Context, providers []clust } // createCustomPlan creates a custom upgrade plan from a set of upgrade items, taking care of ensuring all the providers -// in a management cluster are consistent with the API Version of Cluster API (contract). +// in a management cluster are consistent with the contract version of the core provider (or compatible ones). func (u *providerUpgrader) createCustomPlan(ctx context.Context, upgradeItems []UpgradeItem) (*UpgradePlan, error) { - // Gets the API Version of Cluster API (contract). + // Gets the contract version of the core provider. // The this is required to ensure all the providers in a management cluster are consistent with the contract supported by the core provider. // e.g if the core provider is v1beta1, all the provider should be v1beta1 as well. @@ -233,7 +238,7 @@ func (u *providerUpgrader) createCustomPlan(ctx context.Context, upgradeItems [] } coreProviders := providerList.FilterCore() if len(coreProviders) != 1 { - return nil, errors.Errorf("invalid management cluster: there should a core provider, found %d", len(coreProviders)) + return nil, errors.Errorf("invalid management cluster: there must be one core provider, found %d", len(coreProviders)) } coreProvider := coreProviders[0] @@ -250,9 +255,10 @@ func (u *providerUpgrader) createCustomPlan(ctx context.Context, upgradeItems [] return nil, err } - if targetContract != clusterv1.GroupVersion.Version { - return nil, errors.Errorf("current version of clusterctl could only upgrade to %s contract, requested %s", clusterv1.GroupVersion.Version, targetContract) + if targetContract != u.currentContractVersion { + return nil, errors.Errorf("current version of clusterctl could only upgrade the core provider to %s contract version, requested %s", u.currentContractVersion, targetContract) } + compatibleContracts := u.getCompatibleContractVersions(targetContract) // Builds the custom upgrade plan, by adding all the upgrade items after checking consistency with the targetContract. upgradeInstanceNames := sets.Set[string]{} @@ -270,7 +276,7 @@ func (u *providerUpgrader) createCustomPlan(ctx context.Context, upgradeItems [] } } if provider == nil { - return nil, errors.Errorf("unable to complete that upgrade: the provider %s in not part of the management cluster", upgradeItem.InstanceName()) + return nil, errors.Errorf("unable to perform upgrade: the provider %s in not part of the management cluster", upgradeItem.InstanceName()) } if upgradeItem.Version == "" { @@ -283,15 +289,15 @@ func (u *providerUpgrader) createCustomPlan(ctx context.Context, upgradeItems [] return nil, err } - if contract != targetContract { - return nil, errors.Errorf("unable to complete that upgrade: the target version for the provider %s supports the %s API Version of Cluster API (contract), while the management cluster is using %s", upgradeItem.InstanceName(), contract, targetContract) + if !compatibleContracts.Has(contract) { + return nil, errors.Errorf("unable to perform upgrade: the target version for the provider %s implements the %s contract version, while the core provider supports %s contract versions", upgradeItem.InstanceName(), contract, strings.Join(compatibleContracts.UnsortedList(), ", ")) } upgradePlan.Providers = append(upgradePlan.Providers, upgradeItem) upgradeInstanceNames.Insert(upgradeItem.InstanceName()) } - // Before doing upgrades, checks if other providers in the management cluster are lagging behind the target contract. + // Before performing the upgrades, checks if other providers in the management cluster are lagging behind the target contract. for _, provider := range providerList.Items { // skip providers already included in the upgrade plan if upgradeInstanceNames.Has(provider.InstanceName()) { @@ -304,8 +310,8 @@ func (u *providerUpgrader) createCustomPlan(ctx context.Context, upgradeItems [] return nil, err } - if contract != targetContract { - return nil, errors.Errorf("unable to complete that upgrade: the provider %s supports the %s API Version of Cluster API (contract), while the management cluster is being updated to %s. Please include the %[1]s provider in the upgrade", provider.InstanceName(), contract, targetContract) + if !compatibleContracts.Has(contract) { + return nil, errors.Errorf("unable to perform upgrade: the provider %s implements the %s contract version, while the core provider is getting updated to a version that supports %s contract versions. Please include the %[1]s provider in the upgrade", provider.InstanceName(), contract, strings.Join(compatibleContracts.UnsortedList(), ", ")) } } return upgradePlan, nil @@ -355,12 +361,9 @@ func (u *providerUpgrader) getUpgradeComponents(ctx context.Context, provider Up } func (u *providerUpgrader) doUpgrade(ctx context.Context, upgradePlan *UpgradePlan, opts UpgradeOptions) error { - // Check for multiple instances of the same provider if current contract is v1alpha3. - // TODO(killianmuldoon) Assess if we can remove this piece of code. - if upgradePlan.Contract == clusterv1.GroupVersion.Version { - if err := u.providerInventory.CheckSingleProviderInstance(ctx); err != nil { - return err - } + // Check for multiple instances of the same provider (not supported). + if err := u.providerInventory.CheckSingleProviderInstance(ctx); err != nil { + return err } // Block unsupported skip upgrades for Core, Kubeadm Bootstrap, Kubeadm ControlPlane. @@ -479,13 +482,6 @@ func (u *providerUpgrader) doUpgrade(ctx context.Context, upgradePlan *UpgradePl } } - // Delete webhook namespace since it's not needed from v1alpha4. - if upgradePlan.Contract == clusterv1.GroupVersion.Version { - if err := u.providerComponents.DeleteWebhookNamespace(ctx); err != nil { - return err - } - } - installOpts := InstallOptions{ WaitProviders: opts.WaitProviders, WaitProviderTimeout: opts.WaitProviderTimeout, @@ -573,12 +569,14 @@ func scaleDownDeployment(ctx context.Context, c client.Client, deploy appsv1.Dep return nil } -func newProviderUpgrader(configClient config.Client, proxy Proxy, repositoryClientFactory RepositoryClientFactory, providerInventory InventoryClient, providerComponents ComponentsClient) *providerUpgrader { +func newProviderUpgrader(configClient config.Client, proxy Proxy, repositoryClientFactory RepositoryClientFactory, providerInventory InventoryClient, providerComponents ComponentsClient, currentContractVersion string, getCompatibleContractVersions func(string) sets.Set[string]) *providerUpgrader { return &providerUpgrader{ - configClient: configClient, - proxy: proxy, - repositoryClientFactory: repositoryClientFactory, - providerInventory: providerInventory, - providerComponents: providerComponents, + configClient: configClient, + proxy: proxy, + repositoryClientFactory: repositoryClientFactory, + providerInventory: providerInventory, + providerComponents: providerComponents, + currentContractVersion: currentContractVersion, + getCompatibleContractVersions: getCompatibleContractVersions, } } diff --git a/cmd/clusterctl/client/cluster/upgrader_info.go b/cmd/clusterctl/client/cluster/upgrader_info.go index 97b58fdf4bc8..d0afda5a7abf 100644 --- a/cmd/clusterctl/client/cluster/upgrader_info.go +++ b/cmd/clusterctl/client/cluster/upgrader_info.go @@ -30,7 +30,8 @@ import ( // upgradeInfo holds all the information required for taking upgrade decisions for a provider. type upgradeInfo struct { - // metadata holds the information about releaseSeries and the link between release series and the API Version of Cluster API (contract). + // metadata holds the information about releaseSeries and the link between release series and the Cluster API contract + // version implemented in that release series. // e.g. release series 0.5.x for the AWS provider --> v1alpha3 metadata *clusterctlv1.Metadata @@ -122,7 +123,7 @@ func (u *providerUpgrader) getUpgradeInfo(ctx context.Context, provider clusterc } func newUpgradeInfo(metadata *clusterctlv1.Metadata, currentVersion *version.Version, nextVersions []version.Version) *upgradeInfo { - // Sorts release series; this ensures also an implicit ordering of API Version of Cluster API (contract). + // Sorts release series; this ensures also an implicit ordering of contract versions. sort.Slice(metadata.ReleaseSeries, func(i, j int) bool { return metadata.ReleaseSeries[i].Major < metadata.ReleaseSeries[j].Major || (metadata.ReleaseSeries[i].Major == metadata.ReleaseSeries[j].Major && metadata.ReleaseSeries[i].Minor < metadata.ReleaseSeries[j].Minor) @@ -148,7 +149,7 @@ func newUpgradeInfo(metadata *clusterctlv1.Metadata, currentVersion *version.Ver } } -// getContractsForUpgrade return the list of API Version of Cluster API (contract) version available for a provider upgrade. +// getContractsForUpgrade return the list of contract version available for a provider upgrade. func (i *upgradeInfo) getContractsForUpgrade() []string { contractsForUpgrade := sets.Set[string]{} for _, releaseSeries := range i.metadata.ReleaseSeries { @@ -162,13 +163,13 @@ func (i *upgradeInfo) getContractsForUpgrade() []string { return sets.List(contractsForUpgrade) } -// getLatestNextVersion returns the next available version for a provider within the target API Version of Cluster API (contract). -// the next available version is the latest version available in the for the target contract version. -func (i *upgradeInfo) getLatestNextVersion(contract string) *version.Version { +// getLatestNextVersion returns the next available version for a provider within target contract versions or a compatible contract version, if available. +// the next available version is the latest version available that implements one of the contract version. +func (i *upgradeInfo) getLatestNextVersion(compatibleContracts sets.Set[string]) *version.Version { var latestNextVersion *version.Version for _, releaseSeries := range i.metadata.ReleaseSeries { - // Skip the release series if not linked with the target contract version - if releaseSeries.Contract != contract { + // Skip the release series if not linked with the compatible contract versions. + if !compatibleContracts.Has(releaseSeries.Contract) { continue } diff --git a/cmd/clusterctl/client/cluster/upgrader_info_test.go b/cmd/clusterctl/client/cluster/upgrader_info_test.go index ca1bd0fe9576..faf813c4f8f6 100644 --- a/cmd/clusterctl/client/cluster/upgrader_info_test.go +++ b/cmd/clusterctl/client/cluster/upgrader_info_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/version" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -55,8 +56,8 @@ func Test_providerUpgrader_getUpgradeInfo(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v1.0.2", "v1.1.0"). WithMetadata("v1.1.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 1, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 1, Minor: 1, Contract: currentContractVersion}, }, }), }, @@ -70,12 +71,12 @@ func Test_providerUpgrader_getUpgradeInfo(t *testing.T) { Kind: "Metadata", }, ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 1, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 1, Minor: 1, Contract: currentContractVersion}, }, }, currentVersion: version.MustParseSemantic("v1.0.1"), - currentContract: test.CurrentCAPIContract, + currentContract: currentContractVersion, nextVersions: []version.Version{ // v1.0.1 (the current version) and older are ignored *version.MustParseSemantic("v1.0.2"), @@ -93,8 +94,8 @@ func Test_providerUpgrader_getUpgradeInfo(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v1.0.2", "v1.1.0"). WithMetadata("v1.1.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 1, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 1, Minor: 1, Contract: currentContractVersion}, }, }), }, @@ -108,12 +109,12 @@ func Test_providerUpgrader_getUpgradeInfo(t *testing.T) { Kind: "Metadata", }, ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 1, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 1, Minor: 1, Contract: currentContractVersion}, }, }, currentVersion: version.MustParseSemantic("v1.0.1"), - currentContract: test.PreviousCAPIContractNotSupported, + currentContract: oldContractVersionNotSupportedAnymore, nextVersions: []version.Version{ // v1.0.1 (the current version) and older are ignored *version.MustParseSemantic("v1.0.2"), // not supported, but upgrade plan should report these options @@ -131,8 +132,8 @@ func Test_providerUpgrader_getUpgradeInfo(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v1.0.2", "v1.1.0"). WithMetadata("v1.1.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 1, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 1, Minor: 1, Contract: nextContractVersionNotSupportedYet}, }, }), }, @@ -146,12 +147,12 @@ func Test_providerUpgrader_getUpgradeInfo(t *testing.T) { Kind: "Metadata", }, ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 1, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 1, Minor: 1, Contract: nextContractVersionNotSupportedYet}, }, }, currentVersion: version.MustParseSemantic("v1.0.1"), - currentContract: test.CurrentCAPIContract, + currentContract: currentContractVersion, nextVersions: []version.Version{ // v1.0.1 (the current version) and older are ignored *version.MustParseSemantic("v1.0.2"), @@ -213,7 +214,7 @@ func Test_providerUpgrader_getUpgradeInfo(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v1.1.1"). WithMetadata("v1.1.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, // missing 1.1 series }, }), @@ -263,53 +264,53 @@ func Test_upgradeInfo_getContractsForUpgrade(t *testing.T) { field: field{ metadata: &clusterctlv1.Metadata{ // metadata defining more release series, all linked to a single contract ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 1, Contract: test.CurrentCAPIContract}, - {Major: 0, Minor: 2, Contract: test.CurrentCAPIContract}, - {Major: 0, Minor: 3, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 1, Contract: currentContractVersion}, + {Major: 0, Minor: 2, Contract: currentContractVersion}, + {Major: 0, Minor: 3, Contract: currentContractVersion}, }, }, currentVersion: "v0.2.1", // current version belonging of one of the above series }, - want: []string{test.CurrentCAPIContract}, + want: []string{currentContractVersion}, }, { name: "Multiple contracts (previous and current), all valid for upgrades", // upgrade plan should report unsupported options field: field{ metadata: &clusterctlv1.Metadata{ // metadata defining more release series, linked to different contracts ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 1, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 0, Minor: 2, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 1, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 0, Minor: 2, Contract: currentContractVersion}, }, }, currentVersion: "v0.1.1", // current version linked to the first contract }, - want: []string{test.PreviousCAPIContractNotSupported, test.CurrentCAPIContract}, + want: []string{oldContractVersionNotSupportedAnymore, currentContractVersion}, }, { name: "Multiple contracts (current and next), all valid for upgrades", // upgrade plan should report unsupported options field: field{ metadata: &clusterctlv1.Metadata{ // metadata defining more release series, linked to different contracts ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 1, Contract: test.CurrentCAPIContract}, - {Major: 0, Minor: 2, Contract: test.NextCAPIContractNotSupported}, + {Major: 0, Minor: 1, Contract: currentContractVersion}, + {Major: 0, Minor: 2, Contract: nextContractVersionNotSupportedYet}, }, }, currentVersion: "v0.1.1", // current version linked to the first contract }, - want: []string{test.CurrentCAPIContract, test.NextCAPIContractNotSupported}, + want: []string{currentContractVersion, nextContractVersionNotSupportedYet}, }, { name: "Multiple contract supported (current and next), only one valid for upgrades", // upgrade plan should report unsupported options field: field{ metadata: &clusterctlv1.Metadata{ // metadata defining more release series, linked to different contracts ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 1, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 0, Minor: 2, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 1, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 0, Minor: 2, Contract: currentContractVersion}, }, }, currentVersion: "v0.2.1", // current version linked to the second/the last contract, so the first one is not anymore valid for upgrades }, - want: []string{test.CurrentCAPIContract}, + want: []string{currentContractVersion}, }, { name: "Current version does not match the release series", @@ -339,7 +340,7 @@ func Test_upgradeInfo_getLatestNextVersion(t *testing.T) { metadata *clusterctlv1.Metadata } type args struct { - contract string + compatibleContracts []string } tests := []struct { name string @@ -354,12 +355,12 @@ func Test_upgradeInfo_getLatestNextVersion(t *testing.T) { nextVersions: []string{}, // Next versions empty metadata: &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 2, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 2, Contract: currentContractVersion}, }, }, }, args: args{ - contract: test.CurrentCAPIContract, + compatibleContracts: []string{currentContractVersion}, }, want: "", }, @@ -370,12 +371,28 @@ func Test_upgradeInfo_getLatestNextVersion(t *testing.T) { nextVersions: []string{"v1.2.4", "v1.2.5"}, metadata: &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 2, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 2, Contract: currentContractVersion}, }, }, }, args: args{ - contract: test.CurrentCAPIContract, + compatibleContracts: []string{currentContractVersion}, + }, + want: "v1.2.5", // skipping v1.2.4 because it is not the latest version available + }, + { + name: "Find an upgrade version in the same release series, compatible contract", + field: field{ + currentVersion: "v1.2.3", + nextVersions: []string{"v1.2.4", "v1.2.5"}, + metadata: &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 1, Minor: 2, Contract: oldContractVersionStillSupported}, + }, + }, + }, + args: args{ + compatibleContracts: []string{currentContractVersion, oldContractVersionStillSupported}, }, want: "v1.2.5", // skipping v1.2.4 because it is not the latest version available }, @@ -386,14 +403,50 @@ func Test_upgradeInfo_getLatestNextVersion(t *testing.T) { nextVersions: []string{"v1.2.4", "v1.3.1", "v2.0.1", "v2.0.2"}, metadata: &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 2, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 3, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 2, Contract: currentContractVersion}, + {Major: 1, Minor: 3, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, + }, + }, + }, + args: args{ + compatibleContracts: []string{currentContractVersion}, + }, + want: "v1.3.1", // skipping v1.2.4 because it is not the latest version available; ignoring v2.0.* because linked to a different contract + }, + { + name: "Find an upgrade version in the next release series, compatible contract", + field: field{ + currentVersion: "v1.2.3", + nextVersions: []string{"v1.2.4", "v1.3.1", "v2.0.1", "v2.0.2"}, + metadata: &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 1, Minor: 2, Contract: oldContractVersionStillSupported}, + {Major: 1, Minor: 3, Contract: oldContractVersionStillSupported}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, + }, + }, + }, + args: args{ + compatibleContracts: []string{currentContractVersion, oldContractVersionStillSupported}, + }, + want: "v1.3.1", // skipping v1.2.4 because it is not the latest version available; ignoring v2.0.* because linked to a different contract + }, + { + name: "Find an upgrade version in the next release series, current contract (transition from a compatible contract)", + field: field{ + currentVersion: "v1.2.3", + nextVersions: []string{"v1.2.4", "v1.3.1", "v2.0.1", "v2.0.2"}, + metadata: &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 1, Minor: 2, Contract: oldContractVersionStillSupported}, + {Major: 1, Minor: 3, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }, }, args: args{ - contract: test.CurrentCAPIContract, + compatibleContracts: []string{currentContractVersion, oldContractVersionStillSupported}, }, want: "v1.3.1", // skipping v1.2.4 because it is not the latest version available; ignoring v2.0.* because linked to a different contract }, @@ -404,14 +457,14 @@ func Test_upgradeInfo_getLatestNextVersion(t *testing.T) { nextVersions: []string{"v1.2.4", "v1.3.1", "v2.0.1", "v2.0.2"}, metadata: &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 2, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 3, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 2, Contract: currentContractVersion}, + {Major: 1, Minor: 3, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }, }, args: args{ - contract: test.NextCAPIContractNotSupported, + compatibleContracts: []string{nextContractVersionNotSupportedYet}, }, want: "v2.0.2", // skipping v2.0.1 because it is not the latest version available; ignoring v1.* because linked to a different contract }, @@ -422,7 +475,7 @@ func Test_upgradeInfo_getLatestNextVersion(t *testing.T) { upgradeInfo := newUpgradeInfo(tt.field.metadata, version.MustParseSemantic(tt.field.currentVersion), toSemanticVersions(tt.field.nextVersions)) - got := upgradeInfo.getLatestNextVersion(tt.args.contract) + got := upgradeInfo.getLatestNextVersion(sets.New(tt.args.compatibleContracts...)) g.Expect(versionTag(got)).To(Equal(tt.want)) }) } diff --git a/cmd/clusterctl/client/cluster/upgrader_test.go b/cmd/clusterctl/client/cluster/upgrader_test.go index 4a2cf716611e..2eb73428a463 100644 --- a/cmd/clusterctl/client/cluster/upgrader_test.go +++ b/cmd/clusterctl/client/cluster/upgrader_test.go @@ -53,14 +53,14 @@ func Test_providerUpgrader_Plan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1"). WithMetadata("v1.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }), "infrastructure-infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1"). WithMetadata("v2.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -71,7 +71,52 @@ func Test_providerUpgrader_Plan(t *testing.T) { }, want: []UpgradePlan{ { // one upgrade plan with the latest releases the current contract - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, + Providers: []UpgradeItem{ + { + Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), + NextVersion: "v1.0.1", + }, + { + Provider: fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + NextVersion: "v2.0.1", + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Upgrade within the current and compatible contract", + fields: fields{ + // config for two providers + reader: test.NewFakeReader(). + WithProvider("cluster-api", clusterctlv1.CoreProviderType, "https://somewhere.com"). + WithProvider("infra", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"), + repository: map[string]repository.Repository{ + "cluster-api": repository.NewMemoryRepository(). + WithVersions("v1.0.0", "v1.0.1"). + WithMetadata("v1.0.1", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 1, Minor: 0, Contract: currentContractVersion}, + }, + }), + "infrastructure-infra": repository.NewMemoryRepository(). + WithVersions("v2.0.0", "v2.0.1"). + WithMetadata("v2.0.1", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 2, Minor: 0, Contract: oldContractVersionStillSupported}, + }, + }), + }, + // two providers existing in the cluster + proxy: test.NewFakeProxy(). + WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"). + WithProviderInventory("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + }, + want: []UpgradePlan{ + { // one upgrade plan with the latest releases the current contract + Contract: currentContractVersion, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -98,20 +143,20 @@ func Test_providerUpgrader_Plan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1"). WithMetadata("v1.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }), "infrastructure-infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1", "v3.0.0-alpha.0"). WithMetadata("v2.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }). WithMetadata("v3.0.0-alpha.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -122,7 +167,7 @@ func Test_providerUpgrader_Plan(t *testing.T) { }, want: []UpgradePlan{ { // one upgrade plan with the latest releases the current contract - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -149,16 +194,16 @@ func Test_providerUpgrader_Plan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), "infrastructure-infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -169,7 +214,7 @@ func Test_providerUpgrader_Plan(t *testing.T) { }, want: []UpgradePlan{ { // one upgrade plan with the latest releases in the previous contract (not supported, but upgrade plan should report these options) - Contract: test.PreviousCAPIContractNotSupported, + Contract: oldContractVersionNotSupportedAnymore, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -182,7 +227,67 @@ func Test_providerUpgrader_Plan(t *testing.T) { }, }, { // one upgrade plan with the latest releases in the current contract - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, + Providers: []UpgradeItem{ + { + Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), + NextVersion: "v2.0.0", + }, + { + Provider: fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + NextVersion: "v3.0.0", + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Upgrade for compatible contract, current contract", // upgrade plan should report unsupported options + fields: fields{ + // config for two providers + reader: test.NewFakeReader(). + WithProvider("cluster-api", clusterctlv1.CoreProviderType, "https://somewhere.com"). + WithProvider("infra", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"), + repository: map[string]repository.Repository{ + "cluster-api": repository.NewMemoryRepository(). + WithVersions("v1.0.0", "v1.0.1", "v2.0.0"). + WithMetadata("v2.0.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 1, Minor: 0, Contract: oldContractVersionStillSupported}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, + }, + }), + "infrastructure-infra": repository.NewMemoryRepository(). + WithVersions("v2.0.0", "v2.0.1", "v3.0.0"). + WithMetadata("v3.0.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 2, Minor: 0, Contract: oldContractVersionStillSupported}, + {Major: 3, Minor: 0, Contract: oldContractVersionStillSupported}, + }, + }), + }, + // two providers existing in the cluster + proxy: test.NewFakeProxy(). + WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"). + WithProviderInventory("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + }, + want: []UpgradePlan{ + { // one upgrade plan with the latest releases in the previous contract (not supported, but upgrade plan should report these options) + Contract: oldContractVersionStillSupported, + Providers: []UpgradeItem{ + { + Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), + NextVersion: "v1.0.1", + }, + { + Provider: fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + NextVersion: "v3.0.0", + }, + }, + }, + { // one upgrade plan with the latest releases in the current contract + Contract: currentContractVersion, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -209,16 +314,16 @@ func Test_providerUpgrader_Plan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), "infrastructure-infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 3, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, + {Major: 3, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), }, @@ -229,7 +334,7 @@ func Test_providerUpgrader_Plan(t *testing.T) { }, want: []UpgradePlan{ { // one upgrade plan with the latest releases in the current - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -242,7 +347,7 @@ func Test_providerUpgrader_Plan(t *testing.T) { }, }, { // one upgrade plan with the latest releases in the next contract (not supported, but upgrade plan should report these options) - Contract: test.NextCAPIContractNotSupported, + Contract: nextContractVersionNotSupportedYet, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -269,15 +374,15 @@ func Test_providerUpgrader_Plan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), "infrastructure-infra": repository.NewMemoryRepository(). WithVersions("v2.0.0"). // no new releases available for the infra provider (only the current release exists) WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), }, @@ -288,7 +393,7 @@ func Test_providerUpgrader_Plan(t *testing.T) { }, want: []UpgradePlan{ { // one upgrade plan with the latest releases in the current contract - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -318,7 +423,9 @@ func Test_providerUpgrader_Plan(t *testing.T) { repositoryClientFactory: func(ctx context.Context, provider config.Provider, configClient config.Client, _ ...repository.Option) (repository.Client, error) { return repository.New(ctx, provider, configClient, repository.InjectRepository(tt.fields.repository[provider.ManifestLabel()])) }, - providerInventory: newInventoryClient(tt.fields.proxy, nil), + providerInventory: newInventoryClient(tt.fields.proxy, nil, currentContractVersion), + currentContractVersion: currentContractVersion, + getCompatibleContractVersions: getCompatibleContractVersions, } got, err := u.Plan(ctx) if tt.wantErr { @@ -361,14 +468,14 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1"). WithMetadata("v1.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }), "infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1"). WithMetadata("v2.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -387,7 +494,55 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { }, }, want: &UpgradePlan{ - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, + Providers: []UpgradeItem{ + { + Provider: fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + NextVersion: "v2.0.1", + }, + }, + }, + wantErr: false, + }, + { + name: "pass if upgrade infra provider, compatible contract", + fields: fields{ + // config for two providers + reader: test.NewFakeReader(). + WithProvider("cluster-api", clusterctlv1.CoreProviderType, "https://somewhere.com"). + WithProvider("infra", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"), + repository: map[string]repository.Repository{ + "cluster-api": repository.NewMemoryRepository(). + WithVersions("v1.0.0", "v1.0.1"). + WithMetadata("v1.0.1", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 1, Minor: 0, Contract: currentContractVersion}, + }, + }), + "infra": repository.NewMemoryRepository(). + WithVersions("v2.0.0", "v2.0.1"). + WithMetadata("v2.0.1", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 2, Minor: 0, Contract: oldContractVersionStillSupported}, + }, + }), + }, + // two providers existing in the cluster + proxy: test.NewFakeProxy(). + WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"). + WithProviderInventory("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + }, + args: args{ + coreProvider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "", "cluster-api-system"), + providersToUpgrade: []UpgradeItem{ + { + Provider: fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + NextVersion: "v2.0.1", // upgrade to next release in the current contract + }, + }, + }, + want: &UpgradePlan{ + Contract: currentContractVersion, Providers: []UpgradeItem{ { Provider: fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), @@ -409,14 +564,14 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1"). WithMetadata("v1.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }), "infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1"). WithMetadata("v2.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -435,7 +590,7 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { }, }, want: &UpgradePlan{ - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -457,16 +612,16 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), "infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -489,7 +644,7 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { }, }, want: &UpgradePlan{ - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -515,16 +670,16 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), "infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 3, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, + {Major: 3, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), }, @@ -557,16 +712,16 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), "infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -599,16 +754,16 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), "infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 3, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, + {Major: 3, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), }, @@ -641,16 +796,16 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), "infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -671,6 +826,56 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { want: nil, wantErr: true, }, + { + name: "pass if upgrade core provider alone from previous to the current contract and infra provider remains on a compatible version", + fields: fields{ + // config for two providers + reader: test.NewFakeReader(). + WithProvider("cluster-api", clusterctlv1.CoreProviderType, "https://somewhere.com"). + WithProvider("infra", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"), + repository: map[string]repository.Repository{ + "cluster-api": repository.NewMemoryRepository(). + WithVersions("v1.0.0", "v2.0.0"). + WithMetadata("v2.0.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, + }, + }), + "infra": repository.NewMemoryRepository(). + WithVersions("v2.0.0", "v3.0.0"). + WithMetadata("v3.0.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 2, Minor: 0, Contract: oldContractVersionStillSupported}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, + }, + }), + }, + // two providers existing in the cluster + proxy: test.NewFakeProxy(). + WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"). + WithProviderInventory("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + }, + args: args{ + coreProvider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "", "cluster-api-system"), + providersToUpgrade: []UpgradeItem{ + { + Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), + NextVersion: "v2.0.0", // upgrade to next release in the current contract + }, + }, + }, + want: &UpgradePlan{ + Contract: currentContractVersion, + Providers: []UpgradeItem{ + { + Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), + NextVersion: "v2.0.0", + }, + }, + }, + wantErr: false, + }, { name: "fail if upgrade core and infra provider to the next contract", // not supported in current clusterctl release fields: fields{ @@ -683,16 +888,16 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, + {Major: 2, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), "infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, - {Major: 3, Minor: 0, Contract: test.NextCAPIContractNotSupported}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, + {Major: 3, Minor: 0, Contract: nextContractVersionNotSupportedYet}, }, }), }, @@ -729,16 +934,16 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), "infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -761,7 +966,7 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { }, }, want: &UpgradePlan{ - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, Providers: []UpgradeItem{ { Provider: fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), @@ -789,7 +994,9 @@ func Test_providerUpgrader_createCustomPlan(t *testing.T) { repositoryClientFactory: func(ctx context.Context, provider config.Provider, configClient config.Client, _ ...repository.Option) (repository.Client, error) { return repository.New(ctx, provider, configClient, repository.InjectRepository(tt.fields.repository[provider.Name()])) }, - providerInventory: newInventoryClient(tt.fields.proxy, nil), + providerInventory: newInventoryClient(tt.fields.proxy, nil, currentContractVersion), + currentContractVersion: currentContractVersion, + getCompatibleContractVersions: getCompatibleContractVersions, } got, err := u.createCustomPlan(ctx, tt.args.providersToUpgrade) if tt.wantErr { @@ -832,16 +1039,16 @@ func Test_providerUpgrader_ApplyPlan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), "infrastructure-infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -851,7 +1058,7 @@ func Test_providerUpgrader_ApplyPlan(t *testing.T) { WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system-1"). WithProviderInventory("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), }, - contract: test.CurrentCAPIContract, + contract: currentContractVersion, wantErr: true, errorMsg: "detected multiple instances of the same provider", opts: UpgradeOptions{}, @@ -869,16 +1076,16 @@ func Test_providerUpgrader_ApplyPlan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), "infrastructure-infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -888,7 +1095,7 @@ func Test_providerUpgrader_ApplyPlan(t *testing.T) { WithProviderInventory("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"). WithProviderInventory("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system-1"), }, - contract: test.CurrentCAPIContract, + contract: currentContractVersion, wantErr: true, errorMsg: "detected multiple instances of the same provider", opts: UpgradeOptions{}, @@ -908,7 +1115,9 @@ func Test_providerUpgrader_ApplyPlan(t *testing.T) { repositoryClientFactory: func(ctx context.Context, provider config.Provider, configClient config.Client, _ ...repository.Option) (repository.Client, error) { return repository.New(ctx, provider, configClient, repository.InjectRepository(tt.fields.repository[provider.ManifestLabel()])) }, - providerInventory: newInventoryClient(tt.fields.proxy, nil), + providerInventory: newInventoryClient(tt.fields.proxy, nil, currentContractVersion), + currentContractVersion: currentContractVersion, + getCompatibleContractVersions: getCompatibleContractVersions, } err := u.ApplyPlan(ctx, tt.opts, tt.contract) if tt.wantErr { @@ -939,7 +1148,7 @@ func Test_providerUpgrader_ApplyCustomPlan(t *testing.T) { opts UpgradeOptions }{ { - name: "fails to upgrade to v1alpha4 when there are multiple instances of the core provider", + name: "fails to upgrade when there are multiple instances of the core provider", fields: fields{ // config for two providers reader: test.NewFakeReader(). @@ -952,15 +1161,15 @@ func Test_providerUpgrader_ApplyCustomPlan(t *testing.T) { WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ {Major: 1, Minor: 0, Contract: "v1alpha3"}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), "infrastructure-infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -981,11 +1190,11 @@ func Test_providerUpgrader_ApplyCustomPlan(t *testing.T) { }, }, wantErr: true, - errorMsg: "invalid management cluster: there should a core provider, found 2", + errorMsg: "invalid management cluster: there must be one core provider, found 2", opts: UpgradeOptions{}, }, { - name: "fails to upgrade to v1alpha4 when there are multiple instances of the infra provider", + name: "fails to upgrade when there are multiple instances of the infra provider", fields: fields{ // config for two providers reader: test.NewFakeReader(). @@ -997,16 +1206,16 @@ func Test_providerUpgrader_ApplyCustomPlan(t *testing.T) { WithVersions("v1.0.0", "v1.0.1", "v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }), "infrastructure-infra": repository.NewMemoryRepository(). WithVersions("v2.0.0", "v2.0.1", "v3.0.0"). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, }, }), }, @@ -1044,11 +1253,11 @@ func Test_providerUpgrader_ApplyCustomPlan(t *testing.T) { WithVersions("v1.10.0", "v1.11.0", "v1.12.0", "v1.13.0", "v1.14.0"). WithMetadata("v1.14.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 10, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 11, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 12, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 13, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 14, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 10, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 1, Minor: 11, Contract: currentContractVersion}, + {Major: 1, Minor: 12, Contract: currentContractVersion}, + {Major: 1, Minor: 13, Contract: currentContractVersion}, + {Major: 1, Minor: 14, Contract: currentContractVersion}, }, }), }, @@ -1076,18 +1285,18 @@ func Test_providerUpgrader_ApplyCustomPlan(t *testing.T) { WithVersions("v1.0.0"). WithMetadata("v1.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }), "bootstrap-kubeadm": repository.NewMemoryRepository(). WithVersions("v1.10.0", "v1.11.0", "v1.12.0", "v1.13.0", "v1.14.0"). WithMetadata("v1.14.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 10, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 11, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 12, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 13, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 14, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 10, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 1, Minor: 11, Contract: currentContractVersion}, + {Major: 1, Minor: 12, Contract: currentContractVersion}, + {Major: 1, Minor: 13, Contract: currentContractVersion}, + {Major: 1, Minor: 14, Contract: currentContractVersion}, }, }), }, @@ -1116,18 +1325,18 @@ func Test_providerUpgrader_ApplyCustomPlan(t *testing.T) { WithVersions("v1.0.0"). WithMetadata("v1.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }), "control-plane-kubeadm": repository.NewMemoryRepository(). WithVersions("v1.10.0", "v1.11.0", "v1.12.0", "v1.13.0", "v1.14.0"). WithMetadata("v1.14.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 10, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 11, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 12, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 13, Contract: test.CurrentCAPIContract}, - {Major: 1, Minor: 14, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 10, Contract: oldContractVersionNotSupportedAnymore}, + {Major: 1, Minor: 11, Contract: currentContractVersion}, + {Major: 1, Minor: 12, Contract: currentContractVersion}, + {Major: 1, Minor: 13, Contract: currentContractVersion}, + {Major: 1, Minor: 14, Contract: currentContractVersion}, }, }), }, @@ -1160,7 +1369,9 @@ func Test_providerUpgrader_ApplyCustomPlan(t *testing.T) { repositoryClientFactory: func(ctx context.Context, provider config.Provider, configClient config.Client, _ ...repository.Option) (repository.Client, error) { return repository.New(ctx, provider, configClient, repository.InjectRepository(tt.fields.repository[provider.ManifestLabel()])) }, - providerInventory: newInventoryClient(tt.fields.proxy, nil), + providerInventory: newInventoryClient(tt.fields.proxy, nil, currentContractVersion), + currentContractVersion: currentContractVersion, + getCompatibleContractVersions: getCompatibleContractVersions, } err := u.ApplyCustomPlan(ctx, tt.opts, tt.providersToUpgrade...) if tt.wantErr { diff --git a/cmd/clusterctl/client/config_test.go b/cmd/clusterctl/client/config_test.go index 22674804e977..b85cac9547e9 100644 --- a/cmd/clusterctl/client/config_test.go +++ b/cmd/clusterctl/client/config_test.go @@ -34,7 +34,6 @@ import ( "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository" - "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" ) func Test_clusterctlClient_GetProvidersConfig(t *testing.T) { @@ -559,7 +558,7 @@ func Test_clusterctlClient_GetClusterTemplate(t *testing.T) { cluster1 := newFakeCluster(cluster.Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"}, config1). WithProviderInventory(infraProviderConfig.Name(), infraProviderConfig.Type(), "v3.0.0", "foo"). WithObjs(configMap). - WithObjs(test.FakeCAPISetupObjects()...) + WithObjs(fakeCAPISetupObjects()...) client := newFakeClient(ctx, config1). WithCluster(cluster1). @@ -720,7 +719,7 @@ func Test_clusterctlClient_GetClusterTemplate_withClusterClass(t *testing.T) { cluster1 := newFakeCluster(cluster.Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"}, config1). WithProviderInventory(infraProviderConfig.Name(), infraProviderConfig.Type(), "v3.0.0", "ns4"). - WithObjs(test.FakeCAPISetupObjects()...) + WithObjs(fakeCAPISetupObjects()...) client := newFakeClient(ctx, config1). WithCluster(cluster1). diff --git a/cmd/clusterctl/client/delete_test.go b/cmd/clusterctl/client/delete_test.go index 91fb05e4d76f..a4e53b9015f3 100644 --- a/cmd/clusterctl/client/delete_test.go +++ b/cmd/clusterctl/client/delete_test.go @@ -237,7 +237,7 @@ func fakeClusterForDelete() *fakeClient { cluster1.fakeProxy.WithProviderInventory(bootstrapProviderConfig.Name(), bootstrapProviderConfig.Type(), providerVersion, "capi-kubeadm-bootstrap-system") cluster1.fakeProxy.WithProviderInventory(controlPlaneProviderConfig.Name(), controlPlaneProviderConfig.Type(), providerVersion, "capi-kubeadm-control-plane-system") cluster1.fakeProxy.WithProviderInventory(infraProviderConfig.Name(), infraProviderConfig.Type(), providerVersion, namespace) - cluster1.fakeProxy.WithFakeCAPISetup() + cluster1.fakeProxy.WithObjs(fakeCAPISetupObjects()...) client := newFakeClient(ctx, config1). // fake repository for capi, bootstrap, controlplane and infra provider (matching provider's config) diff --git a/cmd/clusterctl/client/generate_provider.go b/cmd/clusterctl/client/generate_provider.go index a59c6aee0158..e5fa7db25415 100644 --- a/cmd/clusterctl/client/generate_provider.go +++ b/cmd/clusterctl/client/generate_provider.go @@ -18,11 +18,11 @@ package client import ( "context" + "strings" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/util/version" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" ) @@ -61,8 +61,9 @@ func (c *clusterctlClient) GenerateProvider(ctx context.Context, provider string return nil, errors.Errorf("invalid provider metadata: version %s for the provider %s does not match any release series", providerVersion, providerName) } - if releaseSeries.Contract != clusterv1.GroupVersion.Version { - return nil, errors.Errorf("current version of clusterctl is only compatible with %s providers, detected %s for provider %s", clusterv1.GroupVersion.Version, releaseSeries.Contract, providerName) + compatibleContracts := c.getCompatibleContractVersions(c.currentContractVersion) + if !compatibleContracts.Has(releaseSeries.Contract) { + return nil, errors.Errorf("current version of clusterctl is only compatible with provider implementing %s conctract versions, detected %s for provider %s", strings.Join(compatibleContracts.UnsortedList(), ", "), releaseSeries.Contract, providerName) } return c.GetProviderComponents(ctx, provider, providerType, options) diff --git a/cmd/clusterctl/client/get_kubeconfig_test.go b/cmd/clusterctl/client/get_kubeconfig_test.go index e9c58d289909..19f367fe6dc1 100644 --- a/cmd/clusterctl/client/get_kubeconfig_test.go +++ b/cmd/clusterctl/client/get_kubeconfig_test.go @@ -34,7 +34,7 @@ func Test_clusterctlClient_GetKubeconfig(t *testing.T) { clusterClient := newFakeCluster(cluster.Kubeconfig{Path: "cluster1"}, configClient) // create a clusterctl client where the proxy returns an empty namespace - clusterClient.fakeProxy = test.NewFakeProxy().WithNamespace("").WithFakeCAPISetup() + clusterClient.fakeProxy = test.NewFakeProxy().WithNamespace("").WithObjs(fakeCAPISetupObjects()...) badClient := newFakeClient(ctx, configClient).WithCluster(clusterClient) tests := []struct { diff --git a/cmd/clusterctl/client/init.go b/cmd/clusterctl/client/init.go index 562f21ee737c..9f13804095a5 100644 --- a/cmd/clusterctl/client/init.go +++ b/cmd/clusterctl/client/init.go @@ -130,7 +130,7 @@ func (c *clusterctlClient) Init(ctx context.Context, options InitOptions) ([]Com // Before installing the providers, validates the management cluster resulting by the planned installation. The following checks are performed: // - There should be only one instance of the same provider. - // - All the providers must support the same API Version of Cluster API (contract) + // - All the providers must support the same contract version or compatible versions. // - All provider CRDs that are referenced in core Cluster API CRDs must comply with the CRD naming scheme, // otherwise a warning is logged. if err := installer.Validate(ctx); err != nil { diff --git a/cmd/clusterctl/client/init_test.go b/cmd/clusterctl/client/init_test.go index 3a98ee88b94a..e68946211684 100644 --- a/cmd/clusterctl/client/init_test.go +++ b/cmd/clusterctl/client/init_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/gomega" "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/sets" ctrl "sigs.k8s.io/controller-runtime" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -36,6 +37,26 @@ var ( ctx = ctrl.SetupSignalHandler() ) +var ( + // oldContractVersionNotSupported define the previous Cluster API contract, not supported by this release of clusterctl. + // (it has been removed by version of CAPI older that the version in use). + oldContractVersionNotSupported = "v1alpha4" + + // oldContractVersionStillSupported define an old Cluster API contract still supported. + oldContractVersionStillSupported = "v1beta1" + + // currentContractVersion define the current Cluster API contract. + currentContractVersion = "v1beta2" + + getCompatibleContractVersions = func(contract string) sets.Set[string] { + compatibleContracts := sets.New(contract) + if contract == currentContractVersion { + compatibleContracts.Insert(oldContractVersionStillSupported) + } + return compatibleContracts + } +) + func Test_clusterctlClient_InitImages(t *testing.T) { type field struct { client *fakeClient @@ -279,6 +300,43 @@ func Test_clusterctlClient_Init(t *testing.T) { }, wantErr: false, }, + { + name: "Init (with an empty cluster) with default provider versions/current contract + compatible contract", + field: field{ + client: fakeEmptyCluster(), // clusterctl client for an empty management cluster (with repository setup for capi, bootstrap, control plane and infra provider) + hasCRD: false, + }, + args: args{ + coreProvider: "", // with an empty cluster, a core provider should be added automatically + bootstrapProvider: nil, // with an empty cluster, a bootstrap provider should be added automatically + controlPlaneProvider: nil, // with an empty cluster, a control plane provider should be added automatically + infrastructureProvider: []string{"infra-compatible"}, + targetNameSpace: "", + }, + want: []want{ + { + provider: capiProviderConfig, + version: "v1.0.0", + targetNamespace: "ns1", + }, + { + provider: bootstrapProviderConfig, + version: "v2.0.0", + targetNamespace: "ns2", + }, + { + provider: controlPlaneProviderConfig, + version: "v2.0.0", + targetNamespace: "ns3", + }, + { + provider: infraCompatibleProviderConfig, + version: "v3.0.0", + targetNamespace: "ns4", + }, + }, + wantErr: false, + }, { name: "Init (with an empty cluster) opting out from automatic install of providers/current contract", field: field{ @@ -343,6 +401,43 @@ func Test_clusterctlClient_Init(t *testing.T) { }, wantErr: false, }, + { + name: "Init (with an empty cluster) with custom provider versions/current contract + compatible contract", + field: field{ + client: fakeEmptyCluster(), // clusterctl client for an empty management cluster (with repository setup for capi, bootstrap, control plane and infra provider) + hasCRD: false, + }, + args: args{ + coreProvider: fmt.Sprintf("%s:v1.1.0", config.ClusterAPIProviderName), + bootstrapProvider: []string{fmt.Sprintf("%s:v2.1.0", config.KubeadmBootstrapProviderName)}, + controlPlaneProvider: []string{fmt.Sprintf("%s:v2.1.0", config.KubeadmControlPlaneProviderName)}, + infrastructureProvider: []string{"infra-compatible:v3.1.0"}, + targetNameSpace: "", + }, + want: []want{ + { + provider: capiProviderConfig, + version: "v1.1.0", + targetNamespace: "ns1", + }, + { + provider: bootstrapProviderConfig, + version: "v2.1.0", + targetNamespace: "ns2", + }, + { + provider: controlPlaneProviderConfig, + version: "v2.1.0", + targetNamespace: "ns3", + }, + { + provider: infraCompatibleProviderConfig, + version: "v3.1.0", + targetNamespace: "ns4", + }, + }, + wantErr: false, + }, { name: "Init (with an empty cluster) with target namespace/current contract", field: field{ @@ -510,7 +605,7 @@ func Test_clusterctlClient_Init(t *testing.T) { wantErr: true, }, { - name: "Init (with an NOT empty cluster) adds the same core providers version again - should ignore duplicate", + name: "Init (with an NOT empty cluster) adds the infrastructure provider/current contract", field: field{ client: fakeClusterWithCoreProvider(), // clusterctl client for an management cluster with CoreProvider cluster-api already installed. hasCRD: true, @@ -531,6 +626,28 @@ func Test_clusterctlClient_Init(t *testing.T) { }, wantErr: false, }, + { + name: "Init (with an NOT empty cluster) adds the infrastructure provider/compatible contract", + field: field{ + client: fakeClusterWithCoreProvider(), // clusterctl client for an management cluster with CoreProvider cluster-api already installed. + hasCRD: true, + }, + args: args{ + coreProvider: "cluster-api:v1.0.0", // core provider of the same version is already installed on the cluster. should be skipped. + bootstrapProvider: []string{}, + infrastructureProvider: []string{"infra-compatible"}, + targetNameSpace: "", + }, + want: []want{ + // Only the infra provider should be installed. Core provider should be skipped. + { + provider: infraCompatibleProviderConfig, + version: "v3.0.0", + targetNamespace: "ns4", + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -568,10 +685,11 @@ func Test_clusterctlClient_Init(t *testing.T) { } var ( - capiProviderConfig = config.NewProvider(config.ClusterAPIProviderName, "url", clusterctlv1.CoreProviderType) - bootstrapProviderConfig = config.NewProvider(config.KubeadmBootstrapProviderName, "url", clusterctlv1.BootstrapProviderType) - controlPlaneProviderConfig = config.NewProvider(config.KubeadmControlPlaneProviderName, "url", clusterctlv1.ControlPlaneProviderType) - infraProviderConfig = config.NewProvider("infra", "url", clusterctlv1.InfrastructureProviderType) + capiProviderConfig = config.NewProvider(config.ClusterAPIProviderName, "url", clusterctlv1.CoreProviderType) + bootstrapProviderConfig = config.NewProvider(config.KubeadmBootstrapProviderName, "url", clusterctlv1.BootstrapProviderType) + controlPlaneProviderConfig = config.NewProvider(config.KubeadmControlPlaneProviderName, "url", clusterctlv1.ControlPlaneProviderType) + infraProviderConfig = config.NewProvider("infra", "url", clusterctlv1.InfrastructureProviderType) + infraCompatibleProviderConfig = config.NewProvider("infra-compatible", "url", clusterctlv1.InfrastructureProviderType) ) // setup a cluster client and the fake configuration for testing. @@ -581,7 +699,8 @@ func setupCluster(providers []Provider, certManagerClient cluster.CertManagerCli cfg := newFakeConfig(ctx). WithVar("ANOTHER_VARIABLE", "value"). WithProvider(capiProviderConfig). - WithProvider(infraProviderConfig) + WithProvider(infraProviderConfig). + WithProvider(infraCompatibleProviderConfig) for _, provider := range providers { cfg.WithProvider(provider) @@ -598,7 +717,7 @@ func fakeEmptyCluster() *fakeClient { // create a config variables client which contains the value for the // variable required config1 := fakeConfig( - []config.Provider{capiProviderConfig, bootstrapProviderConfig, controlPlaneProviderConfig, infraProviderConfig}, + []config.Provider{capiProviderConfig, bootstrapProviderConfig, controlPlaneProviderConfig, infraProviderConfig, infraCompatibleProviderConfig}, map[string]string{"SOME_VARIABLE": "value"}, ) @@ -641,21 +760,21 @@ func fakeRepositories(config *fakeConfigClient, providers []Provider) []*fakeRep WithFile("v0.9.0", "components.yaml", componentsYAML("ns1")). WithMetadata("v0.9.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, }, }). WithFile("v1.0.0", "components.yaml", componentsYAML("ns1")). WithMetadata("v1.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }). WithFile("v1.1.0", "components.yaml", componentsYAML("ns1")). WithMetadata("v1.1.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 1, Minor: 1, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 1, Minor: 1, Contract: currentContractVersion}, }, }) repository2 := newFakeRepository(ctx, bootstrapProviderConfig, config). @@ -664,21 +783,21 @@ func fakeRepositories(config *fakeConfigClient, providers []Provider) []*fakeRep WithFile("v0.9.0", "components.yaml", componentsYAML("ns1")). WithMetadata("v0.9.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, }, }). WithFile("v2.0.0", "components.yaml", componentsYAML("ns2")). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }). WithFile("v2.1.0", "components.yaml", componentsYAML("ns2")). WithMetadata("v2.1.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 1, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 2, Minor: 1, Contract: currentContractVersion}, }, }) repository3 := newFakeRepository(ctx, controlPlaneProviderConfig, config). @@ -687,21 +806,21 @@ func fakeRepositories(config *fakeConfigClient, providers []Provider) []*fakeRep WithFile("v0.9.0", "components.yaml", componentsYAML("ns1")). WithMetadata("v0.9.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, }, }). WithFile("v2.0.0", "components.yaml", componentsYAML("ns3")). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, }). WithFile("v2.1.0", "components.yaml", componentsYAML("ns3")). WithMetadata("v2.1.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 2, Minor: 1, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 2, Minor: 1, Contract: currentContractVersion}, }, }) repository4 := newFakeRepository(ctx, infraProviderConfig, config). @@ -710,26 +829,51 @@ func fakeRepositories(config *fakeConfigClient, providers []Provider) []*fakeRep WithFile("v0.9.0", "components.yaml", componentsYAML("ns1")). WithMetadata("v0.9.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + }, + }). + WithFile("v3.0.0", "components.yaml", infraComponentsYAML("ns4")). + WithMetadata("v3.0.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 3, Minor: 0, Contract: currentContractVersion}, + }, + }). + WithFile("v3.1.0", "components.yaml", infraComponentsYAML("ns4")). + WithMetadata("v3.1.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 3, Minor: 1, Contract: currentContractVersion}, + }, + }). + WithFile("v3.0.0", "cluster-template.yaml", templateYAML("ns4", "test")) + + repository5 := newFakeRepository(ctx, infraCompatibleProviderConfig, config). + WithPaths("root", "components.yaml"). + WithDefaultVersion("v3.0.0"). + WithFile("v0.9.0", "components.yaml", componentsYAML("ns1")). + WithMetadata("v0.9.0", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, }, }). WithFile("v3.0.0", "components.yaml", infraComponentsYAML("ns4")). WithMetadata("v3.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 3, Minor: 0, Contract: oldContractVersionStillSupported}, }, }). WithFile("v3.1.0", "components.yaml", infraComponentsYAML("ns4")). WithMetadata("v3.1.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported}, - {Major: 3, Minor: 1, Contract: test.CurrentCAPIContract}, + {Major: 0, Minor: 9, Contract: oldContractVersionNotSupported}, + {Major: 3, Minor: 1, Contract: oldContractVersionStillSupported}, }, }). WithFile("v3.0.0", "cluster-template.yaml", templateYAML("ns4", "test")) - var providerRepositories = []*fakeRepositoryClient{repository1, repository2, repository3, repository4} + var providerRepositories = []*fakeRepositoryClient{repository1, repository2, repository3, repository4, repository5} for _, provider := range providers { providerRepositories = append(providerRepositories, @@ -739,7 +883,7 @@ func fakeRepositories(config *fakeConfigClient, providers []Provider) []*fakeRep WithFile("v2.0.0", "components.yaml", componentsYAML("ns2")). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, }, })) } diff --git a/cmd/clusterctl/client/move_test.go b/cmd/clusterctl/client/move_test.go index 83ad30d67a82..dfd4287f55fd 100644 --- a/cmd/clusterctl/client/move_test.go +++ b/cmd/clusterctl/client/move_test.go @@ -26,7 +26,6 @@ import ( clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" - "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" ) func Test_clusterctlClient_Move(t *testing.T) { @@ -286,13 +285,13 @@ func fakeClientForMove() *fakeClient { WithProviderInventory(core.Name(), core.Type(), "v1.0.0", "cluster-api-system"). WithProviderInventory(infra.Name(), infra.Type(), "v2.0.0", "infra-system"). WithObjectMover(&fakeObjectMover{}). - WithObjs(test.FakeCAPISetupObjects()...) + WithObjs(fakeCAPISetupObjects()...) // Creating this cluster for move_test cluster2 := newFakeCluster(cluster.Kubeconfig{Path: "kubeconfig", Context: "worker-context"}, config1). WithProviderInventory(core.Name(), core.Type(), "v1.0.0", "cluster-api-system"). WithProviderInventory(infra.Name(), infra.Type(), "v2.0.0", "infra-system"). - WithObjs(test.FakeCAPISetupObjects()...) + WithObjs(fakeCAPISetupObjects()...) client := newFakeClient(ctx, config1). WithCluster(cluster1). diff --git a/cmd/clusterctl/client/repository/metadata_client_test.go b/cmd/clusterctl/client/repository/metadata_client_test.go index 84ceb260aa34..e194784e47f6 100644 --- a/cmd/clusterctl/client/repository/metadata_client_test.go +++ b/cmd/clusterctl/client/repository/metadata_client_test.go @@ -29,6 +29,8 @@ import ( ) func Test_metadataClient_Get(t *testing.T) { + var contractVersion = "foo" + type fields struct { provider config.Provider version string @@ -50,7 +52,7 @@ func Test_metadataClient_Get(t *testing.T) { WithDefaultVersion("v1.0.0"). WithMetadata("v1.0.0", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 2, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 2, Contract: contractVersion}, }, }), }, @@ -63,7 +65,7 @@ func Test_metadataClient_Get(t *testing.T) { { Major: 1, Minor: 2, - Contract: test.CurrentCAPIContract, + Contract: contractVersion, }, }, }, @@ -91,7 +93,7 @@ func Test_metadataClient_Get(t *testing.T) { WithDefaultVersion("v2.0.0"). WithMetadata("v2.0.0", &clusterctlv1.Metadata{ // metadata file exists for version 2.0.0, while we are checking metadata for v1.0.0 ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 2, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 2, Contract: contractVersion}, }, }), }, diff --git a/cmd/clusterctl/client/upgrade.go b/cmd/clusterctl/client/upgrade.go index 6707d622a99a..76b5d8432435 100644 --- a/cmd/clusterctl/client/upgrade.go +++ b/cmd/clusterctl/client/upgrade.go @@ -88,7 +88,7 @@ type ApplyUpgradeOptions struct { // Kubeconfig to use for accessing the management cluster. If empty, default discovery rules apply. Kubeconfig Kubeconfig - // Contract defines the API Version of Cluster API (contract e.g. v1alpha4) the management cluster should upgrade to. + // Contract defines Cluster API contract version (e.g. v1alpha4) the management cluster should upgrade to. // When upgrading by contract, the latest versions available will be used for all the providers; if you want // a more granular control on upgrade, use CoreProvider, BootstrapProviders, ControlPlaneProviders, InfrastructureProviders. Contract string @@ -129,7 +129,7 @@ type ApplyUpgradeOptions struct { } func (c *clusterctlClient) ApplyUpgrade(ctx context.Context, options ApplyUpgradeOptions) error { - if options.Contract != "" && options.Contract != clusterv1.GroupVersion.Version { + if options.Contract != "" && options.Contract != c.currentContractVersion { return errors.Errorf("current version of clusterctl could only upgrade to %s contract, requested %s", clusterv1.GroupVersion.Version, options.Contract) } diff --git a/cmd/clusterctl/client/upgrade_test.go b/cmd/clusterctl/client/upgrade_test.go index c5512807bbfe..260e0cdbdcd9 100644 --- a/cmd/clusterctl/client/upgrade_test.go +++ b/cmd/clusterctl/client/upgrade_test.go @@ -29,7 +29,6 @@ import ( clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" - "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" ) func Test_clusterctlClient_PlanCertUpgrade(t *testing.T) { @@ -168,12 +167,12 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { { name: "apply a plan", fields: fields{ - client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available) + client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available), infra-compatible v2.0.0 (v2.0.1 available) }, args: args{ options: ApplyUpgradeOptions{ Kubeconfig: Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"}, - Contract: test.CurrentCAPIContract, + Contract: currentContractVersion, CoreProvider: "", BootstrapProviders: nil, ControlPlaneProviders: nil, @@ -185,6 +184,7 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { Items: []clusterctlv1.Provider{ // both providers should be upgraded fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.1", "cluster-api-system"), fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.1", "infra-system"), + fakeProvider("infra-compatible", clusterctlv1.InfrastructureProviderType, "v2.0.1", "infra-compatible-system"), }, }, wantErr: false, @@ -192,7 +192,7 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { { name: "apply a custom plan - core provider only", fields: fields{ - client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available) + client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available), infra-compatible v2.0.0 (v2.0.1 available) }, args: args{ options: ApplyUpgradeOptions{ @@ -209,6 +209,7 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { Items: []clusterctlv1.Provider{ // only one provider should be upgraded fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.1", "cluster-api-system"), fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"), + fakeProvider("infra-compatible", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-compatible-system"), }, }, wantErr: false, @@ -216,7 +217,7 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { { name: "apply a custom plan - infra provider only", fields: fields{ - client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available) + client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available), infra-compatible v2.0.0 (v2.0.1 available) }, args: args{ options: ApplyUpgradeOptions{ @@ -225,7 +226,7 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { CoreProvider: "", BootstrapProviders: nil, ControlPlaneProviders: nil, - InfrastructureProviders: []string{"infra-system/infra:v2.0.1"}, + InfrastructureProviders: []string{"infra-system/infra:v2.0.1", "infra-compatible-system/infra-compatible:v2.0.1"}, }, }, wantProviders: &clusterctlv1.ProviderList{ @@ -233,6 +234,7 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { Items: []clusterctlv1.Provider{ // only one provider should be upgraded fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"), fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.1", "infra-system"), + fakeProvider("infra-compatible", clusterctlv1.InfrastructureProviderType, "v2.0.1", "infra-compatible-system"), }, }, wantErr: false, @@ -240,7 +242,7 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { { name: "apply a custom plan - both providers", fields: fields{ - client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available) + client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available), infra-compatible v2.0.0 (v2.0.1 available) }, args: args{ options: ApplyUpgradeOptions{ @@ -249,7 +251,7 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { CoreProvider: "cluster-api-system/cluster-api:v1.0.1", BootstrapProviders: nil, ControlPlaneProviders: nil, - InfrastructureProviders: []string{"infra-system/infra:v2.0.1"}, + InfrastructureProviders: []string{"infra-system/infra:v2.0.1", "infra-compatible-system/infra-compatible:v2.0.1"}, }, }, wantProviders: &clusterctlv1.ProviderList{ @@ -257,6 +259,7 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { Items: []clusterctlv1.Provider{ fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.1", "cluster-api-system"), fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.1", "infra-system"), + fakeProvider("infra-compatible", clusterctlv1.InfrastructureProviderType, "v2.0.1", "infra-compatible-system"), }, }, wantErr: false, @@ -302,12 +305,14 @@ func Test_clusterctlClient_ApplyUpgrade(t *testing.T) { func fakeClientForUpgrade() *fakeClient { core := config.NewProvider("cluster-api", "https://somewhere.com", clusterctlv1.CoreProviderType) infra := config.NewProvider("infra", "https://somewhere.com", clusterctlv1.InfrastructureProviderType) + infraCompatible := config.NewProvider("infra-compatible", "https://somewhere.com", clusterctlv1.InfrastructureProviderType) ctx := context.Background() config1 := newFakeConfig(ctx). WithProvider(core). - WithProvider(infra) + WithProvider(infra). + WithProvider(infraCompatible) repository1 := newFakeRepository(ctx, core, config1). WithPaths("root", "components.yaml"). @@ -316,7 +321,7 @@ func fakeClientForUpgrade() *fakeClient { WithVersions("v1.0.0", "v1.0.1"). WithMetadata("v1.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 1, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 1, Minor: 0, Contract: currentContractVersion}, }, }) repository2 := newFakeRepository(ctx, infra, config1). @@ -326,20 +331,33 @@ func fakeClientForUpgrade() *fakeClient { WithVersions("v2.0.0", "v2.0.1"). WithMetadata("v2.0.1", &clusterctlv1.Metadata{ ReleaseSeries: []clusterctlv1.ReleaseSeries{ - {Major: 2, Minor: 0, Contract: test.CurrentCAPIContract}, + {Major: 2, Minor: 0, Contract: currentContractVersion}, + }, + }) + repository3 := newFakeRepository(ctx, infraCompatible, config1). + WithPaths("root", "components.yaml"). + WithDefaultVersion("v2.0.0"). + WithFile("v2.0.1", "components.yaml", componentsYAML("ns2")). + WithVersions("v2.0.0", "v2.0.1"). + WithMetadata("v2.0.1", &clusterctlv1.Metadata{ + ReleaseSeries: []clusterctlv1.ReleaseSeries{ + {Major: 2, Minor: 0, Contract: oldContractVersionStillSupported}, }, }) cluster1 := newFakeCluster(cluster.Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"}, config1). WithRepository(repository1). WithRepository(repository2). + WithRepository(repository3). WithProviderInventory(core.Name(), core.Type(), "v1.0.0", "cluster-api-system"). WithProviderInventory(infra.Name(), infra.Type(), "v2.0.0", "infra-system"). - WithObjs(test.FakeCAPISetupObjects()...) + WithProviderInventory(infraCompatible.Name(), infra.Type(), "v2.0.0", "infra-compatible-system"). + WithObjs(fakeCAPISetupObjects()...) client := newFakeClient(ctx, config1). WithRepository(repository1). WithRepository(repository2). + WithRepository(repository3). WithCluster(cluster1) return client diff --git a/cmd/clusterctl/cmd/upgrade_apply.go b/cmd/clusterctl/cmd/upgrade_apply.go index 7c2dfd6b2da2..d682dd896c08 100644 --- a/cmd/clusterctl/cmd/upgrade_apply.go +++ b/cmd/clusterctl/cmd/upgrade_apply.go @@ -55,11 +55,10 @@ var upgradeApplyCmd = &cobra.Command{ in order to guarantee the proper functioning of the management cluster. Specifying the provider using namespace/name:version is deprecated and will be dropped in a future release.`), - Example: templates.Examples(` # Upgrades all the providers in the management cluster to the latest version available which is compliant - # to the v1alpha4 API Version of Cluster API (contract). - clusterctl upgrade apply --contract v1alpha4 + # to the v1beta2 Cluster API contract version. + clusterctl upgrade apply --contract v1beta2 # Upgrades only the aws provider to the v2.0.1 version. clusterctl upgrade apply --infrastructure aws:v2.0.1`), @@ -75,7 +74,7 @@ func init() { upgradeApplyCmd.Flags().StringVar(&ua.kubeconfigContext, "kubeconfig-context", "", "Context to be used within the kubeconfig file. If empty, current context will be used.") upgradeApplyCmd.Flags().StringVar(&ua.contract, "contract", "", - "The API Version of Cluster API (contract, e.g. v1alpha4) the management cluster should upgrade to") + "The Cluster API contract version (e.g. v1beta2) the management cluster should upgrade to") upgradeApplyCmd.Flags().StringVar(&ua.coreProvider, "core", "", "Core provider instance version (e.g. cluster-api:v1.1.5) to upgrade to. This flag can be used as alternative to --contract.") diff --git a/cmd/clusterctl/cmd/upgrade_plan.go b/cmd/clusterctl/cmd/upgrade_plan.go index a514e1b6314a..9d8b25959a25 100644 --- a/cmd/clusterctl/cmd/upgrade_plan.go +++ b/cmd/clusterctl/cmd/upgrade_plan.go @@ -43,12 +43,12 @@ var upgradePlanCmd = &cobra.Command{ The upgrade plan command provides a list of recommended target versions for upgrading the Cluster API providers in a management cluster. - All the providers should be supporting the same API Version of Cluster API (contract) in order + All the providers should be supporting the same API Version of the Cluster API contract or compatible versions in order to guarantee the proper functioning of the management cluster. Then, for each provider, the following upgrade options are provided: - - The latest patch release for the current API Version of Cluster API (contract). - - The latest patch release for the next API Version of Cluster API (contract), if available.`), + - The latest patch release for the current Cluster API contract version. + - The latest patch release for the next Cluster API contract version, if available.`), Example: templates.Examples(` # Gets the recommended target versions for upgrading Cluster API providers. @@ -111,7 +111,7 @@ func runUpgradePlan() error { upgradeAvailable := false fmt.Println("") - fmt.Printf("Latest release available for the %s API Version of Cluster API (contract):\n", plan.Contract) + fmt.Printf("Latest release available for the %s Cluster API contract version:\n", plan.Contract) fmt.Println("") w := tabwriter.NewWriter(os.Stdout, 10, 4, 3, ' ', 0) fmt.Fprintln(w, "NAME\tNAMESPACE\tTYPE\tCURRENT VERSION\tNEXT VERSION") diff --git a/cmd/clusterctl/config/crd/bases/clusterctl.cluster.x-k8s.io_metadata.yaml b/cmd/clusterctl/config/crd/bases/clusterctl.cluster.x-k8s.io_metadata.yaml index c143598fd9e9..3f4c8fdff5cd 100644 --- a/cmd/clusterctl/config/crd/bases/clusterctl.cluster.x-k8s.io_metadata.yaml +++ b/cmd/clusterctl/config/crd/bases/clusterctl.cluster.x-k8s.io_metadata.yaml @@ -38,10 +38,10 @@ spec: type: object releaseSeries: description: releaseSeries maps a provider release series (major/minor) - with an API Version of Cluster API (contract). + with a Cluster API contract version. items: description: ReleaseSeries maps a provider release series (major/minor) - with a API Version of Cluster API (contract). + with a Cluster API contract version. properties: contract: description: |- diff --git a/cmd/clusterctl/internal/test/contracts.go b/cmd/clusterctl/internal/test/contracts.go deleted file mode 100644 index ba8ab44a988b..000000000000 --- a/cmd/clusterctl/internal/test/contracts.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -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 test - -import ( - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" -) - -// PreviousCAPIContractNotSupported define the previous Cluster API contract, not supported by this release of clusterctl. -var PreviousCAPIContractNotSupported = "v1alpha4" - -// CurrentCAPIContract define the current Cluster API contract. -var CurrentCAPIContract = clusterv1.GroupVersion.Version - -// NextCAPIContractNotSupported define the next Cluster API contract, not supported by this release of clusterctl. -const NextCAPIContractNotSupported = "v99" diff --git a/cmd/clusterctl/internal/test/fake_proxy.go b/cmd/clusterctl/internal/test/fake_proxy.go index 07134121aa82..7fe64f7f62bb 100644 --- a/cmd/clusterctl/internal/test/fake_proxy.go +++ b/cmd/clusterctl/internal/test/fake_proxy.go @@ -191,36 +191,7 @@ func (f *FakeProxy) WithProviderInventory(name string, providerType clusterctlv1 return f } -// WithFakeCAPISetup adds required objects in order to make kubeadm pass checks -// ensuring that management cluster has a proper release of Cluster API installed. -// NOTE: When using the fake client it is not required to install CRDs, given that type information are -// derived from the schema. However, CheckCAPIContract looks for CRDs to be installed, so this -// helper provide a way to get around to this difference between fake client and a real API server. -func (f *FakeProxy) WithFakeCAPISetup() *FakeProxy { - f.objs = append(f.objs, FakeCAPISetupObjects()...) - - return f -} - func (f *FakeProxy) WithClusterAvailable(available bool) *FakeProxy { f.available = ptr.To(available) return f } - -// FakeCAPISetupObjects return required objects in order to make kubeadm pass checks -// ensuring that management cluster has a proper release of Cluster API installed. -func FakeCAPISetupObjects() []client.Object { - return []client.Object{ - &apiextensionsv1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{Name: "clusters.cluster.x-k8s.io"}, - Spec: apiextensionsv1.CustomResourceDefinitionSpec{ - Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ - { - Name: clusterv1.GroupVersion.Version, // Current Cluster API contract - Storage: true, - }, - }, - }, - }, - } -} diff --git a/docs/book/src/clusterctl/commands/upgrade.md b/docs/book/src/clusterctl/commands/upgrade.md index 2d62e8ffbed0..62e810e90638 100644 --- a/docs/book/src/clusterctl/commands/upgrade.md +++ b/docs/book/src/clusterctl/commands/upgrade.md @@ -34,7 +34,7 @@ You can now apply the upgrade by executing the following command: clusterctl upgrade apply --contract v1beta1 ``` -The output contains the latest release available for each API Version of Cluster API (contract) +The output contains the latest release available for each Cluster API contract version. available at the moment.