diff --git a/components/automate-cli/cmd/chef-automate/upgrade.go b/components/automate-cli/cmd/chef-automate/upgrade.go index e8fff555662..21058f36789 100644 --- a/components/automate-cli/cmd/chef-automate/upgrade.go +++ b/components/automate-cli/cmd/chef-automate/upgrade.go @@ -249,12 +249,12 @@ func statusUpgradeCmd(cmd *cobra.Command, args []string) error { case api.UpgradeStatusResponse_IDLE: switch { //Todo(milestone) - update the comparison logic of current version and latest available version - case resp.CurrentVersion != "" && resp.CurrentVersion < resp.LatestAvailableVersion: - writer.Printf("Automate is out-of-date (current version: %s; latest available: %s; airgapped: %v)\n", - resp.CurrentVersion, resp.LatestAvailableVersion, resp.IsAirgapped) case resp.CurrentVersion != "": if resp.IsAirgapped { writer.Printf("Automate is up-to-date with airgap bundle (%s)\n", resp.CurrentVersion) + } else if resp.CurrentVersion < resp.LatestAvailableVersion { + writer.Printf("Automate is out-of-date (current version: %s; latest available: %s; airgapped: %v)\n", + resp.CurrentVersion, resp.LatestAvailableVersion, resp.IsAirgapped) } else { writer.Printf("Automate is up-to-date (%s)\n", resp.CurrentVersion) } diff --git a/components/automate-deployment/pkg/server/server.go b/components/automate-deployment/pkg/server/server.go index 32630d672ba..9a4b0fdd142 100644 --- a/components/automate-deployment/pkg/server/server.go +++ b/components/automate-deployment/pkg/server/server.go @@ -1864,9 +1864,45 @@ func (s *server) IsValidUpgrade(ctx context.Context, req *api.UpgradeRequest) (* nextManifestVersion = compVersion } } + } else { + m, err := s.releaseManifestProvider.RefreshManifest(ctx, channel) + if err != nil { + return nil, err + } + nextManifestVersion = m.Version() + + //compare minimum compatible version with current version, i.e current version should be greater than or equal than min compatible version + minCompVersion := m.MinCompatibleVer + if minCompVersion == "" { + return nil, status.Error(codes.InvalidArgument, "mandatory minimum compatable version is missing") + } else if !isCompatibleForAirgap(currentRelease, minCompVersion) { + return nil, status.Errorf(codes.OutOfRange, "the version specified %q is not compatible for the current version %q. The compatible version is %q", nextManifestVersion, currentRelease, minCompVersion) + } + + //check for upgrade or degrade, we should not allow degrading + if isDegrade(currentRelease, nextManifestVersion) { + return nil, status.Errorf(codes.OutOfRange, "the version specified %q is not compatible for the current version %q", nextManifestVersion, currentRelease) + } + + isActualMajorUpgrade := isMajorUpgrade(currentRelease, nextManifestVersion) + + //check the upgrade is major or not, and if the upgrade is minor/patch, user should not provide --major flag + if !isActualMajorUpgrade && req.IsMajorUpgrade { + return nil, status.Errorf(codes.InvalidArgument, "please use `chef-automate upgrade run --airgap-bundle` to further upgrade") + } + + //check the upgrade is major or not, and if the upgrade is major user should provide --major flag. + if isActualMajorUpgrade && !req.IsMajorUpgrade { + return nil, status.Errorf(codes.InvalidArgument, "please use `chef-automate upgrade run --major --airgap-bundle` to further upgrade") + } + } if req.Version != "" { + if airgap.AirgapInUse() { + return nil, status.Errorf(codes.InvalidArgument, "specifying a version is not allowed in airgap mode, please use `chef-automate upgrade run --airgap-bundle`") + } + if nextManifestVersion != req.Version && !isCompatible(currentRelease, req.Version, nextManifestVersion) { return nil, status.Errorf(codes.OutOfRange, "the version specified %q is not compatible for the current version %q", req.Version, currentRelease) } @@ -1888,65 +1924,13 @@ func (s *server) IsValidUpgrade(ctx context.Context, req *api.UpgradeRequest) (* // Upgrade requests the deployment-service pulls down the latest manifest and applies it func (s *server) Upgrade(ctx context.Context, req *api.UpgradeRequest) (*api.UpgradeResponse, error) { - if !s.HasConfiguredDeployment() { - return nil, ErrorNotConfigured - } - - var currentRelease = "" - s.deployment.Lock() - if s.deployment.CurrentReleaseManifest != nil { - currentRelease = s.deployment.CurrentReleaseManifest.Version() - } - channel := s.deployment.Channel() - s.deployment.Unlock() - - var m *manifest.A2 - var err error - - nextManifestVersion := currentRelease - if !airgap.AirgapInUse() { //internet connected machine - isMinorAvailable, isMajorAvailable, compVersion, err := s.releaseManifestProvider.GetCompatibleVersion(ctx, channel, currentRelease) - if err != nil { - return nil, err - } - - if !req.IsMajorUpgrade { //normal upgrade - if isMinorAvailable { - nextManifestVersion = compVersion - } else if isMajorAvailable { - return nil, status.Errorf(codes.InvalidArgument, "please use `chef-automate upgrade run --major` to further upgrade") - } - } else { //major upgrade - if isMinorAvailable { - return nil, status.Errorf(codes.InvalidArgument, "minor/patch version is available, please use `chef-automate upgrade run`") - } else if isMajorAvailable { - nextManifestVersion = compVersion - } - } - } - - if req.Version != "" { - if airgap.AirgapInUse() { - return nil, status.Errorf(codes.InvalidArgument, "specifying a version is not allowed in airgap mode, please use `chef-automate upgrade run --airgap-bundle`") - } - - if !isCompatible(currentRelease, req.Version, nextManifestVersion) { - return nil, status.Errorf(codes.OutOfRange, "the version specified %q is not compatible for the current version %q", req.Version, currentRelease) - } - nextManifestVersion = req.Version - - // TODO(ssd) 2018-09-13: We are not currently - // requiring that the version passed is actually in - // the channel they have configured. Should we? + validatedResp, err := s.IsValidUpgrade(ctx, req) + if err != nil { + return nil, err } - m, err = s.releaseManifestProvider.GetManifest(ctx, nextManifestVersion) - /*} else { - //m, err = s.releaseManifestProvider.RefreshManifest(ctx, channel) - //Todo(milestone) in airgap, get the manifest and perform the compatibility check. - }*/ - //Todo(milestone) incase of airgap find out the upgrade is major or minor + m, err := s.releaseManifestProvider.GetManifest(ctx, validatedResp.TargetVersion) _, ok := err.(*manifest.NoSuchManifestError) if ok { @@ -1980,12 +1964,66 @@ func (s *server) Upgrade(ctx context.Context, req *api.UpgradeRequest) (*api.Upg } return &api.UpgradeResponse{ - PreviousVersion: currentRelease, + PreviousVersion: validatedResp.CurrentVersion, NextVersion: m.Version(), TaskId: task.ID.String(), }, nil } +//isDegrade return true, if the v2 is previous version of v1 +func isDegrade(v1, v2 string) bool { + _, isV1Sem := manifest.IsSemVersionFmt(v1) + _, isV2Sem := manifest.IsSemVersionFmt(v2) + + if !isV1Sem && !isV2Sem && v1 > v2 { + return true + } + if isV1Sem && isV2Sem && !manifest.IsCompareSemVersions(v1, v2) { + return true + } + + if isV1Sem && !isV2Sem { + return true + } + return false +} + +// IsMajorUpgrade returns true if the v2 is next major upgrade of v1 else return false +func isMajorUpgrade(v1, v2 string) bool { + v1Major, isV1Sem := manifest.IsSemVersionFmt(v1) + v2Major, isV2Sem := manifest.IsSemVersionFmt(v2) + + if !isV1Sem && isV2Sem { + return true + } + + if isV1Sem && isV2Sem && v1Major != v2Major { + return true + } + + return false +} + +//isCompatibleForAirgap will return true if the current version is greater than or equal to min compatible version +func isCompatibleForAirgap(current, minCompatibleVer string) bool { + _, isCurrentSem := manifest.IsSemVersionFmt(current) + _, isMinCompSem := manifest.IsSemVersionFmt(minCompatibleVer) + + if !isCurrentSem && !isMinCompSem { + return current >= minCompatibleVer + } + + if isCurrentSem && isMinCompSem { + return manifest.IsCompareSemVersions(minCompatibleVer, current) + } + + //if minCompatibleVer is in timestampversion and current version is semantic version - we need to allow the upgrade + if !isMinCompSem && isCurrentSem { + return true + } + return false +} + func isCompatible(current, givenVersion, maxPossibleVersion string) bool { _, isCurrentSem := manifest.IsSemVersionFmt(current) givenMajor, isGivenVSem := manifest.IsSemVersionFmt(givenVersion) diff --git a/components/automate-deployment/pkg/server/server_test.go b/components/automate-deployment/pkg/server/server_test.go index 9f73e1df1a4..83708a7ac41 100644 --- a/components/automate-deployment/pkg/server/server_test.go +++ b/components/automate-deployment/pkg/server/server_test.go @@ -449,3 +449,219 @@ func TestIsCompatibleForConverge(t *testing.T) { }) } } + +func TestIsCompatibleForAirgap(t *testing.T) { + tests := []struct { + name string + currentVersion string + minCompatibleVer string + isCompatible bool + }{ + { + name: "timestampversion,timestampversion_equal_success", + currentVersion: "20220112175624", + minCompatibleVer: "20220112175624", + isCompatible: true, + }, + { + name: "timestampversion,timestampversion_success", + currentVersion: "20220112175624", + minCompatibleVer: "20220112175620", + isCompatible: true, + }, + { + name: "timestampversion,timestampversion_success", + currentVersion: "20220112175624", + minCompatibleVer: "20220112175628", + isCompatible: false, + }, + { + name: "timestampversion,semanticversion", + currentVersion: "20220112175624", + minCompatibleVer: "22.0.1", + isCompatible: false, + }, + { + name: "semanticversion,timestampversion", + currentVersion: "22.0.1", + minCompatibleVer: "20220112175624", + isCompatible: true, + }, + { + name: "semanticversion,semanticversion", + currentVersion: "22.0.1", + minCompatibleVer: "22.0.0", + isCompatible: true, + }, + { + name: "semanticversion,semanticversion_success", + currentVersion: "23.12.14", + minCompatibleVer: "22.10.14", + isCompatible: true, + }, + { + name: "semanticversion,semanticversion_success_1", + currentVersion: "22.10.14", + minCompatibleVer: "22.9.14", + isCompatible: true, + }, + { + name: "semanticversion,semanticversion_success_2", + currentVersion: "22.10.14", + minCompatibleVer: "22.10.8", + isCompatible: true, + }, + { + name: "semanticversion,semanticversion_fail", + currentVersion: "22.10.14", + minCompatibleVer: "23.12.14", + isCompatible: false, + }, + { + name: "semanticversion,semanticversion_fail_1", + currentVersion: "22.10.9", + minCompatibleVer: "22.10.15", + isCompatible: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := isCompatibleForAirgap(tc.currentVersion, tc.minCompatibleVer) + assert.Equal(t, tc.isCompatible, result) + }) + } +} + +func TestIsDegrade(t *testing.T) { + tests := []struct { + name string + v1 string + v2 string + isDegrade bool + }{ + { + name: "timestamp,timestamp_success", + v1: "20220110000009", + v2: "20220110000000", + isDegrade: true, + }, + { + name: "timestamp,timestamp_equal", + v1: "20220110000009", + v2: "20220110000009", + isDegrade: false, + }, + { + name: "timestamp,timestamp_fail", + v1: "20220110000000", + v2: "20220110000009", + isDegrade: false, + }, + { + name: "timestamp,semantic_fail", + v1: "20220110000000", + v2: "22.0.1", + isDegrade: false, + }, + { + name: "semantic,timestamp", + v1: "22.0.1", + v2: "20220110000000", + isDegrade: true, + }, + { + name: "semantic,semantic_patch", + v1: "22.4.10", + v2: "22.4.9", + isDegrade: true, + }, + { + name: "semantic,semantic_patch_fail", + v1: "22.4.9", + v2: "22.4.10", + isDegrade: false, + }, + { + name: "semantic,semantic_minor", + v1: "22.4.10", + v2: "22.3.10", + isDegrade: true, + }, + { + name: "semantic,semantic_minor_fail", + v1: "22.3.10", + v2: "22.4.10", + isDegrade: false, + }, + { + name: "semantic,semantic_major", + v1: "23.3.10", + v2: "22.3.10", + isDegrade: true, + }, + { + name: "semantic,semantic_major_fail", + v1: "22.3.10", + v2: "23.3.10", + isDegrade: false, + }, + { + name: "semantic,semantic_major_equal", + v1: "22.3.10", + v2: "22.3.10", + isDegrade: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := isDegrade(tc.v1, tc.v2) + assert.Equal(t, tc.isDegrade, result) + }) + } +} + +func TestIsMajorUpgrade(t *testing.T) { + tests := []struct { + name string + v1 string + v2 string + isMajorUpgrade bool + }{ + { + name: "timestamp,timestamp", + v1: "20220110000000", + v2: "20220110000009", + isMajorUpgrade: false, + }, + { + name: "timestamp,semantic", + v1: "20220110000000", + v2: "21.2.0", + isMajorUpgrade: true, + }, + { + name: "semantic,timestamp", + v1: "21.2.0", + v2: "20220110000000", + isMajorUpgrade: false, + }, + { + name: "semantic,semantic_success", + v1: "21.2.0", + v2: "22.10.0", + isMajorUpgrade: true, + }, + { + name: "semantic,semantic_fail", + v1: "21.2.0", + v2: "21.8.0", + isMajorUpgrade: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := isMajorUpgrade(tc.v1, tc.v2) + assert.Equal(t, tc.isMajorUpgrade, result) + }) + } +} diff --git a/components/automate-deployment/pkg/server/upgrade_status.go b/components/automate-deployment/pkg/server/upgrade_status.go index 8e80f84b883..927888e48ad 100644 --- a/components/automate-deployment/pkg/server/upgrade_status.go +++ b/components/automate-deployment/pkg/server/upgrade_status.go @@ -31,7 +31,9 @@ func (s *server) UpgradeStatus(ctx context.Context, _ *api.UpgradeStatusRequest) return nil, ErrorNotConfigured } - response := &api.UpgradeStatusResponse{} + response := &api.UpgradeStatusResponse{ + IsAirgapped: airgap.AirgapInUse(), + } if s.deployment.CurrentReleaseManifest == nil { response.CurrentVersion = "" } else { @@ -39,7 +41,7 @@ func (s *server) UpgradeStatus(ctx context.Context, _ *api.UpgradeStatusRequest) } var latestManifest *manifest.A2 - if response.CurrentVersion != "" { + if response.CurrentVersion != "" && !response.IsAirgapped { isMinorAvailable, isMajorAvailable, compVersion, err := s.releaseManifestProvider.GetCompatibleVersion(ctx, s.deployment.Channel(), response.CurrentVersion) if err != nil { return response, err @@ -83,7 +85,6 @@ func (s *server) UpgradeStatus(ctx context.Context, _ *api.UpgradeStatusRequest) } } response.DesiredVersion = desiredManifest.Version() - response.IsAirgapped = airgap.AirgapInUse() response.IsConvergeDisable = (s.convergeLoop != nil && !s.convergeLoop.IsRunning()) || s.convergeDisabled() // TODO(ssd) 2018-02-06: This address now exists in a few