1
1
package kotsstore
2
2
3
3
import (
4
+ "bytes"
4
5
"encoding/base64"
6
+ "encoding/json"
5
7
"fmt"
6
8
"strings"
7
9
"time"
@@ -13,8 +15,10 @@ import (
13
15
"github.com/replicatedhq/kots/pkg/kotsutil"
14
16
"github.com/replicatedhq/kots/pkg/logger"
15
17
"github.com/replicatedhq/kots/pkg/persistence"
18
+ "github.com/replicatedhq/kots/pkg/store"
16
19
"github.com/replicatedhq/kots/pkg/store/types"
17
20
"github.com/replicatedhq/kots/pkg/tasks"
21
+ "github.com/replicatedhq/kots/pkg/util"
18
22
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
19
23
"github.com/rqlite/gorqlite"
20
24
)
@@ -423,7 +427,10 @@ func (s *KOTSStore) GetDownstreamVersions(appID string, clusterID string, downlo
423
427
if err := s .AddDownstreamVersionDetails (appID , clusterID , v , false ); err != nil {
424
428
return nil , errors .Wrap (err , "failed to add details to latest downloaded version" )
425
429
}
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
+ }
427
434
break
428
435
}
429
436
@@ -676,7 +683,10 @@ func (s *KOTSStore) AddDownstreamVersionsDetails(appID string, clusterID string,
676
683
}
677
684
678
685
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
+ }
680
690
}
681
691
}
682
692
@@ -866,28 +876,32 @@ func isSameUpstreamRelease(v1 *downstreamtypes.DownstreamVersion, v2 *downstream
866
876
return v1 .Semver .EQ (* v2 .Semver )
867
877
}
868
878
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 ) {
870
884
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
872
886
}
873
887
874
888
if version .Status == types .VersionPendingDownload {
875
- return false , "Version is pending download."
889
+ return false , "Version is pending download." , nil
876
890
}
877
891
878
892
if version .Status == types .VersionPendingConfig {
879
- return false , "Version is pending configuration."
893
+ return false , "Version is pending configuration." , nil
880
894
}
881
895
882
896
if appVersions .CurrentVersion == nil {
883
897
// 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
885
899
}
886
900
887
901
if version .Sequence == appVersions .CurrentVersion .Sequence {
888
902
// version is currently deployed, so previous required versions should've already been deployed.
889
903
// 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
891
905
}
892
906
893
907
// rollback support is determined across all versions from all channels
@@ -906,18 +920,41 @@ func isAppVersionDeployable(version *downstreamtypes.DownstreamVersion, appVersi
906
920
break
907
921
}
908
922
}
923
+
924
+ // This is a past version
909
925
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.
912
928
for _ , v := range appVersions .AllVersions {
929
+ // Find the first version that is not pending download. This will be the latest
930
+ // version.
913
931
if v .Status == types .VersionPendingDownload {
914
932
continue
915
933
}
916
934
if v .KOTSKinds == nil || ! v .KOTSKinds .KotsApplication .Spec .AllowRollback {
917
- return false , "Rollback is not supported."
935
+ return false , "Rollback is not supported." , nil
918
936
}
919
937
break
920
938
}
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
+ }
921
958
}
922
959
923
960
// 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
951
988
952
989
if deployedVersionIndex == - 1 {
953
990
// the deployed version is from a different channel
954
- return true , ""
991
+ return true , "" , nil
955
992
}
956
993
957
994
// find required versions between the deployed version and the desired version
@@ -969,7 +1006,7 @@ ALL_VERSIONS_LOOP:
969
1006
// this is a past version
970
1007
// >= because if the deployed version is required, rolling back isn't allowed
971
1008
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
973
1010
}
974
1011
continue
975
1012
}
@@ -997,12 +1034,24 @@ ALL_VERSIONS_LOOP:
997
1034
}
998
1035
versionLabelsStr := strings .Join (versionLabels , ", " )
999
1036
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
1001
1038
}
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
1003
1040
}
1004
1041
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
1006
1055
}
1007
1056
1008
1057
func getReleaseNotes (appID string , parentSequence int64 ) (string , error ) {
0 commit comments