Skip to content

Commit

Permalink
Collect correct manifest config map per provider while performing upg…
Browse files Browse the repository at this point in the history
…rade

Clusterctl upgrade logic validates other installed providers, while
perfoming upgrade for version compatibility. Operator stores this data
in a ConfigMap to allow air-gapped support. This ensures we fetch
correct configMap first, to validate version against correct metadata.yaml.

Signed-off-by: Danil Grigorev <danil.grigorev@suse.com>
  • Loading branch information
Danil-Grigorev committed Mar 7, 2024
1 parent 19c0388 commit eb267f4
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 11 deletions.
34 changes: 25 additions & 9 deletions internal/controller/manifests_downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (p *phaseReconciler) downloadManifests(ctx context.Context) (reconcile.Resu
MatchLabels: p.prepareConfigMapLabels(),
}

exists, err := p.checkConfigMapExists(ctx, labelSelector)
exists, err := p.checkConfigMapExists(ctx, labelSelector, p.provider.GetNamespace())
if err != nil {
return reconcile.Result{}, wrapPhaseError(err, "failed to check that config map with manifests exists", operatorv1.ProviderInstalledCondition)
}
Expand Down Expand Up @@ -123,11 +123,11 @@ func (p *phaseReconciler) downloadManifests(ctx context.Context) (reconcile.Resu
}

// checkConfigMapExists checks if a config map exists in Kubernetes with the given LabelSelector.
func (p *phaseReconciler) checkConfigMapExists(ctx context.Context, labelSelector metav1.LabelSelector) (bool, error) {
func (p *phaseReconciler) checkConfigMapExists(ctx context.Context, labelSelector metav1.LabelSelector, namespace string) (bool, error) {
labelSet := labels.Set(labelSelector.MatchLabels)
listOpts := []client.ListOption{
client.MatchingLabelsSelector{Selector: labels.SelectorFromSet(labelSet)},
client.InNamespace(p.provider.GetNamespace()),
client.InNamespace(namespace),
}

var configMapList corev1.ConfigMapList
Expand All @@ -145,12 +145,7 @@ func (p *phaseReconciler) checkConfigMapExists(ctx context.Context, labelSelecto

// prepareConfigMapLabels returns labels that identify a config map with downloaded manifests.
func (p *phaseReconciler) prepareConfigMapLabels() map[string]string {
return map[string]string{
configMapVersionLabel: p.provider.GetSpec().Version,
configMapTypeLabel: p.provider.GetType(),
configMapNameLabel: p.provider.GetName(),
operatorManagedLabel: "true",
}
return providerLabels(p.provider)
}

// createManifestsConfigMap creates a config map with downloaded manifests.
Expand Down Expand Up @@ -210,6 +205,27 @@ func (p *phaseReconciler) createManifestsConfigMap(ctx context.Context, metadata
return nil
}

func providerLabelSelector(provider operatorv1.GenericProvider) *metav1.LabelSelector {
// Replace label selector if user wants to use custom config map
if provider.GetSpec().FetchConfig != nil && provider.GetSpec().FetchConfig.Selector != nil {
return provider.GetSpec().FetchConfig.Selector
}

return &metav1.LabelSelector{
MatchLabels: providerLabels(provider),
}
}

// prepareConfigMapLabels returns default set of labels that identify a config map with downloaded manifests.
func providerLabels(provider operatorv1.GenericProvider) map[string]string {
return map[string]string{
configMapVersionLabel: provider.GetSpec().Version,
configMapTypeLabel: provider.GetType(),
configMapNameLabel: provider.GetName(),
operatorManagedLabel: "true",
}
}

// needToCompress checks whether the input data exceeds the maximum configmap
// size limit and returns whether it should be compressed.
func needToCompress(bs ...[]byte) bool {
Expand Down
31 changes: 29 additions & 2 deletions internal/controller/phases.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const metadataFile = "metadata.yaml"
const (
metadataFile = "metadata.yaml"
)

// phaseReconciler holds all required information for interacting with clusterctl code and
// helps to iterate through provider reconciliation phases.
Expand Down Expand Up @@ -574,7 +576,32 @@ func clusterctlProviderName(provider operatorv1.GenericProvider) client.ObjectKe
}

func (p *phaseReconciler) repositoryProxy(ctx context.Context, provider configclient.Provider, configClient configclient.Client, options ...repository.Option) (repository.Client, error) {
cl, err := repository.New(ctx, provider, configClient, append([]repository.Option{repository.InjectRepository(p.repo)}, options...)...)
injectRepo := p.repo

if !provider.SameAs(p.providerConfig) {
genericProvider, err := util.GetGenericProvider(ctx, p.ctrlClient, provider)
if err != nil {
return nil, wrapPhaseError(err, "unable to find generic provider for configclient "+string(provider.Type())+": "+provider.Name(), operatorv1.ProviderUpgradedCondition)
}

if exists, err := p.checkConfigMapExists(ctx, *providerLabelSelector(genericProvider), genericProvider.GetNamespace()); err != nil {
provider := client.ObjectKeyFromObject(genericProvider)
return nil, wrapPhaseError(err, "failed to check the config map repository existence for provider "+provider.String(), operatorv1.ProviderUpgradedCondition)
} else if !exists {
provider := client.ObjectKeyFromObject(genericProvider)
return nil, wrapPhaseError(fmt.Errorf("config map not found"), "config map repository required for validation does not exist yet for provider "+provider.String(), operatorv1.ProviderUpgradedCondition)
}

repo, err := p.configmapRepository(ctx, providerLabelSelector(genericProvider), genericProvider.GetNamespace(), "")
if err != nil {
provider := client.ObjectKeyFromObject(genericProvider)
return nil, wrapPhaseError(err, "failed to load the repository for provider "+provider.String(), operatorv1.ProviderUpgradedCondition)
}

injectRepo = repo
}

cl, err := repository.New(ctx, provider, configClient, append([]repository.Option{repository.InjectRepository(injectRepo)}, options...)...)
if err != nil {
return nil, err
}
Expand Down
40 changes: 40 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
configclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
)

const (
Expand All @@ -36,6 +37,11 @@ const (
gitlabPackagesAPIPrefix = "/api/v4/projects/"
)

type genericProviderList interface {
ctrlclient.ObjectList
operatorv1.GenericProviderList
}

func IsCoreProvider(p genericprovider.GenericProvider) bool {
_, ok := p.(*operatorv1.CoreProvider)
return ok
Expand All @@ -61,6 +67,40 @@ func ClusterctlProviderType(genericProvider operatorv1.GenericProvider) clusterc
return clusterctlv1.ProviderTypeUnknown
}

// GetGenericProvider returns the first of generic providers matching the type and the name from the configclient.Provider.
func GetGenericProvider(ctx context.Context, cl ctrlclient.Client, provider configclient.Provider) (operatorv1.GenericProvider, error) {
var list genericProviderList

switch provider.Type() {
case clusterctlv1.CoreProviderType:
list = &operatorv1.CoreProviderList{}
case clusterctlv1.ControlPlaneProviderType:
list = &operatorv1.ControlPlaneProviderList{}
case clusterctlv1.InfrastructureProviderType:
list = &operatorv1.InfrastructureProviderList{}
case clusterctlv1.BootstrapProviderType:
list = &operatorv1.BootstrapProviderList{}
case clusterctlv1.AddonProviderType:
list = &operatorv1.AddonProviderList{}
case clusterctlv1.IPAMProviderType:
list = &operatorv1.IPAMProviderList{}
case clusterctlv1.RuntimeExtensionProviderType, clusterctlv1.ProviderTypeUnknown:
return nil, fmt.Errorf("provider %s type is not supported %s", provider.Name(), provider.Type())
}

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

for _, p := range list.GetItems() {
if p.GetName() == provider.Name() {
return p, nil
}
}

return nil, fmt.Errorf("unable to find provider manifest with name %s", provider.Name())
}

// RepositoryFactory returns the repository implementation corresponding to the provider URL.
// inspired by https://github.com/kubernetes-sigs/cluster-api/blob/124d9be7035e492f027cdc7a701b6b179451190a/cmd/clusterctl/client/repository/client.go#L170
func RepositoryFactory(ctx context.Context, providerConfig configclient.Provider, configVariablesClient configclient.VariablesClient) (repository.Repository, error) {
Expand Down

0 comments on commit eb267f4

Please sign in to comment.