github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/action/list.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package action 18 19 import ( 20 "path" 21 "regexp" 22 23 "k8s.io/apimachinery/pkg/labels" 24 25 "github.com/stefanmcshane/helm/pkg/release" 26 "github.com/stefanmcshane/helm/pkg/releaseutil" 27 ) 28 29 // ListStates represents zero or more status codes that a list item may have set 30 // 31 // Because this is used as a bitmask filter, more than one bit can be flipped 32 // in the ListStates. 33 type ListStates uint 34 35 const ( 36 // ListDeployed filters on status "deployed" 37 ListDeployed ListStates = 1 << iota 38 // ListUninstalled filters on status "uninstalled" 39 ListUninstalled 40 // ListUninstalling filters on status "uninstalling" (uninstall in progress) 41 ListUninstalling 42 // ListPendingInstall filters on status "pending" (deployment in progress) 43 ListPendingInstall 44 // ListPendingUpgrade filters on status "pending_upgrade" (upgrade in progress) 45 ListPendingUpgrade 46 // ListPendingRollback filters on status "pending_rollback" (rollback in progress) 47 ListPendingRollback 48 // ListSuperseded filters on status "superseded" (historical release version that is no longer deployed) 49 ListSuperseded 50 // ListFailed filters on status "failed" (release version not deployed because of error) 51 ListFailed 52 // ListUnknown filters on an unknown status 53 ListUnknown 54 ) 55 56 // FromName takes a state name and returns a ListStates representation. 57 // 58 // Currently, there are only names for individual flipped bits, so the returned 59 // ListStates will only match one of the constants. However, it is possible that 60 // this behavior could change in the future. 61 func (s ListStates) FromName(str string) ListStates { 62 switch str { 63 case "deployed": 64 return ListDeployed 65 case "uninstalled": 66 return ListUninstalled 67 case "superseded": 68 return ListSuperseded 69 case "failed": 70 return ListFailed 71 case "uninstalling": 72 return ListUninstalling 73 case "pending-install": 74 return ListPendingInstall 75 case "pending-upgrade": 76 return ListPendingUpgrade 77 case "pending-rollback": 78 return ListPendingRollback 79 } 80 return ListUnknown 81 } 82 83 // ListAll is a convenience for enabling all list filters 84 const ListAll = ListDeployed | ListUninstalled | ListUninstalling | ListPendingInstall | ListPendingRollback | ListPendingUpgrade | ListSuperseded | ListFailed 85 86 // Sorter is a top-level sort 87 type Sorter uint 88 89 const ( 90 // ByNameDesc sorts by descending lexicographic order 91 ByNameDesc Sorter = iota + 1 92 // ByDateAsc sorts by ascending dates (oldest updated release first) 93 ByDateAsc 94 // ByDateDesc sorts by descending dates (latest updated release first) 95 ByDateDesc 96 ) 97 98 // List is the action for listing releases. 99 // 100 // It provides, for example, the implementation of 'helm list'. 101 // It returns no more than one revision of every release in one specific, or in 102 // all, namespaces. 103 // To list all the revisions of a specific release, see the History action. 104 type List struct { 105 cfg *Configuration 106 107 // All ignores the limit/offset 108 All bool 109 // AllNamespaces searches across namespaces 110 AllNamespaces bool 111 // Sort indicates the sort to use 112 // 113 // see pkg/releaseutil for several useful sorters 114 Sort Sorter 115 // Overrides the default lexicographic sorting 116 ByDate bool 117 SortReverse bool 118 // StateMask accepts a bitmask of states for items to show. 119 // The default is ListDeployed 120 StateMask ListStates 121 // Limit is the number of items to return per Run() 122 Limit int 123 // Offset is the starting index for the Run() call 124 Offset int 125 // Filter is a filter that is applied to the results 126 Filter string 127 Short bool 128 NoHeaders bool 129 TimeFormat string 130 Uninstalled bool 131 Superseded bool 132 Uninstalling bool 133 Deployed bool 134 Failed bool 135 Pending bool 136 Selector string 137 } 138 139 // NewList constructs a new *List 140 func NewList(cfg *Configuration) *List { 141 return &List{ 142 StateMask: ListDeployed | ListFailed, 143 cfg: cfg, 144 } 145 } 146 147 // Run executes the list command, returning a set of matches. 148 func (l *List) Run() ([]*release.Release, error) { 149 if err := l.cfg.KubeClient.IsReachable(); err != nil { 150 return nil, err 151 } 152 153 var filter *regexp.Regexp 154 if l.Filter != "" { 155 var err error 156 filter, err = regexp.Compile(l.Filter) 157 if err != nil { 158 return nil, err 159 } 160 } 161 162 results, err := l.cfg.Releases.List(func(rel *release.Release) bool { 163 // Skip anything that doesn't match the filter. 164 if filter != nil && !filter.MatchString(rel.Name) { 165 return false 166 } 167 168 return true 169 }) 170 171 if err != nil { 172 return nil, err 173 } 174 175 if results == nil { 176 return results, nil 177 } 178 179 // by definition, superseded releases are never shown if 180 // only the latest releases are returned. so if requested statemask 181 // is _only_ ListSuperseded, skip the latest release filter 182 if l.StateMask != ListSuperseded { 183 results = filterLatestReleases(results) 184 } 185 186 // State mask application must occur after filtering to 187 // latest releases, otherwise outdated entries can be returned 188 results = l.filterStateMask(results) 189 190 // Skip anything that doesn't match the selector 191 selectorObj, err := labels.Parse(l.Selector) 192 if err != nil { 193 return nil, err 194 } 195 results = l.filterSelector(results, selectorObj) 196 197 // Unfortunately, we have to sort before truncating, which can incur substantial overhead 198 l.sort(results) 199 200 // Guard on offset 201 if l.Offset >= len(results) { 202 return []*release.Release{}, nil 203 } 204 205 // Calculate the limit and offset, and then truncate results if necessary. 206 limit := len(results) 207 if l.Limit > 0 && l.Limit < limit { 208 limit = l.Limit 209 } 210 last := l.Offset + limit 211 if l := len(results); l < last { 212 last = l 213 } 214 results = results[l.Offset:last] 215 216 return results, err 217 } 218 219 // sort is an in-place sort where order is based on the value of a.Sort 220 func (l *List) sort(rels []*release.Release) { 221 if l.SortReverse { 222 l.Sort = ByNameDesc 223 } 224 225 if l.ByDate { 226 l.Sort = ByDateDesc 227 if l.SortReverse { 228 l.Sort = ByDateAsc 229 } 230 } 231 232 switch l.Sort { 233 case ByDateDesc: 234 releaseutil.SortByDate(rels) 235 case ByDateAsc: 236 releaseutil.Reverse(rels, releaseutil.SortByDate) 237 case ByNameDesc: 238 releaseutil.Reverse(rels, releaseutil.SortByName) 239 default: 240 releaseutil.SortByName(rels) 241 } 242 } 243 244 // filterLatestReleases returns a list scrubbed of old releases. 245 func filterLatestReleases(releases []*release.Release) []*release.Release { 246 latestReleases := make(map[string]*release.Release) 247 248 for _, rls := range releases { 249 name, namespace := rls.Name, rls.Namespace 250 key := path.Join(namespace, name) 251 if latestRelease, exists := latestReleases[key]; exists && latestRelease.Version > rls.Version { 252 continue 253 } 254 latestReleases[key] = rls 255 } 256 257 var list = make([]*release.Release, 0, len(latestReleases)) 258 for _, rls := range latestReleases { 259 list = append(list, rls) 260 } 261 return list 262 } 263 264 func (l *List) filterStateMask(releases []*release.Release) []*release.Release { 265 desiredStateReleases := make([]*release.Release, 0) 266 267 for _, rls := range releases { 268 currentStatus := l.StateMask.FromName(rls.Info.Status.String()) 269 mask := l.StateMask & currentStatus 270 if mask == 0 { 271 continue 272 } 273 desiredStateReleases = append(desiredStateReleases, rls) 274 } 275 276 return desiredStateReleases 277 } 278 279 func (l *List) filterSelector(releases []*release.Release, selector labels.Selector) []*release.Release { 280 desiredStateReleases := make([]*release.Release, 0) 281 282 for _, rls := range releases { 283 if selector.Matches(labels.Set(rls.Labels)) { 284 desiredStateReleases = append(desiredStateReleases, rls) 285 } 286 } 287 288 return desiredStateReleases 289 } 290 291 // SetStateMask calculates the state mask based on parameters. 292 func (l *List) SetStateMask() { 293 if l.All { 294 l.StateMask = ListAll 295 return 296 } 297 298 state := ListStates(0) 299 if l.Deployed { 300 state |= ListDeployed 301 } 302 if l.Uninstalled { 303 state |= ListUninstalled 304 } 305 if l.Uninstalling { 306 state |= ListUninstalling 307 } 308 if l.Pending { 309 state |= ListPendingInstall | ListPendingRollback | ListPendingUpgrade 310 } 311 if l.Failed { 312 state |= ListFailed 313 } 314 if l.Superseded { 315 state |= ListSuperseded 316 } 317 318 // Apply a default 319 if state == 0 { 320 state = ListDeployed | ListFailed 321 } 322 323 l.StateMask = state 324 }