Skip to content

Commit 630ec4b

Browse files
authored
Merge pull request #407 from Fedosin/no_downgrades
✨ Add a preflight check to prevent provider downgrades
2 parents f5d8954 + a22f799 commit 630ec4b

File tree

3 files changed

+147
-11
lines changed

3 files changed

+147
-11
lines changed

api/v1alpha2/conditions_consts.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ const (
6161

6262
// NoDeploymentAvailableConditionReason documents that there is no Available condition for provider deployment yet.
6363
NoDeploymentAvailableConditionReason = "NoDeploymentAvailableConditionReason"
64+
65+
// UnsupportedProviderDowngradeReason documents that the provider downgrade is not supported.
66+
UnsupportedProviderDowngradeReason = "UnsupportedProviderDowngradeReason"
6467
)
6568

6669
const (

internal/controller/preflight_checks.go

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ var (
4848
invalidGithubTokenMessage = "Invalid github token, please check your github token value and its permissions" //nolint:gosec
4949
waitingForCoreProviderReadyMessage = "Waiting for the core provider to be installed."
5050
incorrectCoreProviderNameMessage = "Incorrect CoreProvider name: %s. It should be %s"
51+
unsupportedProviderDowngradeMessage = "Downgrade is not supported for provider %s"
5152
)
5253

5354
// preflightChecks performs preflight checks before installing provider.
@@ -58,18 +59,10 @@ func preflightChecks(ctx context.Context, c client.Client, provider genericprovi
5859

5960
spec := provider.GetSpec()
6061

61-
// Check that provider version contains a valid value if it's not empty.
6262
if spec.Version != "" {
63-
if _, err := version.ParseSemantic(spec.Version); err != nil {
64-
log.Info("Version contains invalid value")
65-
conditions.Set(provider, conditions.FalseCondition(
66-
operatorv1.PreflightCheckCondition,
67-
operatorv1.IncorrectVersionFormatReason,
68-
clusterv1.ConditionSeverityError,
69-
err.Error(),
70-
))
71-
72-
return ctrl.Result{}, fmt.Errorf("version contains invalid value for provider %q", provider.GetName())
63+
// Check that the provider version is supported.
64+
if err := checkProviderVersion(ctx, spec.Version, provider); err != nil {
65+
return ctrl.Result{}, err
7366
}
7467
}
7568

@@ -208,6 +201,46 @@ func preflightChecks(ctx context.Context, c client.Client, provider genericprovi
208201
return ctrl.Result{}, nil
209202
}
210203

204+
// checkProviderVersion verifies that target and installed provider versions are correct.
205+
func checkProviderVersion(ctx context.Context, providerVersion string, provider genericprovider.GenericProvider) error {
206+
log := ctrl.LoggerFrom(ctx)
207+
208+
// Check that provider version contains a valid value if it's not empty.
209+
targetVersion, err := version.ParseSemantic(providerVersion)
210+
if err != nil {
211+
log.Info("Version contains invalid value")
212+
conditions.Set(provider, conditions.FalseCondition(
213+
operatorv1.PreflightCheckCondition,
214+
operatorv1.IncorrectVersionFormatReason,
215+
clusterv1.ConditionSeverityError,
216+
err.Error(),
217+
))
218+
219+
return fmt.Errorf("version contains invalid value for provider %q", provider.GetName())
220+
}
221+
222+
// Cluster API doesn't support downgrades by design. We need to report that for the user.
223+
if provider.GetStatus().InstalledVersion != nil && *provider.GetStatus().InstalledVersion != "" {
224+
installedVersion, err := version.ParseSemantic(*provider.GetStatus().InstalledVersion)
225+
if err != nil {
226+
return fmt.Errorf("installed version contains invalid value for provider %q", provider.GetName())
227+
}
228+
229+
if targetVersion.Major() < installedVersion.Major() || targetVersion.Major() == installedVersion.Major() && targetVersion.Minor() < installedVersion.Minor() {
230+
conditions.Set(provider, conditions.FalseCondition(
231+
operatorv1.PreflightCheckCondition,
232+
operatorv1.UnsupportedProviderDowngradeReason,
233+
clusterv1.ConditionSeverityError,
234+
fmt.Sprintf(unsupportedProviderDowngradeMessage, provider.GetName(), configclient.ClusterAPIProviderName),
235+
))
236+
237+
return fmt.Errorf("downgrade is not supported for provider %q", provider.GetName())
238+
}
239+
}
240+
241+
return nil
242+
}
243+
211244
// coreProviderIsReady returns true if the core provider is ready.
212245
func coreProviderIsReady(ctx context.Context, c client.Client) (bool, error) {
213246
cpl := &operatorv1.CoreProviderList{}

internal/controller/preflight_checks_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,3 +623,103 @@ func TestPreflightChecks(t *testing.T) {
623623
})
624624
}
625625
}
626+
627+
func TestPreflightChecksUpgradesDowngrades(t *testing.T) {
628+
testCases := []struct {
629+
name string
630+
installedVersion string
631+
targetVersion string
632+
expectedConditionStatus corev1.ConditionStatus
633+
expectedError bool
634+
}{
635+
{
636+
name: "upgrade core provider major version",
637+
expectedConditionStatus: corev1.ConditionTrue,
638+
installedVersion: "v1.9.0",
639+
targetVersion: "v2.0.0",
640+
},
641+
{
642+
name: "upgrade core provider minor version",
643+
expectedConditionStatus: corev1.ConditionTrue,
644+
installedVersion: "v1.9.0",
645+
targetVersion: "v1.10.0",
646+
},
647+
{
648+
name: "downgrade core provider major version",
649+
expectedConditionStatus: corev1.ConditionFalse,
650+
installedVersion: "v2.0.0",
651+
targetVersion: "v1.9.0",
652+
expectedError: true,
653+
},
654+
{
655+
name: "downgrade core provider minor version",
656+
expectedConditionStatus: corev1.ConditionFalse,
657+
installedVersion: "v1.10.0",
658+
targetVersion: "v1.9.0",
659+
expectedError: true,
660+
},
661+
{
662+
name: "downgrade core provider patch version",
663+
expectedConditionStatus: corev1.ConditionTrue,
664+
installedVersion: "v1.10.1",
665+
targetVersion: "v1.10.0",
666+
},
667+
{
668+
name: "same version",
669+
expectedConditionStatus: corev1.ConditionTrue,
670+
installedVersion: "v1.10.0",
671+
targetVersion: "v1.10.0",
672+
},
673+
}
674+
675+
for _, tc := range testCases {
676+
t.Run(tc.name, func(t *testing.T) {
677+
gs := NewWithT(t)
678+
679+
provider := &operatorv1.CoreProvider{
680+
ObjectMeta: metav1.ObjectMeta{
681+
Name: "cluster-api",
682+
Namespace: "provider-test-ns-1",
683+
},
684+
TypeMeta: metav1.TypeMeta{
685+
Kind: "CoreProvider",
686+
APIVersion: "operator.cluster.x-k8s.io/v1alpha1",
687+
},
688+
Spec: operatorv1.CoreProviderSpec{
689+
ProviderSpec: operatorv1.ProviderSpec{
690+
Version: tc.targetVersion,
691+
FetchConfig: &operatorv1.FetchConfiguration{
692+
URL: "https://example.com",
693+
},
694+
},
695+
},
696+
Status: operatorv1.CoreProviderStatus{
697+
ProviderStatus: operatorv1.ProviderStatus{
698+
InstalledVersion: &tc.installedVersion,
699+
},
700+
},
701+
}
702+
703+
fakeclient := fake.NewClientBuilder().WithObjects().Build()
704+
705+
gs.Expect(fakeclient.Create(ctx, provider)).To(Succeed())
706+
707+
_, err := preflightChecks(context.Background(), fakeclient, provider, &operatorv1.CoreProviderList{})
708+
if tc.expectedError {
709+
gs.Expect(err).To(HaveOccurred())
710+
} else {
711+
gs.Expect(err).ToNot(HaveOccurred())
712+
}
713+
714+
// Check if proper condition is returned
715+
gs.Expect(provider.GetStatus().Conditions).To(HaveLen(1))
716+
gs.Expect(provider.GetStatus().Conditions[0].Type).To(Equal(operatorv1.PreflightCheckCondition))
717+
gs.Expect(provider.GetStatus().Conditions[0].Status).To(Equal(tc.expectedConditionStatus))
718+
719+
if tc.expectedConditionStatus == corev1.ConditionFalse {
720+
gs.Expect(provider.GetStatus().Conditions[0].Reason).To(Equal(operatorv1.UnsupportedProviderDowngradeReason))
721+
gs.Expect(provider.GetStatus().Conditions[0].Severity).To(Equal(clusterv1.ConditionSeverityError))
722+
}
723+
})
724+
}
725+
}

0 commit comments

Comments
 (0)