Skip to content

Commit

Permalink
Ensure provider upgrades when a version changes
Browse files Browse the repository at this point in the history
Signed-off-by: Danil Grigorev <danil.grigorev@suse.com>
  • Loading branch information
Danil-Grigorev committed Jan 18, 2024
1 parent 6f7c927 commit 01a95b2
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 17 deletions.
67 changes: 66 additions & 1 deletion internal/controller/client_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,82 @@ import (

"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"

operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// clientProxy implements the Proxy interface from the clusterctl. It is used to
// interact with the management cluster.
type clientProxy struct {
client.Client
}

func (c clientProxy) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
switch l := list.(type) {
case *clusterctlv1.ProviderList:
return listProviders(ctx, c.Client, l)
default:
return c.Client.List(ctx, l, opts...)
}
}

func (c clientProxy) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
switch o := obj.(type) {
case *clusterctlv1.Provider:
return nil
default:
return c.Client.Get(ctx, key, o, opts...)
}
}

func (c clientProxy) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
switch o := obj.(type) {
case *clusterctlv1.Provider:
return nil
default:
return c.Client.Patch(ctx, o, patch, opts...)
}
}

func listProviders(ctx context.Context, cl client.Client, list *clusterctlv1.ProviderList) error {
providers := []operatorv1.GenericProviderList{
&operatorv1.CoreProviderList{},
&operatorv1.InfrastructureProviderList{},
&operatorv1.BootstrapProviderList{},
&operatorv1.ControlPlaneProviderList{},
&operatorv1.AddonProviderList{},
&operatorv1.IPAMProviderList{},
}

for _, group := range providers {
g, ok := group.(client.ObjectList)
if !ok {
continue
}

if err := cl.List(ctx, g); err != nil {
return err
}

for _, p := range group.GetItems() {
list.Items = append(list.Items, getProvider(p, ""))
}
}

return nil
}

// controllerProxy implements the Proxy interface from the clusterctl. It is used to
// interact with the management cluster.
type controllerProxy struct {
ctrlClient client.Client
ctrlClient clientProxy
ctrlConfig *rest.Config
}

Expand Down
52 changes: 36 additions & 16 deletions internal/controller/phases.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"net/url"
"os"
"strings"
"time"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -458,7 +459,7 @@ func (p *phaseReconciler) fetch(ctx context.Context) (reconcile.Result, error) {
}

// preInstall ensure all the clusterctl CRDs are available before installing the provider,
// and delete existing components if required for upgrade.
// and update existing components if required.
func (p *phaseReconciler) preInstall(ctx context.Context) (reconcile.Result, error) {
log := ctrl.LoggerFrom(ctx)

Expand All @@ -467,9 +468,23 @@ func (p *phaseReconciler) preInstall(ctx context.Context) (reconcile.Result, err
return reconcile.Result{}, nil
}

log.Info("Changes detected, deleting existing components")
if *p.provider.GetStatus().InstalledVersion == p.provider.GetSpec().Version {
return reconcile.Result{}, nil
}

log.Info("Version changes detected, updating existing components")

return p.delete(ctx)
if err := p.newClusterClient().ProviderUpgrader().ApplyCustomPlan(ctx, cluster.UpgradeOptions{
WaitProviders: true,
WaitProviderTimeout: 5 * time.Minute,
}, cluster.UpgradeItem{
NextVersion: p.provider.GetSpec().Version,
Provider: getProvider(p.provider, p.options.Version),
}); err != nil {
return reconcile.Result{}, err
}

return reconcile.Result{}, nil
}

// install installs the provider components using clusterctl library.
Expand Down Expand Up @@ -501,26 +516,31 @@ func (p *phaseReconciler) install(ctx context.Context) (reconcile.Result, error)
return reconcile.Result{}, nil
}

func getProvider(provider operatorv1.GenericProvider, defaultVersion string) clusterctlv1.Provider {
clusterctlProvider := &clusterctlv1.Provider{}
clusterctlProvider.Name = clusterctlProviderName(provider).Name
clusterctlProvider.Namespace = provider.GetNamespace()
clusterctlProvider.Type = string(util.ClusterctlProviderType(provider))
clusterctlProvider.ProviderName = provider.GetName()

if provider.GetStatus().InstalledVersion != nil {
clusterctlProvider.Version = *provider.GetStatus().InstalledVersion
} else {
clusterctlProvider.Version = defaultVersion
}

return *clusterctlProvider
}

// delete deletes the provider components using clusterctl library.
func (p *phaseReconciler) delete(ctx context.Context) (reconcile.Result, error) {
log := ctrl.LoggerFrom(ctx)
log.Info("Deleting provider")

clusterClient := p.newClusterClient()

p.clusterctlProvider.Name = clusterctlProviderName(p.provider).Name
p.clusterctlProvider.Namespace = p.provider.GetNamespace()
p.clusterctlProvider.Type = string(util.ClusterctlProviderType(p.provider))
p.clusterctlProvider.ProviderName = p.provider.GetName()

if p.provider.GetStatus().InstalledVersion != nil {
p.clusterctlProvider.Version = *p.provider.GetStatus().InstalledVersion
} else {
p.clusterctlProvider.Version = p.options.Version
}

err := clusterClient.ProviderComponents().Delete(ctx, cluster.DeleteOptions{
Provider: *p.clusterctlProvider,
Provider: getProvider(p.provider, p.options.Version),
IncludeNamespace: false,
IncludeCRDs: false,
})
Expand Down Expand Up @@ -549,7 +569,7 @@ func clusterctlProviderName(provider operatorv1.GenericProvider) client.ObjectKe
// newClusterClient returns a clusterctl client for interacting with management cluster.
func (p *phaseReconciler) newClusterClient() cluster.Client {
return cluster.New(cluster.Kubeconfig{}, p.configClient, cluster.InjectProxy(&controllerProxy{
ctrlClient: p.ctrlClient,
ctrlClient: clientProxy{p.ctrlClient},
ctrlConfig: p.ctrlConfig,
}))
}
Expand Down

0 comments on commit 01a95b2

Please sign in to comment.