github.com/defensepoint-snyk-test/helm-new@v0.0.0-20211130153739-c57ea64d6603/pkg/repo/index.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 repo 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "path" 26 "path/filepath" 27 "sort" 28 "strings" 29 "time" 30 31 "github.com/Masterminds/semver" 32 "github.com/ghodss/yaml" 33 34 "k8s.io/helm/pkg/chartutil" 35 "k8s.io/helm/pkg/proto/hapi/chart" 36 "k8s.io/helm/pkg/provenance" 37 "k8s.io/helm/pkg/urlutil" 38 ) 39 40 var indexPath = "index.yaml" 41 42 // APIVersionV1 is the v1 API version for index and repository files. 43 const APIVersionV1 = "v1" 44 45 var ( 46 // ErrNoAPIVersion indicates that an API version was not specified. 47 ErrNoAPIVersion = errors.New("no API version specified") 48 // ErrNoChartVersion indicates that a chart with the given version is not found. 49 ErrNoChartVersion = errors.New("no chart version found") 50 // ErrNoChartName indicates that a chart with the given name is not found. 51 ErrNoChartName = errors.New("no chart name found") 52 ) 53 54 // ChartVersions is a list of versioned chart references. 55 // Implements a sorter on Version. 56 type ChartVersions []*ChartVersion 57 58 // Len returns the length. 59 func (c ChartVersions) Len() int { return len(c) } 60 61 // Swap swaps the position of two items in the versions slice. 62 func (c ChartVersions) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 63 64 // Less returns true if the version of entry a is less than the version of entry b. 65 func (c ChartVersions) Less(a, b int) bool { 66 // Failed parse pushes to the back. 67 i, err := semver.NewVersion(c[a].Version) 68 if err != nil { 69 return true 70 } 71 j, err := semver.NewVersion(c[b].Version) 72 if err != nil { 73 return false 74 } 75 return i.LessThan(j) 76 } 77 78 // IndexFile represents the index file in a chart repository 79 type IndexFile struct { 80 APIVersion string `json:"apiVersion"` 81 Generated time.Time `json:"generated"` 82 Entries map[string]ChartVersions `json:"entries"` 83 PublicKeys []string `json:"publicKeys,omitempty"` 84 } 85 86 // NewIndexFile initializes an index. 87 func NewIndexFile() *IndexFile { 88 return &IndexFile{ 89 APIVersion: APIVersionV1, 90 Generated: time.Now(), 91 Entries: map[string]ChartVersions{}, 92 PublicKeys: []string{}, 93 } 94 } 95 96 // LoadIndexFile takes a file at the given path and returns an IndexFile object 97 func LoadIndexFile(path string) (*IndexFile, error) { 98 b, err := ioutil.ReadFile(path) 99 if err != nil { 100 return nil, err 101 } 102 return loadIndex(b) 103 } 104 105 // Add adds a file to the index 106 // This can leave the index in an unsorted state 107 func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { 108 u := filename 109 if baseURL != "" { 110 var err error 111 _, file := filepath.Split(filename) 112 u, err = urlutil.URLJoin(baseURL, file) 113 if err != nil { 114 u = path.Join(baseURL, file) 115 } 116 } 117 cr := &ChartVersion{ 118 URLs: []string{u}, 119 Metadata: md, 120 Digest: digest, 121 Created: time.Now(), 122 } 123 if ee, ok := i.Entries[md.Name]; !ok { 124 i.Entries[md.Name] = ChartVersions{cr} 125 } else { 126 i.Entries[md.Name] = append(ee, cr) 127 } 128 } 129 130 // Has returns true if the index has an entry for a chart with the given name and exact version. 131 func (i IndexFile) Has(name, version string) bool { 132 _, err := i.Get(name, version) 133 return err == nil 134 } 135 136 // SortEntries sorts the entries by version in descending order. 137 // 138 // In canonical form, the individual version records should be sorted so that 139 // the most recent release for every version is in the 0th slot in the 140 // Entries.ChartVersions array. That way, tooling can predict the newest 141 // version without needing to parse SemVers. 142 func (i IndexFile) SortEntries() { 143 for _, versions := range i.Entries { 144 sort.Sort(sort.Reverse(versions)) 145 } 146 } 147 148 // Get returns the ChartVersion for the given name. 149 // 150 // If version is empty, this will return the chart with the highest version. 151 func (i IndexFile) Get(name, version string) (*ChartVersion, error) { 152 vs, ok := i.Entries[name] 153 if !ok { 154 return nil, ErrNoChartName 155 } 156 if len(vs) == 0 { 157 return nil, ErrNoChartVersion 158 } 159 160 var constraint *semver.Constraints 161 if len(version) == 0 { 162 constraint, _ = semver.NewConstraint("*") 163 } else { 164 var err error 165 constraint, err = semver.NewConstraint(version) 166 if err != nil { 167 return nil, err 168 } 169 } 170 171 for _, ver := range vs { 172 test, err := semver.NewVersion(ver.Version) 173 if err != nil { 174 continue 175 } 176 177 if constraint.Check(test) { 178 return ver, nil 179 } 180 } 181 return nil, fmt.Errorf("No chart version found for %s-%s", name, version) 182 } 183 184 // WriteFile writes an index file to the given destination path. 185 // 186 // The mode on the file is set to 'mode'. 187 func (i IndexFile) WriteFile(dest string, mode os.FileMode) error { 188 b, err := yaml.Marshal(i) 189 if err != nil { 190 return err 191 } 192 return ioutil.WriteFile(dest, b, mode) 193 } 194 195 // Merge merges the given index file into this index. 196 // 197 // This merges by name and version. 198 // 199 // If one of the entries in the given index does _not_ already exist, it is added. 200 // In all other cases, the existing record is preserved. 201 // 202 // This can leave the index in an unsorted state 203 func (i *IndexFile) Merge(f *IndexFile) { 204 for _, cvs := range f.Entries { 205 for _, cv := range cvs { 206 if !i.Has(cv.Name, cv.Version) { 207 e := i.Entries[cv.Name] 208 i.Entries[cv.Name] = append(e, cv) 209 } 210 } 211 } 212 } 213 214 // Need both JSON and YAML annotations until we get rid of gopkg.in/yaml.v2 215 216 // ChartVersion represents a chart entry in the IndexFile 217 type ChartVersion struct { 218 *chart.Metadata 219 URLs []string `json:"urls"` 220 Created time.Time `json:"created,omitempty"` 221 Removed bool `json:"removed,omitempty"` 222 Digest string `json:"digest,omitempty"` 223 } 224 225 // IndexDirectory reads a (flat) directory and generates an index. 226 // 227 // It indexes only charts that have been packaged (*.tgz). 228 // 229 // The index returned will be in an unsorted state 230 func IndexDirectory(dir, baseURL string) (*IndexFile, error) { 231 archives, err := filepath.Glob(filepath.Join(dir, "*.tgz")) 232 if err != nil { 233 return nil, err 234 } 235 moreArchives, err := filepath.Glob(filepath.Join(dir, "**/*.tgz")) 236 if err != nil { 237 return nil, err 238 } 239 archives = append(archives, moreArchives...) 240 241 index := NewIndexFile() 242 for _, arch := range archives { 243 fname, err := filepath.Rel(dir, arch) 244 if err != nil { 245 return index, err 246 } 247 248 var parentDir string 249 parentDir, fname = filepath.Split(fname) 250 // filepath.Split appends an extra slash to the end of parentDir. We want to strip that out. 251 parentDir = strings.TrimSuffix(parentDir, string(os.PathSeparator)) 252 parentURL, err := urlutil.URLJoin(baseURL, parentDir) 253 if err != nil { 254 parentURL = path.Join(baseURL, parentDir) 255 } 256 257 c, err := chartutil.Load(arch) 258 if err != nil { 259 // Assume this is not a chart. 260 continue 261 } 262 hash, err := provenance.DigestFile(arch) 263 if err != nil { 264 return index, err 265 } 266 index.Add(c.Metadata, fname, parentURL, hash) 267 } 268 return index, nil 269 } 270 271 // loadIndex loads an index file and does minimal validity checking. 272 // 273 // This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails. 274 func loadIndex(data []byte) (*IndexFile, error) { 275 i := &IndexFile{} 276 if err := yaml.Unmarshal(data, i); err != nil { 277 return i, err 278 } 279 i.SortEntries() 280 if i.APIVersion == "" { 281 // When we leave Beta, we should remove legacy support and just 282 // return this error: 283 //return i, ErrNoAPIVersion 284 return loadUnversionedIndex(data) 285 } 286 return i, nil 287 } 288 289 // unversionedEntry represents a deprecated pre-Alpha.5 format. 290 // 291 // This will be removed prior to v2.0.0 292 type unversionedEntry struct { 293 Checksum string `json:"checksum"` 294 URL string `json:"url"` 295 Chartfile *chart.Metadata `json:"chartfile"` 296 } 297 298 // loadUnversionedIndex loads a pre-Alpha.5 index.yaml file. 299 // 300 // This format is deprecated. This function will be removed prior to v2.0.0. 301 func loadUnversionedIndex(data []byte) (*IndexFile, error) { 302 fmt.Fprintln(os.Stderr, "WARNING: Deprecated index file format. Try 'helm repo update'") 303 i := map[string]unversionedEntry{} 304 305 // This gets around an error in the YAML parser. Instead of parsing as YAML, 306 // we convert to JSON, and then decode again. 307 var err error 308 data, err = yaml.YAMLToJSON(data) 309 if err != nil { 310 return nil, err 311 } 312 if err := json.Unmarshal(data, &i); err != nil { 313 return nil, err 314 } 315 316 if len(i) == 0 { 317 return nil, ErrNoAPIVersion 318 } 319 ni := NewIndexFile() 320 for n, item := range i { 321 if item.Chartfile == nil || item.Chartfile.Name == "" { 322 parts := strings.Split(n, "-") 323 ver := "" 324 if len(parts) > 1 { 325 ver = strings.TrimSuffix(parts[1], ".tgz") 326 } 327 item.Chartfile = &chart.Metadata{Name: parts[0], Version: ver} 328 } 329 ni.Add(item.Chartfile, item.URL, "", item.Checksum) 330 } 331 return ni, nil 332 }