-
Notifications
You must be signed in to change notification settings - Fork 177
/
Copy pathstate.go
201 lines (180 loc) · 8.64 KB
/
state.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
Copyright 2022 The Flux 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 reconcile
import (
"context"
"errors"
"fmt"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/ssa/jsondiff"
"helm.sh/helm/v3/pkg/kube"
helmrelease "helm.sh/helm/v3/pkg/release"
ctrl "sigs.k8s.io/controller-runtime"
"github.com/fluxcd/helm-controller/internal/action"
"github.com/fluxcd/helm-controller/internal/digest"
interrors "github.com/fluxcd/helm-controller/internal/errors"
"github.com/fluxcd/helm-controller/internal/postrender"
)
// ReleaseStatus represents the status of a Helm release as determined by
// comparing the Helm storage with the v2.HelmRelease object.
type ReleaseStatus string
// String returns the string representation of the release status.
func (s ReleaseStatus) String() string {
return string(s)
}
const (
// ReleaseStatusUnknown indicates that the status of the release could not
// be determined.
ReleaseStatusUnknown ReleaseStatus = "Unknown"
// ReleaseStatusAbsent indicates that the release is not present in the
// Helm storage.
ReleaseStatusAbsent ReleaseStatus = "Absent"
// ReleaseStatusUnmanaged indicates that the release is present in the Helm
// storage, but is not managed by the v2.HelmRelease object.
ReleaseStatusUnmanaged ReleaseStatus = "Unmanaged"
// ReleaseStatusOutOfSync indicates that the release is present in the Helm
// storage, but is not in sync with the v2.HelmRelease object.
ReleaseStatusOutOfSync ReleaseStatus = "OutOfSync"
// ReleaseStatusDrifted indicates that the release is present in the Helm
// storage, but the cluster state has drifted from the manifest in the
// storage.
ReleaseStatusDrifted ReleaseStatus = "Drifted"
// ReleaseStatusLocked indicates that the release is present in the Helm
// storage, but is locked.
ReleaseStatusLocked ReleaseStatus = "Locked"
// ReleaseStatusUntested indicates that the release is present in the Helm
// storage, but has not been tested.
ReleaseStatusUntested ReleaseStatus = "Untested"
// ReleaseStatusInSync indicates that the release is present in the Helm
// storage, and is in sync with the v2.HelmRelease object.
ReleaseStatusInSync ReleaseStatus = "InSync"
// ReleaseStatusFailed indicates that the release is present in the Helm
// storage, but has failed.
ReleaseStatusFailed ReleaseStatus = "Failed"
)
// ReleaseState represents the state of a Helm release as determined by
// comparing the Helm storage with the v2.HelmRelease object.
type ReleaseState struct {
// Status is the status of the release.
Status ReleaseStatus
// Reason for the Status.
Reason string
// Diff contains any differences between the Helm storage manifest and the
// cluster state when Status equals ReleaseStatusDrifted.
Diff jsondiff.DiffSet
}
// DetermineReleaseState determines the state of the Helm release as compared
// to the v2.HelmRelease object. It returns a ReleaseState that indicates
// the status of the release, and an error if the state could not be determined.
func DetermineReleaseState(ctx context.Context, cfg *action.ConfigFactory, req *Request) (ReleaseState, error) {
rls, err := action.LastRelease(cfg.Build(nil), req.Object.GetReleaseName())
if err != nil {
if errors.Is(err, action.ErrReleaseNotFound) {
return ReleaseState{Status: ReleaseStatusAbsent, Reason: "no release in storage for object"}, nil
}
return ReleaseState{Status: ReleaseStatusUnknown}, fmt.Errorf("failed to retrieve last release from storage: %w", err)
}
// If the release is in a pending state, it must be unlocked before any
// further action can be taken.
if rls.Info.Status.IsPending() {
return ReleaseState{Status: ReleaseStatusLocked, Reason: fmt.Sprintf("release with status '%s'", rls.Info.Status)}, err
}
// Confirm we have a release object to compare against.
if req.Object.Status.History.Len() == 0 {
if rls.Info.Status == helmrelease.StatusUninstalled {
return ReleaseState{Status: ReleaseStatusAbsent, Reason: "found uninstalled release in storage"}, nil
}
return ReleaseState{Status: ReleaseStatusUnmanaged, Reason: "found existing release in storage"}, err
}
// Verify the release object against the state we observed during our
// last reconciliation.
cur := req.Object.Status.History.Latest()
if err := action.VerifyReleaseObject(cur, rls); err != nil {
if interrors.IsOneOf(err, action.ErrReleaseDigest, action.ErrReleaseNotObserved) {
// The release object has been mutated in such a way that we are
// unable to determine the state of the release.
// Effectively, this means that the object no longer manages the
// release, and we should e.g. perform an upgrade to bring
// the release back in-sync and under management.
return ReleaseState{Status: ReleaseStatusUnmanaged, Reason: err.Error()}, nil
}
return ReleaseState{Status: ReleaseStatusUnknown}, fmt.Errorf("failed to verify release object: %w", err)
}
// Further determine the state of the release based on the Helm release
// status, which can now be considered reliable.
switch rls.Info.Status {
case helmrelease.StatusFailed:
return ReleaseState{Status: ReleaseStatusFailed}, nil
case helmrelease.StatusUninstalled:
return ReleaseState{Status: ReleaseStatusAbsent, Reason: "found uninstalled release in storage"}, nil
case helmrelease.StatusDeployed:
// Verify the release is in sync with the desired configuration.
if err = action.VerifyRelease(rls, cur, req.Chart.Metadata, req.Values); err != nil {
switch err {
case action.ErrChartChanged, action.ErrConfigDigest:
return ReleaseState{Status: ReleaseStatusOutOfSync, Reason: err.Error()}, nil
default:
return ReleaseState{Status: ReleaseStatusUnknown}, err
}
}
// Verify if postrender digest has changed if config has not been
// processed. For the processed or partially processed generation, the
// updated observation will only be reflected at the end of a successful
// reconciliation. Comparing here would result the reconciliation to
// get stuck in this check due to a mismatch forever. The value can't
// change without a new generation. Hence, compare the observed digest
// for new generations only.
ready := conditions.Get(req.Object, meta.ReadyCondition)
if ready != nil && ready.ObservedGeneration != req.Object.Generation {
var postrenderersDigest string
if req.Object.Spec.PostRenderers != nil {
postrenderersDigest = postrender.Digest(digest.Canonical, req.Object.Spec.PostRenderers).String()
}
if postrenderersDigest != req.Object.Status.ObservedPostRenderersDigest {
return ReleaseState{Status: ReleaseStatusOutOfSync, Reason: "postrenderers digest has changed"}, nil
}
}
// For the further determination of test results, we look at the
// observed state of the object. As tests can be run manually by
// users running e.g. `helm test`.
if testSpec := req.Object.GetTest(); testSpec.Enable {
// Confirm the release has been tested if enabled.
if !cur.HasBeenTested() {
return ReleaseState{Status: ReleaseStatusUntested}, nil
}
// Act on any observed test failure.
remediation := req.Object.GetActiveRemediation()
if remediation != nil && !remediation.MustIgnoreTestFailures(testSpec.IgnoreFailures) && cur.HasTestInPhase(helmrelease.HookPhaseFailed.String()) {
return ReleaseState{Status: ReleaseStatusFailed, Reason: "release has test in failed phase"}, nil
}
}
// Confirm the cluster state matches the desired config.
if diffOpts := req.Object.GetDriftDetection(); diffOpts.MustDetectChanges() {
diffSet, err := action.Diff(ctx, cfg.Build(nil), rls, kube.ManagedFieldsManager, req.Object.GetDriftDetection().Ignore...)
hasChanges := diffSet.HasChanges()
if err != nil {
if !hasChanges {
return ReleaseState{Status: ReleaseStatusUnknown}, fmt.Errorf("unable to determine cluster state: %w", err)
}
ctrl.LoggerFrom(ctx).Error(err, "diff of release against cluster state completed with error")
}
if hasChanges {
return ReleaseState{Status: ReleaseStatusDrifted, Diff: diffSet}, nil
}
}
return ReleaseState{Status: ReleaseStatusInSync}, nil
default:
return ReleaseState{Status: ReleaseStatusUnknown}, fmt.Errorf("unable to determine state for release with status '%s'", rls.Info.Status)
}
}