github.com/felipejfc/helm@v2.1.2+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  	"strings"
    24  
    25  	"github.com/ghodss/yaml"
    26  
    27  	"k8s.io/helm/pkg/proto/hapi/release"
    28  )
    29  
    30  // hookAnno is the label name for a hook
    31  const hookAnno = "helm.sh/hook"
    32  
    33  const (
    34  	preInstall   = "pre-install"
    35  	postInstall  = "post-install"
    36  	preDelete    = "pre-delete"
    37  	postDelete   = "post-delete"
    38  	preUpgrade   = "pre-upgrade"
    39  	postUpgrade  = "post-upgrade"
    40  	preRollback  = "pre-rollback"
    41  	postRollback = "post-rollback"
    42  )
    43  
    44  var events = map[string]release.Hook_Event{
    45  	preInstall:   release.Hook_PRE_INSTALL,
    46  	postInstall:  release.Hook_POST_INSTALL,
    47  	preDelete:    release.Hook_PRE_DELETE,
    48  	postDelete:   release.Hook_POST_DELETE,
    49  	preUpgrade:   release.Hook_PRE_UPGRADE,
    50  	postUpgrade:  release.Hook_POST_UPGRADE,
    51  	preRollback:  release.Hook_PRE_ROLLBACK,
    52  	postRollback: release.Hook_POST_ROLLBACK,
    53  }
    54  
    55  type simpleHead struct {
    56  	Version  string `json:"apiVersion"`
    57  	Kind     string `json:"kind,omitempty"`
    58  	Metadata *struct {
    59  		Name        string            `json:"name"`
    60  		Annotations map[string]string `json:"annotations"`
    61  	} `json:"metadata,omitempty"`
    62  }
    63  
    64  type versionSet map[string]struct{}
    65  
    66  func newVersionSet(apiVersions ...string) versionSet {
    67  	vs := versionSet{}
    68  	for _, v := range apiVersions {
    69  		vs[v] = struct{}{}
    70  	}
    71  	return vs
    72  }
    73  
    74  func (v versionSet) Has(apiVersion string) bool {
    75  	_, ok := v[apiVersion]
    76  	return ok
    77  }
    78  
    79  // manifest represents a manifest file, which has a name and some content.
    80  type manifest struct {
    81  	name    string
    82  	content string
    83  	head    *simpleHead
    84  }
    85  
    86  // sortManifests takes a map of filename/YAML contents and sorts them into hook types.
    87  //
    88  // The resulting hooks struct will be populated with all of the generated hooks.
    89  // Any file that does not declare one of the hook types will be placed in the
    90  // 'generic' bucket.
    91  //
    92  // To determine hook type, this looks for a YAML structure like this:
    93  //
    94  //  kind: SomeKind
    95  //  apiVersion: v1
    96  // 	metadata:
    97  //		annotations:
    98  //			helm.sh/hook: pre-install
    99  //
   100  // Where HOOK_NAME is one of the known hooks.
   101  //
   102  // If a file declares more than one hook, it will be copied into all of the applicable
   103  // hook buckets. (Note: label keys are not unique within the labels section).
   104  //
   105  // Files that do not parse into the expected format are simply placed into a map and
   106  // returned.
   107  func sortManifests(files map[string]string, apis versionSet, sort SortOrder) ([]*release.Hook, []manifest, error) {
   108  	hs := []*release.Hook{}
   109  	generic := []manifest{}
   110  
   111  	for n, c := range files {
   112  		// Skip partials. We could return these as a separate map, but there doesn't
   113  		// seem to be any need for that at this time.
   114  		if strings.HasPrefix(path.Base(n), "_") {
   115  			continue
   116  		}
   117  		// Skip empty files, and log this.
   118  		if len(strings.TrimSpace(c)) == 0 {
   119  			log.Printf("info: manifest %q is empty. Skipping.", n)
   120  			continue
   121  		}
   122  
   123  		var sh simpleHead
   124  		err := yaml.Unmarshal([]byte(c), &sh)
   125  
   126  		if err != nil {
   127  			e := fmt.Errorf("YAML parse error on %s: %s", n, err)
   128  			return hs, generic, e
   129  		}
   130  
   131  		if sh.Version != "" && !apis.Has(sh.Version) {
   132  			return hs, generic, fmt.Errorf("apiVersion %q in %s is not available", sh.Version, n)
   133  		}
   134  
   135  		if sh.Metadata == nil || sh.Metadata.Annotations == nil || len(sh.Metadata.Annotations) == 0 {
   136  			generic = append(generic, manifest{name: n, content: c, head: &sh})
   137  			continue
   138  		}
   139  
   140  		hookTypes, ok := sh.Metadata.Annotations[hookAnno]
   141  		if !ok {
   142  			generic = append(generic, manifest{name: n, content: c, head: &sh})
   143  			continue
   144  		}
   145  		h := &release.Hook{
   146  			Name:     sh.Metadata.Name,
   147  			Kind:     sh.Kind,
   148  			Path:     n,
   149  			Manifest: c,
   150  			Events:   []release.Hook_Event{},
   151  		}
   152  
   153  		isHook := false
   154  		for _, hookType := range strings.Split(hookTypes, ",") {
   155  			hookType = strings.ToLower(strings.TrimSpace(hookType))
   156  			e, ok := events[hookType]
   157  			if ok {
   158  				isHook = true
   159  				h.Events = append(h.Events, e)
   160  			}
   161  		}
   162  
   163  		if !isHook {
   164  			log.Printf("info: skipping unknown hook: %q", hookTypes)
   165  			continue
   166  		}
   167  		hs = append(hs, h)
   168  	}
   169  	return hs, sortByKind(generic, sort), nil
   170  }