@@ -16,8 +16,10 @@ import (
16
16
"helm.sh/helm/v3/pkg/release"
17
17
"helm.sh/helm/v3/pkg/storage/driver"
18
18
corev1 "k8s.io/api/core/v1"
19
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
20
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
20
21
apimachyaml "k8s.io/apimachinery/pkg/util/yaml"
22
+ corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
21
23
"sigs.k8s.io/controller-runtime/pkg/client"
22
24
"sigs.k8s.io/controller-runtime/pkg/log"
23
25
@@ -36,6 +38,8 @@ const (
36
38
StateUnchanged string = "Unchanged"
37
39
StateError string = "Error"
38
40
maxHelmReleaseHistory = 10
41
+
42
+ secretTypeIndexV1 = "type=operatorframework.io/index.v1"
39
43
)
40
44
41
45
// Preflight is a check that should be run before making any changes to the cluster
@@ -54,8 +58,26 @@ type Preflight interface {
54
58
}
55
59
56
60
type Helm struct {
57
- ActionClientGetter helmclient.ActionClientGetter
58
- Preflights []Preflight
61
+ actionClientGetter helmclient.ActionClientGetter
62
+ secretsClientGetter corev1client.SecretsGetter
63
+ preflights []Preflight
64
+ systemNamespace string
65
+ }
66
+
67
+ func NewHelm (acg helmclient.ActionClientGetter , scg corev1client.SecretsGetter , preflights []Preflight , systemNamespace string ) (* Helm , error ) {
68
+ if acg == nil {
69
+ return nil , fmt .Errorf ("action client getter is nil" )
70
+ }
71
+ if scg == nil {
72
+ return nil , fmt .Errorf ("secrets client getter is nil" )
73
+ }
74
+
75
+ return & Helm {
76
+ actionClientGetter : acg ,
77
+ secretsClientGetter : scg ,
78
+ preflights : preflights ,
79
+ systemNamespace : systemNamespace ,
80
+ }, nil
59
81
}
60
82
61
83
// shouldSkipPreflight is a helper to determine if the preflight check is CRDUpgradeSafety AND
@@ -85,7 +107,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
85
107
}
86
108
values := chartutil.Values {}
87
109
88
- ac , err := h .ActionClientGetter .ActionClientFor (ctx , ext )
110
+ ac , err := h .actionClientGetter .ActionClientFor (ctx , ext )
89
111
if err != nil {
90
112
return nil , "" , err
91
113
}
@@ -94,12 +116,12 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
94
116
labels : objectLabels ,
95
117
}
96
118
97
- rel , desiredRel , state , err := h .getReleaseState (ac , ext , chrt , values , post )
119
+ rel , desiredRel , state , err := h .getReleaseState (ctx , ac , ext , chrt , values , post )
98
120
if err != nil {
99
121
return nil , "" , err
100
122
}
101
123
102
- for _ , preflight := range h .Preflights {
124
+ for _ , preflight := range h .preflights {
103
125
if shouldSkipPreflight (ctx , preflight , ext , state ) {
104
126
continue
105
127
}
@@ -152,9 +174,28 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
152
174
return relObjects , state , nil
153
175
}
154
176
155
- func (h * Helm ) getReleaseState (cl helmclient.ActionInterface , ext * ocv1.ClusterExtension , chrt * chart.Chart , values chartutil.Values , post postrender.PostRenderer ) (* release.Release , * release.Release , string , error ) {
177
+ func (h * Helm ) getReleaseState (ctx context.Context , cl helmclient.ActionInterface , ext * ocv1.ClusterExtension , chrt * chart.Chart , values chartutil.Values , post postrender.PostRenderer ) (* release.Release , * release.Release , string , error ) {
178
+ logger := log .FromContext (ctx )
156
179
currentRelease , err := cl .Get (ext .GetName ())
180
+
181
+ // if a release is pending at this point, that means that a helm action
182
+ // (installation/upgrade) we were attempting was likely interrupted in-flight.
183
+ // Pending release would leave us in reconciliation error loop because helm
184
+ // wouldn't be able to progress automatically from it.
185
+ //
186
+ // one of the workarounds is to try and remove all helm secrets relating to
187
+ // that pending release which should 'reset' its state communicated to helm
188
+ // and the next reconciliation should be able to successfully pick up from here
189
+ // for context see: https://github.com/helm/helm/issues/5595 and https://github.com/helm/helm/issues/7476
190
+ if err == nil && currentRelease .Info .Status .IsPending () {
191
+ logger .V (4 ).Info ("ClusterExtension release pending" , "extension" , ext .GetName (), "release" , currentRelease .Name )
192
+ if err = h .deleteReleaseSecrets (ctx , currentRelease .Name ); err != nil {
193
+ return nil , nil , StateError , fmt .Errorf ("failed deleting secrets for pending release %q: %w" , currentRelease .Name , err )
194
+ }
195
+ }
196
+
157
197
if errors .Is (err , driver .ErrReleaseNotFound ) {
198
+ logger .V (4 ).Info ("ClusterExtension dry-run install" , "extension" , ext .GetName ())
158
199
desiredRelease , err := cl .Install (ext .GetName (), ext .Spec .Namespace , chrt , values , func (i * action.Install ) error {
159
200
i .DryRun = true
160
201
i .DryRunOption = "server"
@@ -174,6 +215,7 @@ func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1.ClusterE
174
215
}
175
216
176
217
desiredRelease , err := cl .Upgrade (ext .GetName (), ext .Spec .Namespace , chrt , values , func (upgrade * action.Upgrade ) error {
218
+ logger .V (4 ).Info ("ClusterExtension dry-run upgrade" , "extension" , ext .GetName ())
177
219
upgrade .MaxHistory = maxHelmReleaseHistory
178
220
upgrade .DryRun = true
179
221
upgrade .DryRunOption = "server"
@@ -220,3 +262,20 @@ func (p *postrenderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, erro
220
262
}
221
263
return & buf , nil
222
264
}
265
+
266
+ func (h * Helm ) deleteReleaseSecrets (ctx context.Context , releaseName string ) error {
267
+ return h .secretsClientGetter .Secrets (h .systemNamespace ).DeleteCollection (
268
+ ctx ,
269
+ metav1.DeleteOptions {},
270
+ metav1.ListOptions {
271
+ FieldSelector : secretTypeIndexV1 ,
272
+ LabelSelector : fmt .Sprintf (
273
+ "name in (%s),status in(%s, %s, %s)" ,
274
+ releaseName ,
275
+ release .StatusPendingInstall ,
276
+ release .StatusPendingUpgrade ,
277
+ release .StatusPendingRollback ,
278
+ ),
279
+ },
280
+ )
281
+ }
0 commit comments