Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions pkg/handlers/upgrade_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/replicatedhq/kots/pkg/replicatedapp"
"github.com/replicatedhq/kots/pkg/reporting"
"github.com/replicatedhq/kots/pkg/store"
storetypes "github.com/replicatedhq/kots/pkg/store/types"
"github.com/replicatedhq/kots/pkg/tasks"
"github.com/replicatedhq/kots/pkg/update"
"github.com/replicatedhq/kots/pkg/upgradeservice"
Expand Down Expand Up @@ -125,6 +126,20 @@ func canStartUpgradeService(a *apptypes.App, r StartUpgradeServiceRequest) (bool
if r.ChannelID != airgap.Spec.ChannelID {
return false, "channel mismatch", nil
}
downstreams, err := store.GetStore().ListDownstreamsForApp(a.ID)
if err != nil {
return false, "", errors.Wrap(err, "failed to list downstreams for app")
}
if len(downstreams) > 0 {
currentVersion, err := store.GetStore().GetCurrentDownstreamVersion(a.ID, downstreams[0].ClusterID)
if err != nil {
return false, "", errors.Wrap(err, "failed to get current downstream version")
}
if currentVersion != nil && currentVersion.Status == storetypes.VersionFailed && currentVersion.IsRequired {
return false, fmt.Sprintf("Cannot deploy this version because required version %s failed to deploy. Please retry deploying version %s or check for new updates.", currentVersion.VersionLabel, currentVersion.VersionLabel), nil
}
}

currentECVersion := util.EmbeddedClusterVersion()
isDeployable, nonDeployableCause, err := update.IsAirgapUpdateDeployable(a, airgap, currentECVersion)
if err != nil {
Expand Down
47 changes: 47 additions & 0 deletions pkg/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/reporting"
storepkg "github.com/replicatedhq/kots/pkg/store"
storetypes "github.com/replicatedhq/kots/pkg/store/types"
"github.com/replicatedhq/kots/pkg/update/types"
upstreampkg "github.com/replicatedhq/kots/pkg/upstream"
upstreamtypes "github.com/replicatedhq/kots/pkg/upstream/types"
Expand Down Expand Up @@ -62,6 +63,29 @@ func GetAvailableUpdates(kotsStore storepkg.Store, app *apptypes.App, license *l

availableUpdates := getAvailableUpdates(updates.Updates, currentECVersion)

// additional deployable checks against current version
downstreams, err := kotsStore.ListDownstreamsForApp(app.ID)
if err != nil {
return nil, errors.Wrap(err, "failed to list downstreams for app")
}
if len(downstreams) == 0 {
return availableUpdates, nil
}

currentVersion, err := kotsStore.GetCurrentDownstreamVersion(app.ID, downstreams[0].ClusterID)
if err != nil {
return nil, errors.Wrap(err, "failed to get current downstream version")
}
if currentVersion != nil &&
currentVersion.Status == storetypes.VersionFailed &&
currentVersion.IsRequired {
// none of the upstream available updates are deployable if current version is required and failed to deploy
for i := range availableUpdates {
availableUpdates[i].IsDeployable = false
availableUpdates[i].NonDeployableCause = fmt.Sprintf("Cannot deploy this version because required version %s failed to deploy. Please retry deploying version %s or check for new updates.", currentVersion.VersionLabel, currentVersion.VersionLabel)
}
}

return availableUpdates, nil
}

Expand Down Expand Up @@ -114,6 +138,29 @@ func GetAvailableAirgapUpdates(app *apptypes.App, license *licensewrapper.Licens
return nil, errors.Wrap(err, "failed to walk airgap root dir")
}

// additional deployable checks against current version
downstreams, err := storepkg.GetStore().ListDownstreamsForApp(app.ID)
if err != nil {
return nil, errors.Wrap(err, "failed to list downstreams for app")
}
if len(downstreams) == 0 {
return updates, nil
}

currentVersion, err := storepkg.GetStore().GetCurrentDownstreamVersion(app.ID, downstreams[0].ClusterID)
if err != nil {
return nil, errors.Wrap(err, "failed to get current downstream version")
}
if currentVersion != nil &&
currentVersion.Status == storetypes.VersionFailed &&
currentVersion.IsRequired {
// none of the airgap available updates are deployable if current version is required and failed to deploy
for i := range updates {
updates[i].IsDeployable = false
updates[i].NonDeployableCause = fmt.Sprintf("Cannot deploy this version because required version %s failed to deploy. Please retry deploying version %s or check for new updates.", currentVersion.VersionLabel, currentVersion.VersionLabel)
}
}

return updates, nil
}

Expand Down
153 changes: 153 additions & 0 deletions pkg/update/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (

"github.com/golang/mock/gomock"
apptypes "github.com/replicatedhq/kots/pkg/app/types"
downstreamtypes "github.com/replicatedhq/kots/pkg/api/downstream/types"
storepkg "github.com/replicatedhq/kots/pkg/store"
mock_store "github.com/replicatedhq/kots/pkg/store/mock"
storetypes "github.com/replicatedhq/kots/pkg/store/types"
"github.com/replicatedhq/kots/pkg/update/types"
"github.com/replicatedhq/kots/pkg/upstream"
upstreamtypes "github.com/replicatedhq/kots/pkg/upstream/types"
Expand Down Expand Up @@ -62,6 +64,7 @@ func TestGetAvailableUpdates(t *testing.T) {
t.Setenv("USE_MOCK_REPORTING", "1")
args.license.Spec.Endpoint = licenseEndpoint
mockStore.EXPECT().GetCurrentUpdateCursor(args.app.ID, args.license.Spec.ChannelID).Return("1", nil)
mockStore.EXPECT().ListDownstreamsForApp(args.app.ID).Return(nil, nil)
},
want: []types.AvailableUpdate{},
wantErr: false,
Expand Down Expand Up @@ -128,6 +131,7 @@ func TestGetAvailableUpdates(t *testing.T) {
t.Setenv("EMBEDDED_CLUSTER_VERSION", "2.4.0+k8s-1.30")
args.license.Spec.Endpoint = licenseEndpoint
mockStore.EXPECT().GetCurrentUpdateCursor(args.app.ID, args.license.Spec.ChannelID).Return("1", nil)
mockStore.EXPECT().ListDownstreamsForApp(args.app.ID).Return(nil, nil)
},
want: []types.AvailableUpdate{
{
Expand Down Expand Up @@ -263,6 +267,7 @@ func TestGetAvailableUpdates(t *testing.T) {
t.Setenv("USE_MOCK_REPORTING", "1")
args.license.Spec.Endpoint = licenseEndpoint
mockStore.EXPECT().GetCurrentUpdateCursor(args.app.ID, args.license.Spec.Channels[1].ChannelID).Return("1", nil)
mockStore.EXPECT().ListDownstreamsForApp(args.app.ID).Return(nil, nil)
},
want: []types.AvailableUpdate{
{
Expand All @@ -278,6 +283,154 @@ func TestGetAvailableUpdates(t *testing.T) {
wantErr: false,
expectedSelectedChannelId: "channel-id2",
},
{
name: "current version is failed and required - all updates blocked",
args: args{
kotsStore: mockStore,
app: &apptypes.App{
ID: "app-id",
SelectedChannelID: "channel-id",
},
license: &kotsv1beta1.License{
Spec: kotsv1beta1.LicenseSpec{
ChannelID: "channel-id",
ChannelName: "channel-name",
AppSlug: "app-slug",
LicenseID: "license-id",
},
},
},
perChannelReleases: map[string][]upstream.ChannelRelease{
"channel-id": {
{
ChannelSequence: 2,
ReleaseSequence: 2,
VersionLabel: "0.0.2",
IsRequired: false,
CreatedAt: testTime.Format(time.RFC3339),
ReleaseNotes: "release notes",
},
{
ChannelSequence: 1,
ReleaseSequence: 1,
VersionLabel: "0.0.1",
IsRequired: false,
CreatedAt: testTime.Format(time.RFC3339),
ReleaseNotes: "release notes",
},
},
},
setup: func(t *testing.T, args args, licenseEndpoint string) {
t.Setenv("USE_MOCK_REPORTING", "1")
args.license.Spec.Endpoint = licenseEndpoint
mockStore.EXPECT().GetCurrentUpdateCursor(args.app.ID, args.license.Spec.ChannelID).Return("0", nil)
mockStore.EXPECT().ListDownstreamsForApp(args.app.ID).Return([]downstreamtypes.Downstream{
{ClusterID: "cluster-id"},
}, nil)
mockStore.EXPECT().GetCurrentDownstreamVersion(args.app.ID, "cluster-id").Return(&downstreamtypes.DownstreamVersion{
VersionLabel: "0.0.0",
Status: storetypes.VersionFailed,
IsRequired: true,
}, nil)
},
want: []types.AvailableUpdate{
{
VersionLabel: "0.0.2",
UpdateCursor: "2",
ChannelID: "channel-id",
IsRequired: false,
UpstreamReleasedAt: &testTime,
ReleaseNotes: "release notes",
IsDeployable: false,
NonDeployableCause: "Cannot deploy this version because required version 0.0.0 failed to deploy. Please retry deploying version 0.0.0 or check for new updates.",
},
{
VersionLabel: "0.0.1",
UpdateCursor: "1",
ChannelID: "channel-id",
IsRequired: false,
UpstreamReleasedAt: &testTime,
ReleaseNotes: "release notes",
IsDeployable: false,
NonDeployableCause: "Cannot deploy this version because required version 0.0.0 failed to deploy. Please retry deploying version 0.0.0 or check for new updates.",
},
},
wantErr: false,
expectedSelectedChannelId: "channel-id",
},
{
name: "current version is failed but not required - updates unaffected",
args: args{
kotsStore: mockStore,
app: &apptypes.App{
ID: "app-id",
SelectedChannelID: "channel-id",
},
license: &kotsv1beta1.License{
Spec: kotsv1beta1.LicenseSpec{
ChannelID: "channel-id",
ChannelName: "channel-name",
AppSlug: "app-slug",
LicenseID: "license-id",
},
},
},
perChannelReleases: map[string][]upstream.ChannelRelease{
"channel-id": {
{
ChannelSequence: 2,
ReleaseSequence: 2,
VersionLabel: "0.0.2",
IsRequired: false,
CreatedAt: testTime.Format(time.RFC3339),
ReleaseNotes: "release notes",
},
{
ChannelSequence: 1,
ReleaseSequence: 1,
VersionLabel: "0.0.1",
IsRequired: false,
CreatedAt: testTime.Format(time.RFC3339),
ReleaseNotes: "release notes",
},
},
},
setup: func(t *testing.T, args args, licenseEndpoint string) {
t.Setenv("USE_MOCK_REPORTING", "1")
args.license.Spec.Endpoint = licenseEndpoint
mockStore.EXPECT().GetCurrentUpdateCursor(args.app.ID, args.license.Spec.ChannelID).Return("0", nil)
mockStore.EXPECT().ListDownstreamsForApp(args.app.ID).Return([]downstreamtypes.Downstream{
{ClusterID: "cluster-id"},
}, nil)
mockStore.EXPECT().GetCurrentDownstreamVersion(args.app.ID, "cluster-id").Return(&downstreamtypes.DownstreamVersion{
VersionLabel: "0.0.0",
Status: storetypes.VersionFailed,
IsRequired: false,
}, nil)
},
want: []types.AvailableUpdate{
{
VersionLabel: "0.0.2",
UpdateCursor: "2",
ChannelID: "channel-id",
IsRequired: false,
UpstreamReleasedAt: &testTime,
ReleaseNotes: "release notes",
IsDeployable: true,
},
{
VersionLabel: "0.0.1",
UpdateCursor: "1",
ChannelID: "channel-id",
IsRequired: false,
UpstreamReleasedAt: &testTime,
ReleaseNotes: "release notes",
IsDeployable: true,
},
},
wantErr: false,
expectedSelectedChannelId: "channel-id",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading