Moved release objects to enable versioning

Signed-off-by: Matt Farina <matt.farina@suse.com>
pull/31372/head
Matt Farina 3 months ago
parent 2247a0074b
commit f80cbe43d0

@ -19,24 +19,24 @@ package output
import (
"github.com/fatih/color"
release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/release/common"
)
// ColorizeStatus returns a colorized version of the status string based on the status value
func ColorizeStatus(status release.Status, noColor bool) string {
func ColorizeStatus(status common.Status, noColor bool) string {
// Disable color if requested
if noColor {
return status.String()
}
switch status {
case release.StatusDeployed:
case common.StatusDeployed:
return color.GreenString(status.String())
case release.StatusFailed:
case common.StatusFailed:
return color.RedString(status.String())
case release.StatusPendingInstall, release.StatusPendingUpgrade, release.StatusPendingRollback, release.StatusUninstalling:
case common.StatusPendingInstall, common.StatusPendingUpgrade, common.StatusPendingRollback, common.StatusUninstalling:
return color.YellowString(status.String())
case release.StatusUnknown:
case common.StatusUnknown:
return color.RedString(status.String())
default:
// For uninstalled, superseded, and any other status

@ -20,63 +20,63 @@ import (
"strings"
"testing"
release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/release/common"
)
func TestColorizeStatus(t *testing.T) {
tests := []struct {
name string
status release.Status
status common.Status
noColor bool
envNoColor string
wantColor bool // whether we expect color codes in output
}{
{
name: "deployed status with color",
status: release.StatusDeployed,
status: common.StatusDeployed,
noColor: false,
envNoColor: "",
wantColor: true,
},
{
name: "deployed status without color flag",
status: release.StatusDeployed,
status: common.StatusDeployed,
noColor: true,
envNoColor: "",
wantColor: false,
},
{
name: "deployed status with NO_COLOR env",
status: release.StatusDeployed,
status: common.StatusDeployed,
noColor: false,
envNoColor: "1",
wantColor: false,
},
{
name: "failed status with color",
status: release.StatusFailed,
status: common.StatusFailed,
noColor: false,
envNoColor: "",
wantColor: true,
},
{
name: "pending install status with color",
status: release.StatusPendingInstall,
status: common.StatusPendingInstall,
noColor: false,
envNoColor: "",
wantColor: true,
},
{
name: "unknown status with color",
status: release.StatusUnknown,
status: common.StatusUnknown,
noColor: false,
envNoColor: "",
wantColor: true,
},
{
name: "superseded status with color",
status: release.StatusSuperseded,
status: common.StatusSuperseded,
noColor: false,
envNoColor: "",
wantColor: false, // superseded doesn't get colored

@ -47,6 +47,7 @@ import (
"helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/postrenderer"
"helm.sh/helm/v4/pkg/registry"
ri "helm.sh/helm/v4/pkg/release"
release "helm.sh/helm/v4/pkg/release/v1"
releaseutil "helm.sh/helm/v4/pkg/release/v1/util"
"helm.sh/helm/v4/pkg/storage"
@ -412,7 +413,7 @@ func (cfg *Configuration) Now() time.Time {
return Timestamper()
}
func (cfg *Configuration) releaseContent(name string, version int) (*release.Release, error) {
func (cfg *Configuration) releaseContent(name string, version int) (ri.Releaser, error) {
if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, fmt.Errorf("releaseContent: Release name is invalid: %s", name)
}

@ -36,6 +36,7 @@ import (
"helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/registry"
rcommon "helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v4/pkg/storage/driver"
@ -249,10 +250,10 @@ func withKube(version string) chartOption {
// releaseStub creates a release stub, complete with the chartStub as its chart.
func releaseStub() *release.Release {
return namedReleaseStub("angry-panda", release.StatusDeployed)
return namedReleaseStub("angry-panda", rcommon.StatusDeployed)
}
func namedReleaseStub(name string, status release.Status) *release.Release {
func namedReleaseStub(name string, status rcommon.Status) *release.Release {
now := time.Now()
return &release.Release{
Name: name,

@ -17,12 +17,15 @@ limitations under the License.
package action
import (
"errors"
"log/slog"
"sort"
"strings"
"time"
ci "helm.sh/helm/v4/pkg/chart"
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/release"
)
// GetMetadata is the action for checking a given release's metadata.
@ -69,24 +72,40 @@ func (g *GetMetadata) Run(name string) (*Metadata, error) {
return nil, err
}
ac, err := ci.NewAccessor(rel.Chart)
rac, err := release.NewAccessor(rel)
if err != nil {
return nil, err
}
ac, err := ci.NewAccessor(rac.Chart())
if err != nil {
return nil, err
}
charti := rac.Chart()
var chrt *chart.Chart
switch c := charti.(type) {
case *chart.Chart:
chrt = c
case chart.Chart:
chrt = &c
default:
return nil, errors.New("invalid chart apiVersion")
}
return &Metadata{
Name: rel.Name,
Chart: rel.Chart.Metadata.Name,
Version: rel.Chart.Metadata.Version,
AppVersion: rel.Chart.Metadata.AppVersion,
Name: rac.Name(),
Chart: chrt.Metadata.Name,
Version: chrt.Metadata.Version,
AppVersion: chrt.Metadata.AppVersion,
Dependencies: ac.MetaDependencies(),
Annotations: rel.Chart.Metadata.Annotations,
Labels: rel.Labels,
Namespace: rel.Namespace,
Revision: rel.Version,
Status: rel.Info.Status.String(),
DeployedAt: rel.Info.LastDeployed.Format(time.RFC3339),
ApplyMethod: rel.ApplyMethod,
Annotations: chrt.Metadata.Annotations,
Labels: rac.Labels(),
Namespace: rac.Namespace(),
Revision: rac.Version(),
Status: rac.Status(),
DeployedAt: rac.DeployedAt().Format(time.RFC3339),
ApplyMethod: rac.ApplyMethod(),
}, nil
}

@ -28,6 +28,7 @@ import (
ci "helm.sh/helm/v4/pkg/chart"
chart "helm.sh/helm/v4/pkg/chart/v2"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -50,7 +51,7 @@ func TestGetMetadata_Run_BasicMetadata(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
LastDeployed: deployedTime,
},
Chart: &chart.Chart{
@ -64,7 +65,8 @@ func TestGetMetadata_Run_BasicMetadata(t *testing.T) {
Namespace: "default",
}
cfg.Releases.Create(rel)
err := cfg.Releases.Create(rel)
require.NoError(t, err)
result, err := client.Run(releaseName)
require.NoError(t, err)
@ -104,7 +106,7 @@ func TestGetMetadata_Run_WithDependencies(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
LastDeployed: deployedTime,
},
Chart: &chart.Chart{
@ -163,7 +165,7 @@ func TestGetMetadata_Run_WithDependenciesAliases(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
LastDeployed: deployedTime,
},
Chart: &chart.Chart{
@ -234,7 +236,7 @@ func TestGetMetadata_Run_WithMixedDependencies(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
LastDeployed: deployedTime,
},
Chart: &chart.Chart{
@ -298,7 +300,7 @@ func TestGetMetadata_Run_WithAnnotations(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
LastDeployed: deployedTime,
},
Chart: &chart.Chart{
@ -337,7 +339,7 @@ func TestGetMetadata_Run_SpecificVersion(t *testing.T) {
rel1 := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusSuperseded,
Status: common.StatusSuperseded,
LastDeployed: deployedTime.Add(-time.Hour),
},
Chart: &chart.Chart{
@ -354,7 +356,7 @@ func TestGetMetadata_Run_SpecificVersion(t *testing.T) {
rel2 := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
LastDeployed: deployedTime,
},
Chart: &chart.Chart{
@ -388,16 +390,16 @@ func TestGetMetadata_Run_DifferentStatuses(t *testing.T) {
testCases := []struct {
name string
status release.Status
status common.Status
expected string
}{
{"deployed", release.StatusDeployed, "deployed"},
{"failed", release.StatusFailed, "failed"},
{"uninstalled", release.StatusUninstalled, "uninstalled"},
{"pending-install", release.StatusPendingInstall, "pending-install"},
{"pending-upgrade", release.StatusPendingUpgrade, "pending-upgrade"},
{"pending-rollback", release.StatusPendingRollback, "pending-rollback"},
{"superseded", release.StatusSuperseded, "superseded"},
{"deployed", common.StatusDeployed, "deployed"},
{"failed", common.StatusFailed, "failed"},
{"uninstalled", common.StatusUninstalled, "uninstalled"},
{"pending-install", common.StatusPendingInstall, "pending-install"},
{"pending-upgrade", common.StatusPendingUpgrade, "pending-upgrade"},
{"pending-rollback", common.StatusPendingRollback, "pending-rollback"},
{"superseded", common.StatusSuperseded, "superseded"},
}
for _, tc := range testCases {
@ -464,7 +466,7 @@ func TestGetMetadata_Run_EmptyAppVersion(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
LastDeployed: deployedTime,
},
Chart: &chart.Chart{
@ -640,7 +642,7 @@ func TestMetadata_FormattedDepNames_WithAliases(t *testing.T) {
func TestGetMetadata_Labels(t *testing.T) {
rel := releaseStub()
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
customLabels := map[string]string{"key1": "value1", "key2": "value2"}
rel.Labels = customLabels

@ -16,7 +16,13 @@ limitations under the License.
package action
import "helm.sh/helm/v4/pkg/chart/common/util"
import (
"fmt"
"helm.sh/helm/v4/pkg/chart/common/util"
release "helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
// GetValues is the action for checking a given release's values.
//
@ -41,7 +47,12 @@ func (g *GetValues) Run(name string) (map[string]interface{}, error) {
return nil, err
}
rel, err := g.cfg.releaseContent(name, g.Version)
reli, err := g.cfg.releaseContent(name, g.Version)
if err != nil {
return nil, err
}
rel, err := releaserToV1Release(reli)
if err != nil {
return nil, err
}
@ -56,3 +67,18 @@ func (g *GetValues) Run(name string) (map[string]interface{}, error) {
}
return rel.Config, nil
}
// releaserToV1Release is a helper function to convert a v1 release passed by interface
// into the type object.
func releaserToV1Release(rel release.Releaser) (*rspb.Release, error) {
switch r := rel.(type) {
case rspb.Release:
return &r, nil
case *rspb.Release:
return r, nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("unsupported release type: %T", rel)
}
}

@ -26,6 +26,7 @@ import (
chart "helm.sh/helm/v4/pkg/chart/v2"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -58,7 +59,7 @@ func TestGetValues_Run_UserConfigOnly(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
@ -112,7 +113,7 @@ func TestGetValues_Run_AllValues(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
@ -147,7 +148,7 @@ func TestGetValues_Run_EmptyValues(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
@ -198,7 +199,7 @@ func TestGetValues_Run_NilConfig(t *testing.T) {
rel := &release.Release{
Name: releaseName,
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{

@ -22,7 +22,7 @@ import (
"fmt"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
release "helm.sh/helm/v4/pkg/release/v1"
release "helm.sh/helm/v4/pkg/release"
)
// History is the action for checking the release's ledger.
@ -46,7 +46,7 @@ func NewHistory(cfg *Configuration) *History {
}
// Run executes 'helm history' against the given release.
func (h *History) Run(name string) ([]*release.Release, error) {
func (h *History) Run(name string) ([]release.Releaser, error) {
if err := h.cfg.KubeClient.IsReachable(); err != nil {
return nil, err
}

@ -33,6 +33,7 @@ import (
"helm.sh/helm/v4/pkg/chart/common"
"helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
rcommon "helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v4/pkg/storage/driver"
@ -187,7 +188,7 @@ func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace str
res, err := instAction.Run(buildChartWithTemplates(templates), vals)
is.NoError(err)
is.Equal(expectedOutput, outBuffer.String())
is.Equal(release.StatusDeployed, res.Info.Status)
is.Equal(rcommon.StatusDeployed, res.Info.Status)
}
func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace string, shouldOutput bool) {
@ -215,7 +216,7 @@ func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace str
is.Error(err)
is.Contains(res.Info.Description, "failed pre-install")
is.Equal(expectedOutput, outBuffer.String())
is.Equal(release.StatusFailed, res.Info.Status)
is.Equal(rcommon.StatusFailed, res.Info.Status)
}
type HookFailedError struct{}

@ -53,6 +53,8 @@ import (
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/postrenderer"
"helm.sh/helm/v4/pkg/registry"
ri "helm.sh/helm/v4/pkg/release"
rcommon "helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
releaseutil "helm.sh/helm/v4/pkg/release/v1/util"
"helm.sh/helm/v4/pkg/repo/v1"
@ -353,13 +355,13 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
}
// Check error from render
if err != nil {
rel.SetStatus(release.StatusFailed, fmt.Sprintf("failed to render resource: %s", err.Error()))
rel.SetStatus(rcommon.StatusFailed, fmt.Sprintf("failed to render resource: %s", err.Error()))
// Return a release with partial data so that the client can show debugging information.
return rel, err
}
// Mark this release as in-progress
rel.SetStatus(release.StatusPendingInstall, "Initial install underway")
rel.SetStatus(rcommon.StatusPendingInstall, "Initial install underway")
var toBeAdopted kube.ResourceList
resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation)
@ -524,9 +526,9 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
}
if len(i.Description) > 0 {
rel.SetStatus(release.StatusDeployed, i.Description)
rel.SetStatus(rcommon.StatusDeployed, i.Description)
} else {
rel.SetStatus(release.StatusDeployed, "Install complete")
rel.SetStatus(rcommon.StatusDeployed, "Install complete")
}
// This is a tricky case. The release has been created, but the result
@ -544,7 +546,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
}
func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) {
rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error()))
rel.SetStatus(rcommon.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error()))
if i.RollbackOnFailure {
slog.Debug("install failed and rollback-on-failure is set, uninstalling release", "release", i.ReleaseName)
uninstall := NewUninstall(i.cfg)
@ -583,15 +585,43 @@ func (i *Install) availableName() error {
if err != nil || len(h) < 1 {
return nil
}
releaseutil.Reverse(h, releaseutil.SortByRevision)
rel := h[0]
if st := rel.Info.Status; i.Replace && (st == release.StatusUninstalled || st == release.StatusFailed) {
hl, err := releaseListToV1List(h)
if err != nil {
return err
}
releaseutil.Reverse(hl, releaseutil.SortByRevision)
rel := hl[0]
if st := rel.Info.Status; i.Replace && (st == rcommon.StatusUninstalled || st == rcommon.StatusFailed) {
return nil
}
return errors.New("cannot reuse a name that is still in use")
}
func releaseListToV1List(ls []ri.Releaser) ([]*release.Release, error) {
rls := make([]*release.Release, 0, len(ls))
for _, val := range ls {
rel, err := releaserToV1Release(val)
if err != nil {
return nil, err
}
rls = append(rls, rel)
}
return rls, nil
}
func releaseV1ListToReleaserList(ls []*release.Release) ([]ri.Releaser, error) {
rls := make([]ri.Releaser, 0, len(ls))
for _, val := range ls {
rls = append(rls, val)
}
return rls, nil
}
// createRelease creates a new release object
func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}, labels map[string]string) *release.Release {
ts := i.cfg.Now()
@ -604,7 +634,7 @@ func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{
Info: &release.Info{
FirstDeployed: ts,
LastDeployed: ts,
Status: release.StatusUnknown,
Status: rcommon.StatusUnknown,
},
Version: 1,
Labels: labels,
@ -630,20 +660,24 @@ func (i *Install) replaceRelease(rel *release.Release) error {
// No releases exist for this name, so we can return early
return nil
}
hl, err := releaseListToV1List(hist)
if err != nil {
return err
}
releaseutil.Reverse(hist, releaseutil.SortByRevision)
last := hist[0]
releaseutil.Reverse(hl, releaseutil.SortByRevision)
last := hl[0]
// Update version to the next available
rel.Version = last.Version + 1
// Do not change the status of a failed release.
if last.Info.Status == release.StatusFailed {
if last.Info.Status == rcommon.StatusFailed {
return nil
}
// For any other status, mark it as superseded and store the old record
last.SetStatus(release.StatusSuperseded, "superseded by new release")
last.SetStatus(rcommon.StatusSuperseded, "superseded by new release")
return i.recordRelease(last)
}

@ -47,6 +47,7 @@ import (
"helm.sh/helm/v4/pkg/chart/common"
"helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
rcommon "helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver"
)
@ -137,7 +138,10 @@ func TestInstallRelease(t *testing.T) {
is.Equal(res.Name, "test-install-release", "Expected release name.")
is.Equal(res.Namespace, "spaced")
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
r, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
rel, err := releaserToV1Release(r)
is.NoError(err)
is.Len(rel.Hooks, 1)
@ -156,7 +160,9 @@ func TestInstallRelease(t *testing.T) {
time.Sleep(time.Millisecond * 100)
lastRelease, err := instAction.cfg.Releases.Last(rel.Name)
req.NoError(err)
is.Equal(lastRelease.Info.Status, release.StatusDeployed)
lrel, err := releaserToV1Release(lastRelease)
is.NoError(err)
is.Equal(lrel.Info.Status, rcommon.StatusDeployed)
}
func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) {
@ -180,7 +186,10 @@ func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) {
t.Fatalf("Failed install: %s", err)
}
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
r, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
rel, err := releaserToV1Release(r)
is.NoError(err)
is.Equal(rel.Info.Description, "Install complete")
@ -197,7 +206,10 @@ func TestInstallReleaseWithTakeOwnership_ResourceOwned(t *testing.T) {
if err != nil {
t.Fatalf("Failed install: %s", err)
}
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
r, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
rel, err := releaserToV1Release(r)
is.NoError(err)
is.Equal(rel.Info.Description, "Install complete")
@ -234,7 +246,10 @@ func TestInstallReleaseWithValues(t *testing.T) {
is.Equal(res.Name, "test-install-release", "Expected release name.")
is.Equal(res.Namespace, "spaced")
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
r, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
rel, err := releaserToV1Release(r)
is.NoError(err)
is.Len(rel.Hooks, 1)
@ -273,7 +288,9 @@ func TestInstallRelease_WithNotes(t *testing.T) {
is.Equal(res.Name, "with-notes")
is.Equal(res.Namespace, "spaced")
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
r, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
rel, err := releaserToV1Release(r)
is.NoError(err)
is.Len(rel.Hooks, 1)
is.Equal(rel.Hooks[0].Manifest, manifestWithHook)
@ -297,7 +314,9 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
t.Fatalf("Failed install: %s", err)
}
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
r, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
rel, err := releaserToV1Release(r)
is.NoError(err)
expectedNotes := fmt.Sprintf("got-%s", res.Name)
@ -316,7 +335,9 @@ func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) {
t.Fatalf("Failed install: %s", err)
}
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
r, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
rel, err := releaserToV1Release(r)
is.NoError(err)
is.Equal("with-notes", rel.Name)
is.Equal("parent", rel.Info.Notes)
@ -335,7 +356,9 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) {
t.Fatalf("Failed install: %s", err)
}
rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
r, err := instAction.cfg.Releases.Get(res.Name, res.Version)
is.NoError(err)
rel, err := releaserToV1Release(r)
is.NoError(err)
is.Equal("with-notes", rel.Name)
// test run can return as either 'parent\nchild' or 'child\nparent'
@ -478,7 +501,7 @@ func TestInstallRelease_FailedHooks(t *testing.T) {
is.Error(err)
is.Contains(res.Info.Description, "failed post-install")
is.Equal("", outBuffer.String())
is.Equal(release.StatusFailed, res.Info.Status)
is.Equal(rcommon.StatusFailed, res.Info.Status)
}
func TestInstallRelease_ReplaceRelease(t *testing.T) {
@ -487,7 +510,7 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) {
instAction.Replace = true
rel := releaseStub()
rel.Info.Status = release.StatusUninstalled
rel.Info.Status = rcommon.StatusUninstalled
instAction.cfg.Releases.Create(rel)
instAction.ReleaseName = rel.Name
@ -499,9 +522,11 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) {
is.Equal(2, res.Version)
is.Equal(res.Name, rel.Name)
getres, err := instAction.cfg.Releases.Get(rel.Name, res.Version)
r, err := instAction.cfg.Releases.Get(rel.Name, res.Version)
is.NoError(err)
getres, err := releaserToV1Release(r)
is.NoError(err)
is.Equal(getres.Info.Status, release.StatusDeployed)
is.Equal(getres.Info.Status, rcommon.StatusDeployed)
}
func TestInstallRelease_KubeVersion(t *testing.T) {
@ -534,7 +559,7 @@ func TestInstallRelease_Wait(t *testing.T) {
res, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
is.Equal(res.Info.Status, rcommon.StatusFailed)
is.Equal(goroutines, instAction.getGoroutineCount())
}
@ -575,7 +600,7 @@ func TestInstallRelease_WaitForJobs(t *testing.T) {
res, err := instAction.Run(buildChart(), vals)
is.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
is.Equal(res.Info.Status, rcommon.StatusFailed)
}
func TestInstallRelease_RollbackOnFailure(t *testing.T) {

@ -22,6 +22,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
ri "helm.sh/helm/v4/pkg/release"
release "helm.sh/helm/v4/pkg/release/v1"
releaseutil "helm.sh/helm/v4/pkg/release/v1/util"
)
@ -145,7 +146,7 @@ func NewList(cfg *Configuration) *List {
}
// Run executes the list command, returning a set of matches.
func (l *List) Run() ([]*release.Release, error) {
func (l *List) Run() ([]ri.Releaser, error) {
if err := l.cfg.KubeClient.IsReachable(); err != nil {
return nil, err
}
@ -159,9 +160,13 @@ func (l *List) Run() ([]*release.Release, error) {
}
}
results, err := l.cfg.Releases.List(func(rel *release.Release) bool {
results, err := l.cfg.Releases.List(func(rel ri.Releaser) bool {
r, err := releaserToV1Release(rel)
if err != nil {
return false
}
// Skip anything that doesn't match the filter.
if filter != nil && !filter.MatchString(rel.Name) {
if filter != nil && !filter.MatchString(r.Name) {
return false
}
@ -176,30 +181,35 @@ func (l *List) Run() ([]*release.Release, error) {
return results, nil
}
rresults, err := releaseListToV1List(results)
if err != nil {
return nil, err
}
// by definition, superseded releases are never shown if
// only the latest releases are returned. so if requested statemask
// is _only_ ListSuperseded, skip the latest release filter
if l.StateMask != ListSuperseded {
results = filterLatestReleases(results)
rresults = filterLatestReleases(rresults)
}
// State mask application must occur after filtering to
// latest releases, otherwise outdated entries can be returned
results = l.filterStateMask(results)
rresults = l.filterStateMask(rresults)
// Skip anything that doesn't match the selector
selectorObj, err := labels.Parse(l.Selector)
if err != nil {
return nil, err
}
results = l.filterSelector(results, selectorObj)
rresults = l.filterSelector(rresults, selectorObj)
// Unfortunately, we have to sort before truncating, which can incur substantial overhead
l.sort(results)
l.sort(rresults)
// Guard on offset
if l.Offset >= len(results) {
return []*release.Release{}, nil
if l.Offset >= len(rresults) {
return releaseV1ListToReleaserList([]*release.Release{})
}
// Calculate the limit and offset, and then truncate results if necessary.
@ -208,12 +218,12 @@ func (l *List) Run() ([]*release.Release, error) {
limit = l.Limit
}
last := l.Offset + limit
if l := len(results); l < last {
if l := len(rresults); l < last {
last = l
}
results = results[l.Offset:last]
rresults = rresults[l.Offset:last]
return results, err
return releaseV1ListToReleaserList(rresults)
}
// sort is an in-place sort where order is based on the value of a.Sort

@ -24,6 +24,8 @@ import (
"github.com/stretchr/testify/assert"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
ri "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage"
)
@ -96,8 +98,11 @@ func TestList_Sort(t *testing.T) {
lister := newListFixture(t)
lister.Sort = ByNameDesc // Other sorts are tested elsewhere
makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
l, err := lister.Run()
is.NoError(err)
list, err := releaseListToV1List(l)
is.NoError(err)
is.Len(list, 3)
is.Equal("two", list[0].Name)
is.Equal("three", list[1].Name)
@ -109,7 +114,9 @@ func TestList_Limit(t *testing.T) {
lister := newListFixture(t)
lister.Limit = 2
makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
l, err := lister.Run()
is.NoError(err)
list, err := releaseListToV1List(l)
is.NoError(err)
is.Len(list, 2)
// Lex order means one, three, two
@ -122,7 +129,9 @@ func TestList_BigLimit(t *testing.T) {
lister := newListFixture(t)
lister.Limit = 20
makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
l, err := lister.Run()
is.NoError(err)
list, err := releaseListToV1List(l)
is.NoError(err)
is.Len(list, 3)
@ -138,7 +147,9 @@ func TestList_LimitOffset(t *testing.T) {
lister.Limit = 2
lister.Offset = 1
makeMeSomeReleases(t, lister.cfg.Releases)
list, err := lister.Run()
l, err := lister.Run()
is.NoError(err)
list, err := releaseListToV1List(l)
is.NoError(err)
is.Len(list, 2)
@ -168,23 +179,42 @@ func TestList_StateMask(t *testing.T) {
is := assert.New(t)
lister := newListFixture(t)
makeMeSomeReleases(t, lister.cfg.Releases)
one, err := lister.cfg.Releases.Get("one", 1)
oner, err := lister.cfg.Releases.Get("one", 1)
is.NoError(err)
one.SetStatus(release.StatusUninstalled, "uninstalled")
var one release.Release
switch v := oner.(type) {
case release.Release:
one = v
case *release.Release:
one = *v
default:
t.Fatal("unsupported release type")
}
one.SetStatus(common.StatusUninstalled, "uninstalled")
err = lister.cfg.Releases.Update(one)
is.NoError(err)
res, err := lister.Run()
is.NoError(err)
is.Len(res, 2)
is.Equal("three", res[0].Name)
is.Equal("two", res[1].Name)
ac0, err := ri.NewAccessor(res[0])
is.NoError(err)
ac1, err := ri.NewAccessor(res[1])
is.NoError(err)
is.Equal("three", ac0.Name())
is.Equal("two", ac1.Name())
lister.StateMask = ListUninstalled
res, err = lister.Run()
is.NoError(err)
is.Len(res, 1)
is.Equal("one", res[0].Name)
ac0, err = ri.NewAccessor(res[0])
is.NoError(err)
is.Equal("one", ac0.Name())
lister.StateMask |= ListDeployed
res, err = lister.Run()
@ -206,28 +236,30 @@ func TestList_StateMaskWithStaleRevisions(t *testing.T) {
// "dirty" release should _not_ be present as most recent
// release is deployed despite failed release in past
is.Equal("failed", res[0].Name)
ac0, err := ri.NewAccessor(res[0])
is.NoError(err)
is.Equal("failed", ac0.Name())
}
func makeMeSomeReleasesWithStaleFailure(t *testing.T, store *storage.Storage) {
t.Helper()
one := namedReleaseStub("clean", release.StatusDeployed)
one := namedReleaseStub("clean", common.StatusDeployed)
one.Namespace = "default"
one.Version = 1
two := namedReleaseStub("dirty", release.StatusDeployed)
two := namedReleaseStub("dirty", common.StatusDeployed)
two.Namespace = "default"
two.Version = 1
three := namedReleaseStub("dirty", release.StatusFailed)
three := namedReleaseStub("dirty", common.StatusFailed)
three.Namespace = "default"
three.Version = 2
four := namedReleaseStub("dirty", release.StatusDeployed)
four := namedReleaseStub("dirty", common.StatusDeployed)
four.Namespace = "default"
four.Version = 3
five := namedReleaseStub("failed", release.StatusFailed)
five := namedReleaseStub("failed", common.StatusFailed)
five.Namespace = "default"
five.Version = 1
@ -251,7 +283,9 @@ func TestList_Filter(t *testing.T) {
res, err := lister.Run()
is.NoError(err)
is.Len(res, 1)
is.Equal("three", res[0].Name)
ac0, err := ri.NewAccessor(res[0])
is.NoError(err)
is.Equal("three", ac0.Name())
}
func TestList_FilterFailsCompile(t *testing.T) {

@ -28,6 +28,7 @@ import (
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube"
ri "helm.sh/helm/v4/pkg/release"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -56,7 +57,7 @@ func NewReleaseTesting(cfg *Configuration) *ReleaseTesting {
}
// Run executes 'helm test' against the given release.
func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
func (r *ReleaseTesting) Run(name string) (ri.Releaser, error) {
if err := r.cfg.KubeClient.IsReachable(); err != nil {
return nil, err
}
@ -66,7 +67,12 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
}
// finds the non-deleted release with the given name
rel, err := r.cfg.Releases.Last(name)
reli, err := r.cfg.Releases.Last(name)
if err != nil {
return reli, err
}
rel, err := releaserToV1Release(reli)
if err != nil {
return rel, err
}

@ -25,6 +25,7 @@ import (
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -111,7 +112,12 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
return nil, nil, false, errInvalidRevision
}
currentRelease, err := r.cfg.Releases.Last(name)
currentReleasei, err := r.cfg.Releases.Last(name)
if err != nil {
return nil, nil, false, err
}
currentRelease, err := releaserToV1Release(currentReleasei)
if err != nil {
return nil, nil, false, err
}
@ -128,7 +134,11 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
// Check if the history version to be rolled back exists
previousVersionExist := false
for _, historyRelease := range historyReleases {
for _, historyReleasei := range historyReleases {
historyRelease, err := releaserToV1Release(historyReleasei)
if err != nil {
return nil, nil, false, err
}
version := historyRelease.Version
if previousVersion == version {
previousVersionExist = true
@ -141,7 +151,11 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
slog.Debug("rolling back", "name", name, "currentVersion", currentRelease.Version, "targetVersion", previousVersion)
previousRelease, err := r.cfg.Releases.Get(name, previousVersion)
previousReleasei, err := r.cfg.Releases.Get(name, previousVersion)
if err != nil {
return nil, nil, false, err
}
previousRelease, err := releaserToV1Release(previousReleasei)
if err != nil {
return nil, nil, false, err
}
@ -160,7 +174,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
Info: &release.Info{
FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: time.Now(),
Status: release.StatusPendingRollback,
Status: common.StatusPendingRollback,
Notes: previousRelease.Info.Notes,
// Because we lose the reference to previous version elsewhere, we set the
// message here, and only override it later if we experience failure.
@ -217,8 +231,8 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
if err != nil {
msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err)
slog.Warn(msg)
currentRelease.Info.Status = release.StatusSuperseded
targetRelease.Info.Status = release.StatusFailed
currentRelease.Info.Status = common.StatusSuperseded
targetRelease.Info.Status = common.StatusFailed
targetRelease.Info.Description = msg
r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease)
@ -241,14 +255,14 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
}
if r.WaitForJobs {
if err := waiter.WaitWithJobs(target, r.Timeout); err != nil {
targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
targetRelease.SetStatus(common.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease)
return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err)
}
} else {
if err := waiter.Wait(target, r.Timeout); err != nil {
targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
targetRelease.SetStatus(common.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease)
return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err)
@ -267,13 +281,17 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
return nil, err
}
// Supersede all previous deployments, see issue #2941.
for _, rel := range deployed {
for _, reli := range deployed {
rel, err := releaserToV1Release(reli)
if err != nil {
return nil, err
}
slog.Debug("superseding previous deployment", "version", rel.Version)
rel.Info.Status = release.StatusSuperseded
rel.Info.Status = common.StatusSuperseded
r.cfg.recordRelease(rel)
}
targetRelease.Info.Status = release.StatusDeployed
targetRelease.Info.Status = common.StatusDeployed
return targetRelease, nil
}

@ -50,7 +50,12 @@ func (s *Status) Run(name string) (*release.Release, error) {
return nil, err
}
rel, err := s.cfg.releaseContent(name, s.Version)
reli, err := s.cfg.releaseContent(name, s.Version)
if err != nil {
return nil, err
}
rel, err := releaserToV1Release(reli)
if err != nil {
return nil, err
}

@ -27,6 +27,7 @@ import (
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
releaseutil "helm.sh/helm/v4/pkg/release/v1/util"
"helm.sh/helm/v4/pkg/storage/driver"
@ -67,13 +68,18 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
}
if u.DryRun {
r, err := u.cfg.releaseContent(name, 0)
ri, err := u.cfg.releaseContent(name, 0)
if err != nil {
if u.IgnoreNotFound && errors.Is(err, driver.ErrReleaseNotFound) {
return nil, nil
}
return &release.UninstallReleaseResponse{}, err
}
r, err := releaserToV1Release(ri)
if err != nil {
return nil, err
}
return &release.UninstallReleaseResponse{Release: r}, nil
}
@ -81,23 +87,28 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
return nil, fmt.Errorf("uninstall: Release name is invalid: %s", name)
}
rels, err := u.cfg.Releases.History(name)
relsi, err := u.cfg.Releases.History(name)
if err != nil {
if u.IgnoreNotFound {
return nil, nil
}
return nil, fmt.Errorf("uninstall: Release not loaded: %s: %w", name, err)
}
if len(rels) < 1 {
if len(relsi) < 1 {
return nil, errMissingRelease
}
rels, err := releaseListToV1List(relsi)
if err != nil {
return nil, err
}
releaseutil.SortByRevision(rels)
rel := rels[len(rels)-1]
// TODO: Are there any cases where we want to force a delete even if it's
// already marked deleted?
if rel.Info.Status == release.StatusUninstalled {
if rel.Info.Status == common.StatusUninstalled {
if !u.KeepHistory {
if err := u.purgeReleases(rels...); err != nil {
return nil, fmt.Errorf("uninstall: Failed to purge the release: %w", err)
@ -108,7 +119,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
}
slog.Debug("uninstall: deleting release", "name", name)
rel.Info.Status = release.StatusUninstalling
rel.Info.Status = common.StatusUninstalling
rel.Info.Deleted = time.Now()
rel.Info.Description = "Deletion in progress (or silently failed)"
res := &release.UninstallReleaseResponse{Release: rel}
@ -150,7 +161,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
}
}
rel.Info.Status = release.StatusUninstalled
rel.Info.Status = common.StatusUninstalled
if len(u.Description) > 0 {
rel.Info.Description = u.Description
} else {

@ -27,7 +27,7 @@ import (
"helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/release/common"
)
func uninstallAction(t *testing.T) *Uninstall {
@ -119,7 +119,7 @@ func TestUninstallRelease_Wait(t *testing.T) {
res, err := unAction.Run(rel.Name)
is.Error(err)
is.Contains(err.Error(), "U timed out")
is.Equal(res.Release.Info.Status, release.StatusUninstalled)
is.Equal(res.Release.Info.Status, common.StatusUninstalled)
}
func TestUninstallRelease_Cascade(t *testing.T) {

@ -36,6 +36,7 @@ import (
"helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/postrenderer"
"helm.sh/helm/v4/pkg/registry"
rcommon "helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
releaseutil "helm.sh/helm/v4/pkg/release/v1/util"
"helm.sh/helm/v4/pkg/storage/driver"
@ -219,7 +220,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
}
// finds the last non-deleted release with the given name
lastRelease, err := u.cfg.Releases.Last(name)
lastReleasei, err := u.cfg.Releases.Last(name)
if err != nil {
// to keep existing behavior of returning the "%q has no deployed releases" error when an existing release does not exist
if errors.Is(err, driver.ErrReleaseNotFound) {
@ -228,26 +229,37 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
return nil, nil, false, err
}
lastRelease, err := releaserToV1Release(lastReleasei)
if err != nil {
return nil, nil, false, err
}
// Concurrent `helm upgrade`s will either fail here with `errPending` or when creating the release with "already exists". This should act as a pessimistic lock.
if lastRelease.Info.Status.IsPending() {
return nil, nil, false, errPending
}
var currentRelease *release.Release
if lastRelease.Info.Status == release.StatusDeployed {
if lastRelease.Info.Status == rcommon.StatusDeployed {
// no need to retrieve the last deployed release from storage as the last release is deployed
currentRelease = lastRelease
} else {
// finds the deployed release with the given name
currentRelease, err = u.cfg.Releases.Deployed(name)
currentReleasei, err := u.cfg.Releases.Deployed(name)
var cerr error
currentRelease, cerr = releaserToV1Release(currentReleasei)
if cerr != nil {
return nil, nil, false, err
}
if err != nil {
if errors.Is(err, driver.ErrNoDeployedReleases) &&
(lastRelease.Info.Status == release.StatusFailed || lastRelease.Info.Status == release.StatusSuperseded) {
(lastRelease.Info.Status == rcommon.StatusFailed || lastRelease.Info.Status == rcommon.StatusSuperseded) {
currentRelease = lastRelease
} else {
return nil, nil, false, err
}
}
}
// determine if values will be reused
@ -305,7 +317,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
Info: &release.Info{
FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: Timestamper(),
Status: release.StatusPendingUpgrade,
Status: rcommon.StatusPendingUpgrade,
Description: "Preparing upgrade", // This should be overwritten later.
},
Version: revision,
@ -487,10 +499,10 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
}
}
originalRelease.Info.Status = release.StatusSuperseded
originalRelease.Info.Status = rcommon.StatusSuperseded
u.cfg.recordRelease(originalRelease)
upgradedRelease.Info.Status = release.StatusDeployed
upgradedRelease.Info.Status = rcommon.StatusDeployed
if len(u.Description) > 0 {
upgradedRelease.Info.Description = u.Description
} else {
@ -503,7 +515,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err)
slog.Warn("upgrade failed", "name", rel.Name, slog.Any("error", err))
rel.Info.Status = release.StatusFailed
rel.Info.Status = rcommon.StatusFailed
rel.Info.Description = msg
u.cfg.recordRelease(rel)
if u.CleanupOnFail && len(created) > 0 {
@ -533,12 +545,16 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
return rel, fmt.Errorf("an error occurred while finding last successful release. original upgrade error: %w: %w", err, herr)
}
fullHistoryV1, herr := releaseListToV1List(fullHistory)
if herr != nil {
return nil, herr
}
// There isn't a way to tell if a previous release was successful, but
// generally failed releases do not get superseded unless the next
// release is successful, so this should be relatively safe
filteredHistory := releaseutil.FilterFunc(func(r *release.Release) bool {
return r.Info.Status == release.StatusSuperseded || r.Info.Status == release.StatusDeployed
}).Filter(fullHistory)
return r.Info.Status == rcommon.StatusSuperseded || r.Info.Status == rcommon.StatusDeployed
}).Filter(fullHistoryV1)
if len(filteredHistory) == 0 {
return rel, fmt.Errorf("unable to find a previously successful release when attempting to rollback. original upgrade error: %w", err)
}

@ -33,6 +33,7 @@ import (
"github.com/stretchr/testify/require"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -52,7 +53,7 @@ func TestUpgradeRelease_Success(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "previous-release"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
req.NoError(upAction.cfg.Releases.Create(rel))
upAction.WaitStrategy = kube.StatusWatcherStrategy
@ -61,15 +62,17 @@ func TestUpgradeRelease_Success(t *testing.T) {
ctx, done := context.WithCancel(t.Context())
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
req.NoError(err)
is.Equal(res.Info.Status, release.StatusDeployed)
is.Equal(res.Info.Status, common.StatusDeployed)
done()
// Detecting previous bug where context termination after successful release
// caused release to fail.
time.Sleep(time.Millisecond * 100)
lastRelease, err := upAction.cfg.Releases.Last(rel.Name)
lastReleasei, err := upAction.cfg.Releases.Last(rel.Name)
req.NoError(err)
is.Equal(lastRelease.Info.Status, release.StatusDeployed)
lastRelease, err := releaserToV1Release(lastReleasei)
req.NoError(err)
is.Equal(lastRelease.Info.Status, common.StatusDeployed)
}
func TestUpgradeRelease_Wait(t *testing.T) {
@ -79,7 +82,7 @@ func TestUpgradeRelease_Wait(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "come-fail-away"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
@ -91,7 +94,7 @@ func TestUpgradeRelease_Wait(t *testing.T) {
res, err := upAction.Run(rel.Name, buildChart(), vals)
req.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
is.Equal(res.Info.Status, common.StatusFailed)
}
func TestUpgradeRelease_WaitForJobs(t *testing.T) {
@ -101,7 +104,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "come-fail-away"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
@ -114,7 +117,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) {
res, err := upAction.Run(rel.Name, buildChart(), vals)
req.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
is.Equal(res.Info.Status, common.StatusFailed)
}
func TestUpgradeRelease_CleanupOnFail(t *testing.T) {
@ -124,7 +127,7 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "come-fail-away"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
@ -139,7 +142,7 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) {
req.Error(err)
is.NotContains(err.Error(), "unable to cleanup resources")
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
is.Equal(res.Info.Status, common.StatusFailed)
}
func TestUpgradeRelease_RollbackOnFailure(t *testing.T) {
@ -151,7 +154,7 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) {
rel := releaseStub()
rel.Name = "nuketown"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
@ -167,17 +170,19 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) {
is.Contains(err.Error(), "rollback-on-failure")
// Now make sure it is actually upgraded
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 3)
is.NoError(err)
updatedRes, err := releaserToV1Release(updatedResi)
is.NoError(err)
// Should have rolled back to the previous
is.Equal(updatedRes.Info.Status, release.StatusDeployed)
is.Equal(updatedRes.Info.Status, common.StatusDeployed)
})
t.Run("rollback-on-failure uninstall fails", func(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "fallout"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
@ -218,7 +223,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
rel := releaseStub()
rel.Name = "nuketown"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
rel.Config = existingValues
err := upAction.cfg.Releases.Create(rel)
@ -230,14 +235,17 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
is.NoError(err)
// Now make sure it is actually upgraded
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2)
is.NoError(err)
if updatedRes == nil {
if updatedResi == nil {
is.Fail("Updated Release is nil")
return
}
is.Equal(release.StatusDeployed, updatedRes.Info.Status)
updatedRes, err := releaserToV1Release(updatedResi)
is.NoError(err)
is.Equal(common.StatusDeployed, updatedRes.Info.Status)
is.Equal(expectedValues, updatedRes.Config)
})
@ -270,7 +278,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
Info: &release.Info{
FirstDeployed: now,
LastDeployed: now,
Status: release.StatusDeployed,
Status: common.StatusDeployed,
Description: "Named Release Stub",
},
Chart: sampleChart,
@ -292,14 +300,17 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
is.NoError(err)
// Now get the upgraded release
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2)
is.NoError(err)
if updatedRes == nil {
if updatedResi == nil {
is.Fail("Updated Release is nil")
return
}
is.Equal(release.StatusDeployed, updatedRes.Info.Status)
updatedRes, err := releaserToV1Release(updatedResi)
is.NoError(err)
is.Equal(common.StatusDeployed, updatedRes.Info.Status)
is.Equal(0, len(updatedRes.Chart.Dependencies()), "expected 0 dependencies")
expectedValues := map[string]interface{}{
@ -339,7 +350,7 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) {
rel := releaseStub()
rel.Name = "nuketown"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
rel.Config = existingValues
err := upAction.cfg.Releases.Create(rel)
@ -351,14 +362,17 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) {
is.NoError(err)
// Now make sure it is actually upgraded
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2)
is.NoError(err)
if updatedRes == nil {
if updatedResi == nil {
is.Fail("Updated Release is nil")
return
}
is.Equal(release.StatusDeployed, updatedRes.Info.Status)
updatedRes, err := releaserToV1Release(updatedResi)
is.NoError(err)
is.Equal(common.StatusDeployed, updatedRes.Info.Status)
is.Equal(expectedValues, updatedRes.Config)
is.Equal(newChartValues, updatedRes.Chart.Values)
})
@ -370,11 +384,11 @@ func TestUpgradeRelease_Pending(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "come-fail-away"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
upAction.cfg.Releases.Create(rel)
rel2 := releaseStub()
rel2.Name = "come-fail-away"
rel2.Info.Status = release.StatusPendingUpgrade
rel2.Info.Status = common.StatusPendingUpgrade
rel2.Version = 2
upAction.cfg.Releases.Create(rel2)
@ -391,7 +405,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "interrupted-release"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
@ -407,7 +421,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
req.Error(err)
is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled")
is.Equal(res.Info.Status, release.StatusFailed)
is.Equal(res.Info.Status, common.StatusFailed)
}
func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) {
@ -418,7 +432,7 @@ func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "interrupted-release"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
@ -436,10 +450,12 @@ func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) {
is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to rollback-on-failure being set: context canceled")
// Now make sure it is actually upgraded
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 3)
is.NoError(err)
updatedRes, err := releaserToV1Release(updatedResi)
is.NoError(err)
// Should have rolled back to the previous
is.Equal(updatedRes.Info.Status, release.StatusDeployed)
is.Equal(updatedRes.Info.Status, common.StatusDeployed)
}
func TestMergeCustomLabels(t *testing.T) {
@ -468,7 +484,7 @@ func TestUpgradeRelease_Labels(t *testing.T) {
"key1": "val1",
"key2": "val2.1",
}
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
err := upAction.cfg.Releases.Create(rel)
is.NoError(err)
@ -483,25 +499,29 @@ func TestUpgradeRelease_Labels(t *testing.T) {
is.NoError(err)
// Now make sure it is actually upgraded and labels were merged
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2)
is.NoError(err)
if updatedRes == nil {
if updatedResi == nil {
is.Fail("Updated Release is nil")
return
}
is.Equal(release.StatusDeployed, updatedRes.Info.Status)
updatedRes, err := releaserToV1Release(updatedResi)
is.NoError(err)
is.Equal(common.StatusDeployed, updatedRes.Info.Status)
is.Equal(mergeCustomLabels(rel.Labels, upAction.Labels), updatedRes.Labels)
// Now make sure it is suppressed release still contains original labels
initialRes, err := upAction.cfg.Releases.Get(res.Name, 1)
initialResi, err := upAction.cfg.Releases.Get(res.Name, 1)
is.NoError(err)
if initialRes == nil {
if initialResi == nil {
is.Fail("Updated Release is nil")
return
}
is.Equal(initialRes.Info.Status, release.StatusSuperseded)
initialRes, err := releaserToV1Release(initialResi)
is.NoError(err)
is.Equal(initialRes.Info.Status, common.StatusSuperseded)
is.Equal(initialRes.Labels, rel.Labels)
}
@ -516,7 +536,7 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) {
"key1": "val1",
"key2": "val2.1",
}
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
err := upAction.cfg.Releases.Create(rel)
is.NoError(err)
@ -542,7 +562,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "previous-release"
rel.Info.Status = release.StatusDeployed
rel.Info.Status = common.StatusDeployed
req.NoError(upAction.cfg.Releases.Create(rel))
upAction.DryRunStrategy = DryRunClient
@ -552,12 +572,14 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done()
req.NoError(err)
is.Equal(release.StatusPendingUpgrade, res.Info.Status)
is.Equal(common.StatusPendingUpgrade, res.Info.Status)
is.Contains(res.Manifest, "kind: Secret")
lastRelease, err := upAction.cfg.Releases.Last(rel.Name)
lastReleasei, err := upAction.cfg.Releases.Last(rel.Name)
req.NoError(err)
lastRelease, err := releaserToV1Release(lastReleasei)
req.NoError(err)
is.Equal(lastRelease.Info.Status, release.StatusDeployed)
is.Equal(lastRelease.Info.Status, common.StatusDeployed)
is.Equal(1, lastRelease.Version)
// Test the case for hiding the secret to ensure it is not displayed
@ -568,12 +590,14 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
done()
req.NoError(err)
is.Equal(release.StatusPendingUpgrade, res.Info.Status)
is.Equal(common.StatusPendingUpgrade, res.Info.Status)
is.NotContains(res.Manifest, "kind: Secret")
lastRelease, err = upAction.cfg.Releases.Last(rel.Name)
lastReleasei, err = upAction.cfg.Releases.Last(rel.Name)
req.NoError(err)
lastRelease, err = releaserToV1Release(lastReleasei)
req.NoError(err)
is.Equal(lastRelease.Info.Status, release.StatusDeployed)
is.Equal(lastRelease.Info.Status, common.StatusDeployed)
is.Equal(1, lastRelease.Version)
// Ensure in a dry run mode when using HideSecret

@ -22,6 +22,7 @@ import (
"testing"
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -31,7 +32,7 @@ func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) {
storage := storageFixture()
storage.Create(&release.Release{
Name: "myrelease",
Info: &release.Info{Status: release.StatusDeployed},
Info: &release.Info{Status: common.StatusDeployed},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "Myrelease-Chart",

@ -25,6 +25,7 @@ import (
"helm.sh/helm/v4/pkg/action"
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -64,35 +65,35 @@ func outputFlagCompletionTest(t *testing.T, cmdName string) {
cmd: fmt.Sprintf("__complete %s --output ''", cmdName),
golden: "output/output-comp.txt",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
}),
}, {
name: "completion for output flag long and after arg",
cmd: fmt.Sprintf("__complete %s aramis --output ''", cmdName),
golden: "output/output-comp.txt",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
}),
}, {
name: "completion for output flag short and before arg",
cmd: fmt.Sprintf("__complete %s -o ''", cmdName),
golden: "output/output-comp.txt",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
}),
}, {
name: "completion for output flag short and after arg",
cmd: fmt.Sprintf("__complete %s aramis -o ''", cmdName),
golden: "output/output-comp.txt",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
}),
}, {
name: "completion for output flag, no filter",
cmd: fmt.Sprintf("__complete %s --output jso", cmdName),
golden: "output/output-comp.txt",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
}),
}}
runTestCmd(t, tests)

@ -111,7 +111,11 @@ func (r releaseHistory) WriteTable(out io.Writer) error {
}
func getHistory(client *action.History, name string) (releaseHistory, error) {
hist, err := client.Run(name)
histi, err := client.Run(name)
if err != nil {
return nil, err
}
hist, err := releaseListToV1List(histi)
if err != nil {
return nil, err
}
@ -180,7 +184,11 @@ func compListRevisions(_ string, cfg *action.Configuration, releaseName string)
client := action.NewHistory(cfg)
var revisions []string
if hist, err := client.Run(releaseName); err == nil {
if histi, err := client.Run(releaseName); err == nil {
hist, err := releaseListToV1List(histi)
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
for _, version := range hist {
appVersion := fmt.Sprintf("App: %s", version.Chart.Metadata.AppVersion)
chartDesc := fmt.Sprintf("Chart: %s-%s", version.Chart.Metadata.Name, version.Chart.Metadata.Version)

@ -20,11 +20,12 @@ import (
"fmt"
"testing"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
func TestHistoryCmd(t *testing.T) {
mk := func(name string, vers int, status release.Status) *release.Release {
mk := func(name string, vers int, status common.Status) *release.Release {
return release.Mock(&release.MockReleaseOptions{
Name: name,
Version: vers,
@ -36,34 +37,34 @@ func TestHistoryCmd(t *testing.T) {
name: "get history for release",
cmd: "history angry-bird",
rels: []*release.Release{
mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, release.StatusSuperseded),
mk("angry-bird", 2, release.StatusSuperseded),
mk("angry-bird", 1, release.StatusSuperseded),
mk("angry-bird", 4, common.StatusDeployed),
mk("angry-bird", 3, common.StatusSuperseded),
mk("angry-bird", 2, common.StatusSuperseded),
mk("angry-bird", 1, common.StatusSuperseded),
},
golden: "output/history.txt",
}, {
name: "get history with max limit set",
cmd: "history angry-bird --max 2",
rels: []*release.Release{
mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, release.StatusSuperseded),
mk("angry-bird", 4, common.StatusDeployed),
mk("angry-bird", 3, common.StatusSuperseded),
},
golden: "output/history-limit.txt",
}, {
name: "get history with yaml output format",
cmd: "history angry-bird --output yaml",
rels: []*release.Release{
mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, release.StatusSuperseded),
mk("angry-bird", 4, common.StatusDeployed),
mk("angry-bird", 3, common.StatusSuperseded),
},
golden: "output/history.yaml",
}, {
name: "get history with json output format",
cmd: "history angry-bird --output json",
rels: []*release.Release{
mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, release.StatusSuperseded),
mk("angry-bird", 4, common.StatusDeployed),
mk("angry-bird", 3, common.StatusSuperseded),
},
golden: "output/history.json",
}}
@ -76,7 +77,7 @@ func TestHistoryOutputCompletion(t *testing.T) {
func revisionFlagCompletionTest(t *testing.T, cmdName string) {
t.Helper()
mk := func(name string, vers int, status release.Status) *release.Release {
mk := func(name string, vers int, status common.Status) *release.Release {
return release.Mock(&release.MockReleaseOptions{
Name: name,
Version: vers,
@ -85,10 +86,10 @@ func revisionFlagCompletionTest(t *testing.T, cmdName string) {
}
releases := []*release.Release{
mk("musketeers", 11, release.StatusDeployed),
mk("musketeers", 10, release.StatusSuperseded),
mk("musketeers", 9, release.StatusSuperseded),
mk("musketeers", 8, release.StatusSuperseded),
mk("musketeers", 11, common.StatusDeployed),
mk("musketeers", 10, common.StatusSuperseded),
mk("musketeers", 9, common.StatusSuperseded),
mk("musketeers", 8, common.StatusSuperseded),
}
tests := []cmdTestCase{{

@ -30,6 +30,7 @@ import (
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -79,7 +80,11 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
client.SetStateMask()
results, err := client.Run()
resultsi, err := client.Run()
if err != nil {
return err
}
results, err := releaseListToV1List(resultsi)
if err != nil {
return err
}
@ -193,28 +198,28 @@ func (w *releaseListWriter) WriteTable(out io.Writer) error {
}
for _, r := range w.releases {
// Parse the status string back to a release.Status to use color
var status release.Status
var status common.Status
switch r.Status {
case "deployed":
status = release.StatusDeployed
status = common.StatusDeployed
case "failed":
status = release.StatusFailed
status = common.StatusFailed
case "pending-install":
status = release.StatusPendingInstall
status = common.StatusPendingInstall
case "pending-upgrade":
status = release.StatusPendingUpgrade
status = common.StatusPendingUpgrade
case "pending-rollback":
status = release.StatusPendingRollback
status = common.StatusPendingRollback
case "uninstalling":
status = release.StatusUninstalling
status = common.StatusUninstalling
case "uninstalled":
status = release.StatusUninstalled
status = common.StatusUninstalled
case "superseded":
status = release.StatusSuperseded
status = common.StatusSuperseded
case "unknown":
status = release.StatusUnknown
status = common.StatusUnknown
default:
status = release.Status(r.Status)
status = common.Status(r.Status)
}
table.AddRow(r.Name, coloroutput.ColorizeNamespace(r.Namespace, w.noColor), r.Revision, r.Updated, coloroutput.ColorizeStatus(status, w.noColor), r.Chart, r.AppVersion)
}
@ -264,7 +269,11 @@ func compListReleases(toComplete string, ignoredReleaseNames []string, cfg *acti
// client.Filter = fmt.Sprintf("^%s", toComplete)
client.SetStateMask()
releases, err := client.Run()
releasesi, err := client.Run()
if err != nil {
return nil, cobra.ShellCompDirectiveDefault
}
releases, err := releaseListToV1List(releasesi)
if err != nil {
return nil, cobra.ShellCompDirectiveDefault
}

@ -21,6 +21,7 @@ import (
"time"
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -47,7 +48,7 @@ func TestListCmd(t *testing.T) {
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp1,
Status: release.StatusSuperseded,
Status: common.StatusSuperseded,
},
Chart: chartInfo,
},
@ -57,7 +58,7 @@ func TestListCmd(t *testing.T) {
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp1,
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
Chart: chartInfo,
},
@ -67,7 +68,7 @@ func TestListCmd(t *testing.T) {
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp1,
Status: release.StatusUninstalled,
Status: common.StatusUninstalled,
},
Chart: chartInfo,
},
@ -77,7 +78,7 @@ func TestListCmd(t *testing.T) {
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp1,
Status: release.StatusSuperseded,
Status: common.StatusSuperseded,
},
Chart: chartInfo,
},
@ -87,7 +88,7 @@ func TestListCmd(t *testing.T) {
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp2,
Status: release.StatusFailed,
Status: common.StatusFailed,
},
Chart: chartInfo,
},
@ -97,7 +98,7 @@ func TestListCmd(t *testing.T) {
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp1,
Status: release.StatusUninstalling,
Status: common.StatusUninstalling,
},
Chart: chartInfo,
},
@ -107,7 +108,7 @@ func TestListCmd(t *testing.T) {
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp1,
Status: release.StatusPendingInstall,
Status: common.StatusPendingInstall,
},
Chart: chartInfo,
},
@ -117,7 +118,7 @@ func TestListCmd(t *testing.T) {
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp3,
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
Chart: chartInfo,
},
@ -127,7 +128,7 @@ func TestListCmd(t *testing.T) {
Namespace: defaultNamespace,
Info: &release.Info{
LastDeployed: timestamp4,
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
Chart: chartInfo,
},
@ -137,7 +138,7 @@ func TestListCmd(t *testing.T) {
Namespace: "milano",
Info: &release.Info{
LastDeployed: timestamp1,
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
Chart: chartInfo,
},

@ -65,13 +65,17 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
client.Filters[action.ExcludeNameFilter] = append(client.Filters[action.ExcludeNameFilter], notName.ReplaceAllLiteralString(f, ""))
}
}
rel, runErr := client.Run(args[0])
reli, runErr := client.Run(args[0])
// We only return an error if we weren't even able to get the
// release, otherwise we keep going so we can print status and logs
// if requested
if runErr != nil && rel == nil {
if runErr != nil && reli == nil {
return runErr
}
rel, err := releaserToV1Release(reli)
if err != nil {
return err
}
if err := outfmt.Write(out, &statusPrinter{
release: rel,

@ -26,6 +26,7 @@ import (
"helm.sh/helm/v4/pkg/chart/common"
chart "helm.sh/helm/v4/pkg/chart/v2"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
rcommon "helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -46,7 +47,7 @@ func TestReleaseTestNotesHandling(t *testing.T) {
Name: "test-release",
Namespace: "default",
Info: &release.Info{
Status: release.StatusDeployed,
Status: rcommon.StatusDeployed,
Notes: "Some important notes that should be hidden by default",
},
Chart: &chart.Chart{Metadata: &chart.Metadata{Name: "test", Version: "1.0.0"}},

@ -22,6 +22,7 @@ import (
"testing"
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -29,13 +30,13 @@ func TestRollbackCmd(t *testing.T) {
rels := []*release.Release{
{
Name: "funny-honey",
Info: &release.Info{Status: release.StatusSuperseded},
Info: &release.Info{Status: common.StatusSuperseded},
Chart: &chart.Chart{},
Version: 1,
},
{
Name: "funny-honey",
Info: &release.Info{Status: release.StatusDeployed},
Info: &release.Info{Status: common.StatusDeployed},
Chart: &chart.Chart{},
Version: 2,
},
@ -83,7 +84,7 @@ func TestRollbackCmd(t *testing.T) {
}
func TestRollbackRevisionCompletion(t *testing.T) {
mk := func(name string, vers int, status release.Status) *release.Release {
mk := func(name string, vers int, status common.Status) *release.Release {
return release.Mock(&release.MockReleaseOptions{
Name: name,
Version: vers,
@ -92,11 +93,11 @@ func TestRollbackRevisionCompletion(t *testing.T) {
}
releases := []*release.Release{
mk("musketeers", 11, release.StatusDeployed),
mk("musketeers", 10, release.StatusSuperseded),
mk("musketeers", 9, release.StatusSuperseded),
mk("musketeers", 8, release.StatusSuperseded),
mk("carabins", 1, release.StatusSuperseded),
mk("musketeers", 11, common.StatusDeployed),
mk("musketeers", 10, common.StatusSuperseded),
mk("musketeers", 9, common.StatusSuperseded),
mk("musketeers", 8, common.StatusSuperseded),
mk("carabins", 1, common.StatusSuperseded),
}
tests := []cmdTestCase{{
@ -132,14 +133,14 @@ func TestRollbackWithLabels(t *testing.T) {
rels := []*release.Release{
{
Name: releaseName,
Info: &release.Info{Status: release.StatusSuperseded},
Info: &release.Info{Status: common.StatusSuperseded},
Chart: &chart.Chart{},
Version: 1,
Labels: labels1,
},
{
Name: releaseName,
Info: &release.Info{Status: release.StatusDeployed},
Info: &release.Info{Status: common.StatusDeployed},
Chart: &chart.Chart{},
Version: 2,
Labels: labels2,
@ -155,7 +156,11 @@ func TestRollbackWithLabels(t *testing.T) {
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := storage.Get(releaseName, 3)
updatedReli, err := storage.Get(releaseName, 3)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := releaserToV1Release(updatedReli)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}

@ -39,6 +39,7 @@ import (
"helm.sh/helm/v4/pkg/cli"
kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/registry"
ri "helm.sh/helm/v4/pkg/release"
release "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/repo/v1"
"helm.sh/helm/v4/pkg/storage/driver"
@ -465,3 +466,31 @@ type CommandError struct {
error
ExitCode int
}
// releaserToV1Release is a helper function to convert a v1 release passed by interface
// into the type object.
func releaserToV1Release(rel ri.Releaser) (*release.Release, error) {
switch r := rel.(type) {
case release.Release:
return &r, nil
case *release.Release:
return r, nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("unsupported release type: %T", rel)
}
}
func releaseListToV1List(ls []ri.Releaser) ([]*release.Release, error) {
rls := make([]*release.Release, 0, len(ls))
for _, val := range ls {
rel, err := releaserToV1Release(val)
if err != nil {
return nil, err
}
rls = append(rls, rel)
}
return rls, nil
}

@ -21,6 +21,7 @@ import (
"time"
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -41,14 +42,14 @@ func TestStatusCmd(t *testing.T) {
cmd: "status flummoxed-chickadee",
golden: "output/status.txt",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
}),
}, {
name: "get status of a deployed release, with desc",
cmd: "status flummoxed-chickadee",
golden: "output/status-with-desc.txt",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
Description: "Mock description",
}),
}, {
@ -56,7 +57,7 @@ func TestStatusCmd(t *testing.T) {
cmd: "status flummoxed-chickadee",
golden: "output/status-with-notes.txt",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
Notes: "release notes",
}),
}, {
@ -64,7 +65,7 @@ func TestStatusCmd(t *testing.T) {
cmd: "status flummoxed-chickadee -o json",
golden: "output/status.json",
rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
Notes: "release notes",
}),
}, {
@ -73,7 +74,7 @@ func TestStatusCmd(t *testing.T) {
golden: "output/status-with-resources.txt",
rels: releasesMockWithStatus(
&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
),
}, {
@ -82,7 +83,7 @@ func TestStatusCmd(t *testing.T) {
golden: "output/status-with-resources.json",
rels: releasesMockWithStatus(
&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
),
}, {
@ -91,7 +92,7 @@ func TestStatusCmd(t *testing.T) {
golden: "output/status-with-test-suite.txt",
rels: releasesMockWithStatus(
&release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
&release.Hook{
Name: "never-run-test",
@ -140,7 +141,7 @@ func TestStatusCompletion(t *testing.T) {
Name: "athos",
Namespace: "default",
Info: &release.Info{
Status: release.StatusDeployed,
Status: common.StatusDeployed,
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
@ -152,7 +153,7 @@ func TestStatusCompletion(t *testing.T) {
Name: "porthos",
Namespace: "default",
Info: &release.Info{
Status: release.StatusFailed,
Status: common.StatusFailed,
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
@ -164,7 +165,7 @@ func TestStatusCompletion(t *testing.T) {
Name: "aramis",
Namespace: "default",
Info: &release.Info{
Status: release.StatusUninstalled,
Status: common.StatusUninstalled,
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
@ -176,7 +177,7 @@ func TestStatusCompletion(t *testing.T) {
Name: "dartagnan",
Namespace: "gascony",
Info: &release.Info{
Status: release.StatusUnknown,
Status: common.StatusUnknown,
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{

@ -37,7 +37,8 @@ import (
"helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v4/pkg/getter"
release "helm.sh/helm/v4/pkg/release/v1"
ri "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common"
"helm.sh/helm/v4/pkg/storage/driver"
)
@ -318,6 +319,11 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return cmd
}
func isReleaseUninstalled(versions []*release.Release) bool {
return len(versions) > 0 && versions[len(versions)-1].Info.Status == release.StatusUninstalled
func isReleaseUninstalled(versionsi []ri.Releaser) bool {
versions, err := releaseListToV1List(versionsi)
if err != nil {
slog.Error("cannot convert release list to v1 release list", "error", err)
return false
}
return len(versions) > 0 && versions[len(versions)-1].Info.Status == common.StatusUninstalled
}

@ -28,6 +28,7 @@ import (
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/v2/loader"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
rcommon "helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1"
)
@ -82,7 +83,7 @@ func TestUpgradeCmd(t *testing.T) {
badDepsPath := "testdata/testcharts/chart-bad-requirements"
presentDepsPath := "testdata/testcharts/chart-with-subchart-update"
relWithStatusMock := func(n string, v int, ch *chart.Chart, status release.Status) *release.Release {
relWithStatusMock := func(n string, v int, ch *chart.Chart, status rcommon.Status) *release.Release {
return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch, Status: status})
}
@ -173,20 +174,20 @@ func TestUpgradeCmd(t *testing.T) {
name: "upgrade a failed release",
cmd: fmt.Sprintf("upgrade funny-bunny '%s'", chartPath),
golden: "output/upgrade.txt",
rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusFailed)},
rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, rcommon.StatusFailed)},
},
{
name: "upgrade a pending install release",
cmd: fmt.Sprintf("upgrade funny-bunny '%s'", chartPath),
golden: "output/upgrade-with-pending-install.txt",
wantError: true,
rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusPendingInstall)},
rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, rcommon.StatusPendingInstall)},
},
{
name: "install a previously uninstalled release with '--keep-history' using 'upgrade --install'",
cmd: fmt.Sprintf("upgrade funny-bunny -i '%s'", chartPath),
golden: "output/upgrade-uninstalled-with-keep-history.txt",
rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusUninstalled)},
rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, rcommon.StatusUninstalled)},
},
}
runTestCmd(t, tests)
@ -208,7 +209,11 @@ func TestUpgradeWithValue(t *testing.T) {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := store.Get(releaseName, 4)
updatedReli, err := store.Get(releaseName, 4)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := releaserToV1Release(updatedReli)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
@ -235,7 +240,11 @@ func TestUpgradeWithStringValue(t *testing.T) {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := store.Get(releaseName, 4)
updatedReli, err := store.Get(releaseName, 4)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := releaserToV1Release(updatedReli)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
@ -263,7 +272,11 @@ func TestUpgradeInstallWithSubchartNotes(t *testing.T) {
t.Errorf("unexpected error, got '%v'", err)
}
upgradedRel, err := store.Get(releaseName, 2)
upgradedReli, err := store.Get(releaseName, 2)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
upgradedRel, err := releaserToV1Release(upgradedReli)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
@ -295,7 +308,11 @@ func TestUpgradeWithValuesFile(t *testing.T) {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := store.Get(releaseName, 4)
updatedReli, err := store.Get(releaseName, 4)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := releaserToV1Release(updatedReli)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
@ -328,7 +345,11 @@ func TestUpgradeWithValuesFromStdin(t *testing.T) {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := store.Get(releaseName, 4)
updatedReli, err := store.Get(releaseName, 4)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := releaserToV1Release(updatedReli)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
@ -358,7 +379,11 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := store.Get(releaseName, 1)
updatedReli, err := store.Get(releaseName, 1)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := releaserToV1Release(updatedReli)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
@ -463,7 +488,11 @@ func TestUpgradeInstallWithLabels(t *testing.T) {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := store.Get(releaseName, 1)
updatedReli, err := store.Get(releaseName, 1)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := releaserToV1Release(updatedReli)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}

@ -18,7 +18,10 @@ package release
import (
"errors"
"fmt"
"time"
"helm.sh/helm/v4/pkg/chart"
v1release "helm.sh/helm/v4/pkg/release/v1"
)
@ -33,7 +36,7 @@ func NewDefaultAccessor(rel Releaser) (Accessor, error) {
case *v1release.Release:
return &v1Accessor{v}, nil
default:
return nil, errors.New("unsupported release type")
return nil, fmt.Errorf("unsupported release type: %T", rel)
}
}
@ -52,6 +55,18 @@ type v1Accessor struct {
rel *v1release.Release
}
func (a *v1Accessor) Name() string {
return a.rel.Name
}
func (a *v1Accessor) Namespace() string {
return a.rel.Namespace
}
func (a *v1Accessor) Version() int {
return a.rel.Version
}
func (a *v1Accessor) Hooks() []Hook {
var hooks = make([]Hook, len(a.rel.Hooks))
for i, h := range a.rel.Hooks {
@ -68,6 +83,26 @@ func (a *v1Accessor) Notes() string {
return a.rel.Info.Notes
}
func (a *v1Accessor) Labels() map[string]string {
return a.rel.Labels
}
func (a *v1Accessor) Chart() chart.Charter {
return a.rel.Chart
}
func (a *v1Accessor) Status() string {
return a.rel.Info.Status.String()
}
func (a *v1Accessor) ApplyMethod() string {
return a.rel.ApplyMethod
}
func (a *v1Accessor) DeployedAt() time.Time {
return a.rel.Info.LastDeployed
}
type v1HookAccessor struct {
hook *v1release.Hook
}

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
package common
// Status is the status of a release
type Status string

@ -16,14 +16,28 @@ limitations under the License.
package release
import (
"time"
"helm.sh/helm/v4/pkg/chart"
)
type Releaser interface{}
type Hook interface{}
type Accessor interface {
Name() string
Namespace() string
Version() int
Hooks() []Hook
Manifest() string
Notes() string
Labels() map[string]string
Chart() chart.Charter
Status() string
ApplyMethod() string
DeployedAt() time.Time
}
type HookAccessor interface {

@ -18,6 +18,7 @@ package v1
import (
"time"
"helm.sh/helm/v4/pkg/release/common"
"k8s.io/apimachinery/pkg/runtime"
)
@ -32,7 +33,7 @@ type Info struct {
// Description is human-friendly "log entry" about this release.
Description string `json:"description,omitempty"`
// Status is the current state of the release
Status Status `json:"status,omitempty"`
Status common.Status `json:"status,omitempty"`
// Contains the rendered templates/NOTES.txt if available
Notes string `json:"notes,omitempty"`
// Contains the deployed resources information

@ -23,6 +23,7 @@ import (
"helm.sh/helm/v4/pkg/chart/common"
chart "helm.sh/helm/v4/pkg/chart/v2"
rcommon "helm.sh/helm/v4/pkg/release/common"
)
// MockHookTemplate is the hook template used for all mock release objects.
@ -45,7 +46,7 @@ type MockReleaseOptions struct {
Name string
Version int
Chart *chart.Chart
Status Status
Status rcommon.Status
Namespace string
Labels map[string]string
}
@ -105,7 +106,7 @@ func Mock(opts *MockReleaseOptions) *Release {
}
}
scode := StatusDeployed
scode := rcommon.StatusDeployed
if len(opts.Status) > 0 {
scode = opts.Status
}

@ -17,6 +17,7 @@ package v1
import (
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/release/common"
)
type ApplyMethod string
@ -53,7 +54,7 @@ type Release struct {
}
// SetStatus is a helper for setting the status on a release.
func (r *Release) SetStatus(status Status, msg string) {
func (r *Release) SetStatus(status common.Status, msg string) {
r.Info.Status = status
r.Info.Description = msg
}

@ -16,7 +16,10 @@ limitations under the License.
package util // import "helm.sh/helm/v4/pkg/release/v1/util"
import rspb "helm.sh/helm/v4/pkg/release/v1"
import (
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
// FilterFunc returns true if the release object satisfies
// the predicate of the underlying filter func.
@ -68,7 +71,7 @@ func All(filters ...FilterFunc) FilterFunc {
}
// StatusFilter filters a set of releases by status code.
func StatusFilter(status rspb.Status) FilterFunc {
func StatusFilter(status common.Status) FilterFunc {
return FilterFunc(func(rls *rspb.Release) bool {
if rls == nil {
return true

@ -19,20 +19,21 @@ package util // import "helm.sh/helm/v4/pkg/release/v1/util"
import (
"testing"
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
func TestFilterAny(t *testing.T) {
ls := Any(StatusFilter(rspb.StatusUninstalled)).Filter(releases)
ls := Any(StatusFilter(common.StatusUninstalled)).Filter(releases)
if len(ls) != 2 {
t.Fatalf("expected 2 results, got '%d'", len(ls))
}
r0, r1 := ls[0], ls[1]
switch {
case r0.Info.Status != rspb.StatusUninstalled:
case r0.Info.Status != common.StatusUninstalled:
t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String())
case r1.Info.Status != rspb.StatusUninstalled:
case r1.Info.Status != common.StatusUninstalled:
t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String())
}
}
@ -40,7 +41,7 @@ func TestFilterAny(t *testing.T) {
func TestFilterAll(t *testing.T) {
fn := FilterFunc(func(rls *rspb.Release) bool {
// true if not uninstalled and version < 4
v0 := !StatusFilter(rspb.StatusUninstalled).Check(rls)
v0 := !StatusFilter(common.StatusUninstalled).Check(rls)
v1 := rls.Version < 4
return v0 && v1
})
@ -53,7 +54,7 @@ func TestFilterAll(t *testing.T) {
switch r0 := ls[0]; {
case r0.Version == 4:
t.Fatal("got release with status revision 4")
case r0.Info.Status == rspb.StatusUninstalled:
case r0.Info.Status == common.StatusUninstalled:
t.Fatal("got release with status UNINSTALLED")
}
}

@ -20,19 +20,20 @@ import (
"testing"
"time"
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
// note: this test data is shared with filter_test.go.
var releases = []*rspb.Release{
tsRelease("quiet-bear", 2, 2000, rspb.StatusSuperseded),
tsRelease("angry-bird", 4, 3000, rspb.StatusDeployed),
tsRelease("happy-cats", 1, 4000, rspb.StatusUninstalled),
tsRelease("vocal-dogs", 3, 6000, rspb.StatusUninstalled),
tsRelease("quiet-bear", 2, 2000, common.StatusSuperseded),
tsRelease("angry-bird", 4, 3000, common.StatusDeployed),
tsRelease("happy-cats", 1, 4000, common.StatusUninstalled),
tsRelease("vocal-dogs", 3, 6000, common.StatusUninstalled),
}
func tsRelease(name string, vers int, dur time.Duration, status rspb.Status) *rspb.Release {
func tsRelease(name string, vers int, dur time.Duration, status common.Status) *rspb.Release {
info := &rspb.Info{Status: status, LastDeployed: time.Now().Add(dur)}
return &rspb.Release{
Name: name,

@ -31,6 +31,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -60,7 +61,7 @@ func (cfgmaps *ConfigMaps) Name() string {
// Get fetches the release named by key. The corresponding release is returned
// or error if not found.
func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) {
func (cfgmaps *ConfigMaps) Get(key string) (release.Releaser, error) {
// fetch the configmap holding the release named by key
obj, err := cfgmaps.impl.Get(context.Background(), key, metav1.GetOptions{})
if err != nil {
@ -85,7 +86,7 @@ func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) {
// List fetches all releases and returns the list releases such
// that filter(release) == true. An error is returned if the
// configmap fails to retrieve the releases.
func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
func (cfgmaps *ConfigMaps) List(filter func(release.Releaser) bool) ([]release.Releaser, error) {
lsel := kblabels.Set{"owner": "helm"}.AsSelector()
opts := metav1.ListOptions{LabelSelector: lsel.String()}
@ -95,7 +96,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas
return nil, err
}
var results []*rspb.Release
var results []release.Releaser
// iterate over the configmaps object list
// and decode each release
@ -117,7 +118,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas
// Query fetches all releases that match the provided map of labels.
// An error is returned if the configmap fails to retrieve the releases.
func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, error) {
func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]release.Releaser, error) {
ls := kblabels.Set{}
for k, v := range labels {
if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
@ -138,7 +139,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err
return nil, ErrReleaseNotFound
}
var results []*rspb.Release
var results []release.Releaser
for _, item := range list.Items {
rls, err := decodeRelease(item.Data["release"])
if err != nil {
@ -153,18 +154,28 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err
// Create creates a new ConfigMap holding the release. If the
// ConfigMap already exists, ErrReleaseExists is returned.
func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error {
func (cfgmaps *ConfigMaps) Create(key string, rls release.Releaser) error {
// set labels for configmaps object meta data
var lbs labels
rac, err := release.NewAccessor(rls)
if err != nil {
return err
}
lbs.init()
lbs.fromMap(rls.Labels)
lbs.fromMap(rac.Labels())
lbs.set("createdAt", fmt.Sprintf("%v", time.Now().Unix()))
rel, err := releaserToV1Release(rls)
if err != nil {
return err
}
// create a new configmap to hold the release
obj, err := newConfigMapsObject(key, rls, lbs)
obj, err := newConfigMapsObject(key, rel, lbs)
if err != nil {
slog.Debug("failed to encode release", "name", rls.Name, slog.Any("error", err))
slog.Debug("failed to encode release", "name", rac.Name(), slog.Any("error", err))
return err
}
// push the configmap object out into the kubiverse
@ -181,10 +192,15 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error {
// Update updates the ConfigMap holding the release. If not found
// the ConfigMap is created to hold the release.
func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
func (cfgmaps *ConfigMaps) Update(key string, rel release.Releaser) error {
// set labels for configmaps object meta data
var lbs labels
rls, err := releaserToV1Release(rel)
if err != nil {
return err
}
lbs.init()
lbs.fromMap(rls.Labels)
lbs.set("modifiedAt", fmt.Sprintf("%v", time.Now().Unix()))
@ -205,7 +221,7 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
}
// Delete deletes the ConfigMap holding the release named by key.
func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) {
func (cfgmaps *ConfigMaps) Delete(key string) (rls release.Releaser, err error) {
// fetch the release to check existence
if rls, err = cfgmaps.Get(key); err != nil {
return nil, err

@ -22,6 +22,8 @@ import (
v1 "k8s.io/api/core/v1"
"helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -37,7 +39,7 @@ func TestConfigMapGet(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)
@ -57,7 +59,7 @@ func TestUncompressedConfigMapGet(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
// Create a test fixture which contains an uncompressed release
cfgmap, err := newConfigMapsObject(key, rel, nil)
@ -84,19 +86,35 @@ func TestUncompressedConfigMapGet(t *testing.T) {
}
}
func convertReleaserToV1(t *testing.T, rel release.Releaser) *rspb.Release {
t.Helper()
switch r := rel.(type) {
case rspb.Release:
return &r
case *rspb.Release:
return r
case nil:
return nil
}
t.Fatalf("Unsupported release type: %T", rel)
return nil
}
func TestConfigMapList(t *testing.T) {
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{
releaseStub("key-1", 1, "default", rspb.StatusUninstalled),
releaseStub("key-2", 1, "default", rspb.StatusUninstalled),
releaseStub("key-3", 1, "default", rspb.StatusDeployed),
releaseStub("key-4", 1, "default", rspb.StatusDeployed),
releaseStub("key-5", 1, "default", rspb.StatusSuperseded),
releaseStub("key-6", 1, "default", rspb.StatusSuperseded),
releaseStub("key-1", 1, "default", common.StatusUninstalled),
releaseStub("key-2", 1, "default", common.StatusUninstalled),
releaseStub("key-3", 1, "default", common.StatusDeployed),
releaseStub("key-4", 1, "default", common.StatusDeployed),
releaseStub("key-5", 1, "default", common.StatusSuperseded),
releaseStub("key-6", 1, "default", common.StatusSuperseded),
}...)
// list all deleted releases
del, err := cfgmaps.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusUninstalled
del, err := cfgmaps.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusUninstalled
})
// check
if err != nil {
@ -107,8 +125,9 @@ func TestConfigMapList(t *testing.T) {
}
// list all deployed releases
dpl, err := cfgmaps.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusDeployed
dpl, err := cfgmaps.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusDeployed
})
// check
if err != nil {
@ -119,8 +138,9 @@ func TestConfigMapList(t *testing.T) {
}
// list all superseded releases
ssd, err := cfgmaps.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusSuperseded
ssd, err := cfgmaps.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusSuperseded
})
// check
if err != nil {
@ -130,7 +150,7 @@ func TestConfigMapList(t *testing.T) {
t.Errorf("Expected 2 superseded, got %d", len(ssd))
}
// Check if release having both system and custom labels, this is needed to ensure that selector filtering would work.
rls := ssd[0]
rls := convertReleaserToV1(t, ssd[0])
_, ok := rls.Labels["name"]
if !ok {
t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels)
@ -143,12 +163,12 @@ func TestConfigMapList(t *testing.T) {
func TestConfigMapQuery(t *testing.T) {
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{
releaseStub("key-1", 1, "default", rspb.StatusUninstalled),
releaseStub("key-2", 1, "default", rspb.StatusUninstalled),
releaseStub("key-3", 1, "default", rspb.StatusDeployed),
releaseStub("key-4", 1, "default", rspb.StatusDeployed),
releaseStub("key-5", 1, "default", rspb.StatusSuperseded),
releaseStub("key-6", 1, "default", rspb.StatusSuperseded),
releaseStub("key-1", 1, "default", common.StatusUninstalled),
releaseStub("key-2", 1, "default", common.StatusUninstalled),
releaseStub("key-3", 1, "default", common.StatusDeployed),
releaseStub("key-4", 1, "default", common.StatusDeployed),
releaseStub("key-5", 1, "default", common.StatusSuperseded),
releaseStub("key-6", 1, "default", common.StatusSuperseded),
}...)
rls, err := cfgmaps.Query(map[string]string{"status": "deployed"})
@ -172,7 +192,7 @@ func TestConfigMapCreate(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
// store the release in a configmap
if err := cfgmaps.Create(key, rel); err != nil {
@ -196,12 +216,12 @@ func TestConfigMapUpdate(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)
// modify release status code
rel.Info.Status = rspb.StatusSuperseded
rel.Info.Status = common.StatusSuperseded
// perform the update
if err := cfgmaps.Update(key, rel); err != nil {
@ -209,10 +229,11 @@ func TestConfigMapUpdate(t *testing.T) {
}
// fetch the updated release
got, err := cfgmaps.Get(key)
goti, err := cfgmaps.Get(key)
if err != nil {
t.Fatalf("Failed to get release with key %q: %s", key, err)
}
got := convertReleaserToV1(t, goti)
// check release has actually been updated by comparing modified fields
if rel.Info.Status != got.Info.Status {
@ -225,7 +246,7 @@ func TestConfigMapDelete(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)

@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -58,7 +59,7 @@ func NewErrNoDeployedReleases(releaseName string) error {
// Create stores the release or returns ErrReleaseExists
// if an identical release already exists.
type Creator interface {
Create(key string, rls *rspb.Release) error
Create(key string, rls release.Releaser) error
}
// Updator is the interface that wraps the Update method.
@ -66,7 +67,7 @@ type Creator interface {
// Update updates an existing release or returns
// ErrReleaseNotFound if the release does not exist.
type Updator interface {
Update(key string, rls *rspb.Release) error
Update(key string, rls release.Releaser) error
}
// Deletor is the interface that wraps the Delete method.
@ -74,7 +75,7 @@ type Updator interface {
// Delete deletes the release named by key or returns
// ErrReleaseNotFound if the release does not exist.
type Deletor interface {
Delete(key string) (*rspb.Release, error)
Delete(key string) (release.Releaser, error)
}
// Queryor is the interface that wraps the Get and List methods.
@ -86,9 +87,9 @@ type Deletor interface {
//
// Query returns the set of all releases that match the provided label set.
type Queryor interface {
Get(key string) (*rspb.Release, error)
List(filter func(*rspb.Release) bool) ([]*rspb.Release, error)
Query(labels map[string]string) ([]*rspb.Release, error)
Get(key string) (release.Releaser, error)
List(filter func(release.Releaser) bool) ([]release.Releaser, error)
Query(labels map[string]string) ([]release.Releaser, error)
}
// Driver is the interface composed of Creator, Updator, Deletor, and Queryor
@ -102,3 +103,18 @@ type Driver interface {
Queryor
Name() string
}
// releaserToV1Release is a helper function to convert a v1 release passed by interface
// into the type object.
func releaserToV1Release(rel release.Releaser) (*rspb.Release, error) {
switch r := rel.(type) {
case rspb.Release:
return &r, nil
case *rspb.Release:
return r, nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("unsupported release type: %T", rel)
}
}

@ -21,7 +21,7 @@ import (
"strings"
"sync"
rspb "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/release"
)
var _ Driver = (*Memory)(nil)
@ -61,7 +61,7 @@ func (mem *Memory) Name() string {
}
// Get returns the release named by key or returns ErrReleaseNotFound.
func (mem *Memory) Get(key string) (*rspb.Release, error) {
func (mem *Memory) Get(key string) (release.Releaser, error) {
defer unlock(mem.rlock())
keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.")
@ -83,10 +83,10 @@ func (mem *Memory) Get(key string) (*rspb.Release, error) {
}
// List returns the list of all releases such that filter(release) == true
func (mem *Memory) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
func (mem *Memory) List(filter func(release.Releaser) bool) ([]release.Releaser, error) {
defer unlock(mem.rlock())
var ls []*rspb.Release
var ls []release.Releaser
for namespace := range mem.cache {
if mem.namespace != "" {
// Should only list releases of this namespace
@ -109,7 +109,7 @@ func (mem *Memory) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error
}
// Query returns the set of releases that match the provided set of labels
func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) {
func (mem *Memory) Query(keyvals map[string]string) ([]release.Releaser, error) {
defer unlock(mem.rlock())
var lbs labels
@ -117,7 +117,7 @@ func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) {
lbs.init()
lbs.fromMap(keyvals)
var ls []*rspb.Release
var ls []release.Releaser
for namespace := range mem.cache {
if mem.namespace != "" {
// Should only query releases of this namespace
@ -150,9 +150,13 @@ func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) {
}
// Create creates a new release or returns ErrReleaseExists.
func (mem *Memory) Create(key string, rls *rspb.Release) error {
func (mem *Memory) Create(key string, rel release.Releaser) error {
defer unlock(mem.wlock())
rls, err := releaserToV1Release(rel)
if err != nil {
return err
}
// For backwards compatibility, we protect against an unset namespace
namespace := rls.Namespace
if namespace == "" {
@ -176,9 +180,14 @@ func (mem *Memory) Create(key string, rls *rspb.Release) error {
}
// Update updates a release or returns ErrReleaseNotFound.
func (mem *Memory) Update(key string, rls *rspb.Release) error {
func (mem *Memory) Update(key string, rel release.Releaser) error {
defer unlock(mem.wlock())
rls, err := releaserToV1Release(rel)
if err != nil {
return err
}
// For backwards compatibility, we protect against an unset namespace
namespace := rls.Namespace
if namespace == "" {
@ -196,7 +205,7 @@ func (mem *Memory) Update(key string, rls *rspb.Release) error {
}
// Delete deletes a release or returns ErrReleaseNotFound.
func (mem *Memory) Delete(key string) (*rspb.Release, error) {
func (mem *Memory) Delete(key string) (release.Releaser, error) {
defer unlock(mem.wlock())
keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.")

@ -21,6 +21,9 @@ import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -38,22 +41,22 @@ func TestMemoryCreate(t *testing.T) {
}{
{
"create should succeed",
releaseStub("rls-c", 1, "default", rspb.StatusDeployed),
releaseStub("rls-c", 1, "default", common.StatusDeployed),
false,
},
{
"create should fail (release already exists)",
releaseStub("rls-a", 1, "default", rspb.StatusDeployed),
releaseStub("rls-a", 1, "default", common.StatusDeployed),
true,
},
{
"create in namespace should succeed",
releaseStub("rls-a", 1, "mynamespace", rspb.StatusDeployed),
releaseStub("rls-a", 1, "mynamespace", common.StatusDeployed),
false,
},
{
"create in other namespace should fail (release already exists)",
releaseStub("rls-c", 1, "mynamespace", rspb.StatusDeployed),
releaseStub("rls-c", 1, "mynamespace", common.StatusDeployed),
true,
},
}
@ -104,8 +107,9 @@ func TestMemoryList(t *testing.T) {
ts.SetNamespace("default")
// list all deployed releases
dpl, err := ts.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusDeployed
dpl, err := ts.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusDeployed
})
// check
if err != nil {
@ -116,8 +120,9 @@ func TestMemoryList(t *testing.T) {
}
// list all superseded releases
ssd, err := ts.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusSuperseded
ssd, err := ts.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusSuperseded
})
// check
if err != nil {
@ -128,8 +133,9 @@ func TestMemoryList(t *testing.T) {
}
// list all deleted releases
del, err := ts.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusUninstalled
del, err := ts.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusUninstalled
})
// check
if err != nil {
@ -185,25 +191,25 @@ func TestMemoryUpdate(t *testing.T) {
{
"update release status",
"rls-a.v4",
releaseStub("rls-a", 4, "default", rspb.StatusSuperseded),
releaseStub("rls-a", 4, "default", common.StatusSuperseded),
false,
},
{
"update release does not exist",
"rls-c.v1",
releaseStub("rls-c", 1, "default", rspb.StatusUninstalled),
releaseStub("rls-c", 1, "default", common.StatusUninstalled),
true,
},
{
"update release status in namespace",
"rls-c.v4",
releaseStub("rls-c", 4, "mynamespace", rspb.StatusSuperseded),
releaseStub("rls-c", 4, "mynamespace", common.StatusSuperseded),
false,
},
{
"update release in namespace does not exist",
"rls-a.v1",
releaseStub("rls-a", 1, "mynamespace", rspb.StatusUninstalled),
releaseStub("rls-a", 1, "mynamespace", common.StatusUninstalled),
true,
},
}
@ -255,17 +261,23 @@ func TestMemoryDelete(t *testing.T) {
startLen := len(start)
for _, tt := range tests {
ts.SetNamespace(tt.namespace)
if rel, err := ts.Delete(tt.key); err != nil {
rel, err := ts.Delete(tt.key)
var rls *rspb.Release
if err == nil {
rls = convertReleaserToV1(t, rel)
}
if err != nil {
if !tt.err {
t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err)
}
continue
} else if tt.err {
t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key)
} else if fmt.Sprintf("%s.v%d", rel.Name, rel.Version) != tt.key {
t.Fatalf("Asked for delete on %s, but deleted %d", tt.key, rel.Version)
} else if fmt.Sprintf("%s.v%d", rls.Name, rls.Version) != tt.key {
t.Fatalf("Asked for delete on %s, but deleted %d", tt.key, rls.Version)
}
_, err := ts.Get(tt.key)
_, err = ts.Get(tt.key)
if err == nil {
t.Errorf("Expected an error when asking for a deleted key")
}
@ -282,7 +294,9 @@ func TestMemoryDelete(t *testing.T) {
if startLen-2 != endLen {
t.Errorf("expected end to be %d instead of %d", startLen-2, endLen)
for _, ee := range end {
t.Logf("Name: %s, Version: %d", ee.Name, ee.Version)
rac, err := release.NewAccessor(ee)
assert.NoError(t, err, "unable to get release accessor")
t.Logf("Name: %s, Version: %d", rac.Name(), rac.Version())
}
}

@ -31,10 +31,11 @@ import (
kblabels "k8s.io/apimachinery/pkg/labels"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
func releaseStub(name string, vers int, namespace string, status rspb.Status) *rspb.Release {
func releaseStub(name string, vers int, namespace string, status common.Status) *rspb.Release {
return &rspb.Release{
Name: name,
Version: vers,
@ -55,20 +56,20 @@ func tsFixtureMemory(t *testing.T) *Memory {
t.Helper()
hs := []*rspb.Release{
// rls-a
releaseStub("rls-a", 4, "default", rspb.StatusDeployed),
releaseStub("rls-a", 1, "default", rspb.StatusSuperseded),
releaseStub("rls-a", 3, "default", rspb.StatusSuperseded),
releaseStub("rls-a", 2, "default", rspb.StatusSuperseded),
releaseStub("rls-a", 4, "default", common.StatusDeployed),
releaseStub("rls-a", 1, "default", common.StatusSuperseded),
releaseStub("rls-a", 3, "default", common.StatusSuperseded),
releaseStub("rls-a", 2, "default", common.StatusSuperseded),
// rls-b
releaseStub("rls-b", 4, "default", rspb.StatusDeployed),
releaseStub("rls-b", 1, "default", rspb.StatusSuperseded),
releaseStub("rls-b", 3, "default", rspb.StatusSuperseded),
releaseStub("rls-b", 2, "default", rspb.StatusSuperseded),
releaseStub("rls-b", 4, "default", common.StatusDeployed),
releaseStub("rls-b", 1, "default", common.StatusSuperseded),
releaseStub("rls-b", 3, "default", common.StatusSuperseded),
releaseStub("rls-b", 2, "default", common.StatusSuperseded),
// rls-c in other namespace
releaseStub("rls-c", 4, "mynamespace", rspb.StatusDeployed),
releaseStub("rls-c", 1, "mynamespace", rspb.StatusSuperseded),
releaseStub("rls-c", 3, "mynamespace", rspb.StatusSuperseded),
releaseStub("rls-c", 2, "mynamespace", rspb.StatusSuperseded),
releaseStub("rls-c", 4, "mynamespace", common.StatusDeployed),
releaseStub("rls-c", 1, "mynamespace", common.StatusSuperseded),
releaseStub("rls-c", 3, "mynamespace", common.StatusSuperseded),
releaseStub("rls-c", 2, "mynamespace", common.StatusSuperseded),
}
mem := NewMemory()

@ -20,13 +20,13 @@ import (
"reflect"
"testing"
rspb "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/release/common"
)
func TestRecordsAdd(t *testing.T) {
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
})
var tests = []struct {
@ -39,13 +39,13 @@ func TestRecordsAdd(t *testing.T) {
"add valid key",
"rls-a.v3",
false,
newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", common.StatusSuperseded)),
},
{
"add already existing key",
"rls-a.v1",
true,
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusDeployed)),
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusDeployed)),
},
}
@ -70,8 +70,8 @@ func TestRecordsRemove(t *testing.T) {
}
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
})
startLen := rs.Len()
@ -98,8 +98,8 @@ func TestRecordsRemove(t *testing.T) {
func TestRecordsRemoveAt(t *testing.T) {
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
})
if len(rs) != 2 {
@ -114,8 +114,8 @@ func TestRecordsRemoveAt(t *testing.T) {
func TestRecordsGet(t *testing.T) {
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
})
var tests = []struct {
@ -126,7 +126,7 @@ func TestRecordsGet(t *testing.T) {
{
"get valid key",
"rls-a.v1",
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)),
},
{
"get invalid key",
@ -145,8 +145,8 @@ func TestRecordsGet(t *testing.T) {
func TestRecordsIndex(t *testing.T) {
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
})
var tests = []struct {
@ -176,8 +176,8 @@ func TestRecordsIndex(t *testing.T) {
func TestRecordsExists(t *testing.T) {
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
})
var tests = []struct {
@ -207,8 +207,8 @@ func TestRecordsExists(t *testing.T) {
func TestRecordsReplace(t *testing.T) {
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
})
var tests = []struct {
@ -220,13 +220,13 @@ func TestRecordsReplace(t *testing.T) {
{
"replace with existing key",
"rls-a.v2",
newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", common.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
},
{
"replace with non existing key",
"rls-a.v4",
newRecord("rls-a.v4", releaseStub("rls-a", 4, "default", rspb.StatusDeployed)),
newRecord("rls-a.v4", releaseStub("rls-a", 4, "default", common.StatusDeployed)),
nil,
},
}

@ -31,6 +31,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -60,7 +61,7 @@ func (secrets *Secrets) Name() string {
// Get fetches the release named by key. The corresponding release is returned
// or error if not found.
func (secrets *Secrets) Get(key string) (*rspb.Release, error) {
func (secrets *Secrets) Get(key string) (release.Releaser, error) {
// fetch the secret holding the release named by key
obj, err := secrets.impl.Get(context.Background(), key, metav1.GetOptions{})
if err != nil {
@ -81,7 +82,7 @@ func (secrets *Secrets) Get(key string) (*rspb.Release, error) {
// List fetches all releases and returns the list releases such
// that filter(release) == true. An error is returned if the
// secret fails to retrieve the releases.
func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
func (secrets *Secrets) List(filter func(release.Releaser) bool) ([]release.Releaser, error) {
lsel := kblabels.Set{"owner": "helm"}.AsSelector()
opts := metav1.ListOptions{LabelSelector: lsel.String()}
@ -90,7 +91,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release,
return nil, fmt.Errorf("list: failed to list: %w", err)
}
var results []*rspb.Release
var results []release.Releaser
// iterate over the secrets object list
// and decode each release
@ -112,7 +113,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release,
// Query fetches all releases that match the provided map of labels.
// An error is returned if the secret fails to retrieve the releases.
func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) {
func (secrets *Secrets) Query(labels map[string]string) ([]release.Releaser, error) {
ls := kblabels.Set{}
for k, v := range labels {
if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
@ -132,7 +133,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error)
return nil, ErrReleaseNotFound
}
var results []*rspb.Release
var results []release.Releaser
for _, item := range list.Items {
rls, err := decodeRelease(string(item.Data["release"]))
if err != nil {
@ -147,10 +148,15 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error)
// Create creates a new Secret holding the release. If the
// Secret already exists, ErrReleaseExists is returned.
func (secrets *Secrets) Create(key string, rls *rspb.Release) error {
func (secrets *Secrets) Create(key string, rel release.Releaser) error {
// set labels for secrets object meta data
var lbs labels
rls, err := releaserToV1Release(rel)
if err != nil {
return err
}
lbs.init()
lbs.fromMap(rls.Labels)
lbs.set("createdAt", fmt.Sprintf("%v", time.Now().Unix()))
@ -173,10 +179,15 @@ func (secrets *Secrets) Create(key string, rls *rspb.Release) error {
// Update updates the Secret holding the release. If not found
// the Secret is created to hold the release.
func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
func (secrets *Secrets) Update(key string, rel release.Releaser) error {
// set labels for secrets object meta data
var lbs labels
rls, err := releaserToV1Release(rel)
if err != nil {
return err
}
lbs.init()
lbs.fromMap(rls.Labels)
lbs.set("modifiedAt", fmt.Sprintf("%v", time.Now().Unix()))
@ -195,7 +206,7 @@ func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
}
// Delete deletes the Secret holding the release named by key.
func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) {
func (secrets *Secrets) Delete(key string) (rls release.Releaser, err error) {
// fetch the release to check existence
if rls, err = secrets.Get(key); err != nil {
return nil, err

@ -22,6 +22,8 @@ import (
v1 "k8s.io/api/core/v1"
"helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -37,7 +39,7 @@ func TestSecretGet(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)
@ -57,7 +59,7 @@ func TestUNcompressedSecretGet(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
// Create a test fixture which contains an uncompressed release
secret, err := newSecretsObject(key, rel, nil)
@ -86,17 +88,18 @@ func TestUNcompressedSecretGet(t *testing.T) {
func TestSecretList(t *testing.T) {
secrets := newTestFixtureSecrets(t, []*rspb.Release{
releaseStub("key-1", 1, "default", rspb.StatusUninstalled),
releaseStub("key-2", 1, "default", rspb.StatusUninstalled),
releaseStub("key-3", 1, "default", rspb.StatusDeployed),
releaseStub("key-4", 1, "default", rspb.StatusDeployed),
releaseStub("key-5", 1, "default", rspb.StatusSuperseded),
releaseStub("key-6", 1, "default", rspb.StatusSuperseded),
releaseStub("key-1", 1, "default", common.StatusUninstalled),
releaseStub("key-2", 1, "default", common.StatusUninstalled),
releaseStub("key-3", 1, "default", common.StatusDeployed),
releaseStub("key-4", 1, "default", common.StatusDeployed),
releaseStub("key-5", 1, "default", common.StatusSuperseded),
releaseStub("key-6", 1, "default", common.StatusSuperseded),
}...)
// list all deleted releases
del, err := secrets.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusUninstalled
del, err := secrets.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusUninstalled
})
// check
if err != nil {
@ -107,8 +110,9 @@ func TestSecretList(t *testing.T) {
}
// list all deployed releases
dpl, err := secrets.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusDeployed
dpl, err := secrets.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusDeployed
})
// check
if err != nil {
@ -119,8 +123,9 @@ func TestSecretList(t *testing.T) {
}
// list all superseded releases
ssd, err := secrets.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusSuperseded
ssd, err := secrets.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusSuperseded
})
// check
if err != nil {
@ -130,7 +135,7 @@ func TestSecretList(t *testing.T) {
t.Errorf("Expected 2 superseded, got %d", len(ssd))
}
// Check if release having both system and custom labels, this is needed to ensure that selector filtering would work.
rls := ssd[0]
rls := convertReleaserToV1(t, ssd[0])
_, ok := rls.Labels["name"]
if !ok {
t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels)
@ -143,12 +148,12 @@ func TestSecretList(t *testing.T) {
func TestSecretQuery(t *testing.T) {
secrets := newTestFixtureSecrets(t, []*rspb.Release{
releaseStub("key-1", 1, "default", rspb.StatusUninstalled),
releaseStub("key-2", 1, "default", rspb.StatusUninstalled),
releaseStub("key-3", 1, "default", rspb.StatusDeployed),
releaseStub("key-4", 1, "default", rspb.StatusDeployed),
releaseStub("key-5", 1, "default", rspb.StatusSuperseded),
releaseStub("key-6", 1, "default", rspb.StatusSuperseded),
releaseStub("key-1", 1, "default", common.StatusUninstalled),
releaseStub("key-2", 1, "default", common.StatusUninstalled),
releaseStub("key-3", 1, "default", common.StatusDeployed),
releaseStub("key-4", 1, "default", common.StatusDeployed),
releaseStub("key-5", 1, "default", common.StatusSuperseded),
releaseStub("key-6", 1, "default", common.StatusSuperseded),
}...)
rls, err := secrets.Query(map[string]string{"status": "deployed"})
@ -172,7 +177,7 @@ func TestSecretCreate(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
// store the release in a secret
if err := secrets.Create(key, rel); err != nil {
@ -196,12 +201,12 @@ func TestSecretUpdate(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)
// modify release status code
rel.Info.Status = rspb.StatusSuperseded
rel.Info.Status = common.StatusSuperseded
// perform the update
if err := secrets.Update(key, rel); err != nil {
@ -209,10 +214,11 @@ func TestSecretUpdate(t *testing.T) {
}
// fetch the updated release
got, err := secrets.Get(key)
goti, err := secrets.Get(key)
if err != nil {
t.Fatalf("Failed to get release with key %q: %s", key, err)
}
got := convertReleaserToV1(t, goti)
// check release has actually been updated by comparing modified fields
if rel.Info.Status != got.Info.Status {
@ -225,7 +231,7 @@ func TestSecretDelete(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)

@ -32,6 +32,7 @@ import (
// Import pq for postgres dialect
_ "github.com/lib/pq"
"helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -297,7 +298,7 @@ func NewSQL(connectionString string, namespace string) (*SQL, error) {
}
// Get returns the release named by key.
func (s *SQL) Get(key string) (*rspb.Release, error) {
func (s *SQL) Get(key string) (release.Releaser, error) {
var record SQLReleaseWrapper
qb := s.statementBuilder.
@ -333,7 +334,7 @@ func (s *SQL) Get(key string) (*rspb.Release, error) {
}
// List returns the list of all releases such that filter(release) == true
func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, error) {
sb := s.statementBuilder.
Select(sqlReleaseTableKeyColumn, sqlReleaseTableNamespaceColumn, sqlReleaseTableBodyColumn).
From(sqlReleaseTableName).
@ -356,7 +357,7 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
return nil, err
}
var releases []*rspb.Release
var releases []release.Releaser
for _, record := range records {
release, err := decodeRelease(record.Body)
if err != nil {
@ -379,7 +380,7 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
}
// Query returns the set of releases that match the provided set of labels.
func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) {
func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) {
sb := s.statementBuilder.
Select(sqlReleaseTableKeyColumn, sqlReleaseTableNamespaceColumn, sqlReleaseTableBodyColumn).
From(sqlReleaseTableName)
@ -420,7 +421,7 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) {
return nil, ErrReleaseNotFound
}
var releases []*rspb.Release
var releases []release.Releaser
for _, record := range records {
release, err := decodeRelease(record.Body)
if err != nil {
@ -444,7 +445,12 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) {
}
// Create creates a new release.
func (s *SQL) Create(key string, rls *rspb.Release) error {
func (s *SQL) Create(key string, rel release.Releaser) error {
rls, err := releaserToV1Release(rel)
if err != nil {
return err
}
namespace := rls.Namespace
if namespace == "" {
namespace = defaultNamespace
@ -551,7 +557,11 @@ func (s *SQL) Create(key string, rls *rspb.Release) error {
}
// Update updates a release.
func (s *SQL) Update(key string, rls *rspb.Release) error {
func (s *SQL) Update(key string, rel release.Releaser) error {
rls, err := releaserToV1Release(rel)
if err != nil {
return err
}
namespace := rls.Namespace
if namespace == "" {
namespace = defaultNamespace
@ -590,7 +600,7 @@ func (s *SQL) Update(key string, rls *rspb.Release) error {
}
// Delete deletes a release or returns ErrReleaseNotFound.
func (s *SQL) Delete(key string) (*rspb.Release, error) {
func (s *SQL) Delete(key string) (release.Releaser, error) {
transaction, err := s.db.Beginx()
if err != nil {
slog.Debug("failed to start SQL transaction", slog.Any("error", err))

@ -24,6 +24,8 @@ import (
sqlmock "github.com/DATA-DOG/go-sqlmock"
migrate "github.com/rubenv/sql-migrate"
"helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -66,7 +68,7 @@ func TestSQLGet(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
body, _ := encodeRelease(rel)
@ -109,12 +111,12 @@ func TestSQLGet(t *testing.T) {
func TestSQLList(t *testing.T) {
releases := []*rspb.Release{}
releases = append(releases, releaseStub("key-1", 1, "default", rspb.StatusUninstalled))
releases = append(releases, releaseStub("key-2", 1, "default", rspb.StatusUninstalled))
releases = append(releases, releaseStub("key-3", 1, "default", rspb.StatusDeployed))
releases = append(releases, releaseStub("key-4", 1, "default", rspb.StatusDeployed))
releases = append(releases, releaseStub("key-5", 1, "default", rspb.StatusSuperseded))
releases = append(releases, releaseStub("key-6", 1, "default", rspb.StatusSuperseded))
releases = append(releases, releaseStub("key-1", 1, "default", common.StatusUninstalled))
releases = append(releases, releaseStub("key-2", 1, "default", common.StatusUninstalled))
releases = append(releases, releaseStub("key-3", 1, "default", common.StatusDeployed))
releases = append(releases, releaseStub("key-4", 1, "default", common.StatusDeployed))
releases = append(releases, releaseStub("key-5", 1, "default", common.StatusSuperseded))
releases = append(releases, releaseStub("key-6", 1, "default", common.StatusSuperseded))
sqlDriver, mock := newTestFixtureSQL(t)
@ -147,8 +149,9 @@ func TestSQLList(t *testing.T) {
}
// list all deleted releases
del, err := sqlDriver.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusUninstalled
del, err := sqlDriver.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusUninstalled
})
// check
if err != nil {
@ -159,8 +162,9 @@ func TestSQLList(t *testing.T) {
}
// list all deployed releases
dpl, err := sqlDriver.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusDeployed
dpl, err := sqlDriver.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusDeployed
})
// check
if err != nil {
@ -171,8 +175,9 @@ func TestSQLList(t *testing.T) {
}
// list all superseded releases
ssd, err := sqlDriver.List(func(rel *rspb.Release) bool {
return rel.Info.Status == rspb.StatusSuperseded
ssd, err := sqlDriver.List(func(rel release.Releaser) bool {
rls := convertReleaserToV1(t, rel)
return rls.Info.Status == common.StatusSuperseded
})
// check
if err != nil {
@ -187,7 +192,7 @@ func TestSQLList(t *testing.T) {
}
// Check if release having both system and custom labels, this is needed to ensure that selector filtering would work.
rls := ssd[0]
rls := convertReleaserToV1(t, ssd[0])
_, ok := rls.Labels["name"]
if !ok {
t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels)
@ -203,7 +208,7 @@ func TestSqlCreate(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
sqlDriver, mock := newTestFixtureSQL(t)
body, _ := encodeRelease(rel)
@ -260,7 +265,7 @@ func TestSqlCreateAlreadyExists(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
sqlDriver, mock := newTestFixtureSQL(t)
body, _ := encodeRelease(rel)
@ -321,7 +326,7 @@ func TestSqlUpdate(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
sqlDriver, mock := newTestFixtureSQL(t)
body, _ := encodeRelease(rel)
@ -370,9 +375,9 @@ func TestSqlQuery(t *testing.T) {
"owner": sqlReleaseDefaultOwner,
}
supersededRelease := releaseStub("smug-pigeon", 1, "default", rspb.StatusSuperseded)
supersededRelease := releaseStub("smug-pigeon", 1, "default", common.StatusSuperseded)
supersededReleaseBody, _ := encodeRelease(supersededRelease)
deployedRelease := releaseStub("smug-pigeon", 2, "default", rspb.StatusDeployed)
deployedRelease := releaseStub("smug-pigeon", 2, "default", common.StatusDeployed)
deployedReleaseBody, _ := encodeRelease(deployedRelease)
// Let's actually start our test
@ -482,7 +487,7 @@ func TestSqlDelete(t *testing.T) {
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
rel := releaseStub(name, vers, namespace, common.StatusDeployed)
body, _ := encodeRelease(rel)

@ -22,6 +22,8 @@ import (
"log/slog"
"strings"
"helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
relutil "helm.sh/helm/v4/pkg/release/v1/util"
"helm.sh/helm/v4/pkg/storage/driver"
@ -47,7 +49,7 @@ type Storage struct {
// Get retrieves the release from storage. An error is returned
// if the storage driver failed to fetch the release, or the
// release identified by the key, version pair does not exist.
func (s *Storage) Get(name string, version int) (*rspb.Release, error) {
func (s *Storage) Get(name string, version int) (release.Releaser, error) {
slog.Debug("getting release", "key", makeKey(name, version))
return s.Driver.Get(makeKey(name, version))
}
@ -55,62 +57,99 @@ func (s *Storage) Get(name string, version int) (*rspb.Release, error) {
// Create creates a new storage entry holding the release. An
// error is returned if the storage driver fails to store the
// release, or a release with an identical key already exists.
func (s *Storage) Create(rls *rspb.Release) error {
slog.Debug("creating release", "key", makeKey(rls.Name, rls.Version))
func (s *Storage) Create(rls release.Releaser) error {
rac, err := release.NewAccessor(rls)
if err != nil {
return err
}
slog.Debug("creating release", "key", makeKey(rac.Name(), rac.Version()))
if s.MaxHistory > 0 {
// Want to make space for one more release.
if err := s.removeLeastRecent(rls.Name, s.MaxHistory-1); err != nil &&
if err := s.removeLeastRecent(rac.Name(), s.MaxHistory-1); err != nil &&
!errors.Is(err, driver.ErrReleaseNotFound) {
return err
}
}
return s.Driver.Create(makeKey(rls.Name, rls.Version), rls)
return s.Driver.Create(makeKey(rac.Name(), rac.Version()), rls)
}
// Update updates the release in storage. An error is returned if the
// storage backend fails to update the release or if the release
// does not exist.
func (s *Storage) Update(rls *rspb.Release) error {
slog.Debug("updating release", "key", makeKey(rls.Name, rls.Version))
return s.Driver.Update(makeKey(rls.Name, rls.Version), rls)
func (s *Storage) Update(rls release.Releaser) error {
rac, err := release.NewAccessor(rls)
if err != nil {
return err
}
slog.Debug("updating release", "key", makeKey(rac.Name(), rac.Version()))
return s.Driver.Update(makeKey(rac.Name(), rac.Version()), rls)
}
// Delete deletes the release from storage. An error is returned if
// the storage backend fails to delete the release or if the release
// does not exist.
func (s *Storage) Delete(name string, version int) (*rspb.Release, error) {
func (s *Storage) Delete(name string, version int) (release.Releaser, error) {
slog.Debug("deleting release", "key", makeKey(name, version))
return s.Driver.Delete(makeKey(name, version))
}
// ListReleases returns all releases from storage. An error is returned if the
// storage backend fails to retrieve the releases.
func (s *Storage) ListReleases() ([]*rspb.Release, error) {
func (s *Storage) ListReleases() ([]release.Releaser, error) {
slog.Debug("listing all releases in storage")
return s.List(func(_ *rspb.Release) bool { return true })
return s.List(func(_ release.Releaser) bool { return true })
}
// releaserToV1Release is a helper function to convert a v1 release passed by interface
// into the type object.
func releaserToV1Release(rel release.Releaser) (*rspb.Release, error) {
switch r := rel.(type) {
case rspb.Release:
return &r, nil
case *rspb.Release:
return r, nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("unsupported release type: %T", rel)
}
}
// ListUninstalled returns all releases with Status == UNINSTALLED. An error is returned
// if the storage backend fails to retrieve the releases.
func (s *Storage) ListUninstalled() ([]*rspb.Release, error) {
func (s *Storage) ListUninstalled() ([]release.Releaser, error) {
slog.Debug("listing uninstalled releases in storage")
return s.List(func(rls *rspb.Release) bool {
return relutil.StatusFilter(rspb.StatusUninstalled).Check(rls)
return s.List(func(rls release.Releaser) bool {
rel, err := releaserToV1Release(rls)
if err != nil {
// This will only happen if calling code does not pass the proper types. This is
// a problem with the application and not user data.
slog.Error("unable to convert release to typed release", slog.Any("error", err))
panic(fmt.Sprintf("unable to convert release to typed release: %s", err))
}
return relutil.StatusFilter(common.StatusUninstalled).Check(rel)
})
}
// ListDeployed returns all releases with Status == DEPLOYED. An error is returned
// if the storage backend fails to retrieve the releases.
func (s *Storage) ListDeployed() ([]*rspb.Release, error) {
func (s *Storage) ListDeployed() ([]release.Releaser, error) {
slog.Debug("listing all deployed releases in storage")
return s.List(func(rls *rspb.Release) bool {
return relutil.StatusFilter(rspb.StatusDeployed).Check(rls)
return s.List(func(rls release.Releaser) bool {
rel, err := releaserToV1Release(rls)
if err != nil {
// This will only happen if calling code does not pass the proper types. This is
// a problem with the application and not user data.
slog.Error("unable to convert release to typed release", slog.Any("error", err))
panic(fmt.Sprintf("unable to convert release to typed release: %s", err))
}
return relutil.StatusFilter(common.StatusDeployed).Check(rel)
})
}
// Deployed returns the last deployed release with the provided release name, or
// returns driver.NewErrNoDeployedReleases if not found.
func (s *Storage) Deployed(name string) (*rspb.Release, error) {
func (s *Storage) Deployed(name string) (release.Releaser, error) {
ls, err := s.DeployedAll(name)
if err != nil {
return nil, err
@ -120,16 +159,34 @@ func (s *Storage) Deployed(name string) (*rspb.Release, error) {
return nil, driver.NewErrNoDeployedReleases(name)
}
rls, err := releaseListToV1List(ls)
if err != nil {
return nil, err
}
// If executed concurrently, Helm's database gets corrupted
// and multiple releases are DEPLOYED. Take the latest.
relutil.Reverse(ls, relutil.SortByRevision)
relutil.Reverse(rls, relutil.SortByRevision)
return ls[0], nil
return rls[0], nil
}
func releaseListToV1List(ls []release.Releaser) ([]*rspb.Release, error) {
rls := make([]*rspb.Release, 0, len(ls))
for _, val := range ls {
rel, err := releaserToV1Release(val)
if err != nil {
return nil, err
}
rls = append(rls, rel)
}
return rls, nil
}
// DeployedAll returns all deployed releases with the provided name, or
// returns driver.NewErrNoDeployedReleases if not found.
func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) {
func (s *Storage) DeployedAll(name string) ([]release.Releaser, error) {
slog.Debug("getting deployed releases", "name", name)
ls, err := s.Query(map[string]string{
@ -148,7 +205,7 @@ func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) {
// History returns the revision history for the release with the provided name, or
// returns driver.ErrReleaseNotFound if no such release name exists.
func (s *Storage) History(name string) ([]*rspb.Release, error) {
func (s *Storage) History(name string) ([]release.Releaser, error) {
slog.Debug("getting release history", "name", name)
return s.Query(map[string]string{"name": name, "owner": "helm"})
@ -170,23 +227,31 @@ func (s *Storage) removeLeastRecent(name string, maximum int) error {
if len(h) <= maximum {
return nil
}
rls, err := releaseListToV1List(h)
if err != nil {
return err
}
// We want oldest to newest
relutil.SortByRevision(h)
relutil.SortByRevision(rls)
lastDeployed, err := s.Deployed(name)
if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) {
return err
}
var toDelete []*rspb.Release
for _, rel := range h {
var toDelete []release.Releaser
for _, rel := range rls {
// once we have enough releases to delete to reach the maximum, stop
if len(h)-len(toDelete) == maximum {
if len(rls)-len(toDelete) == maximum {
break
}
if lastDeployed != nil {
if rel.Version != lastDeployed.Version {
ldac, err := release.NewAccessor(lastDeployed)
if err != nil {
return err
}
if rel.Version != ldac.Version() {
toDelete = append(toDelete, rel)
}
} else {
@ -198,7 +263,12 @@ func (s *Storage) removeLeastRecent(name string, maximum int) error {
// multiple invocations of this function will eventually delete them all.
errs := []error{}
for _, rel := range toDelete {
err = s.deleteReleaseVersion(name, rel.Version)
rac, err := release.NewAccessor(rel)
if err != nil {
errs = append(errs, err)
continue
}
err = s.deleteReleaseVersion(name, rac.Version())
if err != nil {
errs = append(errs, err)
}
@ -226,7 +296,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error {
}
// Last fetches the last revision of the named release.
func (s *Storage) Last(name string) (*rspb.Release, error) {
func (s *Storage) Last(name string) (release.Releaser, error) {
slog.Debug("getting last revision", "name", name)
h, err := s.History(name)
if err != nil {
@ -235,9 +305,13 @@ func (s *Storage) Last(name string) (*rspb.Release, error) {
if len(h) == 0 {
return nil, fmt.Errorf("no revision for release %q", name)
}
rls, err := releaseListToV1List(h)
if err != nil {
return nil, err
}
relutil.Reverse(h, relutil.SortByRevision)
return h[0], nil
relutil.Reverse(rls, relutil.SortByRevision)
return rls[0], nil
}
// makeKey concatenates the Kubernetes storage object type, a release name and version

@ -22,6 +22,9 @@ import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1"
"helm.sh/helm/v4/pkg/storage/driver"
)
@ -56,13 +59,13 @@ func TestStorageUpdate(t *testing.T) {
rls := ReleaseTestData{
Name: "angry-beaver",
Version: 1,
Status: rspb.StatusDeployed,
Status: common.StatusDeployed,
}.ToRelease()
assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease")
// modify the release
rls.Info.Status = rspb.StatusUninstalled
rls.Info.Status = common.StatusUninstalled
assertErrNil(t.Fatal, storage.Update(rls), "UpdateRelease")
// retrieve the updated release
@ -106,13 +109,16 @@ func TestStorageDelete(t *testing.T) {
t.Errorf("unexpected error: %s", err)
}
rhist, err := releaseListToV1List(hist)
assert.NoError(t, err)
// We have now deleted one of the two records.
if len(hist) != 1 {
if len(rhist) != 1 {
t.Errorf("expected 1 record for deleted release version, got %d", len(hist))
}
if hist[0].Version != 2 {
t.Errorf("Expected version to be 2, got %d", hist[0].Version)
if rhist[0].Version != 2 {
t.Errorf("Expected version to be 2, got %d", rhist[0].Version)
}
}
@ -123,13 +129,13 @@ func TestStorageList(t *testing.T) {
// setup storage with test releases
setup := func() {
// release records
rls0 := ReleaseTestData{Name: "happy-catdog", Status: rspb.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: "livid-human", Status: rspb.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: "relaxed-cat", Status: rspb.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: "hungry-hippo", Status: rspb.StatusDeployed}.ToRelease()
rls4 := ReleaseTestData{Name: "angry-beaver", Status: rspb.StatusDeployed}.ToRelease()
rls5 := ReleaseTestData{Name: "opulent-frog", Status: rspb.StatusUninstalled}.ToRelease()
rls6 := ReleaseTestData{Name: "happy-liger", Status: rspb.StatusUninstalled}.ToRelease()
rls0 := ReleaseTestData{Name: "happy-catdog", Status: common.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: "livid-human", Status: common.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: "relaxed-cat", Status: common.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: "hungry-hippo", Status: common.StatusDeployed}.ToRelease()
rls4 := ReleaseTestData{Name: "angry-beaver", Status: common.StatusDeployed}.ToRelease()
rls5 := ReleaseTestData{Name: "opulent-frog", Status: common.StatusUninstalled}.ToRelease()
rls6 := ReleaseTestData{Name: "happy-liger", Status: common.StatusUninstalled}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'rls0'")
@ -144,7 +150,7 @@ func TestStorageList(t *testing.T) {
var listTests = []struct {
Description string
NumExpected int
ListFunc func() ([]*rspb.Release, error)
ListFunc func() ([]release.Releaser, error)
}{
{"ListDeployed", 2, storage.ListDeployed},
{"ListReleases", 7, storage.ListReleases},
@ -175,10 +181,10 @@ func TestStorageDeployed(t *testing.T) {
// setup storage with test releases
setup := func() {
// release records
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease()
rls0 := ReleaseTestData{Name: name, Version: 1, Status: common.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: common.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusDeployed}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
@ -194,15 +200,18 @@ func TestStorageDeployed(t *testing.T) {
t.Fatalf("Failed to query for deployed release: %s\n", err)
}
rel, err := releaserToV1Release(rls)
assert.NoError(t, err)
switch {
case rls == nil:
t.Fatalf("Release is nil")
case rls.Name != name:
t.Fatalf("Expected release name %q, actual %q\n", name, rls.Name)
case rls.Version != vers:
t.Fatalf("Expected release version %d, actual %d\n", vers, rls.Version)
case rls.Info.Status != rspb.StatusDeployed:
t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rls.Info.Status.String())
case rel.Name != name:
t.Fatalf("Expected release name %q, actual %q\n", name, rel.Name)
case rel.Version != vers:
t.Fatalf("Expected release version %d, actual %d\n", vers, rel.Version)
case rel.Info.Status != common.StatusDeployed:
t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rel.Info.Status.String())
}
}
@ -215,10 +224,10 @@ func TestStorageDeployedWithCorruption(t *testing.T) {
// setup storage with test releases
setup := func() {
// release records (notice odd order and corruption)
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusDeployed}.ToRelease()
rls0 := ReleaseTestData{Name: name, Version: 1, Status: common.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusDeployed}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: common.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusDeployed}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
@ -234,15 +243,18 @@ func TestStorageDeployedWithCorruption(t *testing.T) {
t.Fatalf("Failed to query for deployed release: %s\n", err)
}
rel, err := releaserToV1Release(rls)
assert.NoError(t, err)
switch {
case rls == nil:
t.Fatalf("Release is nil")
case rls.Name != name:
t.Fatalf("Expected release name %q, actual %q\n", name, rls.Name)
case rls.Version != vers:
t.Fatalf("Expected release version %d, actual %d\n", vers, rls.Version)
case rls.Info.Status != rspb.StatusDeployed:
t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rls.Info.Status.String())
case rel.Name != name:
t.Fatalf("Expected release name %q, actual %q\n", name, rel.Name)
case rel.Version != vers:
t.Fatalf("Expected release version %d, actual %d\n", vers, rel.Version)
case rel.Info.Status != common.StatusDeployed:
t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rel.Info.Status.String())
}
}
@ -254,10 +266,10 @@ func TestStorageHistory(t *testing.T) {
// setup storage with test releases
setup := func() {
// release records
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease()
rls0 := ReleaseTestData{Name: name, Version: 1, Status: common.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: common.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusDeployed}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
@ -286,22 +298,22 @@ type MaxHistoryMockDriver struct {
func NewMaxHistoryMockDriver(d driver.Driver) *MaxHistoryMockDriver {
return &MaxHistoryMockDriver{Driver: d}
}
func (d *MaxHistoryMockDriver) Create(key string, rls *rspb.Release) error {
func (d *MaxHistoryMockDriver) Create(key string, rls release.Releaser) error {
return d.Driver.Create(key, rls)
}
func (d *MaxHistoryMockDriver) Update(key string, rls *rspb.Release) error {
func (d *MaxHistoryMockDriver) Update(key string, rls release.Releaser) error {
return d.Driver.Update(key, rls)
}
func (d *MaxHistoryMockDriver) Delete(_ string) (*rspb.Release, error) {
func (d *MaxHistoryMockDriver) Delete(_ string) (release.Releaser, error) {
return nil, errMaxHistoryMockDriverSomethingHappened
}
func (d *MaxHistoryMockDriver) Get(key string) (*rspb.Release, error) {
func (d *MaxHistoryMockDriver) Get(key string) (release.Releaser, error) {
return d.Driver.Get(key)
}
func (d *MaxHistoryMockDriver) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
func (d *MaxHistoryMockDriver) List(filter func(release.Releaser) bool) ([]release.Releaser, error) {
return d.Driver.List(filter)
}
func (d *MaxHistoryMockDriver) Query(labels map[string]string) ([]*rspb.Release, error) {
func (d *MaxHistoryMockDriver) Query(labels map[string]string) ([]release.Releaser, error) {
return d.Driver.Query(labels)
}
func (d *MaxHistoryMockDriver) Name() string {
@ -319,14 +331,14 @@ func TestMaxHistoryErrorHandling(t *testing.T) {
// setup storage with test releases
setup := func() {
// release records
rls1 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 1, Status: common.StatusSuperseded}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Driver.Create(makeKey(rls1.Name, rls1.Version), rls1), "Storing release 'angry-bird' (v1)")
}
setup()
rls2 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusSuperseded}.ToRelease()
wantErr := errMaxHistoryMockDriverSomethingHappened
gotErr := storage.Create(rls2)
if !errors.Is(gotErr, wantErr) {
@ -345,10 +357,10 @@ func TestStorageRemoveLeastRecent(t *testing.T) {
// setup storage with test releases
setup := func() {
// release records
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease()
rls0 := ReleaseTestData{Name: name, Version: 1, Status: common.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: common.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusDeployed}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
@ -367,22 +379,24 @@ func TestStorageRemoveLeastRecent(t *testing.T) {
}
storage.MaxHistory = 3
rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusDeployed}.ToRelease()
rls5 := ReleaseTestData{Name: name, Version: 5, Status: common.StatusDeployed}.ToRelease()
assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'angry-bird' (v5)")
// On inserting the 5th record, we expect two records to be pruned from history.
hist, err := storage.History(name)
rhist, err := releaseListToV1List(hist)
assert.NoError(t, err)
if err != nil {
t.Fatal(err)
} else if len(hist) != storage.MaxHistory {
for _, item := range hist {
} else if len(rhist) != storage.MaxHistory {
for _, item := range rhist {
t.Logf("%s %v", item.Name, item.Version)
}
t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(hist))
t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(rhist))
}
// We expect the existing records to be 3, 4, and 5.
for i, item := range hist {
for i, item := range rhist {
v := item.Version
if expect := i + 3; v != expect {
t.Errorf("Expected release %d, got %d", expect, v)
@ -399,10 +413,10 @@ func TestStorageDoNotDeleteDeployed(t *testing.T) {
// setup storage with test releases
setup := func() {
// release records
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusDeployed}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusFailed}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease()
rls0 := ReleaseTestData{Name: name, Version: 1, Status: common.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusDeployed}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: common.StatusFailed}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusFailed}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
@ -412,7 +426,7 @@ func TestStorageDoNotDeleteDeployed(t *testing.T) {
}
setup()
rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusFailed}.ToRelease()
rls5 := ReleaseTestData{Name: name, Version: 5, Status: common.StatusFailed}.ToRelease()
assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'angry-bird' (v5)")
// On inserting the 5th record, we expect a total of 3 releases, but we expect version 2
@ -421,10 +435,12 @@ func TestStorageDoNotDeleteDeployed(t *testing.T) {
if err != nil {
t.Fatal(err)
} else if len(hist) != storage.MaxHistory {
for _, item := range hist {
rhist, err := releaseListToV1List(hist)
assert.NoError(t, err)
for _, item := range rhist {
t.Logf("%s %v", item.Name, item.Version)
}
t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(hist))
t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(rhist))
}
expectedVersions := map[int]bool{
@ -433,7 +449,9 @@ func TestStorageDoNotDeleteDeployed(t *testing.T) {
5: true,
}
for _, item := range hist {
rhist, err := releaseListToV1List(hist)
assert.NoError(t, err)
for _, item := range rhist {
if !expectedVersions[item.Version] {
t.Errorf("Release version %d, found when not expected", item.Version)
}
@ -448,10 +466,10 @@ func TestStorageLast(t *testing.T) {
// Set up storage with test releases.
setup := func() {
// release records
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease()
rls0 := ReleaseTestData{Name: name, Version: 1, Status: common.StatusSuperseded}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusSuperseded}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: common.StatusSuperseded}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusFailed}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
@ -467,8 +485,11 @@ func TestStorageLast(t *testing.T) {
t.Fatalf("Failed to query for release history (%q): %s\n", name, err)
}
if h.Version != 4 {
t.Errorf("Expected revision 4, got %d", h.Version)
rel, err := releaserToV1Release(h)
assert.NoError(t, err)
if rel.Version != 4 {
t.Errorf("Expected revision 4, got %d", rel.Version)
}
}
@ -483,10 +504,10 @@ func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) {
// setup storage with test releases
setup := func() {
// release records
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusFailed}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusFailed}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusFailed}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease()
rls0 := ReleaseTestData{Name: name, Version: 1, Status: common.StatusFailed}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusFailed}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: common.StatusFailed}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusFailed}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
@ -507,7 +528,7 @@ func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) {
setup()
rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusFailed}.ToRelease()
rls5 := ReleaseTestData{Name: name, Version: 5, Status: common.StatusFailed}.ToRelease()
err := storage.Create(rls5)
if err != nil {
t.Fatalf("Failed to create a new release version: %s", err)
@ -518,13 +539,15 @@ func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) {
t.Fatalf("unexpected error: %s", err)
}
for i, rel := range hist {
rhist, err := releaseListToV1List(hist)
assert.NoError(t, err)
for i, rel := range rhist {
wantVersion := i + 2
if rel.Version != wantVersion {
t.Fatalf("Expected history release %d version to equal %d, got %d", i+1, wantVersion, rel.Version)
}
wantStatus := rspb.StatusFailed
wantStatus := common.StatusFailed
if rel.Info.Status != wantStatus {
t.Fatalf("Expected history release %d status to equal %q, got %q", i+1, wantStatus, rel.Info.Status)
}
@ -536,7 +559,7 @@ type ReleaseTestData struct {
Version int
Manifest string
Namespace string
Status rspb.Status
Status common.Status
}
func (test ReleaseTestData) ToRelease() *rspb.Release {

Loading…
Cancel
Save