github.com/qsis/helm@v3.0.0-beta.3+incompatible/pkg/releaseutil/manifest_sorter.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 releaseutil
    18  
    19  import (
    20  	"log"
    21  	"path"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/pkg/errors"
    26  	"sigs.k8s.io/yaml"
    27  
    28  	"helm.sh/helm/pkg/chartutil"
    29  	"helm.sh/helm/pkg/release"
    30  )
    31  
    32  // Manifest represents a manifest file, which has a name and some content.
    33  type Manifest struct {
    34  	Name    string
    35  	Content string
    36  	Head    *SimpleHead
    37  }
    38  
    39  // manifestFile represents a file that contains a manifest.
    40  type manifestFile struct {
    41  	entries map[string]string
    42  	path    string
    43  	apis    chartutil.VersionSet
    44  }
    45  
    46  // result is an intermediate structure used during sorting.
    47  type result struct {
    48  	hooks   []*release.Hook
    49  	generic []Manifest
    50  }
    51  
    52  // TODO: Refactor this out. It's here because naming conventions were not followed through.
    53  // So fix the Test hook names and then remove this.
    54  var events = map[string]release.HookEvent{
    55  	release.HookPreInstall.String():   release.HookPreInstall,
    56  	release.HookPostInstall.String():  release.HookPostInstall,
    57  	release.HookPreDelete.String():    release.HookPreDelete,
    58  	release.HookPostDelete.String():   release.HookPostDelete,
    59  	release.HookPreUpgrade.String():   release.HookPreUpgrade,
    60  	release.HookPostUpgrade.String():  release.HookPostUpgrade,
    61  	release.HookPreRollback.String():  release.HookPreRollback,
    62  	release.HookPostRollback.String(): release.HookPostRollback,
    63  	release.HookTest.String():         release.HookTest,
    64  	// Support test-success for backward compatibility with Helm 2 tests
    65  	"test-success": release.HookTest,
    66  }
    67  
    68  // SortManifests takes a map of filename/YAML contents, splits the file
    69  // by manifest entries, and sorts the entries into hook types.
    70  //
    71  // The resulting hooks struct will be populated with all of the generated hooks.
    72  // Any file that does not declare one of the hook types will be placed in the
    73  // 'generic' bucket.
    74  //
    75  // Files that do not parse into the expected format are simply placed into a map and
    76  // returned.
    77  func SortManifests(files map[string]string, apis chartutil.VersionSet, sort KindSortOrder) ([]*release.Hook, []Manifest, error) {
    78  	result := &result{}
    79  
    80  	for filePath, c := range files {
    81  
    82  		// Skip partials. We could return these as a separate map, but there doesn't
    83  		// seem to be any need for that at this time.
    84  		if strings.HasPrefix(path.Base(filePath), "_") {
    85  			continue
    86  		}
    87  		// Skip empty files and log this.
    88  		if strings.TrimSpace(c) == "" {
    89  			continue
    90  		}
    91  
    92  		manifestFile := &manifestFile{
    93  			entries: SplitManifests(c),
    94  			path:    filePath,
    95  			apis:    apis,
    96  		}
    97  
    98  		if err := manifestFile.sort(result); err != nil {
    99  			return result.hooks, result.generic, err
   100  		}
   101  	}
   102  
   103  	return result.hooks, sortByKind(result.generic, sort), nil
   104  }
   105  
   106  // sort takes a manifestFile object which may contain multiple resource definition
   107  // entries and sorts each entry by hook types, and saves the resulting hooks and
   108  // generic manifests (or non-hooks) to the result struct.
   109  //
   110  // To determine hook type, it looks for a YAML structure like this:
   111  //
   112  //  kind: SomeKind
   113  //  apiVersion: v1
   114  // 	metadata:
   115  //		annotations:
   116  //			helm.sh/hook: pre-install
   117  //
   118  // To determine the policy to delete the hook, it looks for a YAML structure like this:
   119  //
   120  //  kind: SomeKind
   121  //  apiVersion: v1
   122  //  metadata:
   123  // 		annotations:
   124  // 			helm.sh/hook-delete-policy: hook-succeeded
   125  func (file *manifestFile) sort(result *result) error {
   126  	for _, m := range file.entries {
   127  		var entry SimpleHead
   128  		if err := yaml.Unmarshal([]byte(m), &entry); err != nil {
   129  			return errors.Wrapf(err, "YAML parse error on %s", file.path)
   130  		}
   131  
   132  		if entry.Version != "" && !file.apis.Has(entry.Version) {
   133  			return errors.Errorf("apiVersion %q in %s is not available", entry.Version, file.path)
   134  		}
   135  
   136  		if !hasAnyAnnotation(entry) {
   137  			result.generic = append(result.generic, Manifest{
   138  				Name:    file.path,
   139  				Content: m,
   140  				Head:    &entry,
   141  			})
   142  			continue
   143  		}
   144  
   145  		hookTypes, ok := entry.Metadata.Annotations[release.HookAnnotation]
   146  		if !ok {
   147  			result.generic = append(result.generic, Manifest{
   148  				Name:    file.path,
   149  				Content: m,
   150  				Head:    &entry,
   151  			})
   152  			continue
   153  		}
   154  
   155  		hw := calculateHookWeight(entry)
   156  
   157  		h := &release.Hook{
   158  			Name:           entry.Metadata.Name,
   159  			Kind:           entry.Kind,
   160  			Path:           file.path,
   161  			Manifest:       m,
   162  			Events:         []release.HookEvent{},
   163  			Weight:         hw,
   164  			DeletePolicies: []release.HookDeletePolicy{},
   165  		}
   166  
   167  		isUnknownHook := false
   168  		for _, hookType := range strings.Split(hookTypes, ",") {
   169  			hookType = strings.ToLower(strings.TrimSpace(hookType))
   170  			e, ok := events[hookType]
   171  			if !ok {
   172  				isUnknownHook = true
   173  				break
   174  			}
   175  			h.Events = append(h.Events, e)
   176  		}
   177  
   178  		if isUnknownHook {
   179  			log.Printf("info: skipping unknown hook: %q", hookTypes)
   180  			continue
   181  		}
   182  
   183  		result.hooks = append(result.hooks, h)
   184  
   185  		operateAnnotationValues(entry, release.HookDeleteAnnotation, func(value string) {
   186  			h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value))
   187  		})
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  // hasAnyAnnotation returns true if the given entry has any annotations at all.
   194  func hasAnyAnnotation(entry SimpleHead) bool {
   195  	return entry.Metadata != nil &&
   196  		entry.Metadata.Annotations != nil &&
   197  		len(entry.Metadata.Annotations) != 0
   198  }
   199  
   200  // calculateHookWeight finds the weight in the hook weight annotation.
   201  //
   202  // If no weight is found, the assigned weight is 0
   203  func calculateHookWeight(entry SimpleHead) int {
   204  	hws := entry.Metadata.Annotations[release.HookWeightAnnotation]
   205  	hw, err := strconv.Atoi(hws)
   206  	if err != nil {
   207  		hw = 0
   208  	}
   209  	return hw
   210  }
   211  
   212  // operateAnnotationValues finds the given annotation and runs the operate function with the value of that annotation
   213  func operateAnnotationValues(entry SimpleHead, annotation string, operate func(p string)) {
   214  	if dps, ok := entry.Metadata.Annotations[annotation]; ok {
   215  		for _, dp := range strings.Split(dps, ",") {
   216  			dp = strings.ToLower(strings.TrimSpace(dp))
   217  			operate(dp)
   218  		}
   219  	}
   220  }