Skip to content
Open
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
8 changes: 6 additions & 2 deletions pkg/action/release_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,11 +498,13 @@ func releaseInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc, re
}
}

releaseIsUpToDate, err := release.IsReleaseUpToDate(prevRelease, newRelease)
result, err := release.IsReleaseUpToDate(prevRelease, newRelease)
if err != nil {
return fmt.Errorf("check if release is up to date: %w", err)
}

releaseIsUpToDate := result.UpToDate

installPlanIsUseless := lo.NoneBy(installPlan.Operations(), func(op *plan.Operation) bool {
switch op.Category {
case plan.OperationCategoryResource, plan.OperationCategoryTrack:
Expand Down Expand Up @@ -910,11 +912,13 @@ func runRollbackPlan(ctx context.Context, releaseName, releaseNamespace string,
}
}

releaseIsUpToDate, err := release.IsReleaseUpToDate(failedRelease, newRelease)
releaseUpToDateResult, err := release.IsReleaseUpToDate(failedRelease, newRelease)
if err != nil {
return nil, nonCritErrs, critErrs.Add(fmt.Errorf("check if release is up to date: %w", err))
}

releaseIsUpToDate := releaseUpToDateResult.UpToDate

planIsUseless := lo.NoneBy(rollbackPlan.Operations(), func(op *plan.Operation) bool {
switch op.Category {
case plan.OperationCategoryResource, plan.OperationCategoryTrack:
Expand Down
15 changes: 13 additions & 2 deletions pkg/action/release_plan_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,11 +403,13 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
}
}

releaseIsUpToDate, err := release.IsReleaseUpToDate(prevRelease, newRelease)
result, err := release.IsReleaseUpToDate(prevRelease, newRelease)
if err != nil {
return fmt.Errorf("check if release is up to date: %w", err)
}

releaseIsUpToDate := result.UpToDate

installPlanIsUseless := lo.NoneBy(installPlan.Operations(), func(op *plan.Operation) bool {
switch op.Category {
case plan.OperationCategoryResource, plan.OperationCategoryTrack:
Expand All @@ -427,7 +429,7 @@ func releasePlanInstall(ctx context.Context, ctxCancelFn context.CancelCauseFunc
if releaseIsUpToDate && installPlanIsUseless {
log.Default.Info(ctx, color.Style{color.Bold, color.Green}.Render(fmt.Sprintf("No changes planned for release %q (namespace: %q)", releaseName, releaseNamespace)))
} else if installPlanIsUseless || len(changes) == 0 {
log.Default.Info(ctx, color.Style{color.Bold, color.Yellow}.Render(fmt.Sprintf("No resource changes planned, but still must install release %q (namespace: %q)", releaseName, releaseNamespace)))
log.Default.Info(ctx, color.Style{color.Bold, color.Yellow}.Render(releaseMustInstallMessage(releaseName, releaseNamespace, result.Reason)))
}

if err := logPlannedChanges(ctx, releaseName, releaseNamespace, changes, opts.ResourceDiffOptions); err != nil {
Expand Down Expand Up @@ -593,3 +595,12 @@ func logSummaryLine(ctx context.Context, changes []*plan.ResourceChange, changeT
log.Default.Info(ctx, "- %s: %d resources", filteredChanges[0].TypeStyle.Render(changeType), len(filteredChanges))
}
}

func releaseMustInstallMessage(releaseName, releaseNamespace string, reason release.ReleaseOutdatedReason) string {
msg := fmt.Sprintf("No resource changes planned, but still must install release %q (namespace: %q)", releaseName, releaseNamespace)
if reason != release.ReleaseOutdatedReasonNone {
msg += " because " + string(reason)
}

return msg
}
48 changes: 48 additions & 0 deletions pkg/action/release_plan_install_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package action //nolint:testpackage

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/werf/nelm/pkg/release"
)

func TestReleaseMustInstallMessage(t *testing.T) {
tests := []struct {
name string
releaseName string
releaseNamespace string
reason release.ReleaseOutdatedReason
want string
}{
{
name: "no-reason",
releaseName: "myrelease",
releaseNamespace: "mynamespace",
reason: release.ReleaseOutdatedReasonNone,
want: `No resource changes planned, but still must install release "myrelease" (namespace: "mynamespace")`,
},
{
name: "values-changed",
releaseName: "myrelease",
releaseNamespace: "mynamespace",
reason: release.ReleaseOutdatedReasonValuesChanged,
want: `No resource changes planned, but still must install release "myrelease" (namespace: "mynamespace") because ` + string(release.ReleaseOutdatedReasonValuesChanged),
},
{
name: "notes-changed",
releaseName: "myrelease",
releaseNamespace: "mynamespace",
reason: release.ReleaseOutdatedReasonNotesChanged,
want: `No resource changes planned, but still must install release "myrelease" (namespace: "mynamespace") because ` + string(release.ReleaseOutdatedReasonNotesChanged),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := releaseMustInstallMessage(tt.releaseName, tt.releaseNamespace, tt.reason)
assert.Equal(t, tt.want, got)
})
}
}
4 changes: 3 additions & 1 deletion pkg/action/release_rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,11 +337,13 @@ func releaseRollback(ctx context.Context, ctxCancelFn context.CancelCauseFunc, r
}
}

releaseIsUpToDate, err := release.IsReleaseUpToDate(prevRelease, newRelease)
result, err := release.IsReleaseUpToDate(prevRelease, newRelease)
if err != nil {
return fmt.Errorf("check if release is up to date: %w", err)
}

releaseIsUpToDate := result.UpToDate

installPlanIsUseless := lo.NoneBy(installPlan.Operations(), func(op *plan.Operation) bool {
switch op.Category {
case plan.OperationCategoryResource, plan.OperationCategoryTrack:
Expand Down
57 changes: 40 additions & 17 deletions pkg/release/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,59 +22,82 @@ import (
"github.com/werf/nelm/pkg/resource/spec"
)

const (
ReleaseOutdatedReasonNone ReleaseOutdatedReason = ""
ReleaseOutdatedReasonNoPreviousRelease ReleaseOutdatedReason = "there is no previously deployed release"
ReleaseOutdatedReasonReleaseStatusNotDeployed ReleaseOutdatedReason = "the previously deployed release was not successful"
ReleaseOutdatedReasonNotesChanged ReleaseOutdatedReason = "the release notes changed"
ReleaseOutdatedReasonValuesChanged ReleaseOutdatedReason = "the release values changed"
ReleaseOutdatedReasonHooksChanged ReleaseOutdatedReason = "the release hooks changed"
ReleaseOutdatedReasonManifestsChanged ReleaseOutdatedReason = "the release manifests changed"
)

type ReleaseOutdatedReason string

type ReleaseOptions struct {
InfoAnnotations map[string]string
Labels map[string]string
Notes string
}

type IsReleaseUpToDateResult struct {
Reason ReleaseOutdatedReason
UpToDate bool
}

// Check if the new Release is up-to-date compared to the old Release. It doesn't check any
// resources of the release in the cluster, just compares Release objects.
func IsReleaseUpToDate(oldRel, newRel *helmrelease.Release) (bool, error) {
func IsReleaseUpToDate(oldRel, newRel *helmrelease.Release) (IsReleaseUpToDateResult, error) {
if oldRel == nil {
return false, nil
return IsReleaseUpToDateResult{Reason: ReleaseOutdatedReasonNoPreviousRelease}, nil
}

cmpOpts := cmp.Options{
cmpopts.EquateEmpty(),
}

if oldRel.Info.Status != helmrelease.StatusDeployed ||
oldRel.Info.Notes != newRel.Info.Notes ||
!cmp.Equal(oldRel.Config, newRel.Config, cmpOpts) {
return false, nil
if oldRel.Info.Status != helmrelease.StatusDeployed {
return IsReleaseUpToDateResult{Reason: ReleaseOutdatedReasonReleaseStatusNotDeployed}, nil
}

if oldRel.Info.Notes != newRel.Info.Notes {
return IsReleaseUpToDateResult{Reason: ReleaseOutdatedReasonNotesChanged}, nil
}

if !cmp.Equal(oldRel.Config, newRel.Config, cmpOpts) {
return IsReleaseUpToDateResult{Reason: ReleaseOutdatedReasonValuesChanged}, nil
}

oldHookResourcesHash := fnv.New32a()
for _, oldHook := range oldRel.Hooks {
obj, _, err := scheme.Codecs.UniversalDecoder().Decode([]byte(oldHook.Manifest), nil, &unstructured.Unstructured{})
if err != nil {
return false, fmt.Errorf("decode old hook: %w", err)
return IsReleaseUpToDateResult{}, fmt.Errorf("decode old hook: %w", err)
}

unstruct := cleanUnstruct(obj.(*unstructured.Unstructured))

if err := writeUnstructHash(unstruct, oldHookResourcesHash); err != nil {
return false, fmt.Errorf("write old hook hash: %w", err)
return IsReleaseUpToDateResult{}, fmt.Errorf("write old hook hash: %w", err)
}
}

newHookResourcesHash := fnv.New32a()
for _, newHook := range newRel.Hooks {
obj, _, err := scheme.Codecs.UniversalDecoder().Decode([]byte(newHook.Manifest), nil, &unstructured.Unstructured{})
if err != nil {
return false, fmt.Errorf("decode new hook: %w", err)
return IsReleaseUpToDateResult{}, fmt.Errorf("decode new hook: %w", err)
}

unstruct := cleanUnstruct(obj.(*unstructured.Unstructured))

if err := writeUnstructHash(unstruct, newHookResourcesHash); err != nil {
return false, fmt.Errorf("write new hook hash: %w", err)
return IsReleaseUpToDateResult{}, fmt.Errorf("write new hook hash: %w", err)
}
}

if oldHookResourcesHash.Sum32() != newHookResourcesHash.Sum32() {
return false, nil
return IsReleaseUpToDateResult{Reason: ReleaseOutdatedReasonHooksChanged}, nil
}

oldRelManifests := releaseutil.SplitManifestsToSlice(oldRel.Manifest)
Expand All @@ -83,13 +106,13 @@ func IsReleaseUpToDate(oldRel, newRel *helmrelease.Release) (bool, error) {
for _, manifest := range oldRelManifests {
obj, _, err := scheme.Codecs.UniversalDecoder().Decode([]byte(manifest), nil, &unstructured.Unstructured{})
if err != nil {
return false, fmt.Errorf("decode old regular resource: %w", err)
return IsReleaseUpToDateResult{}, fmt.Errorf("decode old regular resource: %w", err)
}

unstruct := cleanUnstruct(obj.(*unstructured.Unstructured))

if err := writeUnstructHash(unstruct, oldRegularResourcesHash); err != nil {
return false, fmt.Errorf("write old regular resource hash: %w", err)
return IsReleaseUpToDateResult{}, fmt.Errorf("write old regular resource hash: %w", err)
}
}

Expand All @@ -99,21 +122,21 @@ func IsReleaseUpToDate(oldRel, newRel *helmrelease.Release) (bool, error) {
for _, manifest := range newRelManifests {
obj, _, err := scheme.Codecs.UniversalDecoder().Decode([]byte(manifest), nil, &unstructured.Unstructured{})
if err != nil {
return false, fmt.Errorf("decode new regular resource: %w", err)
return IsReleaseUpToDateResult{}, fmt.Errorf("decode new regular resource: %w", err)
}

unstruct := cleanUnstruct(obj.(*unstructured.Unstructured))

if err := writeUnstructHash(unstruct, newRegularResourcesHash); err != nil {
return false, fmt.Errorf("write new regular resource hash: %w", err)
return IsReleaseUpToDateResult{}, fmt.Errorf("write new regular resource hash: %w", err)
}
}

if oldRegularResourcesHash.Sum32() != newRegularResourcesHash.Sum32() {
return false, nil
return IsReleaseUpToDateResult{Reason: ReleaseOutdatedReasonManifestsChanged}, nil
}

return true, nil
return IsReleaseUpToDateResult{UpToDate: true}, nil
}

// Construct Helm release.
Expand Down
Loading