...

Source file src/helm.sh/helm/v3/pkg/releaseutil/manifest_sorter.go

Documentation: helm.sh/helm/v3/pkg/releaseutil

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

View as plain text