Ctrl K
ComunidadGuías…

internal/helm/history.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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
}