|
|
|
/*
|
|
|
|
Copyright The Helm Authors.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package action
|
|
|
|
|
|
|
|
import (
|
|
|
|
"path"
|
|
|
|
"regexp"
|
|
|
|
|
|
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
|
|
|
|
|
|
"helm.sh/helm/v3/pkg/release"
|
|
|
|
"helm.sh/helm/v3/pkg/releaseutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ListStates represents zero or more status codes that a list item may have set
|
|
|
|
//
|
|
|
|
// Because this is used as a bitmask filter, more than one bit can be flipped
|
|
|
|
// in the ListStates.
|
|
|
|
type ListStates uint
|
|
|
|
|
|
|
|
const (
|
|
|
|
// ListDeployed filters on status "deployed"
|
|
|
|
ListDeployed ListStates = 1 << iota
|
|
|
|
// ListUninstalled filters on status "uninstalled"
|
|
|
|
ListUninstalled
|
|
|
|
// ListUninstalling filters on status "uninstalling" (uninstall in progress)
|
|
|
|
ListUninstalling
|
|
|
|
// ListPendingInstall filters on status "pending" (deployment in progress)
|
|
|
|
ListPendingInstall
|
|
|
|
// ListPendingUpgrade filters on status "pending_upgrade" (upgrade in progress)
|
|
|
|
ListPendingUpgrade
|
|
|
|
// ListPendingRollback filters on status "pending_rollback" (rollback in progress)
|
|
|
|
ListPendingRollback
|
|
|
|
// ListSuperseded filters on status "superseded" (historical release version that is no longer deployed)
|
|
|
|
ListSuperseded
|
|
|
|
// ListFailed filters on status "failed" (release version not deployed because of error)
|
|
|
|
ListFailed
|
|
|
|
// ListUnknown filters on an unknown status
|
|
|
|
ListUnknown
|
|
|
|
)
|
|
|
|
|
|
|
|
// FromName takes a state name and returns a ListStates representation.
|
|
|
|
//
|
|
|
|
// Currently, there are only names for individual flipped bits, so the returned
|
|
|
|
// ListStates will only match one of the constants. However, it is possible that
|
|
|
|
// this behavior could change in the future.
|
|
|
|
func (s ListStates) FromName(str string) ListStates {
|
|
|
|
switch str {
|
|
|
|
case "deployed":
|
|
|
|
return ListDeployed
|
|
|
|
case "uninstalled":
|
|
|
|
return ListUninstalled
|
|
|
|
case "superseded":
|
|
|
|
return ListSuperseded
|
|
|
|
case "failed":
|
|
|
|
return ListFailed
|
|
|
|
case "uninstalling":
|
|
|
|
return ListUninstalling
|
|
|
|
case "pending-install":
|
|
|
|
return ListPendingInstall
|
|
|
|
case "pending-upgrade":
|
|
|
|
return ListPendingUpgrade
|
|
|
|
case "pending-rollback":
|
|
|
|
return ListPendingRollback
|
|
|
|
}
|
|
|
|
return ListUnknown
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListAll is a convenience for enabling all list filters
|
|
|
|
const ListAll = ListDeployed | ListUninstalled | ListUninstalling | ListPendingInstall | ListPendingRollback | ListPendingUpgrade | ListSuperseded | ListFailed
|
|
|
|
|
|
|
|
// Sorter is a top-level sort
|
|
|
|
type Sorter uint
|
|
|
|
|
|
|
|
const (
|
|
|
|
// ByNameDesc sorts by descending lexicographic order
|
|
|
|
ByNameDesc Sorter = iota + 1
|
|
|
|
// ByDateAsc sorts by ascending dates (oldest updated release first)
|
|
|
|
ByDateAsc
|
|
|
|
// ByDateDesc sorts by descending dates (latest updated release first)
|
|
|
|
ByDateDesc
|
|
|
|
)
|
|
|
|
|
|
|
|
// List is the action for listing releases.
|
|
|
|
//
|
|
|
|
// It provides, for example, the implementation of 'helm list'.
|
|
|
|
// It returns no more than one revision of every release in one specific, or in
|
|
|
|
// all, namespaces.
|
|
|
|
// To list all the revisions of a specific release, see the History action.
|
|
|
|
type List struct {
|
|
|
|
cfg *Configuration
|
|
|
|
|
|
|
|
// All ignores the limit/offset
|
|
|
|
All bool
|
|
|
|
// AllNamespaces searches across namespaces
|
|
|
|
AllNamespaces bool
|
|
|
|
// Sort indicates the sort to use
|
|
|
|
//
|
|
|
|
// see pkg/releaseutil for several useful sorters
|
|
|
|
Sort Sorter
|
|
|
|
// Overrides the default lexicographic sorting
|
|
|
|
ByDate bool
|
|
|
|
SortReverse bool
|
|
|
|
// StateMask accepts a bitmask of states for items to show.
|
|
|
|
// The default is ListDeployed
|
|
|
|
StateMask ListStates
|
|
|
|
// Limit is the number of items to return per Run()
|
|
|
|
Limit int
|
|
|
|
// Offset is the starting index for the Run() call
|
|
|
|
Offset int
|
|
|
|
// Filter is a filter that is applied to the results
|
|
|
|
Filter string
|
|
|
|
Short bool
|
|
|
|
NoHeaders bool
|
|
|
|
TimeFormat string
|
|
|
|
Uninstalled bool
|
|
|
|
Superseded bool
|
|
|
|
Uninstalling bool
|
|
|
|
Deployed bool
|
|
|
|
Failed bool
|
|
|
|
Pending bool
|
|
|
|
Selector string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewList constructs a new *List
|
|
|
|
func NewList(cfg *Configuration) *List {
|
|
|
|
return &List{
|
|
|
|
StateMask: ListDeployed | ListFailed,
|
|
|
|
cfg: cfg,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run executes the list command, returning a set of matches.
|
|
|
|
func (l *List) Run() ([]*release.Release, error) {
|
|
|
|
if err := l.cfg.KubeClient.IsReachable(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var filter *regexp.Regexp
|
|
|
|
if l.Filter != "" {
|
|
|
|
var err error
|
|
|
|
filter, err = regexp.Compile(l.Filter)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
results, err := l.cfg.Releases.List(func(rel *release.Release) bool {
|
|
|
|
// Skip anything that doesn't match the filter.
|
|
|
|
if filter != nil && !filter.MatchString(rel.Name) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if results == nil {
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
Make helm ls return only current releases if providing state filter
Previously, the `helm ls --$state` operation would display outdated
releases under certain conditions.
Given the following set of releases:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 1 Wed Apr 8 16:54:39 2020 DEPLOYED bar-4.0.0 1.0 default
foo 1 Fri Feb 7 06:16:56 2020 DEPLOYED foo-0.1.0 1.0 default
foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
foo 4 Tue May 5 08:16:56 2020 DEPLOYED foo-0.2.0 1.0 default
qux 1 Tue Jun 9 10:32:00 2020 DEPLOYED qux-4.0.3 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.3 1.0 default
```
`helm ls --failed` produced the following output:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
Including the `qux` release in that `helm ls --failed` output is not
controversial; the most recent revision of `qux` was not successful
and an operator should investigate.
Including the `foo` release in the output, however, is
questionable. Revision 3 of `foo` is _not_ the most recent release of
`foo`, and that FAILED release was fixed in a susubsequent upgrade. A
user may see that FAILED deploy and start taking inappropriate
action. Further, that issue was fixed months ago in this example --
troubleshooting an old deploy may not be safe if significant changes
have occurred. Concern over this behavior was raised in
https://github.com/helm/helm/issues/7495.
This behavior applied to all the state filter flags (--deployed,
--failed, --pending, etc.), and a user could pass multiple state
filter flags to a single command. The previous behavior can be
summarized as follows:
For each release name, all release revisions having any of the
supplied state flags were retrieved, and the most recent revision
among these was returned (regardless of whether a newer revision of an
unspecified state exists).
This change request alters the helm list action to match user
expectations such that only "current" releases are shown when
filtering on release state. After this change, the following output
would be produced by `helm ls --failed`:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
The command now returns only `qux` because it is the only "current" FAILED release.
This behavior change applies to all the state filters _except_
`superseded`, which now becomes a special case. By definition, at
least one newer release exists ahead of each superseded release. A
conditional is included in this change request to maintain the
preexisting behavior (return "most recent" superseded revison for
each release name) if the superseded state filter is requested.
---
Note that there is an alternate perspective that a state filter flag
should return all releases of a given state rather than only the
"current" releases. In the above example, `helm ls --failed` with this
approach would return the following:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
Multiple FAILED `foo` revisions are included in the output, unlike the current behavior.
This approach is logical and achievable. It allows a user to find
exactly what is requested: all historical releases of a given
state. In order to achieve continuity with helm behavior, however, a
new filter (something like "current") would probably need to be
implemented and become the new default.
Given current helm behavior as well as the comments in the #7495, I
did not pursue this approach.
---
Technical details:
- Moved list action state mask filter after latest release filter
Previously, the list operation in helm/pkg/action/list.go skipped
releases that were not covered by the state mask on _retrieval_ from
the Releases store:
```
results, err := l.cfg.Releases.List(func(rel *release.Release) bool {
// Skip anything that the mask doesn't cover
currentStatus := l.StateMask.FromName(rel.Info.Status.String())
if l.StateMask¤tStatus == 0 {
return false
}
...
```
https://github.com/helm/helm/blob/8ea6b970ecd02365a230420692350057d48278e5/pkg/action/list.go#L154-L159
While filtering on retrieval in this manner avoided an extra iteration
through the entire list to check on the supplied condition later, it
introduced the possibility of returning an outdated release to the
user because newer releases (that would have otherwise squashed
outdated releases in the `filterList` function) are simply not
included in the set of working records.
This change moves the state mask filtering process to _after_ the set
of current releases is built. Outdated, potentially misleading
releases are scrubbed out prior to the application of the state mask
filter.
As written, this state mask filtration (in the new `filterStateMask`
method on `*List`) incurs an additional, potentially expensive
iteration over the set of releases to return to the user. An
alternative approach could avoid that extra iteration and fit this
logic into the existing `filterList` function at the cost of making
`filterList` function a little harder to understand.
- Rename filterList to filterLatestReleases for clarity
Another function that filters the list is added, so update
to the more descriptive name here.
- List superseded releases without filtering for latest
This change makes superseded releases a special case, as they would
_never_ be displayed otherwise (by definition, as superseded releases have been
replaced by a newer release), so a conditional maintains current
behavior ("return newest superseded revision for each release name")
Fixes #7495.
Signed-off-by: Andrew Melis <andrewmelis@gmail.com>
5 years ago
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// State mask application must occur after filtering to
|
|
|
|
// latest releases, otherwise outdated entries can be returned
|
|
|
|
results = l.filterStateMask(results)
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
|
|
// Unfortunately, we have to sort before truncating, which can incur substantial overhead
|
|
|
|
l.sort(results)
|
|
|
|
|
|
|
|
// Guard on offset
|
|
|
|
if l.Offset >= len(results) {
|
|
|
|
return []*release.Release{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the limit and offset, and then truncate results if necessary.
|
|
|
|
limit := len(results)
|
|
|
|
if l.Limit > 0 && l.Limit < limit {
|
|
|
|
limit = l.Limit
|
|
|
|
}
|
|
|
|
last := l.Offset + limit
|
|
|
|
if l := len(results); l < last {
|
|
|
|
last = l
|
|
|
|
}
|
|
|
|
results = results[l.Offset:last]
|
|
|
|
|
|
|
|
return results, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort is an in-place sort where order is based on the value of a.Sort
|
|
|
|
func (l *List) sort(rels []*release.Release) {
|
|
|
|
if l.SortReverse {
|
|
|
|
l.Sort = ByNameDesc
|
|
|
|
}
|
|
|
|
|
|
|
|
if l.ByDate {
|
|
|
|
l.Sort = ByDateDesc
|
|
|
|
if l.SortReverse {
|
|
|
|
l.Sort = ByDateAsc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch l.Sort {
|
|
|
|
case ByDateDesc:
|
|
|
|
releaseutil.SortByDate(rels)
|
|
|
|
case ByDateAsc:
|
|
|
|
releaseutil.Reverse(rels, releaseutil.SortByDate)
|
|
|
|
case ByNameDesc:
|
|
|
|
releaseutil.Reverse(rels, releaseutil.SortByName)
|
|
|
|
default:
|
|
|
|
releaseutil.SortByName(rels)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Make helm ls return only current releases if providing state filter
Previously, the `helm ls --$state` operation would display outdated
releases under certain conditions.
Given the following set of releases:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 1 Wed Apr 8 16:54:39 2020 DEPLOYED bar-4.0.0 1.0 default
foo 1 Fri Feb 7 06:16:56 2020 DEPLOYED foo-0.1.0 1.0 default
foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
foo 4 Tue May 5 08:16:56 2020 DEPLOYED foo-0.2.0 1.0 default
qux 1 Tue Jun 9 10:32:00 2020 DEPLOYED qux-4.0.3 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.3 1.0 default
```
`helm ls --failed` produced the following output:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
Including the `qux` release in that `helm ls --failed` output is not
controversial; the most recent revision of `qux` was not successful
and an operator should investigate.
Including the `foo` release in the output, however, is
questionable. Revision 3 of `foo` is _not_ the most recent release of
`foo`, and that FAILED release was fixed in a susubsequent upgrade. A
user may see that FAILED deploy and start taking inappropriate
action. Further, that issue was fixed months ago in this example --
troubleshooting an old deploy may not be safe if significant changes
have occurred. Concern over this behavior was raised in
https://github.com/helm/helm/issues/7495.
This behavior applied to all the state filter flags (--deployed,
--failed, --pending, etc.), and a user could pass multiple state
filter flags to a single command. The previous behavior can be
summarized as follows:
For each release name, all release revisions having any of the
supplied state flags were retrieved, and the most recent revision
among these was returned (regardless of whether a newer revision of an
unspecified state exists).
This change request alters the helm list action to match user
expectations such that only "current" releases are shown when
filtering on release state. After this change, the following output
would be produced by `helm ls --failed`:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
The command now returns only `qux` because it is the only "current" FAILED release.
This behavior change applies to all the state filters _except_
`superseded`, which now becomes a special case. By definition, at
least one newer release exists ahead of each superseded release. A
conditional is included in this change request to maintain the
preexisting behavior (return "most recent" superseded revison for
each release name) if the superseded state filter is requested.
---
Note that there is an alternate perspective that a state filter flag
should return all releases of a given state rather than only the
"current" releases. In the above example, `helm ls --failed` with this
approach would return the following:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
Multiple FAILED `foo` revisions are included in the output, unlike the current behavior.
This approach is logical and achievable. It allows a user to find
exactly what is requested: all historical releases of a given
state. In order to achieve continuity with helm behavior, however, a
new filter (something like "current") would probably need to be
implemented and become the new default.
Given current helm behavior as well as the comments in the #7495, I
did not pursue this approach.
---
Technical details:
- Moved list action state mask filter after latest release filter
Previously, the list operation in helm/pkg/action/list.go skipped
releases that were not covered by the state mask on _retrieval_ from
the Releases store:
```
results, err := l.cfg.Releases.List(func(rel *release.Release) bool {
// Skip anything that the mask doesn't cover
currentStatus := l.StateMask.FromName(rel.Info.Status.String())
if l.StateMask¤tStatus == 0 {
return false
}
...
```
https://github.com/helm/helm/blob/8ea6b970ecd02365a230420692350057d48278e5/pkg/action/list.go#L154-L159
While filtering on retrieval in this manner avoided an extra iteration
through the entire list to check on the supplied condition later, it
introduced the possibility of returning an outdated release to the
user because newer releases (that would have otherwise squashed
outdated releases in the `filterList` function) are simply not
included in the set of working records.
This change moves the state mask filtering process to _after_ the set
of current releases is built. Outdated, potentially misleading
releases are scrubbed out prior to the application of the state mask
filter.
As written, this state mask filtration (in the new `filterStateMask`
method on `*List`) incurs an additional, potentially expensive
iteration over the set of releases to return to the user. An
alternative approach could avoid that extra iteration and fit this
logic into the existing `filterList` function at the cost of making
`filterList` function a little harder to understand.
- Rename filterList to filterLatestReleases for clarity
Another function that filters the list is added, so update
to the more descriptive name here.
- List superseded releases without filtering for latest
This change makes superseded releases a special case, as they would
_never_ be displayed otherwise (by definition, as superseded releases have been
replaced by a newer release), so a conditional maintains current
behavior ("return newest superseded revision for each release name")
Fixes #7495.
Signed-off-by: Andrew Melis <andrewmelis@gmail.com>
5 years ago
|
|
|
// filterLatestReleases returns a list scrubbed of old releases.
|
|
|
|
func filterLatestReleases(releases []*release.Release) []*release.Release {
|
|
|
|
latestReleases := make(map[string]*release.Release)
|
|
|
|
|
|
|
|
for _, rls := range releases {
|
|
|
|
name, namespace := rls.Name, rls.Namespace
|
|
|
|
key := path.Join(namespace, name)
|
|
|
|
if latestRelease, exists := latestReleases[key]; exists && latestRelease.Version > rls.Version {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
latestReleases[key] = rls
|
|
|
|
}
|
|
|
|
|
|
|
|
var list = make([]*release.Release, 0, len(latestReleases))
|
|
|
|
for _, rls := range latestReleases {
|
|
|
|
list = append(list, rls)
|
|
|
|
}
|
|
|
|
return list
|
|
|
|
}
|
|
|
|
|
Make helm ls return only current releases if providing state filter
Previously, the `helm ls --$state` operation would display outdated
releases under certain conditions.
Given the following set of releases:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 1 Wed Apr 8 16:54:39 2020 DEPLOYED bar-4.0.0 1.0 default
foo 1 Fri Feb 7 06:16:56 2020 DEPLOYED foo-0.1.0 1.0 default
foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
foo 4 Tue May 5 08:16:56 2020 DEPLOYED foo-0.2.0 1.0 default
qux 1 Tue Jun 9 10:32:00 2020 DEPLOYED qux-4.0.3 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.3 1.0 default
```
`helm ls --failed` produced the following output:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
Including the `qux` release in that `helm ls --failed` output is not
controversial; the most recent revision of `qux` was not successful
and an operator should investigate.
Including the `foo` release in the output, however, is
questionable. Revision 3 of `foo` is _not_ the most recent release of
`foo`, and that FAILED release was fixed in a susubsequent upgrade. A
user may see that FAILED deploy and start taking inappropriate
action. Further, that issue was fixed months ago in this example --
troubleshooting an old deploy may not be safe if significant changes
have occurred. Concern over this behavior was raised in
https://github.com/helm/helm/issues/7495.
This behavior applied to all the state filter flags (--deployed,
--failed, --pending, etc.), and a user could pass multiple state
filter flags to a single command. The previous behavior can be
summarized as follows:
For each release name, all release revisions having any of the
supplied state flags were retrieved, and the most recent revision
among these was returned (regardless of whether a newer revision of an
unspecified state exists).
This change request alters the helm list action to match user
expectations such that only "current" releases are shown when
filtering on release state. After this change, the following output
would be produced by `helm ls --failed`:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
The command now returns only `qux` because it is the only "current" FAILED release.
This behavior change applies to all the state filters _except_
`superseded`, which now becomes a special case. By definition, at
least one newer release exists ahead of each superseded release. A
conditional is included in this change request to maintain the
preexisting behavior (return "most recent" superseded revison for
each release name) if the superseded state filter is requested.
---
Note that there is an alternate perspective that a state filter flag
should return all releases of a given state rather than only the
"current" releases. In the above example, `helm ls --failed` with this
approach would return the following:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
Multiple FAILED `foo` revisions are included in the output, unlike the current behavior.
This approach is logical and achievable. It allows a user to find
exactly what is requested: all historical releases of a given
state. In order to achieve continuity with helm behavior, however, a
new filter (something like "current") would probably need to be
implemented and become the new default.
Given current helm behavior as well as the comments in the #7495, I
did not pursue this approach.
---
Technical details:
- Moved list action state mask filter after latest release filter
Previously, the list operation in helm/pkg/action/list.go skipped
releases that were not covered by the state mask on _retrieval_ from
the Releases store:
```
results, err := l.cfg.Releases.List(func(rel *release.Release) bool {
// Skip anything that the mask doesn't cover
currentStatus := l.StateMask.FromName(rel.Info.Status.String())
if l.StateMask¤tStatus == 0 {
return false
}
...
```
https://github.com/helm/helm/blob/8ea6b970ecd02365a230420692350057d48278e5/pkg/action/list.go#L154-L159
While filtering on retrieval in this manner avoided an extra iteration
through the entire list to check on the supplied condition later, it
introduced the possibility of returning an outdated release to the
user because newer releases (that would have otherwise squashed
outdated releases in the `filterList` function) are simply not
included in the set of working records.
This change moves the state mask filtering process to _after_ the set
of current releases is built. Outdated, potentially misleading
releases are scrubbed out prior to the application of the state mask
filter.
As written, this state mask filtration (in the new `filterStateMask`
method on `*List`) incurs an additional, potentially expensive
iteration over the set of releases to return to the user. An
alternative approach could avoid that extra iteration and fit this
logic into the existing `filterList` function at the cost of making
`filterList` function a little harder to understand.
- Rename filterList to filterLatestReleases for clarity
Another function that filters the list is added, so update
to the more descriptive name here.
- List superseded releases without filtering for latest
This change makes superseded releases a special case, as they would
_never_ be displayed otherwise (by definition, as superseded releases have been
replaced by a newer release), so a conditional maintains current
behavior ("return newest superseded revision for each release name")
Fixes #7495.
Signed-off-by: Andrew Melis <andrewmelis@gmail.com>
5 years ago
|
|
|
func (l *List) filterStateMask(releases []*release.Release) []*release.Release {
|
|
|
|
desiredStateReleases := make([]*release.Release, 0)
|
|
|
|
|
|
|
|
for _, rls := range releases {
|
|
|
|
currentStatus := l.StateMask.FromName(rls.Info.Status.String())
|
|
|
|
mask := l.StateMask & currentStatus
|
|
|
|
if mask == 0 {
|
Make helm ls return only current releases if providing state filter
Previously, the `helm ls --$state` operation would display outdated
releases under certain conditions.
Given the following set of releases:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 1 Wed Apr 8 16:54:39 2020 DEPLOYED bar-4.0.0 1.0 default
foo 1 Fri Feb 7 06:16:56 2020 DEPLOYED foo-0.1.0 1.0 default
foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
foo 4 Tue May 5 08:16:56 2020 DEPLOYED foo-0.2.0 1.0 default
qux 1 Tue Jun 9 10:32:00 2020 DEPLOYED qux-4.0.3 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.3 1.0 default
```
`helm ls --failed` produced the following output:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
Including the `qux` release in that `helm ls --failed` output is not
controversial; the most recent revision of `qux` was not successful
and an operator should investigate.
Including the `foo` release in the output, however, is
questionable. Revision 3 of `foo` is _not_ the most recent release of
`foo`, and that FAILED release was fixed in a susubsequent upgrade. A
user may see that FAILED deploy and start taking inappropriate
action. Further, that issue was fixed months ago in this example --
troubleshooting an old deploy may not be safe if significant changes
have occurred. Concern over this behavior was raised in
https://github.com/helm/helm/issues/7495.
This behavior applied to all the state filter flags (--deployed,
--failed, --pending, etc.), and a user could pass multiple state
filter flags to a single command. The previous behavior can be
summarized as follows:
For each release name, all release revisions having any of the
supplied state flags were retrieved, and the most recent revision
among these was returned (regardless of whether a newer revision of an
unspecified state exists).
This change request alters the helm list action to match user
expectations such that only "current" releases are shown when
filtering on release state. After this change, the following output
would be produced by `helm ls --failed`:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
The command now returns only `qux` because it is the only "current" FAILED release.
This behavior change applies to all the state filters _except_
`superseded`, which now becomes a special case. By definition, at
least one newer release exists ahead of each superseded release. A
conditional is included in this change request to maintain the
preexisting behavior (return "most recent" superseded revison for
each release name) if the superseded state filter is requested.
---
Note that there is an alternate perspective that a state filter flag
should return all releases of a given state rather than only the
"current" releases. In the above example, `helm ls --failed` with this
approach would return the following:
```
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default
foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default
qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default
```
Multiple FAILED `foo` revisions are included in the output, unlike the current behavior.
This approach is logical and achievable. It allows a user to find
exactly what is requested: all historical releases of a given
state. In order to achieve continuity with helm behavior, however, a
new filter (something like "current") would probably need to be
implemented and become the new default.
Given current helm behavior as well as the comments in the #7495, I
did not pursue this approach.
---
Technical details:
- Moved list action state mask filter after latest release filter
Previously, the list operation in helm/pkg/action/list.go skipped
releases that were not covered by the state mask on _retrieval_ from
the Releases store:
```
results, err := l.cfg.Releases.List(func(rel *release.Release) bool {
// Skip anything that the mask doesn't cover
currentStatus := l.StateMask.FromName(rel.Info.Status.String())
if l.StateMask¤tStatus == 0 {
return false
}
...
```
https://github.com/helm/helm/blob/8ea6b970ecd02365a230420692350057d48278e5/pkg/action/list.go#L154-L159
While filtering on retrieval in this manner avoided an extra iteration
through the entire list to check on the supplied condition later, it
introduced the possibility of returning an outdated release to the
user because newer releases (that would have otherwise squashed
outdated releases in the `filterList` function) are simply not
included in the set of working records.
This change moves the state mask filtering process to _after_ the set
of current releases is built. Outdated, potentially misleading
releases are scrubbed out prior to the application of the state mask
filter.
As written, this state mask filtration (in the new `filterStateMask`
method on `*List`) incurs an additional, potentially expensive
iteration over the set of releases to return to the user. An
alternative approach could avoid that extra iteration and fit this
logic into the existing `filterList` function at the cost of making
`filterList` function a little harder to understand.
- Rename filterList to filterLatestReleases for clarity
Another function that filters the list is added, so update
to the more descriptive name here.
- List superseded releases without filtering for latest
This change makes superseded releases a special case, as they would
_never_ be displayed otherwise (by definition, as superseded releases have been
replaced by a newer release), so a conditional maintains current
behavior ("return newest superseded revision for each release name")
Fixes #7495.
Signed-off-by: Andrew Melis <andrewmelis@gmail.com>
5 years ago
|
|
|
continue
|
|
|
|
}
|
|
|
|
desiredStateReleases = append(desiredStateReleases, rls)
|
|
|
|
}
|
|
|
|
|
|
|
|
return desiredStateReleases
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *List) filterSelector(releases []*release.Release, selector labels.Selector) []*release.Release {
|
|
|
|
desiredStateReleases := make([]*release.Release, 0)
|
|
|
|
|
|
|
|
for _, rls := range releases {
|
|
|
|
if selector.Matches(labels.Set(rls.Labels)) {
|
|
|
|
desiredStateReleases = append(desiredStateReleases, rls)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return desiredStateReleases
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetStateMask calculates the state mask based on parameters.
|
|
|
|
func (l *List) SetStateMask() {
|
|
|
|
if l.All {
|
|
|
|
l.StateMask = ListAll
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
state := ListStates(0)
|
|
|
|
if l.Deployed {
|
|
|
|
state |= ListDeployed
|
|
|
|
}
|
|
|
|
if l.Uninstalled {
|
|
|
|
state |= ListUninstalled
|
|
|
|
}
|
|
|
|
if l.Uninstalling {
|
|
|
|
state |= ListUninstalling
|
|
|
|
}
|
|
|
|
if l.Pending {
|
|
|
|
state |= ListPendingInstall | ListPendingRollback | ListPendingUpgrade
|
|
|
|
}
|
|
|
|
if l.Failed {
|
|
|
|
state |= ListFailed
|
|
|
|
}
|
|
|
|
if l.Superseded {
|
|
|
|
state |= ListSuperseded
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply a default
|
|
|
|
if state == 0 {
|
|
|
|
state = ListDeployed | ListFailed
|
|
|
|
}
|
|
|
|
|
|
|
|
l.StateMask = state
|
|
|
|
}
|