internal/helm/history.go
Rama main · réplica de lectura del repositorio original.
package helm
import (
"cmp"
"context"
"errors"
"fmt"
"slices"
"strconv"
"strings"
"github.com/sophotechlabs/spinoza/internal/api"
)
const historyPageSize = 10
const maxHistoryRefs = maxObjects
var errAmbiguousRevision = errors.New("a helm revision is stored more than once")
func releaseSelector(name string) string {
return ownerLabel + "," + nameLabel + "=" + name
}
func revisionSelector(name string, revisions []int64) string {
values := make([]string, 0, len(revisions))
for _, revision := range revisions {
values = append(values, strconv.FormatInt(revision, 10))
}
selector := releaseSelector(name) + "," + versionLabel
if len(values) == 1 {
return selector + "=" + values[0]
}
return selector + " in (" + strings.Join(values, ",") + ")"
}
func (s *Service) storedRefs(
ctx context.Context,
namespace, selector string,
limit int,
) ([]storedRef, error) {
if s.meta == nil {
return nil, errNoMetadata
}
secrets, err := listRefsLimited(
ctx,
s.meta,
DriverSecret,
secretsGVR,
namespace,
selector,
limit+1,
)
if err != nil {
return nil, err
}
maps, mapErr := listRefsLimited(
ctx,
s.meta,
DriverConfigMap,
configMapsGVR,
namespace,
selector,
limit+1,
)
if mapErr != nil {
return nil, mapErr
}
if secrets.truncated || maps.truncated {
return nil, fmt.Errorf("more than %d stored revisions matched", limit)
}
refs := append([]storedRef{}, secrets.items...)
refs = append(refs, maps.items...)
if len(refs) > limit {
return nil, fmt.Errorf("more than %d stored revisions matched", limit)
}
return refs, nil
}
func newerRef(left, right storedRef) int {
if revision := cmp.Compare(right.revision, left.revision); revision != 0 {
return revision
}
if driver := strings.Compare(left.driver, right.driver); driver != 0 {
return driver
}
return strings.Compare(left.object, right.object)
}
func oneRef(refs []storedRef, namespace, name string) (storedRef, error) {
if len(refs) == 0 {
return storedRef{}, fmt.Errorf("%w: %s/%s", ErrNoRelease, namespace, name)
}
slices.SortFunc(refs, newerRef)
if refs[0].revision < 1 {
return storedRef{}, fmt.Errorf("%w: %s/%s", ErrNoRelease, namespace, name)
}
if len(refs) > 1 && refs[0].revision == refs[1].revision {
return storedRef{}, fmt.Errorf("%w: revision %d", errAmbiguousRevision, refs[0].revision)
}
return refs[0], nil
}
func (s *Service) latestRef(ctx context.Context, namespace, name string) (storedRef, error) {
refs, err := s.storedRefs(ctx, namespace, releaseSelector(name), maxHistoryRefs)
if err != nil {
return storedRef{}, err
}
return oneRef(refs, namespace, name)
}
func (s *Service) refAt(ctx context.Context, namespace, name string, revision int64) (storedRef, error) {
refs, err := s.storedRefs(ctx, namespace, revisionSelector(name, []int64{revision}), 1)
if err != nil {
return storedRef{}, err
}
return oneRef(refs, namespace, name)
}
func selectHistoryRefs(
refs []storedRef,
namespace, name string,
through int64,
) ([]storedRef, int64, error) {
latest, err := oneRef(refs, namespace, name)
if err != nil {
return nil, 0, err
}
for at := 1; at < len(refs); at++ {
if refs[at].revision > 0 && refs[at-1].revision == refs[at].revision {
return nil, 0, fmt.Errorf(
"%w: revision %d",
errAmbiguousRevision,
refs[at].revision,
)
}
}
if through < 1 {
through = latest.revision
}
eligible := make([]storedRef, 0, min(len(refs), historyPageSize+1))
for _, ref := range refs {
if ref.revision > through || ref.revision < 1 {
continue
}
eligible = append(eligible, ref)
if len(eligible) == historyPageSize+1 {
break
}
}
if len(eligible) <= historyPageSize {
return eligible, 0, nil
}
next := eligible[historyPageSize].revision
return eligible[:historyPageSize], next, nil
}
func (s *Service) History(
ctx context.Context,
namespace, name string,
through int64,
) (api.HelmHistoryPage, error) {
if !nameFormat.MatchString(namespace) || !nameFormat.MatchString(name) {
return api.HelmHistoryPage{}, errors.New("namespace and name must be valid kubernetes names")
}
bounded, cancel := context.WithTimeout(ctx, listTimeout)
defer cancel()
refs, err := s.storedRefs(bounded, namespace, releaseSelector(name), maxHistoryRefs)
if err != nil {
return api.HelmHistoryPage{}, err
}
eligible, next, selectionErr := selectHistoryRefs(refs, namespace, name, through)
if selectionErr != nil {
return api.HelmHistoryPage{}, selectionErr
}
releases, readable := s.readWithStatus(bounded, eligible)
revisions := make([]api.HelmRevision, 0, len(releases))
for at, release := range releases {
revision := api.HelmRevision{
Revision: release.Revision,
Status: release.Status,
ChartVersion: release.ChartVersion,
AppVersion: release.AppVersion,
Updated: release.Updated,
Description: release.Description,
}
if !readable[at] {
revision.Description = "this revision's payload could not be read"
}
revisions = append(revisions, revision)
}
return api.HelmHistoryPage{Revisions: revisions, Next: next}, nil
}