...

Source file src/helm.sh/helm/v3/pkg/action/hooks.go

Documentation: helm.sh/helm/v3/pkg/action

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

View as plain text