github.com/mkimuram/operator-sdk@v0.7.1-0.20190410172100-52ad33a4bda0/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 var _ reconcile.Reconciler = &HelmOperatorReconciler{} 35 36 // ReleaseHookFunc defines a function signature for release hooks. 37 type ReleaseHookFunc func(*rpb.Release) error 38 39 // HelmOperatorReconciler reconciles custom resources as Helm releases. 40 type HelmOperatorReconciler struct { 41 Client client.Client 42 GVK schema.GroupVersionKind 43 ManagerFactory release.ManagerFactory 44 ReconcilePeriod time.Duration 45 releaseHook ReleaseHookFunc 46 } 47 48 const ( 49 finalizer = "uninstall-helm-release" 50 ) 51 52 // Reconcile reconciles the requested resource by installing, updating, or 53 // uninstalling a Helm release based on the resource's current state. If no 54 // release changes are necessary, Reconcile will create or patch the underlying 55 // resources to match the expected release manifest. 56 func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) { 57 o := &unstructured.Unstructured{} 58 o.SetGroupVersionKind(r.GVK) 59 o.SetNamespace(request.Namespace) 60 o.SetName(request.Name) 61 log := log.WithValues( 62 "namespace", o.GetNamespace(), 63 "name", o.GetName(), 64 "apiVersion", o.GetAPIVersion(), 65 "kind", o.GetKind(), 66 ) 67 log.V(1).Info("Reconciling") 68 69 err := r.Client.Get(context.TODO(), request.NamespacedName, o) 70 if apierrors.IsNotFound(err) { 71 return reconcile.Result{}, nil 72 } 73 if err != nil { 74 log.Error(err, "Failed to lookup resource") 75 return reconcile.Result{}, err 76 } 77 78 manager, err := r.ManagerFactory.NewManager(o) 79 if err != nil { 80 log.Error(err, "Failed to get release manager") 81 return reconcile.Result{}, err 82 } 83 84 status := types.StatusFor(o) 85 log = log.WithValues("release", manager.ReleaseName()) 86 87 deleted := o.GetDeletionTimestamp() != nil 88 pendingFinalizers := o.GetFinalizers() 89 if !deleted && !contains(pendingFinalizers, finalizer) { 90 log.V(1).Info("Adding finalizer", "finalizer", finalizer) 91 finalizers := append(pendingFinalizers, finalizer) 92 o.SetFinalizers(finalizers) 93 err = r.updateResource(o) 94 95 // Need to requeue because finalizer update does not change metadata.generation 96 return reconcile.Result{Requeue: true}, err 97 } 98 99 status.SetCondition(types.HelmAppCondition{ 100 Type: types.ConditionInitialized, 101 Status: types.StatusTrue, 102 }) 103 104 if err := manager.Sync(context.TODO()); err != nil { 105 log.Error(err, "Failed to sync release") 106 status.SetCondition(types.HelmAppCondition{ 107 Type: types.ConditionIrreconcilable, 108 Status: types.StatusTrue, 109 Reason: types.ReasonReconcileError, 110 Message: err.Error(), 111 }) 112 _ = r.updateResourceStatus(o, status) 113 return reconcile.Result{}, err 114 } 115 status.RemoveCondition(types.ConditionIrreconcilable) 116 117 if deleted { 118 if !contains(pendingFinalizers, finalizer) { 119 log.Info("Resource is terminated, skipping reconciliation") 120 return reconcile.Result{}, nil 121 } 122 123 uninstalledRelease, err := manager.UninstallRelease(context.TODO()) 124 if err != nil && err != release.ErrNotFound { 125 log.Error(err, "Failed to uninstall release") 126 status.SetCondition(types.HelmAppCondition{ 127 Type: types.ConditionReleaseFailed, 128 Status: types.StatusTrue, 129 Reason: types.ReasonUninstallError, 130 Message: err.Error(), 131 }) 132 _ = r.updateResourceStatus(o, status) 133 return reconcile.Result{}, err 134 } 135 status.RemoveCondition(types.ConditionReleaseFailed) 136 137 if err == release.ErrNotFound { 138 log.Info("Release not found, removing finalizer") 139 } else { 140 log.Info("Uninstalled release") 141 if log.Enabled() { 142 fmt.Println(diffutil.Diff(uninstalledRelease.GetManifest(), "")) 143 } 144 status.SetCondition(types.HelmAppCondition{ 145 Type: types.ConditionDeployed, 146 Status: types.StatusFalse, 147 Reason: types.ReasonUninstallSuccessful, 148 }) 149 } 150 if err := r.updateResourceStatus(o, status); err != nil { 151 return reconcile.Result{}, err 152 } 153 154 finalizers := []string{} 155 for _, pendingFinalizer := range pendingFinalizers { 156 if pendingFinalizer != finalizer { 157 finalizers = append(finalizers, pendingFinalizer) 158 } 159 } 160 o.SetFinalizers(finalizers) 161 err = r.updateResource(o) 162 163 // Need to requeue because finalizer update does not change metadata.generation 164 return reconcile.Result{Requeue: true}, err 165 } 166 167 if !manager.IsInstalled() { 168 installedRelease, err := manager.InstallRelease(context.TODO()) 169 if err != nil { 170 log.Error(err, "Failed to install release") 171 status.SetCondition(types.HelmAppCondition{ 172 Type: types.ConditionReleaseFailed, 173 Status: types.StatusTrue, 174 Reason: types.ReasonInstallError, 175 Message: err.Error(), 176 Release: installedRelease, 177 }) 178 _ = r.updateResourceStatus(o, status) 179 return reconcile.Result{}, err 180 } 181 status.RemoveCondition(types.ConditionReleaseFailed) 182 183 if r.releaseHook != nil { 184 if err := r.releaseHook(installedRelease); err != nil { 185 log.Error(err, "Failed to run release hook") 186 return reconcile.Result{}, err 187 } 188 } 189 190 log.Info("Installed release") 191 if log.Enabled() { 192 fmt.Println(diffutil.Diff("", installedRelease.GetManifest())) 193 } 194 log.V(1).Info("Config values", "values", installedRelease.GetConfig()) 195 status.SetCondition(types.HelmAppCondition{ 196 Type: types.ConditionDeployed, 197 Status: types.StatusTrue, 198 Reason: types.ReasonInstallSuccessful, 199 Message: installedRelease.GetInfo().GetStatus().GetNotes(), 200 Release: installedRelease, 201 }) 202 err = r.updateResourceStatus(o, status) 203 return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err 204 } 205 206 if manager.IsUpdateRequired() { 207 previousRelease, updatedRelease, err := manager.UpdateRelease(context.TODO()) 208 if err != nil { 209 log.Error(err, "Failed to update release") 210 status.SetCondition(types.HelmAppCondition{ 211 Type: types.ConditionReleaseFailed, 212 Status: types.StatusTrue, 213 Reason: types.ReasonUpdateError, 214 Message: err.Error(), 215 Release: updatedRelease, 216 }) 217 _ = r.updateResourceStatus(o, status) 218 return reconcile.Result{}, err 219 } 220 status.RemoveCondition(types.ConditionReleaseFailed) 221 222 if r.releaseHook != nil { 223 if err := r.releaseHook(updatedRelease); err != nil { 224 log.Error(err, "Failed to run release hook") 225 return reconcile.Result{}, err 226 } 227 } 228 229 log.Info("Updated release") 230 if log.Enabled() { 231 fmt.Println(diffutil.Diff(previousRelease.GetManifest(), updatedRelease.GetManifest())) 232 } 233 log.V(1).Info("Config values", "values", updatedRelease.GetConfig()) 234 status.SetCondition(types.HelmAppCondition{ 235 Type: types.ConditionDeployed, 236 Status: types.StatusTrue, 237 Reason: types.ReasonUpdateSuccessful, 238 Message: updatedRelease.GetInfo().GetStatus().GetNotes(), 239 Release: updatedRelease, 240 }) 241 err = r.updateResourceStatus(o, status) 242 return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err 243 } 244 245 expectedRelease, err := manager.ReconcileRelease(context.TODO()) 246 if err != nil { 247 log.Error(err, "Failed to reconcile release") 248 status.SetCondition(types.HelmAppCondition{ 249 Type: types.ConditionIrreconcilable, 250 Status: types.StatusTrue, 251 Reason: types.ReasonReconcileError, 252 Message: err.Error(), 253 }) 254 _ = r.updateResourceStatus(o, status) 255 return reconcile.Result{}, err 256 } 257 status.RemoveCondition(types.ConditionIrreconcilable) 258 259 if r.releaseHook != nil { 260 if err := r.releaseHook(expectedRelease); err != nil { 261 log.Error(err, "Failed to run release hook") 262 return reconcile.Result{}, err 263 } 264 } 265 266 log.Info("Reconciled release") 267 err = r.updateResourceStatus(o, status) 268 return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err 269 } 270 271 func (r HelmOperatorReconciler) updateResource(o *unstructured.Unstructured) error { 272 return r.Client.Update(context.TODO(), o) 273 } 274 275 func (r HelmOperatorReconciler) updateResourceStatus(o *unstructured.Unstructured, status *types.HelmAppStatus) error { 276 o.Object["status"] = status 277 return r.Client.Status().Update(context.TODO(), o) 278 } 279 280 func contains(l []string, s string) bool { 281 for _, elem := range l { 282 if elem == s { 283 return true 284 } 285 } 286 return false 287 }