From cdc75396291f4a0b3c7daf1e77fcc62e8e5a4380 Mon Sep 17 00:00:00 2001 From: Ethan Mosbaugh Date: Tue, 29 Oct 2024 09:36:35 -0700 Subject: [PATCH] f --- pkg/store/kotsstore/downstream_store.go | 38 +++-- pkg/store/kotsstore/downstream_store_test.go | 156 ++++++++++++++++++- pkg/store/kotsstore/kots_store.go | 3 + pkg/store/store.go | 10 +- 4 files changed, 181 insertions(+), 26 deletions(-) diff --git a/pkg/store/kotsstore/downstream_store.go b/pkg/store/kotsstore/downstream_store.go index 621d92c852..f35d5b6705 100644 --- a/pkg/store/kotsstore/downstream_store.go +++ b/pkg/store/kotsstore/downstream_store.go @@ -15,6 +15,7 @@ import ( "github.com/replicatedhq/kots/pkg/kotsutil" "github.com/replicatedhq/kots/pkg/logger" "github.com/replicatedhq/kots/pkg/persistence" + "github.com/replicatedhq/kots/pkg/store" "github.com/replicatedhq/kots/pkg/store/types" "github.com/replicatedhq/kots/pkg/tasks" "github.com/replicatedhq/kots/pkg/util" @@ -426,7 +427,7 @@ func (s *KOTSStore) GetDownstreamVersions(appID string, clusterID string, downlo if err := s.AddDownstreamVersionDetails(appID, clusterID, v, false); err != nil { return nil, errors.Wrap(err, "failed to add details to latest downloaded version") } - v.IsDeployable, v.NonDeployableCause, err = s.isAppVersionDeployable(appID, v, result, license.Spec.IsSemverRequired) + v.IsDeployable, v.NonDeployableCause, err = isAppVersionDeployable(s, appID, v, result, license.Spec.IsSemverRequired) if err != nil { return nil, errors.Wrapf(err, "failed to check if version %s is deployable", v.VersionLabel) } @@ -681,7 +682,7 @@ func (s *KOTSStore) AddDownstreamVersionsDetails(appID string, clusterID string, } for _, v := range versions { - v.IsDeployable, v.NonDeployableCause, err = s.isAppVersionDeployable(appID, v, allVersions, license.Spec.IsSemverRequired) + v.IsDeployable, v.NonDeployableCause, err = isAppVersionDeployable(s, appID, v, allVersions, license.Spec.IsSemverRequired) if err != nil { return errors.Wrapf(err, "failed to check if version %s is deployable", v.VersionLabel) } @@ -874,7 +875,7 @@ func isSameUpstreamRelease(v1 *downstreamtypes.DownstreamVersion, v2 *downstream return v1.Semver.EQ(*v2.Semver) } -func (s *KOTSStore) isAppVersionDeployable(appID string, version *downstreamtypes.DownstreamVersion, appVersions *downstreamtypes.DownstreamVersions, isSemverRequired bool) (bool, string, error) { +func isAppVersionDeployable(s store.Store, appID string, version *downstreamtypes.DownstreamVersion, appVersions *downstreamtypes.DownstreamVersions, isSemverRequired bool) (bool, string, error) { if version.HasFailingStrictPreflights { return false, "Deployment is disabled as a strict analyzer in this version's preflight checks has failed or has not been run.", nil } @@ -916,16 +917,6 @@ func (s *KOTSStore) isAppVersionDeployable(appID string, version *downstreamtype } if versionIndex > deployedVersionIndex { - if util.IsEmbeddedCluster() { - changed, err := s.didECClusterConfigChange(appID, version, appVersions.CurrentVersion) - if err != nil { - return false, "", errors.Wrapf(err, "failed to check if embedded cluster config changed for version %s", version.Sequence) - } - if changed { - return false, "Rollback is not supported, cluster configuration has changed.", nil - } - } - // this is a past version // rollback support is based off of the latest downloaded version for _, v := range appVersions.AllVersions { @@ -937,6 +928,19 @@ func (s *KOTSStore) isAppVersionDeployable(appID string, version *downstreamtype } break } + + if util.IsEmbeddedCluster() { + // Compare the embedded cluster config of the version specified to the currently + // deployed version to check if it has changed. If it has, then we do not allow + // rollbacks. + changed, err := didECClusterConfigChange(s, appID, version, appVersions.CurrentVersion) + if err != nil { + return false, "", errors.Wrapf(err, "failed to check if embedded cluster config changed for version %d", version.Sequence) + } + if changed { + return false, "Rollback is not supported, cluster configuration has changed.", nil + } + } } // if semantic versioning is not enabled, only require versions from the same channel AND with a lower cursor/channel sequence @@ -1024,14 +1028,16 @@ ALL_VERSIONS_LOOP: return true, "", nil } -func (s *KOTSStore) didECClusterConfigChange(appID string, version *downstreamtypes.DownstreamVersion, currentVersion *downstreamtypes.DownstreamVersion) (bool, error) { +// didECClusterConfigChange compares the embedded cluster config of the version specified to the +// currently deployed version to check if it has changed. +func didECClusterConfigChange(s store.Store, appID string, version *downstreamtypes.DownstreamVersion, currentVersion *downstreamtypes.DownstreamVersion) (bool, error) { currentConf, err := s.GetEmbeddedClusterConfigForVersion(appID, currentVersion.Sequence) if err != nil { - return false, errors.Wrapf(err, "failed to get embedded cluster config for current version %s", currentVersion.Sequence) + return false, errors.Wrapf(err, "failed to get embedded cluster config for current version %d", currentVersion.Sequence) } currentECConfigBytes, err := json.Marshal(currentConf) if err != nil { - return false, errors.Wrapf(err, "failed to marshal embedded cluster config for current version %s", currentVersion.Sequence) + return false, errors.Wrapf(err, "failed to marshal embedded cluster config for current version %d", currentVersion.Sequence) } thisConf, err := s.GetEmbeddedClusterConfigForVersion(appID, version.Sequence) if err != nil { diff --git a/pkg/store/kotsstore/downstream_store_test.go b/pkg/store/kotsstore/downstream_store_test.go index 140134dcd1..777d7da717 100644 --- a/pkg/store/kotsstore/downstream_store_test.go +++ b/pkg/store/kotsstore/downstream_store_test.go @@ -4,9 +4,12 @@ import ( "testing" "github.com/blang/semver" + "github.com/golang/mock/gomock" + embeddedclusterv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1" downstreamtypes "github.com/replicatedhq/kots/pkg/api/downstream/types" "github.com/replicatedhq/kots/pkg/cursor" "github.com/replicatedhq/kots/pkg/kotsutil" + mock_store "github.com/replicatedhq/kots/pkg/store/mock" "github.com/replicatedhq/kots/pkg/store/types" kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1" "github.com/stretchr/testify/assert" @@ -251,6 +254,7 @@ func Test_isAppVersionDeployable(t *testing.T) { version *downstreamtypes.DownstreamVersion appVersions *downstreamtypes.DownstreamVersions isSemverRequired bool + setup func(t *testing.T, mockStore *mock_store.MockStore) expectedIsDeployable bool expectedCause string wantErr bool @@ -3623,6 +3627,149 @@ func Test_isAppVersionDeployable(t *testing.T) { }, /* ---- Semver rollback tests end here ---- */ /* ---- Semver tests end here ---- */ + /* ---- Embedded cluster config tests start here ---- */ + { + name: "embedded cluster config change should not allow rollbacks", + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "1234") + + mockStore.EXPECT().GetEmbeddedClusterConfigForVersion("APPID", int64(0)).Return(&embeddedclusterv1beta1.Config{ + Spec: embeddedclusterv1beta1.ConfigSpec{ + Version: "1.0.0-ec.0", + }, + }, nil) + mockStore.EXPECT().GetEmbeddedClusterConfigForVersion("APPID", int64(1)).Return(&embeddedclusterv1beta1.Config{ + Spec: embeddedclusterv1beta1.ConfigSpec{ + Version: "1.0.0-ec.1", + }, + }, nil) + }, + version: &downstreamtypes.DownstreamVersion{ + VersionLabel: "1.0.0", + Sequence: 0, + }, + appVersions: &downstreamtypes.DownstreamVersions{ + CurrentVersion: &downstreamtypes.DownstreamVersion{ + VersionLabel: "2.0.0", + Sequence: 1, + }, + AllVersions: []*downstreamtypes.DownstreamVersion{ + { + VersionLabel: "3.0.0", + Sequence: 2, + KOTSKinds: &kotsutil.KotsKinds{ + KotsApplication: kotsv1beta1.Application{ + Spec: kotsv1beta1.ApplicationSpec{ + AllowRollback: true, + }, + }, + }, + }, + { + VersionLabel: "2.0.0", + Sequence: 1, + }, + { + VersionLabel: "1.0.0", + Sequence: 0, + }, + }, + }, + expectedIsDeployable: false, + expectedCause: "Rollback is not supported, cluster configuration has changed.", + wantErr: false, + }, + { + name: "embedded cluster config no change should allow rollbacks", + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "1234") + + mockStore.EXPECT().GetEmbeddedClusterConfigForVersion("APPID", int64(0)).Return(&embeddedclusterv1beta1.Config{ + Spec: embeddedclusterv1beta1.ConfigSpec{ + Version: "1.0.0-ec.0", + }, + }, nil) + mockStore.EXPECT().GetEmbeddedClusterConfigForVersion("APPID", int64(1)).Return(&embeddedclusterv1beta1.Config{ + Spec: embeddedclusterv1beta1.ConfigSpec{ + Version: "1.0.0-ec.0", + }, + }, nil) + }, + version: &downstreamtypes.DownstreamVersion{ + VersionLabel: "1.0.0", + Sequence: 0, + }, + appVersions: &downstreamtypes.DownstreamVersions{ + CurrentVersion: &downstreamtypes.DownstreamVersion{ + VersionLabel: "2.0.0", + Sequence: 1, + }, + AllVersions: []*downstreamtypes.DownstreamVersion{ + { + VersionLabel: "3.0.0", + Sequence: 2, + KOTSKinds: &kotsutil.KotsKinds{ + KotsApplication: kotsv1beta1.Application{ + Spec: kotsv1beta1.ApplicationSpec{ + AllowRollback: true, + }, + }, + }, + }, + { + VersionLabel: "2.0.0", + Sequence: 1, + }, + { + VersionLabel: "1.0.0", + Sequence: 0, + }, + }, + }, + expectedIsDeployable: true, + expectedCause: "", + wantErr: false, + }, + { + name: "embedded cluster, allowRollback = false should not allow rollbacks", + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "1234") + }, + version: &downstreamtypes.DownstreamVersion{ + VersionLabel: "1.0.0", + Sequence: 0, + }, + appVersions: &downstreamtypes.DownstreamVersions{ + CurrentVersion: &downstreamtypes.DownstreamVersion{ + VersionLabel: "2.0.0", + Sequence: 1, + }, + AllVersions: []*downstreamtypes.DownstreamVersion{ + { + VersionLabel: "3.0.0", + Sequence: 2, + KOTSKinds: &kotsutil.KotsKinds{ + KotsApplication: kotsv1beta1.Application{ + Spec: kotsv1beta1.ApplicationSpec{ + AllowRollback: false, + }, + }, + }, + }, + { + VersionLabel: "2.0.0", + Sequence: 1, + }, + { + VersionLabel: "1.0.0", + Sequence: 0, + }, + }, + }, + expectedIsDeployable: false, + expectedCause: "Rollback is not supported.", + wantErr: false, + }, } for _, test := range tests { @@ -3658,7 +3805,14 @@ func Test_isAppVersionDeployable(t *testing.T) { } } - isDeployable, cause, err := isAppVersionDeployable(test.version, test.appVersions, test.isSemverRequired) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockStore := mock_store.NewMockStore(ctrl) + if test.setup != nil { + test.setup(t, mockStore) + } + isDeployable, cause, err := isAppVersionDeployable(mockStore, "APPID", test.version, test.appVersions, test.isSemverRequired) if test.wantErr { require.Error(t, err) } else { diff --git a/pkg/store/kotsstore/kots_store.go b/pkg/store/kotsstore/kots_store.go index e8caf43d36..7cb91a5486 100644 --- a/pkg/store/kotsstore/kots_store.go +++ b/pkg/store/kotsstore/kots_store.go @@ -14,6 +14,7 @@ import ( kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types" "github.com/replicatedhq/kots/pkg/logger" "github.com/replicatedhq/kots/pkg/persistence" + "github.com/replicatedhq/kots/pkg/store" "github.com/replicatedhq/kots/pkg/util" kotsscheme "github.com/replicatedhq/kotskinds/client/kotsclientset/scheme" troubleshootscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme" @@ -34,6 +35,8 @@ type KOTSStore struct { } func init() { + store.SetStore(StoreFromEnv()) + kotsscheme.AddToScheme(scheme.Scheme) veleroscheme.AddToScheme(scheme.Scheme) troubleshootscheme.AddToScheme(scheme.Scheme) diff --git a/pkg/store/store.go b/pkg/store/store.go index a5d9ecc28d..fdcbd91a12 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -1,7 +1,6 @@ package store import ( - "github.com/replicatedhq/kots/pkg/store/kotsstore" "github.com/replicatedhq/kots/pkg/util" ) @@ -10,23 +9,16 @@ var ( globalStore Store ) -var _ Store = (*kotsstore.KOTSStore)(nil) - func GetStore() Store { if util.IsUpgradeService() { panic("store cannot not be used in the upgrade service") } if !hasStore { - globalStore = storeFromEnv() - hasStore = true + panic("store not initialized") } return globalStore } -func storeFromEnv() Store { - return kotsstore.StoreFromEnv() -} - func SetStore(s Store) { if s == nil { hasStore = false