github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/action/hooks.go (about) 1 /* 2 Copyright The Helm Authors. 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 16 package action 17 18 import ( 19 "bytes" 20 "sort" 21 "time" 22 23 "github.com/pkg/errors" 24 25 "github.com/stefanmcshane/helm/pkg/release" 26 helmtime "github.com/stefanmcshane/helm/pkg/time" 27 ) 28 29 // execHook executes all of the hooks for the given hook event. 30 func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration) error { 31 executingHooks := []*release.Hook{} 32 33 for _, h := range rl.Hooks { 34 for _, e := range h.Events { 35 if e == hook { 36 executingHooks = append(executingHooks, h) 37 } 38 } 39 } 40 41 // hooke are pre-ordered by kind, so keep order stable 42 sort.Stable(hookByWeight(executingHooks)) 43 44 for _, h := range executingHooks { 45 // Set default delete policy to before-hook-creation 46 if h.DeletePolicies == nil || len(h.DeletePolicies) == 0 { 47 // TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion 48 // resources. For all other resource types update in place if a 49 // resource with the same name already exists and is owned by the 50 // current release. 51 h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation} 52 } 53 54 if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation); err != nil { 55 return err 56 } 57 58 resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) 59 if err != nil { 60 return errors.Wrapf(err, "unable to build kubernetes object for %s hook %s", hook, h.Path) 61 } 62 63 // Record the time at which the hook was applied to the cluster 64 h.LastRun = release.HookExecution{ 65 StartedAt: helmtime.Now(), 66 Phase: release.HookPhaseRunning, 67 } 68 cfg.recordRelease(rl) 69 70 // As long as the implementation of WatchUntilReady does not panic, HookPhaseFailed or HookPhaseSucceeded 71 // should always be set by this function. If we fail to do that for any reason, then HookPhaseUnknown is 72 // the most appropriate value to surface. 73 h.LastRun.Phase = release.HookPhaseUnknown 74 75 // Create hook resources 76 if _, err := cfg.KubeClient.Create(resources); err != nil { 77 h.LastRun.CompletedAt = helmtime.Now() 78 h.LastRun.Phase = release.HookPhaseFailed 79 return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) 80 } 81 82 // Watch hook resources until they have completed 83 err = cfg.KubeClient.WatchUntilReady(resources, timeout) 84 // Note the time of success/failure 85 h.LastRun.CompletedAt = helmtime.Now() 86 // Mark hook as succeeded or failed 87 if err != nil { 88 h.LastRun.Phase = release.HookPhaseFailed 89 // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted 90 // under failed condition. If so, then clear the corresponding resource object in the hook 91 if err := cfg.deleteHookByPolicy(h, release.HookFailed); err != nil { 92 return err 93 } 94 return err 95 } 96 h.LastRun.Phase = release.HookPhaseSucceeded 97 } 98 99 // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted 100 // under succeeded condition. If so, then clear the corresponding resource object in each hook 101 for _, h := range executingHooks { 102 if err := cfg.deleteHookByPolicy(h, release.HookSucceeded); err != nil { 103 return err 104 } 105 } 106 107 return nil 108 } 109 110 // hookByWeight is a sorter for hooks 111 type hookByWeight []*release.Hook 112 113 func (x hookByWeight) Len() int { return len(x) } 114 func (x hookByWeight) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 115 func (x hookByWeight) Less(i, j int) bool { 116 if x[i].Weight == x[j].Weight { 117 return x[i].Name < x[j].Name 118 } 119 return x[i].Weight < x[j].Weight 120 } 121 122 // deleteHookByPolicy deletes a hook if the hook policy instructs it to 123 func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy) error { 124 // Never delete CustomResourceDefinitions; this could cause lots of 125 // cascading garbage collection. 126 if h.Kind == "CustomResourceDefinition" { 127 return nil 128 } 129 if hookHasDeletePolicy(h, policy) { 130 resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false) 131 if err != nil { 132 return errors.Wrapf(err, "unable to build kubernetes object for deleting hook %s", h.Path) 133 } 134 _, errs := cfg.KubeClient.Delete(resources) 135 if len(errs) > 0 { 136 return errors.New(joinErrors(errs)) 137 } 138 } 139 return nil 140 } 141 142 // hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices 143 // supported by helm. If so, mark the hook as one should be deleted. 144 func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool { 145 for _, v := range h.DeletePolicies { 146 if policy == v { 147 return true 148 } 149 } 150 return false 151 }