github.com/darkowlzz/helm@v2.5.1-0.20171213183701-6707fe0468d4+incompatible/pkg/tiller/hooks.go (about)

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