github.com/Beeketing/helm@v2.12.1+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  // Manifest represents a manifest file, which has a name and some content.
    57  type Manifest = manifest.Manifest
    58  
    59  type result struct {
    60  	hooks   []*release.Hook
    61  	generic []Manifest
    62  }
    63  
    64  type manifestFile struct {
    65  	entries map[string]string
    66  	path    string
    67  	apis    chartutil.VersionSet
    68  }
    69  
    70  // sortManifests takes a map of filename/YAML contents, splits the file
    71  // by manifest entries, and sorts the entries into hook types.
    72  //
    73  // The resulting hooks struct will be populated with all of the generated hooks.
    74  // Any file that does not declare one of the hook types will be placed in the
    75  // 'generic' bucket.
    76  //
    77  // Files that do not parse into the expected format are simply placed into a map and
    78  // returned.
    79  func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []Manifest, error) {
    80  	result := &result{}
    81  
    82  	for filePath, c := range files {
    83  
    84  		// Skip partials. We could return these as a separate map, but there doesn't
    85  		// seem to be any need for that at this time.
    86  		if strings.HasPrefix(path.Base(filePath), "_") {
    87  			continue
    88  		}
    89  		// Skip empty files and log this.
    90  		if len(strings.TrimSpace(c)) == 0 {
    91  			log.Printf("info: manifest %q is empty. Skipping.", filePath)
    92  			continue
    93  		}
    94  
    95  		manifestFile := &manifestFile{
    96  			entries: util.SplitManifests(c),
    97  			path:    filePath,
    98  			apis:    apis,
    99  		}
   100  
   101  		if err := manifestFile.sort(result); err != nil {
   102  			return result.hooks, result.generic, err
   103  		}
   104  	}
   105  
   106  	return result.hooks, sortByKind(result.generic, sort), nil
   107  }
   108  
   109  // sort takes a manifestFile object which may contain multiple resource definition
   110  // entries and sorts each entry by hook types, and saves the resulting hooks and
   111  // generic manifests (or non-hooks) to the result struct.
   112  //
   113  // To determine hook type, it looks for a YAML structure like this:
   114  //
   115  //  kind: SomeKind
   116  //  apiVersion: v1
   117  // 	metadata:
   118  //		annotations:
   119  //			helm.sh/hook: pre-install
   120  //
   121  // To determine the policy to delete the hook, it looks for a YAML structure like this:
   122  //
   123  //  kind: SomeKind
   124  //  apiVersion: v1
   125  //  metadata:
   126  // 		annotations:
   127  // 			helm.sh/hook-delete-policy: hook-succeeded
   128  func (file *manifestFile) sort(result *result) error {
   129  	for _, m := range file.entries {
   130  		var entry util.SimpleHead
   131  		err := yaml.Unmarshal([]byte(m), &entry)
   132  
   133  		if err != nil {
   134  			e := fmt.Errorf("YAML parse error on %s: %s", file.path, err)
   135  			return e
   136  		}
   137  
   138  		if !hasAnyAnnotation(entry) {
   139  			result.generic = append(result.generic, Manifest{
   140  				Name:    file.path,
   141  				Content: m,
   142  				Head:    &entry,
   143  			})
   144  			continue
   145  		}
   146  
   147  		hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno]
   148  		if !ok {
   149  			result.generic = append(result.generic, Manifest{
   150  				Name:    file.path,
   151  				Content: m,
   152  				Head:    &entry,
   153  			})
   154  			continue
   155  		}
   156  
   157  		hw := calculateHookWeight(entry)
   158  
   159  		h := &release.Hook{
   160  			Name:           entry.Metadata.Name,
   161  			Kind:           entry.Kind,
   162  			Path:           file.path,
   163  			Manifest:       m,
   164  			Events:         []release.Hook_Event{},
   165  			Weight:         hw,
   166  			DeletePolicies: []release.Hook_DeletePolicy{},
   167  		}
   168  
   169  		isUnknownHook := false
   170  		for _, hookType := range strings.Split(hookTypes, ",") {
   171  			hookType = strings.ToLower(strings.TrimSpace(hookType))
   172  			e, ok := events[hookType]
   173  			if !ok {
   174  				isUnknownHook = true
   175  				break
   176  			}
   177  			h.Events = append(h.Events, e)
   178  		}
   179  
   180  		if isUnknownHook {
   181  			log.Printf("info: skipping unknown hook: %q", hookTypes)
   182  			continue
   183  		}
   184  
   185  		result.hooks = append(result.hooks, h)
   186  
   187  		operateAnnotationValues(entry, hooks.HookDeleteAnno, func(value string) {
   188  			policy, exist := deletePolices[value]
   189  			if exist {
   190  				h.DeletePolicies = append(h.DeletePolicies, policy)
   191  			} else {
   192  				log.Printf("info: skipping unknown hook delete policy: %q", value)
   193  			}
   194  		})
   195  	}
   196  	return nil
   197  }
   198  
   199  func hasAnyAnnotation(entry util.SimpleHead) bool {
   200  	if entry.Metadata == nil ||
   201  		entry.Metadata.Annotations == nil ||
   202  		len(entry.Metadata.Annotations) == 0 {
   203  		return false
   204  	}
   205  
   206  	return true
   207  }
   208  
   209  func calculateHookWeight(entry util.SimpleHead) int32 {
   210  	hws := entry.Metadata.Annotations[hooks.HookWeightAnno]
   211  	hw, err := strconv.Atoi(hws)
   212  	if err != nil {
   213  		hw = 0
   214  	}
   215  
   216  	return int32(hw)
   217  }
   218  
   219  func operateAnnotationValues(entry util.SimpleHead, annotation string, operate func(p string)) {
   220  	if dps, ok := entry.Metadata.Annotations[annotation]; ok {
   221  		for _, dp := range strings.Split(dps, ",") {
   222  			dp = strings.ToLower(strings.TrimSpace(dp))
   223  			operate(dp)
   224  		}
   225  	}
   226  }