gitlab.azmi.pl/azmi-open-source/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 }