github.com/koderover/helm@v2.17.0+incompatible/pkg/tiller/hooks.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package tiller
    18  
    19  import (
    20  	"fmt"
    21  	"log"
    22  	"path"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/ghodss/yaml"
    27  
    28  	"k8s.io/helm/pkg/chartutil"
    29  	"k8s.io/helm/pkg/hooks"
    30  	"k8s.io/helm/pkg/manifest"
    31  	"k8s.io/helm/pkg/proto/hapi/release"
    32  	util "k8s.io/helm/pkg/releaseutil"
    33  )
    34  
    35  var events = map[string]release.Hook_Event{
    36  	hooks.PreInstall:         release.Hook_PRE_INSTALL,
    37  	hooks.PostInstall:        release.Hook_POST_INSTALL,
    38  	hooks.PreDelete:          release.Hook_PRE_DELETE,
    39  	hooks.PostDelete:         release.Hook_POST_DELETE,
    40  	hooks.PreUpgrade:         release.Hook_PRE_UPGRADE,
    41  	hooks.PostUpgrade:        release.Hook_POST_UPGRADE,
    42  	hooks.PreRollback:        release.Hook_PRE_ROLLBACK,
    43  	hooks.PostRollback:       release.Hook_POST_ROLLBACK,
    44  	hooks.ReleaseTestSuccess: release.Hook_RELEASE_TEST_SUCCESS,
    45  	hooks.ReleaseTestFailure: release.Hook_RELEASE_TEST_FAILURE,
    46  	hooks.CRDInstall:         release.Hook_CRD_INSTALL,
    47  }
    48  
    49  // deletePolices represents a mapping between the key in the annotation for label deleting policy and its real meaning
    50  var deletePolices = map[string]release.Hook_DeletePolicy{
    51  	hooks.HookSucceeded:      release.Hook_SUCCEEDED,
    52  	hooks.HookFailed:         release.Hook_FAILED,
    53  	hooks.BeforeHookCreation: release.Hook_BEFORE_HOOK_CREATION,
    54  }
    55  
    56  // Timeout used when deleting resources with a hook-delete-policy.
    57  const defaultHookDeleteTimeoutInSeconds = int64(60)
    58  
    59  // Manifest represents a manifest file, which has a name and some content.
    60  type Manifest = manifest.Manifest
    61  
    62  type result struct {
    63  	hooks   []*release.Hook
    64  	generic []Manifest
    65  }
    66  
    67  type manifestFile struct {
    68  	entries map[string]string
    69  	path    string
    70  	apis    chartutil.VersionSet
    71  }
    72  
    73  // sortManifests takes a map of filename/YAML contents, splits the file
    74  // by manifest entries, and sorts the entries into hook types.
    75  //
    76  // The resulting hooks struct will be populated with all of the generated hooks.
    77  // Any file that does not declare one of the hook types will be placed in the
    78  // 'generic' bucket.
    79  //
    80  // Files that do not parse into the expected format are simply placed into a map and
    81  // returned.
    82  func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []Manifest, error) {
    83  	result := &result{}
    84  
    85  	for filePath, c := range files {
    86  
    87  		// Skip partials. We could return these as a separate map, but there doesn't
    88  		// seem to be any need for that at this time.
    89  		if strings.HasPrefix(path.Base(filePath), "_") {
    90  			continue
    91  		}
    92  		// Skip empty files and log this.
    93  		if len(strings.TrimSpace(c)) == 0 {
    94  			log.Printf("info: manifest %q is empty. Skipping.", filePath)
    95  			continue
    96  		}
    97  
    98  		manifestFile := &manifestFile{
    99  			entries: util.SplitManifests(c),
   100  			path:    filePath,
   101  			apis:    apis,
   102  		}
   103  
   104  		if err := manifestFile.sort(result); err != nil {
   105  			return result.hooks, result.generic, err
   106  		}
   107  	}
   108  
   109  	return result.hooks, sortByKind(result.generic, sort), nil
   110  }
   111  
   112  // sort takes a manifestFile object which may contain multiple resource definition
   113  // entries and sorts each entry by hook types, and saves the resulting hooks and
   114  // generic manifests (or non-hooks) to the result struct.
   115  //
   116  // To determine hook type, it looks for a YAML structure like this:
   117  //
   118  //  kind: SomeKind
   119  //  apiVersion: v1
   120  // 	metadata:
   121  //		annotations:
   122  //			helm.sh/hook: pre-install
   123  //
   124  // To determine the policy to delete the hook, it looks for a YAML structure like this:
   125  //
   126  //  kind: SomeKind
   127  //  apiVersion: v1
   128  //  metadata:
   129  // 		annotations:
   130  // 			helm.sh/hook-delete-policy: hook-succeeded
   131  func (file *manifestFile) sort(result *result) error {
   132  	for _, m := range file.entries {
   133  		var entry util.SimpleHead
   134  		err := yaml.Unmarshal([]byte(m), &entry)
   135  
   136  		if err != nil {
   137  			e := fmt.Errorf("YAML parse error on %s: %s", file.path, err)
   138  			return e
   139  		}
   140  
   141  		if !hasAnyAnnotation(entry) {
   142  			result.generic = append(result.generic, Manifest{
   143  				Name:    file.path,
   144  				Content: m,
   145  				Head:    &entry,
   146  			})
   147  			continue
   148  		}
   149  
   150  		hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno]
   151  		if !ok {
   152  			result.generic = append(result.generic, Manifest{
   153  				Name:    file.path,
   154  				Content: m,
   155  				Head:    &entry,
   156  			})
   157  			continue
   158  		}
   159  
   160  		hw := calculateHookWeight(entry)
   161  
   162  		h := &release.Hook{
   163  			Name:           entry.Metadata.Name,
   164  			Kind:           entry.Kind,
   165  			Path:           file.path,
   166  			Manifest:       m,
   167  			Events:         []release.Hook_Event{},
   168  			Weight:         hw,
   169  			DeletePolicies: []release.Hook_DeletePolicy{},
   170  		}
   171  
   172  		isUnknownHook := false
   173  		for _, hookType := range strings.Split(hookTypes, ",") {
   174  			hookType = strings.ToLower(strings.TrimSpace(hookType))
   175  			e, ok := events[hookType]
   176  			if !ok {
   177  				isUnknownHook = true
   178  				break
   179  			}
   180  			h.Events = append(h.Events, e)
   181  		}
   182  
   183  		if isUnknownHook {
   184  			log.Printf("info: skipping unknown hook: %q", hookTypes)
   185  			continue
   186  		}
   187  
   188  		result.hooks = append(result.hooks, h)
   189  
   190  		operateAnnotationValues(entry, hooks.HookDeleteAnno, func(value string) {
   191  			policy, exist := deletePolices[value]
   192  			if exist {
   193  				h.DeletePolicies = append(h.DeletePolicies, policy)
   194  			} else {
   195  				log.Printf("info: skipping unknown hook delete policy: %q", value)
   196  			}
   197  		})
   198  
   199  		// Only check for delete timeout annotation if there is a deletion policy.
   200  		if len(h.DeletePolicies) > 0 {
   201  			h.DeleteTimeout = defaultHookDeleteTimeoutInSeconds
   202  			operateAnnotationValues(entry, hooks.HookDeleteTimeoutAnno, func(value string) {
   203  				timeout, err := strconv.ParseInt(value, 10, 64)
   204  				if err != nil || timeout < 0 {
   205  					log.Printf("info: ignoring invalid hook delete timeout value: %q", value)
   206  				}
   207  				h.DeleteTimeout = timeout
   208  			})
   209  		}
   210  	}
   211  	return nil
   212  }
   213  
   214  func hasAnyAnnotation(entry util.SimpleHead) bool {
   215  	if entry.Metadata == nil ||
   216  		entry.Metadata.Annotations == nil ||
   217  		len(entry.Metadata.Annotations) == 0 {
   218  		return false
   219  	}
   220  
   221  	return true
   222  }
   223  
   224  func calculateHookWeight(entry util.SimpleHead) int32 {
   225  	hws := entry.Metadata.Annotations[hooks.HookWeightAnno]
   226  	hw, err := strconv.Atoi(hws)
   227  	if err != nil {
   228  		hw = 0
   229  	}
   230  
   231  	return int32(hw)
   232  }
   233  
   234  func operateAnnotationValues(entry util.SimpleHead, annotation string, operate func(p string)) {
   235  	if dps, ok := entry.Metadata.Annotations[annotation]; ok {
   236  		for _, dp := range strings.Split(dps, ",") {
   237  			dp = strings.ToLower(strings.TrimSpace(dp))
   238  			operate(dp)
   239  		}
   240  	}
   241  }