| 
									
										
										
										
											2016-06-23 02:28:45 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2018-08-25 03:03:55 +08:00
										 |  |  | Copyright The Helm Authors. | 
					
						
							| 
									
										
										
										
											2016-06-23 02:28:45 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  | you may not use this file except in compliance with the License. | 
					
						
							|  |  |  | You may obtain a copy of the License at | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  | distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  | See the License for the specific language governing permissions and | 
					
						
							|  |  |  | limitations under the License. | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-17 23:59:52 +08:00
										 |  |  | package repo | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2020-04-25 05:03:47 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2023-07-21 05:23:35 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2024-11-16 11:07:40 +08:00
										 |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2025-02-27 04:21:47 +08:00
										 |  |  | 	"log/slog" | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2019-02-01 13:31:09 +08:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2016-05-17 23:59:52 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2016-05-17 23:59:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-04 01:42:59 +08:00
										 |  |  | 	"github.com/Masterminds/semver/v3" | 
					
						
							| 
									
										
										
										
											2019-07-12 22:52:15 +08:00
										 |  |  | 	"sigs.k8s.io/yaml" | 
					
						
							| 
									
										
										
										
											2016-05-19 03:00:35 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-27 05:33:51 +08:00
										 |  |  | 	"helm.sh/helm/v4/internal/fileutil" | 
					
						
							|  |  |  | 	"helm.sh/helm/v4/internal/urlutil" | 
					
						
							| 
									
										
										
										
											2025-02-26 04:20:44 +08:00
										 |  |  | 	chart "helm.sh/helm/v4/pkg/chart/v2" | 
					
						
							|  |  |  | 	"helm.sh/helm/v4/pkg/chart/v2/loader" | 
					
						
							| 
									
										
										
										
											2024-12-27 05:33:51 +08:00
										 |  |  | 	"helm.sh/helm/v4/pkg/provenance" | 
					
						
							| 
									
										
										
										
											2016-05-17 23:59:52 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | // APIVersionV1 is the v1 API version for index and repository files.
 | 
					
						
							|  |  |  | const APIVersionV1 = "v1" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | var ( | 
					
						
							|  |  |  | 	// ErrNoAPIVersion indicates that an API version was not specified.
 | 
					
						
							|  |  |  | 	ErrNoAPIVersion = errors.New("no API version specified") | 
					
						
							|  |  |  | 	// ErrNoChartVersion indicates that a chart with the given version is not found.
 | 
					
						
							|  |  |  | 	ErrNoChartVersion = errors.New("no chart version found") | 
					
						
							|  |  |  | 	// ErrNoChartName indicates that a chart with the given name is not found.
 | 
					
						
							|  |  |  | 	ErrNoChartName = errors.New("no chart name found") | 
					
						
							| 
									
										
										
										
											2021-06-04 22:49:24 +08:00
										 |  |  | 	// ErrEmptyIndexYaml indicates that the content of index.yaml is empty.
 | 
					
						
							|  |  |  | 	ErrEmptyIndexYaml = errors.New("empty index.yaml file") | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | ) | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // ChartVersions is a list of versioned chart references.
 | 
					
						
							|  |  |  | // Implements a sorter on Version.
 | 
					
						
							|  |  |  | type ChartVersions []*ChartVersion | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Len returns the length.
 | 
					
						
							|  |  |  | func (c ChartVersions) Len() int { return len(c) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Swap swaps the position of two items in the versions slice.
 | 
					
						
							|  |  |  | func (c ChartVersions) Swap(i, j int) { c[i], c[j] = c[j], c[i] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Less returns true if the version of entry a is less than the version of entry b.
 | 
					
						
							|  |  |  | func (c ChartVersions) Less(a, b int) bool { | 
					
						
							|  |  |  | 	// Failed parse pushes to the back.
 | 
					
						
							|  |  |  | 	i, err := semver.NewVersion(c[a].Version) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	j, err := semver.NewVersion(c[b].Version) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return i.LessThan(j) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | // IndexFile represents the index file in a chart repository
 | 
					
						
							|  |  |  | type IndexFile struct { | 
					
						
							| 
									
										
										
										
											2020-09-18 05:54:55 +08:00
										 |  |  | 	// This is used ONLY for validation against chartmuseum's index files and is discarded after validation.
 | 
					
						
							|  |  |  | 	ServerInfo map[string]interface{}   `json:"serverInfo,omitempty"` | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 	APIVersion string                   `json:"apiVersion"` | 
					
						
							|  |  |  | 	Generated  time.Time                `json:"generated"` | 
					
						
							|  |  |  | 	Entries    map[string]ChartVersions `json:"entries"` | 
					
						
							|  |  |  | 	PublicKeys []string                 `json:"publicKeys,omitempty"` | 
					
						
							| 
									
										
										
										
											2020-09-24 01:55:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Annotations are additional mappings uninterpreted by Helm. They are made available for
 | 
					
						
							|  |  |  | 	// other applications to add information to the index file.
 | 
					
						
							|  |  |  | 	Annotations map[string]string `json:"annotations,omitempty"` | 
					
						
							| 
									
										
										
										
											2016-05-17 23:59:52 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | // NewIndexFile initializes an index.
 | 
					
						
							|  |  |  | func NewIndexFile() *IndexFile { | 
					
						
							|  |  |  | 	return &IndexFile{ | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 		APIVersion: APIVersionV1, | 
					
						
							|  |  |  | 		Generated:  time.Now(), | 
					
						
							|  |  |  | 		Entries:    map[string]ChartVersions{}, | 
					
						
							|  |  |  | 		PublicKeys: []string{}, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | // LoadIndexFile takes a file at the given path and returns an IndexFile object
 | 
					
						
							|  |  |  | func LoadIndexFile(path string) (*IndexFile, error) { | 
					
						
							| 
									
										
										
										
											2023-03-22 21:31:16 +08:00
										 |  |  | 	b, err := os.ReadFile(path) | 
					
						
							| 
									
										
										
										
											2016-11-23 17:17:23 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 	i, err := loadIndex(b, path) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-11-16 11:07:40 +08:00
										 |  |  | 		return nil, fmt.Errorf("error loading %s: %w", path, err) | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return i, nil | 
					
						
							| 
									
										
										
										
											2016-11-23 17:17:23 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | // MustAdd adds a file to the index
 | 
					
						
							| 
									
										
										
										
											2016-10-19 09:04:36 +08:00
										 |  |  | // This can leave the index in an unsorted state
 | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) error { | 
					
						
							| 
									
										
										
										
											2022-11-10 00:11:43 +08:00
										 |  |  | 	if i.Entries == nil { | 
					
						
							|  |  |  | 		return errors.New("entries not initialized") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 	if md.APIVersion == "" { | 
					
						
							|  |  |  | 		md.APIVersion = chart.APIVersionV1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := md.Validate(); err != nil { | 
					
						
							| 
									
										
										
										
											2024-11-16 11:07:40 +08:00
										 |  |  | 		return fmt.Errorf("validate failed for %s: %w", filename, err) | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | 	u := filename | 
					
						
							|  |  |  | 	if baseURL != "" { | 
					
						
							| 
									
										
										
										
											2016-10-07 04:09:18 +08:00
										 |  |  | 		_, file := filepath.Split(filename) | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 		var err error | 
					
						
							| 
									
										
										
										
											2016-11-23 17:17:23 +08:00
										 |  |  | 		u, err = urlutil.URLJoin(baseURL, file) | 
					
						
							| 
									
										
										
										
											2016-10-11 03:50:01 +08:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-01 13:31:09 +08:00
										 |  |  | 			u = path.Join(baseURL, file) | 
					
						
							| 
									
										
										
										
											2016-10-11 03:50:01 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 	cr := &ChartVersion{ | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | 		URLs:     []string{u}, | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 		Metadata: md, | 
					
						
							|  |  |  | 		Digest:   digest, | 
					
						
							|  |  |  | 		Created:  time.Now(), | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 	ee := i.Entries[md.Name] | 
					
						
							|  |  |  | 	i.Entries[md.Name] = append(ee, cr) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Add adds a file to the index and logs an error.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Deprecated: Use index.MustAdd instead.
 | 
					
						
							|  |  |  | func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { | 
					
						
							|  |  |  | 	if err := i.MustAdd(md, filename, baseURL, digest); err != nil { | 
					
						
							| 
									
										
										
										
											2025-02-27 04:21:47 +08:00
										 |  |  | 		slog.Error("skipping loading invalid entry for chart %q %q from %s: %s", md.Name, md.Version, filename, err) | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Has returns true if the index has an entry for a chart with the given name and exact version.
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | func (i IndexFile) Has(name, version string) bool { | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | 	_, err := i.Get(name, version) | 
					
						
							|  |  |  | 	return err == nil | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SortEntries sorts the entries by version in descending order.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // In canonical form, the individual version records should be sorted so that
 | 
					
						
							|  |  |  | // the most recent release for every version is in the 0th slot in the
 | 
					
						
							|  |  |  | // Entries.ChartVersions array. That way, tooling can predict the newest
 | 
					
						
							|  |  |  | // version without needing to parse SemVers.
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | func (i IndexFile) SortEntries() { | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 	for _, versions := range i.Entries { | 
					
						
							|  |  |  | 		sort.Sort(sort.Reverse(versions)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | // Get returns the ChartVersion for the given name.
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-10-24 22:10:47 +08:00
										 |  |  | // If version is empty, this will return the chart with the latest stable version,
 | 
					
						
							|  |  |  | // prerelease versions will be skipped.
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | func (i IndexFile) Get(name, version string) (*ChartVersion, error) { | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | 	vs, ok := i.Entries[name] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return nil, ErrNoChartName | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-10-06 02:43:06 +08:00
										 |  |  | 	if len(vs) == 0 { | 
					
						
							|  |  |  | 		return nil, ErrNoChartVersion | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-19 06:10:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var constraint *semver.Constraints | 
					
						
							| 
									
										
										
										
											2019-08-23 14:31:50 +08:00
										 |  |  | 	if version == "" { | 
					
						
							| 
									
										
										
										
											2017-04-19 06:10:42 +08:00
										 |  |  | 		constraint, _ = semver.NewConstraint("*") | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		constraint, err = semver.NewConstraint(version) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-19 06:10:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-13 05:33:46 +08:00
										 |  |  | 	// when customer inputs specific version, check whether there's an exact match first
 | 
					
						
							| 
									
										
										
										
											2019-10-30 04:40:30 +08:00
										 |  |  | 	if len(version) != 0 { | 
					
						
							|  |  |  | 		for _, ver := range vs { | 
					
						
							|  |  |  | 			if version == ver.Version { | 
					
						
							|  |  |  | 				return ver, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | 	for _, ver := range vs { | 
					
						
							| 
									
										
										
										
											2017-04-19 06:10:42 +08:00
										 |  |  | 		test, err := semver.NewVersion(ver.Version) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if constraint.Check(test) { | 
					
						
							| 
									
										
										
										
											2025-09-06 01:27:44 +08:00
										 |  |  | 			slog.Warn("unable to find exact version; falling back to closest available version", "chart", name, "requested", version, "selected", ver.Version) | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | 			return ver, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-11-16 11:07:40 +08:00
										 |  |  | 	return nil, fmt.Errorf("no chart version found for %s-%s", name, version) | 
					
						
							| 
									
										
										
										
											2016-10-05 13:11:58 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | // WriteFile writes an index file to the given destination path.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // The mode on the file is set to 'mode'.
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | func (i IndexFile) WriteFile(dest string, mode os.FileMode) error { | 
					
						
							| 
									
										
										
										
											2016-09-29 08:51:12 +08:00
										 |  |  | 	b, err := yaml.Marshal(i) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-04-25 05:03:47 +08:00
										 |  |  | 	return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode) | 
					
						
							| 
									
										
										
										
											2016-09-14 03:29:58 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-21 06:32:42 +08:00
										 |  |  | // WriteJSONFile writes an index file in JSON format to the given destination
 | 
					
						
							|  |  |  | // path.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // The mode on the file is set to 'mode'.
 | 
					
						
							|  |  |  | func (i IndexFile) WriteJSONFile(dest string, mode os.FileMode) error { | 
					
						
							|  |  |  | 	b, err := json.MarshalIndent(i, "", "  ") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-19 04:19:03 +08:00
										 |  |  | // Merge merges the given index file into this index.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This merges by name and version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // If one of the entries in the given index does _not_ already exist, it is added.
 | 
					
						
							|  |  |  | // In all other cases, the existing record is preserved.
 | 
					
						
							| 
									
										
										
										
											2016-10-19 09:04:36 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // This can leave the index in an unsorted state
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | func (i *IndexFile) Merge(f *IndexFile) { | 
					
						
							| 
									
										
										
										
											2016-10-19 04:19:03 +08:00
										 |  |  | 	for _, cvs := range f.Entries { | 
					
						
							|  |  |  | 		for _, cv := range cvs { | 
					
						
							|  |  |  | 			if !i.Has(cv.Name, cv.Version) { | 
					
						
							|  |  |  | 				e := i.Entries[cv.Name] | 
					
						
							|  |  |  | 				i.Entries[cv.Name] = append(e, cv) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // ChartVersion represents a chart entry in the IndexFile
 | 
					
						
							|  |  |  | type ChartVersion struct { | 
					
						
							|  |  |  | 	*chart.Metadata | 
					
						
							|  |  |  | 	URLs    []string  `json:"urls"` | 
					
						
							|  |  |  | 	Created time.Time `json:"created,omitempty"` | 
					
						
							|  |  |  | 	Removed bool      `json:"removed,omitempty"` | 
					
						
							|  |  |  | 	Digest  string    `json:"digest,omitempty"` | 
					
						
							| 
									
										
										
										
											2020-09-18 02:33:59 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// ChecksumDeprecated is deprecated in Helm 3, and therefore ignored. Helm 3 replaced
 | 
					
						
							|  |  |  | 	// this with Digest. However, with a strict YAML parser enabled, a field must be
 | 
					
						
							|  |  |  | 	// present on the struct for backwards compatibility.
 | 
					
						
							|  |  |  | 	ChecksumDeprecated string `json:"checksum,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// EngineDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict
 | 
					
						
							|  |  |  | 	// YAML parser enabled, this field must be present.
 | 
					
						
							|  |  |  | 	EngineDeprecated string `json:"engine,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// TillerVersionDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict
 | 
					
						
							|  |  |  | 	// YAML parser enabled, this field must be present.
 | 
					
						
							|  |  |  | 	TillerVersionDeprecated string `json:"tillerVersion,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-16 09:11:57 +08:00
										 |  |  | 	// URLDeprecated is deprecated in Helm 3, superseded by URLs. It is ignored. However,
 | 
					
						
							| 
									
										
										
										
											2020-09-18 02:33:59 +08:00
										 |  |  | 	// with a strict YAML parser enabled, this must be present on the struct.
 | 
					
						
							|  |  |  | 	URLDeprecated string `json:"url,omitempty"` | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IndexDirectory reads a (flat) directory and generates an index.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // It indexes only charts that have been packaged (*.tgz).
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // The index returned will be in an unsorted state
 | 
					
						
							|  |  |  | func IndexDirectory(dir, baseURL string) (*IndexFile, error) { | 
					
						
							|  |  |  | 	archives, err := filepath.Glob(filepath.Join(dir, "*.tgz")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-02-20 05:19:38 +08:00
										 |  |  | 	moreArchives, err := filepath.Glob(filepath.Join(dir, "**/*.tgz")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	archives = append(archives, moreArchives...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | 	index := NewIndexFile() | 
					
						
							|  |  |  | 	for _, arch := range archives { | 
					
						
							| 
									
										
										
										
											2017-02-20 05:19:38 +08:00
										 |  |  | 		fname, err := filepath.Rel(dir, arch) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return index, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var parentDir string | 
					
						
							|  |  |  | 		parentDir, fname = filepath.Split(fname) | 
					
						
							| 
									
										
										
										
											2019-02-01 13:31:09 +08:00
										 |  |  | 		// filepath.Split appends an extra slash to the end of parentDir. We want to strip that out.
 | 
					
						
							|  |  |  | 		parentDir = strings.TrimSuffix(parentDir, string(os.PathSeparator)) | 
					
						
							| 
									
										
										
										
											2017-02-20 05:19:38 +08:00
										 |  |  | 		parentURL, err := urlutil.URLJoin(baseURL, parentDir) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-01 13:31:09 +08:00
										 |  |  | 			parentURL = path.Join(baseURL, parentDir) | 
					
						
							| 
									
										
										
										
											2017-02-20 05:19:38 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 02:28:29 +08:00
										 |  |  | 		c, err := loader.Load(arch) | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			// Assume this is not a chart.
 | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		hash, err := provenance.DigestFile(arch) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return index, err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 		if err := index.MustAdd(c.Metadata, fname, parentURL, hash); err != nil { | 
					
						
							| 
									
										
										
										
											2024-11-16 11:07:40 +08:00
										 |  |  | 			return index, fmt.Errorf("failed adding to %s to index: %w", fname, err) | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return index, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // loadIndex loads an index file and does minimal validity checking.
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | // The source parameter is only used for logging.
 | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | // This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails.
 | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | func loadIndex(data []byte, source string) (*IndexFile, error) { | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | 	i := &IndexFile{} | 
					
						
							| 
									
										
										
										
											2021-06-04 22:49:24 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if len(data) == 0 { | 
					
						
							|  |  |  | 		return i, ErrEmptyIndexYaml | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-21 05:23:35 +08:00
										 |  |  | 	if err := jsonOrYamlUnmarshal(data, i); err != nil { | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | 		return i, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for name, cvs := range i.Entries { | 
					
						
							|  |  |  | 		for idx := len(cvs) - 1; idx >= 0; idx-- { | 
					
						
							| 
									
										
										
										
											2022-11-10 00:11:43 +08:00
										 |  |  | 			if cvs[idx] == nil { | 
					
						
							| 
									
										
										
										
											2025-07-30 03:37:57 +08:00
										 |  |  | 				slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q from %s: empty entry", name, source)) | 
					
						
							|  |  |  | 				cvs = append(cvs[:idx], cvs[idx+1:]...) | 
					
						
							| 
									
										
										
										
											2022-11-10 00:11:43 +08:00
										 |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-02-21 22:45:58 +08:00
										 |  |  | 			// When metadata section missing, initialize with no data
 | 
					
						
							|  |  |  | 			if cvs[idx].Metadata == nil { | 
					
						
							|  |  |  | 				cvs[idx].Metadata = &chart.Metadata{} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 			if cvs[idx].APIVersion == "" { | 
					
						
							|  |  |  | 				cvs[idx].APIVersion = chart.APIVersionV1 | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-02-09 01:08:09 +08:00
										 |  |  | 			if err := cvs[idx].Validate(); ignoreSkippableChartValidationError(err) != nil { | 
					
						
							| 
									
										
										
										
											2025-07-30 03:37:57 +08:00
										 |  |  | 				slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err)) | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 				cvs = append(cvs[:idx], cvs[idx+1:]...) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-12 23:22:10 +08:00
										 |  |  | 		// adjust slice to only contain a set of valid versions
 | 
					
						
							|  |  |  | 		i.Entries[name] = cvs | 
					
						
							| 
									
										
										
										
											2021-02-03 03:18:01 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-25 16:57:25 +08:00
										 |  |  | 	i.SortEntries() | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | 	if i.APIVersion == "" { | 
					
						
							| 
									
										
										
										
											2019-08-23 14:31:50 +08:00
										 |  |  | 		return i, ErrNoAPIVersion | 
					
						
							| 
									
										
										
										
											2017-01-09 19:25:37 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return i, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-07-21 05:23:35 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // jsonOrYamlUnmarshal unmarshals the given byte slice containing JSON or YAML
 | 
					
						
							|  |  |  | // into the provided interface.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // It automatically detects whether the data is in JSON or YAML format by
 | 
					
						
							|  |  |  | // checking its validity as JSON. If the data is valid JSON, it will use the
 | 
					
						
							|  |  |  | // `encoding/json` package to unmarshal it. Otherwise, it will use the
 | 
					
						
							|  |  |  | // `sigs.k8s.io/yaml` package to unmarshal the YAML data.
 | 
					
						
							|  |  |  | func jsonOrYamlUnmarshal(b []byte, i interface{}) error { | 
					
						
							|  |  |  | 	if json.Valid(b) { | 
					
						
							|  |  |  | 		return json.Unmarshal(b, i) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return yaml.UnmarshalStrict(b, i) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-02-09 01:08:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // ignoreSkippableChartValidationError inspect the given error and returns nil if
 | 
					
						
							|  |  |  | // the error isn't important for index loading
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // In particular, charts may introduce validations that don't impact repository indexes
 | 
					
						
							| 
									
										
										
										
											2024-09-05 11:51:39 +08:00
										 |  |  | // And repository indexes may be generated by older/non-compliant software, which doesn't
 | 
					
						
							| 
									
										
										
										
											2024-02-09 01:08:09 +08:00
										 |  |  | // conform to all validations.
 | 
					
						
							|  |  |  | func ignoreSkippableChartValidationError(err error) error { | 
					
						
							|  |  |  | 	verr, ok := err.(chart.ValidationError) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// https://github.com/helm/helm/issues/12748 (JFrog repository strips alias field)
 | 
					
						
							|  |  |  | 	if strings.HasPrefix(verr.Error(), "validation: more than one dependency with name or alias") { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } |