github.xiaoq7.com/operator-framework/operator-sdk@v0.8.2/pkg/helm/controller/reconcile.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package controller 16 17 import ( 18 "context" 19 "fmt" 20 "time" 21 22 apierrors "k8s.io/apimachinery/pkg/api/errors" 23 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 rpb "k8s.io/helm/pkg/proto/hapi/release" 26 "sigs.k8s.io/controller-runtime/pkg/client" 27 "sigs.k8s.io/controller-runtime/pkg/reconcile" 28 29 "github.com/operator-framework/operator-sdk/internal/util/diffutil" 30 "github.com/operator-framework/operator-sdk/pkg/helm/internal/types" 31 "github.com/operator-framework/operator-sdk/pkg/helm/release" 32 ) 33 34 // blank assignment to verify that HelmOperatorReconciler implements reconcile.Reconciler 35 var _ reconcile.Reconciler = &HelmOperatorReconciler{} 36 37 // ReleaseHookFunc defines a function signature for release hooks. 38 type ReleaseHookFunc func(*rpb.Release) error 39 40 // HelmOperatorReconciler reconciles custom resources as Helm releases. 41 type HelmOperatorReconciler struct { 42 Client client.Client 43 GVK schema.GroupVersionKind 44 ManagerFactory release.ManagerFactory 45 ReconcilePeriod time.Duration 46 releaseHook ReleaseHookFunc 47 } 48 49 const ( 50 finalizer = "uninstall-helm-release" 51 ) 52 53 // Reconcile reconciles the requested resource by installing, updating, or 54 // uninstalling a Helm release based on the resource's current state. If no 55 // release changes are necessary, Reconcile will create or patch the underlying 56 // resources to match the expected release manifest. 57 func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) { 58 o := &unstructured.Unstructured{} 59 o.SetGroupVersionKind(r.GVK) 60 o.SetNamespace(request.Namespace) 61 o.SetName(request.Name) 62 log := log.WithValues( 63 "namespace", o.GetNamespace(), 64 "name", o.GetName(), 65 "apiVersion", o.GetAPIVersion(), 66 "kind", o.GetKind(), 67 ) 68 log.V(1).Info("Reconciling") 69 70 err := r.Client.Get(context.TODO(), request.NamespacedName, o) 71 if apierrors.IsNotFound(err) { 72 return reconcile.Result{}, nil 73 } 74 if err != nil { 75 log.Error(err, "Failed to lookup resource") 76 return reconcile.Result{}, err 77 } 78 79 manager, err := r.ManagerFactory.NewManager(o) 80 if err != nil { 81 log.Error(err, "Failed to get release manager") 82 return reconcile.Result{}, err 83 } 84 85 status := types.StatusFor(o) 86 log = log.WithValues("release", manager.ReleaseName()) 87 88 deleted := o.GetDeletionTimestamp() != nil 89 pendingFinalizers := o.GetFinalizers() 90 if !deleted && !contains(pendingFinalizers, finalizer) { 91 log.V(1).Info("Adding finalizer", "finalizer", finalizer) 92 finalizers := append(pendingFinalizers, finalizer) 93 o.SetFinalizers(finalizers) 94 err = r.updateResource(o) 95 96 // Need to requeue because finalizer update does not change metadata.generation 97 return reconcile.Result{Requeue: true}, err 98 } 99 100 status.SetCondition(types.HelmAppCondition{ 101 Type: types.ConditionInitialized, 102 Status: types.StatusTrue, 103 }) 104 105 if err := manager.Sync(context.TODO()); err != nil { 106 log.Error(err, "Failed to sync release") 107 status.SetCondition(types.HelmAppCondition{ 108 Type: types.ConditionIrreconcilable, 109 Status: types.StatusTrue, 110 Reason: types.ReasonReconcileError, 111 Message: err.Error(), 112 }) 113 _ = r.updateResourceStatus(o, status) 114 return reconcile.Result{}, err 115 } 116 status.RemoveCondition(types.ConditionIrreconcilable) 117 118 if deleted { 119 if !contains(pendingFinalizers, finalizer) { 120 log.Info("Resource is terminated, skipping reconciliation") 121 return reconcile.Result{}, nil 122 } 123 124 uninstalledRelease, err := manager.UninstallRelease(context.TODO()) 125 if err != nil && err != release.ErrNotFound { 126 log.Error(err, "Failed to uninstall release") 127 status.SetCondition(types.HelmAppCondition{ 128 Type: types.ConditionReleaseFailed, 129 Status: types.StatusTrue, 130 Reason: types.ReasonUninstallError, 131 Message: err.Error(), 132 }) 133 _ = r.updateResourceStatus(o, status) 134 return reconcile.Result{}, err 135 } 136 status.RemoveCondition(types.ConditionReleaseFailed) 137 138 if err == release.ErrNotFound { 139 log.Info("Release not found, removing finalizer") 140 } else { 141 log.Info("Uninstalled release") 142 if log.Enabled() { 143 fmt.Println(diffutil.Diff(uninstalledRelease.GetManifest(), "")) 144 } 145 status.SetCondition(types.HelmAppCondition{ 146 Type: types.ConditionDeployed, 147 Status: types.StatusFalse, 148 Reason: types.ReasonUninstallSuccessful, 149 }) 150 status.DeployedRelease = nil 151 } 152 if err := r.updateResourceStatus(o, status); err != nil { 153 return reconcile.Result{}, err 154 } 155 156 finalizers := []string{} 157 for _, pendingFinalizer := range pendingFinalizers { 158 if pendingFinalizer != finalizer { 159 finalizers = append(finalizers, pendingFinalizer) 160 } 161 } 162 o.SetFinalizers(finalizers) 163 err = r.updateResource(o) 164 165 // Need to requeue because finalizer update does not change metadata.generation 166 return reconcile.Result{Requeue: true}, err 167 } 168 169 if !manager.IsInstalled() { 170 installedRelease, err := manager.InstallRelease(context.TODO()) 171 if err != nil { 172 log.Error(err, "Failed to install release") 173 status.SetCondition(types.HelmAppCondition{ 174 Type: types.ConditionReleaseFailed, 175 Status: types.StatusTrue, 176 Reason: types.ReasonInstallError, 177 Message: err.Error(), 178 }) 179 _ = r.updateResourceStatus(o, status) 180 return reconcile.Result{}, err 181 } 182 status.RemoveCondition(types.ConditionReleaseFailed) 183 184 if r.releaseHook != nil { 185 if err := r.releaseHook(installedRelease); err != nil { 186 log.Error(err, "Failed to run release hook") 187 return reconcile.Result{}, err 188 } 189 } 190 191 log.Info("Installed release") 192 if log.Enabled() { 193 fmt.Println(diffutil.Diff("", installedRelease.GetManifest())) 194 } 195 log.V(1).Info("Config values", "values", installedRelease.GetConfig()) 196 status.SetCondition(types.HelmAppCondition{ 197 Type: types.ConditionDeployed, 198 Status: types.StatusTrue, 199 Reason: types.ReasonInstallSuccessful, 200 Message: installedRelease.GetInfo().GetStatus().GetNotes(), 201 }) 202 status.DeployedRelease = &types.HelmAppRelease{ 203 Name: installedRelease.Name, 204 Manifest: installedRelease.Manifest, 205 } 206 err = r.updateResourceStatus(o, status) 207 return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err 208 } 209 210 if manager.IsUpdateRequired() { 211 previousRelease, updatedRelease, err := manager.UpdateRelease(context.TODO()) 212 if err != nil { 213 log.Error(err, "Failed to update release") 214 status.SetCondition(types.HelmAppCondition{ 215 Type: types.ConditionReleaseFailed, 216 Status: types.StatusTrue, 217 Reason: types.ReasonUpdateError, 218 Message: err.Error(), 219 }) 220 _ = r.updateResourceStatus(o, status) 221 return reconcile.Result{}, err 222 } 223 status.RemoveCondition(types.ConditionReleaseFailed) 224 225 if r.releaseHook != nil { 226 if err := r.releaseHook(updatedRelease); err != nil { 227 log.Error(err, "Failed to run release hook") 228 return reconcile.Result{}, err 229 } 230 } 231 232 log.Info("Updated release") 233 if log.Enabled() { 234 fmt.Println(diffutil.Diff(previousRelease.GetManifest(), updatedRelease.GetManifest())) 235 } 236 log.V(1).Info("Config values", "values", updatedRelease.GetConfig()) 237 status.SetCondition(types.HelmAppCondition{ 238 Type: types.ConditionDeployed, 239 Status: types.StatusTrue, 240 Reason: types.ReasonUpdateSuccessful, 241 Message: updatedRelease.GetInfo().GetStatus().GetNotes(), 242 }) 243 status.DeployedRelease = &types.HelmAppRelease{ 244 Name: updatedRelease.Name, 245 Manifest: updatedRelease.Manifest, 246 } 247 err = r.updateResourceStatus(o, status) 248 return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err 249 } 250 251 // If a change is made to the CR spec that causes a release failure, a 252 // ConditionReleaseFailed is added to the status conditions. If that change 253 // is then reverted to its previous state, the operator will stop 254 // attempting the release and will resume reconciling. In this case, we 255 // need to remove the ConditionReleaseFailed because the failing release is 256 // no longer being attempted. 257 status.RemoveCondition(types.ConditionReleaseFailed) 258 259 expectedRelease, err := manager.ReconcileRelease(context.TODO()) 260 if err != nil { 261 log.Error(err, "Failed to reconcile release") 262 status.SetCondition(types.HelmAppCondition{ 263 Type: types.ConditionIrreconcilable, 264 Status: types.StatusTrue, 265 Reason: types.ReasonReconcileError, 266 Message: err.Error(), 267 }) 268 _ = r.updateResourceStatus(o, status) 269 return reconcile.Result{}, err 270 } 271 status.RemoveCondition(types.ConditionIrreconcilable) 272 273 if r.releaseHook != nil { 274 if err := r.releaseHook(expectedRelease); err != nil { 275 log.Error(err, "Failed to run release hook") 276 return reconcile.Result{}, err 277 } 278 } 279 280 log.Info("Reconciled release") 281 status.DeployedRelease = &types.HelmAppRelease{ 282 Name: expectedRelease.Name, 283 Manifest: expectedRelease.Manifest, 284 } 285 err = r.updateResourceStatus(o, status) 286 return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err 287 } 288 289 func (r HelmOperatorReconciler) updateResource(o *unstructured.Unstructured) error { 290 return r.Client.Update(context.TODO(), o) 291 } 292 293 func (r HelmOperatorReconciler) updateResourceStatus(o *unstructured.Unstructured, status *types.HelmAppStatus) error { 294 o.Object["status"] = status 295 return r.Client.Status().Update(context.TODO(), o) 296 } 297 298 func contains(l []string, s string) bool { 299 for _, elem := range l { 300 if elem == s { 301 return true 302 } 303 } 304 return false 305 }