Skip to content

Commit

Permalink
api: record observed releases in Status.History
Browse files Browse the repository at this point in the history
Signed-off-by: Hidde Beydals <[email protected]>
  • Loading branch information
hiddeco committed Nov 17, 2023
1 parent e1e2d15 commit e9d6426
Show file tree
Hide file tree
Showing 33 changed files with 2,160 additions and 1,146 deletions.
64 changes: 16 additions & 48 deletions api/v2beta2/helmrelease_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -858,16 +858,17 @@ type HelmReleaseStatus struct {
HelmChart string `json:"helmChart,omitempty"`

// StorageNamespace is the namespace of the Helm release storage for the
// Current release.
// current release.
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Optional
// +optional
StorageNamespace string `json:"storageNamespace,omitempty"`

// History holds the history of Helm releases performed for this HelmRelease.
// History holds the history of Helm releases performed for this HelmRelease
// up to the last successfully completed release.
// +optional
History ReleaseHistory `json:"history,omitempty"`
History Snapshots `json:"history,omitempty"`

// LastAttemptedReleaseAction is the last release action performed for this
// HelmRelease. It is used to determine the active remediation strategy.
Expand Down Expand Up @@ -909,6 +910,18 @@ type HelmReleaseStatus struct {
meta.ReconcileRequestStatus `json:",inline"`
}

// ClearHistory clears the History.
func (in *HelmReleaseStatus) ClearHistory() {
in.History = nil
}

// ClearFailures clears the failure counters.
func (in *HelmReleaseStatus) ClearFailures() {
in.Failures = 0
in.InstallFailures = 0
in.UpgradeFailures = 0
}

// GetHelmChart returns the namespace and name of the HelmChart.
func (in HelmReleaseStatus) GetHelmChart() (string, string) {
if in.HelmChart == "" {
Expand All @@ -920,22 +933,6 @@ func (in HelmReleaseStatus) GetHelmChart() (string, string) {
return "", ""
}

// ReleaseHistory holds the latest observed Snapshot for the current release,
// and the previous (successful) release. The previous release is only
// populated if the current release is not the first release, and if the
// previous release was successful. This is to prevent the previous release
// from being populated with a failed release.
type ReleaseHistory struct {
// Current holds the latest observed Snapshot for the current release.
// +optional
Current *Snapshot `json:"current,omitempty"`

// Previous holds the latest observed Snapshot for the previous
// (successful) release.
// +optional
Previous *Snapshot `json:"previous,omitempty"`
}

const (
// SourceIndexKey is the key used for indexing HelmReleases based on
// their sources.
Expand Down Expand Up @@ -1006,35 +1003,6 @@ func (in *HelmRelease) GetUninstall() Uninstall {
return *in.Spec.Uninstall
}

// GetCurrent returns the current Helm release as observed by the controller.
func (in HelmRelease) GetCurrent() *Snapshot {
if in.HasCurrent() {
return in.Status.History.Current
}
return nil
}

// HasCurrent returns true if the HelmRelease has a current observed Helm
// release.
func (in HelmRelease) HasCurrent() bool {
return in.Status.History.Current != nil
}

// GetPrevious returns the previous successful Helm release made by the
// controller.
func (in HelmRelease) GetPrevious() *Snapshot {
if in.HasPrevious() {
return in.Status.History.Previous
}
return nil
}

// HasPrevious returns true if the HelmRelease has a previous successful Helm
// release.
func (in HelmRelease) HasPrevious() bool {
return in.Status.History.Previous != nil
}

// GetActiveRemediation returns the active Remediation configuration for the
// HelmRelease.
func (in HelmRelease) GetActiveRemediation() Remediation {
Expand Down
104 changes: 103 additions & 1 deletion api/v2beta2/snapshot_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,95 @@ package v2beta2

import (
"fmt"
"sort"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
// snapshotStatusDeployed indicates that the release the snapshot was taken
// from is currently deployed.
snapshotStatusDeployed = "deployed"
// snapshotStatusSuperseded indicates that the release the snapshot was taken
// from has been superseded by a newer release.
snapshotStatusSuperseded = "superseded"

// snapshotTestPhaseFailed indicates that the test of the release the snapshot
// was taken from has failed.
snapshotTestPhaseFailed = "Failed"
)

// Snapshots is a list of Snapshot objects.
type Snapshots []*Snapshot

// Len returns the number of Snapshots.
func (in Snapshots) Len() int {
return len(in)
}

// SortByVersion sorts the Snapshots by version, in descending order.
func (in Snapshots) SortByVersion() {
sort.Slice(in, func(i, j int) bool {
return in[i].Version > in[j].Version
})
}

// Latest returns the most recent Snapshot.
func (in Snapshots) Latest() *Snapshot {
if len(in) == 0 {
return nil
}
in.SortByVersion()
return in[0]
}

// Previous returns the most recent Snapshot before the Latest that has a
// status of "deployed" or "superseded", or nil if there is no such Snapshot.
// Unless ignoreTests is true, Snapshots with a test in the "Failed" phase are
// ignored.
func (in Snapshots) Previous(ignoreTests bool) *Snapshot {
if len(in) < 2 {
return nil
}
in.SortByVersion()
for i := range in[1:] {
s := in[i+1]
if s.Status == snapshotStatusDeployed || s.Status == snapshotStatusSuperseded {
if ignoreTests || !s.HasTestInPhase(snapshotTestPhaseFailed) {
return s
}
}
}
return nil
}

// KeepLatest removes all Snapshots except the most recent.
func (in *Snapshots) KeepLatest() {
if in.Len() == 0 {
return
}
in.SortByVersion()
*in = (*in)[:1]
}

// KeepPrevious removes all Snapshots up to the Previous deployed Snapshot.
// If there is no previous-deployed Snapshot, no Snapshots are removed.
func (in *Snapshots) KeepPrevious(ignoreTests bool) {
if in.Len() < 2 {
return
}
in.SortByVersion()
for i := range (*in)[1:] {
s := (*in)[i+1]
if s.Status == snapshotStatusDeployed || s.Status == snapshotStatusSuperseded {
if ignoreTests || !s.HasTestInPhase(snapshotTestPhaseFailed) {
*in = (*in)[:i+2]
return
}
}
}
}

// Snapshot captures a point-in-time copy of the status information for a Helm release,
// as managed by the controller.
type Snapshot struct {
Expand Down Expand Up @@ -76,12 +161,18 @@ type Snapshot struct {
// FullReleaseName returns the full name of the release in the format
// of '<namespace>/<name>.<version>
func (in *Snapshot) FullReleaseName() string {
if in == nil {
return ""
}
return fmt.Sprintf("%s/%s.v%d", in.Namespace, in.Name, in.Version)
}

// VersionedChartName returns the full name of the chart in the format of
// '<name>@<version>'.
func (in *Snapshot) VersionedChartName() string {
if in == nil {
return ""
}
return fmt.Sprintf("%s@%s", in.ChartName, in.ChartVersion)
}

Expand All @@ -93,7 +184,7 @@ func (in *Snapshot) HasBeenTested() bool {

// GetTestHooks returns the TestHooks for the release if not nil.
func (in *Snapshot) GetTestHooks() map[string]*TestHookStatus {
if in == nil {
if in == nil || in.TestHooks == nil {
return nil
}
return *in.TestHooks
Expand All @@ -113,9 +204,20 @@ func (in *Snapshot) HasTestInPhase(phase string) bool {

// SetTestHooks sets the TestHooks for the release.
func (in *Snapshot) SetTestHooks(hooks map[string]*TestHookStatus) {
if in == nil || hooks == nil {
return
}
in.TestHooks = &hooks
}

// Targets returns true if the Snapshot targets the given release data.
func (in *Snapshot) Targets(name, namespace string, version int) bool {
if in != nil {
return in.Name == name && in.Namespace == namespace && in.Version == version
}
return false
}

// TestHookStatus holds the status information for a test hook as observed
// to be run by the controller.
type TestHookStatus struct {
Expand Down
Loading

0 comments on commit e9d6426

Please sign in to comment.