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  }