Skip to content

Commit ee252ac

Browse files
authored
Merge pull request #451 from Danil-Grigorev/inject-repo-for-upgrade-validation
🐛 Collect correct manifest config map per provider while performing upgrade
2 parents 19c0388 + 5eccd0d commit ee252ac

File tree

5 files changed

+305
-12
lines changed

5 files changed

+305
-12
lines changed

internal/controller/manifests_downloader.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func (p *phaseReconciler) downloadManifests(ctx context.Context) (reconcile.Resu
6666
MatchLabels: p.prepareConfigMapLabels(),
6767
}
6868

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

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

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

146146
// prepareConfigMapLabels returns labels that identify a config map with downloaded manifests.
147147
func (p *phaseReconciler) prepareConfigMapLabels() map[string]string {
148-
return map[string]string{
149-
configMapVersionLabel: p.provider.GetSpec().Version,
150-
configMapTypeLabel: p.provider.GetType(),
151-
configMapNameLabel: p.provider.GetName(),
152-
operatorManagedLabel: "true",
153-
}
148+
return providerLabels(p.provider)
154149
}
155150

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

208+
func providerLabelSelector(provider operatorv1.GenericProvider) *metav1.LabelSelector {
209+
// Replace label selector if user wants to use custom config map
210+
if provider.GetSpec().FetchConfig != nil && provider.GetSpec().FetchConfig.Selector != nil {
211+
return provider.GetSpec().FetchConfig.Selector
212+
}
213+
214+
return &metav1.LabelSelector{
215+
MatchLabels: providerLabels(provider),
216+
}
217+
}
218+
219+
// prepareConfigMapLabels returns default set of labels that identify a config map with downloaded manifests.
220+
func providerLabels(provider operatorv1.GenericProvider) map[string]string {
221+
return map[string]string{
222+
configMapVersionLabel: provider.GetSpec().Version,
223+
configMapTypeLabel: provider.GetType(),
224+
configMapNameLabel: provider.GetName(),
225+
operatorManagedLabel: "true",
226+
}
227+
}
228+
213229
// needToCompress checks whether the input data exceeds the maximum configmap
214230
// size limit and returns whether it should be compressed.
215231
func needToCompress(bs ...[]byte) bool {

internal/controller/manifests_downloader_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func TestManifestsDownloader(t *testing.T) {
6262
MatchLabels: p.prepareConfigMapLabels(),
6363
}
6464

65-
exists, err := p.checkConfigMapExists(ctx, labelSelector)
65+
exists, err := p.checkConfigMapExists(ctx, labelSelector, p.provider.GetNamespace())
6666
g.Expect(err).ToNot(HaveOccurred())
6767

6868
g.Expect(exists).To(BeTrue())

internal/controller/phases.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ import (
4848
"sigs.k8s.io/controller-runtime/pkg/reconcile"
4949
)
5050

51-
const metadataFile = "metadata.yaml"
51+
const (
52+
metadataFile = "metadata.yaml"
53+
)
5254

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

576578
func (p *phaseReconciler) repositoryProxy(ctx context.Context, provider configclient.Provider, configClient configclient.Client, options ...repository.Option) (repository.Client, error) {
577-
cl, err := repository.New(ctx, provider, configClient, append([]repository.Option{repository.InjectRepository(p.repo)}, options...)...)
579+
injectRepo := p.repo
580+
581+
if !provider.SameAs(p.providerConfig) {
582+
genericProvider, err := util.GetGenericProvider(ctx, p.ctrlClient, provider)
583+
if err != nil {
584+
return nil, wrapPhaseError(err, "unable to find generic provider for configclient "+string(provider.Type())+": "+provider.Name(), operatorv1.ProviderUpgradedCondition)
585+
}
586+
587+
if exists, err := p.checkConfigMapExists(ctx, *providerLabelSelector(genericProvider), genericProvider.GetNamespace()); err != nil {
588+
provider := client.ObjectKeyFromObject(genericProvider)
589+
return nil, wrapPhaseError(err, "failed to check the config map repository existence for provider "+provider.String(), operatorv1.ProviderUpgradedCondition)
590+
} else if !exists {
591+
provider := client.ObjectKeyFromObject(genericProvider)
592+
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)
593+
}
594+
595+
repo, err := p.configmapRepository(ctx, providerLabelSelector(genericProvider), genericProvider.GetNamespace(), "")
596+
if err != nil {
597+
provider := client.ObjectKeyFromObject(genericProvider)
598+
return nil, wrapPhaseError(err, "failed to load the repository for provider "+provider.String(), operatorv1.ProviderUpgradedCondition)
599+
}
600+
601+
injectRepo = repo
602+
}
603+
604+
cl, err := repository.New(ctx, provider, configClient, append([]repository.Option{repository.InjectRepository(injectRepo)}, options...)...)
578605
if err != nil {
579606
return nil, err
580607
}

internal/controller/phases_test.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controller
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"testing"
2223

2324
. "github.com/onsi/gomega"
@@ -26,6 +27,7 @@ import (
2627
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
2728
configclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
2829
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
2931
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3032

3133
operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
@@ -412,6 +414,214 @@ metadata:
412414
}
413415
}
414416

417+
func TestRepositoryProxy(t *testing.T) {
418+
coreProvider := configclient.NewProvider("cluster-api", "https://github.com/kubernetes-sigs/cluster-api/releases/latest/core-components.yaml", clusterctlv1.CoreProviderType)
419+
awsProvider := configclient.NewProvider("aws", "https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/v1.4.1/infrastructure-components.yaml", clusterctlv1.InfrastructureProviderType)
420+
421+
provider := &operatorv1.InfrastructureProvider{
422+
ObjectMeta: metav1.ObjectMeta{
423+
Name: "aws",
424+
Namespace: "ns1",
425+
},
426+
TypeMeta: metav1.TypeMeta{
427+
Kind: "InfrastructureProvider",
428+
APIVersion: "operator.cluster.x-k8s.io/v1alpha2",
429+
},
430+
Spec: operatorv1.InfrastructureProviderSpec{
431+
ProviderSpec: operatorv1.ProviderSpec{
432+
Version: "v2.3.5",
433+
FetchConfig: &operatorv1.FetchConfiguration{
434+
Selector: &metav1.LabelSelector{
435+
MatchLabels: map[string]string{"provider-components": "aws"},
436+
},
437+
},
438+
},
439+
},
440+
}
441+
442+
core := &operatorv1.CoreProvider{
443+
ObjectMeta: metav1.ObjectMeta{
444+
Name: "cluster-api",
445+
Namespace: "default",
446+
},
447+
Spec: operatorv1.CoreProviderSpec{
448+
ProviderSpec: operatorv1.ProviderSpec{
449+
Version: testCurrentVersion,
450+
},
451+
},
452+
}
453+
454+
awsMetadata := `
455+
apiVersion: clusterctl.cluster.x-k8s.io/v1alpha3
456+
releaseSeries:
457+
- major: 2
458+
minor: 4
459+
contract: v1beta1
460+
- major: 2
461+
minor: 3
462+
contract: v1beta1`
463+
464+
awsMetaReleaseSeries := []clusterctlv1.ReleaseSeries{
465+
{
466+
Major: 2,
467+
Minor: 4,
468+
Contract: "v1beta1",
469+
}, {
470+
Major: 2,
471+
Minor: 3,
472+
Contract: "v1beta1",
473+
},
474+
}
475+
476+
metadata := `
477+
apiVersion: clusterctl.cluster.x-k8s.io/v1alpha3
478+
releaseSeries:
479+
- major: 0
480+
minor: 4
481+
contract: v1alpha4
482+
- major: 0
483+
minor: 3
484+
contract: v1alpha3`
485+
486+
metaReleaseSeries := []clusterctlv1.ReleaseSeries{{
487+
Major: 0,
488+
Minor: 4,
489+
Contract: "v1alpha4",
490+
}, {
491+
Major: 0,
492+
Minor: 3,
493+
Contract: "v1alpha3",
494+
}}
495+
496+
tests := []struct {
497+
name string
498+
configMaps []corev1.ConfigMap
499+
genericProviders []client.Object
500+
provider configclient.Provider
501+
defaultRepository bool
502+
wantMetadataSeries []clusterctlv1.ReleaseSeries
503+
wantErr string
504+
metadataErr string
505+
wantDefaultVersion string
506+
}{
507+
{
508+
name: "missing configmaps",
509+
provider: coreProvider,
510+
wantDefaultVersion: testCurrentVersion,
511+
genericProviders: []client.Object{core, provider},
512+
metadataErr: "failed to read \"metadata.yaml\" from the repository for provider \"cluster-api\": unable to get files for version v0.4.2",
513+
},
514+
{
515+
name: "correct configmap with data",
516+
genericProviders: []client.Object{core, provider},
517+
provider: coreProvider,
518+
defaultRepository: true,
519+
wantDefaultVersion: testCurrentVersion,
520+
wantMetadataSeries: metaReleaseSeries,
521+
configMaps: []corev1.ConfigMap{
522+
{
523+
TypeMeta: metav1.TypeMeta{
524+
Kind: "ConfigMap",
525+
APIVersion: "v1",
526+
},
527+
ObjectMeta: metav1.ObjectMeta{
528+
Name: testCurrentVersion,
529+
Namespace: "default",
530+
Labels: map[string]string{
531+
configMapVersionLabel: testCurrentVersion,
532+
configMapTypeLabel: core.GetType(),
533+
configMapNameLabel: core.GetName(),
534+
operatorManagedLabel: "true",
535+
},
536+
},
537+
Data: map[string]string{"metadata": metadata, "components": ""},
538+
},
539+
},
540+
},
541+
{
542+
name: "upgrade required validation of another provider missing config map",
543+
genericProviders: []client.Object{core, provider},
544+
provider: awsProvider,
545+
wantErr: wrapPhaseError(fmt.Errorf("config map not found"), "config map repository required for validation does not exist yet for provider ns1/aws", operatorv1.ProviderUpgradedCondition).Error(),
546+
},
547+
{
548+
name: "updgrade requested an unknown provider for the operator",
549+
genericProviders: []client.Object{core},
550+
provider: awsProvider,
551+
wantErr: wrapPhaseError(fmt.Errorf("unable to find provider manifest with name aws"), "unable to find generic provider for configclient InfrastructureProvider: aws", operatorv1.ProviderUpgradedCondition).Error(),
552+
},
553+
{
554+
name: "upgrade required validation of another provider metadata succeeds",
555+
genericProviders: []client.Object{core, provider},
556+
provider: awsProvider,
557+
wantDefaultVersion: "v2.3.5",
558+
wantMetadataSeries: awsMetaReleaseSeries,
559+
configMaps: []corev1.ConfigMap{
560+
{
561+
TypeMeta: metav1.TypeMeta{
562+
Kind: "ConfigMap",
563+
APIVersion: "v1",
564+
},
565+
ObjectMeta: metav1.ObjectMeta{
566+
Name: "v2.3.5",
567+
Namespace: provider.Namespace,
568+
Labels: map[string]string{"provider-components": "aws"},
569+
},
570+
Data: map[string]string{"metadata": awsMetadata, "components": ""},
571+
},
572+
},
573+
},
574+
}
575+
576+
for _, tt := range tests {
577+
t.Run(tt.name, func(t *testing.T) {
578+
g := NewWithT(t)
579+
580+
fakeclient := fake.NewClientBuilder().WithScheme(setupScheme()).WithObjects(tt.genericProviders...).Build()
581+
p := &phaseReconciler{
582+
ctrlClient: fakeclient,
583+
providerConfig: coreProvider,
584+
repo: repository.NewMemoryRepository(),
585+
provider: core,
586+
}
587+
588+
for i := range tt.configMaps {
589+
g.Expect(fakeclient.Create(ctx, &tt.configMaps[i])).To(Succeed())
590+
}
591+
if tt.defaultRepository {
592+
var err error
593+
p.repo, err = p.configmapRepository(ctx, &metav1.LabelSelector{
594+
MatchLabels: map[string]string{
595+
operatorManagedLabel: "true",
596+
},
597+
}, "default", "")
598+
g.Expect(err).To(Succeed())
599+
}
600+
601+
cl, err := configclient.New(ctx, "")
602+
g.Expect(err).To(Succeed())
603+
604+
got, err := p.repositoryProxy(ctx, tt.provider, cl)
605+
if len(tt.wantErr) > 0 {
606+
g.Expect(err).Should(MatchError(tt.wantErr))
607+
return
608+
}
609+
g.Expect(err).To(Succeed())
610+
611+
meta := got.Metadata(tt.wantDefaultVersion)
612+
metadataData, err := meta.Get(ctx)
613+
if len(tt.metadataErr) > 0 {
614+
g.Expect(err).Should(MatchError(tt.metadataErr))
615+
return
616+
}
617+
g.Expect(err).To(Succeed())
618+
g.Expect(metadataData.ReleaseSeries).To(Equal(tt.wantMetadataSeries))
619+
620+
g.Expect(got.DefaultVersion()).To(Equal(tt.wantDefaultVersion))
621+
})
622+
}
623+
}
624+
415625
func TestRepositoryFactory(t *testing.T) {
416626
testCases := []struct {
417627
name string

0 commit comments

Comments
 (0)