Skip to content

Commit 0589dd2

Browse files
authored
feat(ec): allow rollbacks for embedded cluster (#4972)
* feat(ec): allow rollbacks for embedded cluster * f * f * f * f * feedback * feedback * feedback * feedback * feedback * f * assert interface
1 parent 0c11758 commit 0589dd2

File tree

10 files changed

+241
-50
lines changed

10 files changed

+241
-50
lines changed

cmd/kotsadm/cli/root.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"os"
66
"strings"
77

8+
"github.com/replicatedhq/kots/pkg/store"
9+
"github.com/replicatedhq/kots/pkg/store/kotsstore"
810
"github.com/spf13/cobra"
911
"github.com/spf13/viper"
1012
)
@@ -15,6 +17,9 @@ func RootCmd() *cobra.Command {
1517
Short: "kotsadm is the Admin Console for KOTS",
1618
Long: ``,
1719
Args: cobra.MinimumNArgs(1),
20+
PersistentPreRun: func(cmd *cobra.Command, args []string) {
21+
store.SetStore(kotsstore.StoreFromEnv())
22+
},
1823
PreRun: func(cmd *cobra.Command, args []string) {
1924
viper.BindPFlags(cmd.Flags())
2025
},

pkg/apparchive/helm-v1beta1.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,8 @@ import (
1515
)
1616

1717
var (
18-
goTemplateRegex *regexp.Regexp
19-
)
20-
21-
func init() {
2218
goTemplateRegex = regexp.MustCompile(`({{)|(}})`)
23-
}
19+
)
2420

2521
func GetRenderedV1Beta1ChartsArchive(versionArchive string, downstreamName, kustomizeBinPath string) ([]byte, map[string][]byte, error) {
2622
renderedChartsDir := filepath.Join(versionArchive, "rendered", downstreamName, "charts")

pkg/store/kotsstore/downstream_store.go

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package kotsstore
22

33
import (
4+
"bytes"
45
"encoding/base64"
6+
"encoding/json"
57
"fmt"
68
"strings"
79
"time"
@@ -13,8 +15,10 @@ import (
1315
"github.com/replicatedhq/kots/pkg/kotsutil"
1416
"github.com/replicatedhq/kots/pkg/logger"
1517
"github.com/replicatedhq/kots/pkg/persistence"
18+
"github.com/replicatedhq/kots/pkg/store"
1619
"github.com/replicatedhq/kots/pkg/store/types"
1720
"github.com/replicatedhq/kots/pkg/tasks"
21+
"github.com/replicatedhq/kots/pkg/util"
1822
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
1923
"github.com/rqlite/gorqlite"
2024
)
@@ -423,7 +427,10 @@ func (s *KOTSStore) GetDownstreamVersions(appID string, clusterID string, downlo
423427
if err := s.AddDownstreamVersionDetails(appID, clusterID, v, false); err != nil {
424428
return nil, errors.Wrap(err, "failed to add details to latest downloaded version")
425429
}
426-
v.IsDeployable, v.NonDeployableCause = isAppVersionDeployable(v, result, license.Spec.IsSemverRequired)
430+
v.IsDeployable, v.NonDeployableCause, err = isAppVersionDeployable(s, appID, v, result, license.Spec.IsSemverRequired)
431+
if err != nil {
432+
return nil, errors.Wrapf(err, "failed to check if version %s is deployable", v.VersionLabel)
433+
}
427434
break
428435
}
429436

@@ -676,7 +683,10 @@ func (s *KOTSStore) AddDownstreamVersionsDetails(appID string, clusterID string,
676683
}
677684

678685
for _, v := range versions {
679-
v.IsDeployable, v.NonDeployableCause = isAppVersionDeployable(v, allVersions, license.Spec.IsSemverRequired)
686+
v.IsDeployable, v.NonDeployableCause, err = isAppVersionDeployable(s, appID, v, allVersions, license.Spec.IsSemverRequired)
687+
if err != nil {
688+
return errors.Wrapf(err, "failed to check if version %s is deployable", v.VersionLabel)
689+
}
680690
}
681691
}
682692

@@ -866,28 +876,32 @@ func isSameUpstreamRelease(v1 *downstreamtypes.DownstreamVersion, v2 *downstream
866876
return v1.Semver.EQ(*v2.Semver)
867877
}
868878

869-
func isAppVersionDeployable(version *downstreamtypes.DownstreamVersion, appVersions *downstreamtypes.DownstreamVersions, isSemverRequired bool) (bool, string) {
879+
func isAppVersionDeployable(
880+
versionStore store.VersionStore,
881+
appID string, version *downstreamtypes.DownstreamVersion, appVersions *downstreamtypes.DownstreamVersions,
882+
isSemverRequired bool,
883+
) (bool, string, error) {
870884
if version.HasFailingStrictPreflights {
871-
return false, "Deployment is disabled as a strict analyzer in this version's preflight checks has failed or has not been run."
885+
return false, "Deployment is disabled as a strict analyzer in this version's preflight checks has failed or has not been run.", nil
872886
}
873887

874888
if version.Status == types.VersionPendingDownload {
875-
return false, "Version is pending download."
889+
return false, "Version is pending download.", nil
876890
}
877891

878892
if version.Status == types.VersionPendingConfig {
879-
return false, "Version is pending configuration."
893+
return false, "Version is pending configuration.", nil
880894
}
881895

882896
if appVersions.CurrentVersion == nil {
883897
// no version has been deployed yet, treat as an initial install where any version can be deployed at first.
884-
return true, ""
898+
return true, "", nil
885899
}
886900

887901
if version.Sequence == appVersions.CurrentVersion.Sequence {
888902
// version is currently deployed, so previous required versions should've already been deployed.
889903
// also, we shouldn't block re-deploying if a previous release is edited later by the vendor to be required.
890-
return true, ""
904+
return true, "", nil
891905
}
892906

893907
// rollback support is determined across all versions from all channels
@@ -906,18 +920,41 @@ func isAppVersionDeployable(version *downstreamtypes.DownstreamVersion, appVersi
906920
break
907921
}
908922
}
923+
924+
// This is a past version
909925
if versionIndex > deployedVersionIndex {
910-
// this is a past version
911-
// rollback support is based off of the latest downloaded version
926+
// Rollback support is based off of the latest downloaded version so that a vendor can
927+
// toggle on support without requiring the end user to deploy a new version.
912928
for _, v := range appVersions.AllVersions {
929+
// Find the first version that is not pending download. This will be the latest
930+
// version.
913931
if v.Status == types.VersionPendingDownload {
914932
continue
915933
}
916934
if v.KOTSKinds == nil || !v.KOTSKinds.KotsApplication.Spec.AllowRollback {
917-
return false, "Rollback is not supported."
935+
return false, "Rollback is not supported.", nil
918936
}
919937
break
920938
}
939+
940+
if util.IsEmbeddedCluster() && appVersions.CurrentVersion != nil {
941+
currentECConfig, err := getRawEmbeddedClusterConfigForVersion(versionStore, appID, appVersions.CurrentVersion.Sequence)
942+
if err != nil {
943+
return false, "", errors.Wrapf(err, "failed to get embedded cluster config for current version %d", appVersions.CurrentVersion.Sequence)
944+
}
945+
newECConfig, err := getRawEmbeddedClusterConfigForVersion(versionStore, appID, version.Sequence)
946+
if err != nil {
947+
return false, "", errors.Wrapf(err, "failed to get embedded cluster config for version %d", version.Sequence)
948+
}
949+
if util.IsEmbeddedCluster() && currentECConfig != nil {
950+
// Compare the embedded cluster config of the version specified to the currently
951+
// deployed version to check if it has changed. If it has, then we do not allow
952+
// rollbacks.
953+
if !bytes.Equal(currentECConfig, newECConfig) {
954+
return false, "Rollback is not supported, cluster configuration has changed.", nil
955+
}
956+
}
957+
}
921958
}
922959

923960
// if semantic versioning is not enabled, only require versions from the same channel AND with a lower cursor/channel sequence
@@ -951,7 +988,7 @@ func isAppVersionDeployable(version *downstreamtypes.DownstreamVersion, appVersi
951988

952989
if deployedVersionIndex == -1 {
953990
// the deployed version is from a different channel
954-
return true, ""
991+
return true, "", nil
955992
}
956993

957994
// find required versions between the deployed version and the desired version
@@ -969,7 +1006,7 @@ ALL_VERSIONS_LOOP:
9691006
// this is a past version
9701007
// >= because if the deployed version is required, rolling back isn't allowed
9711008
if i >= deployedVersionIndex && i < versionIndex {
972-
return false, "One or more non-reversible versions have been deployed since this version."
1009+
return false, "One or more non-reversible versions have been deployed since this version.", nil
9731010
}
9741011
continue
9751012
}
@@ -997,12 +1034,24 @@ ALL_VERSIONS_LOOP:
9971034
}
9981035
versionLabelsStr := strings.Join(versionLabels, ", ")
9991036
if len(requiredVersions) == 1 {
1000-
return false, fmt.Sprintf("This version cannot be deployed because version %s is required and must be deployed first.", versionLabelsStr)
1037+
return false, fmt.Sprintf("This version cannot be deployed because version %s is required and must be deployed first.", versionLabelsStr), nil
10011038
}
1002-
return false, fmt.Sprintf("This version cannot be deployed because versions %s are required and must be deployed first.", versionLabelsStr)
1039+
return false, fmt.Sprintf("This version cannot be deployed because versions %s are required and must be deployed first.", versionLabelsStr), nil
10031040
}
10041041

1005-
return true, ""
1042+
return true, "", nil
1043+
}
1044+
1045+
func getRawEmbeddedClusterConfigForVersion(versionStore store.VersionStore, appID string, sequence int64) ([]byte, error) {
1046+
currentConf, err := versionStore.GetEmbeddedClusterConfigForVersion(appID, sequence)
1047+
if err != nil {
1048+
return nil, errors.Wrap(err, "failed to get embedded cluster config")
1049+
}
1050+
b, err := json.Marshal(currentConf)
1051+
if err != nil {
1052+
return nil, errors.Wrap(err, "failed to marshal embedded cluster config")
1053+
}
1054+
return b, nil
10061055
}
10071056

10081057
func getReleaseNotes(appID string, parentSequence int64) (string, error) {

0 commit comments

Comments
 (0)