2022-12-01 01:27:49 +00:00
|
|
|
package binary
|
|
|
|
|
|
|
|
// Package common implements common functionality for the helm.
|
|
|
|
// The functionality does not rely on the implementation of `HelmPackageManager`
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"path"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/portainer/portainer/pkg/libhelm/options"
|
2023-10-24 16:55:11 +00:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/segmentio/encoding/json"
|
2022-12-01 01:27:49 +00:00
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
var errRequiredSearchOptions = errors.New("repo is required")
|
2024-02-21 22:35:33 +00:00
|
|
|
var errInvalidRepoURL = errors.New("the request failed since either the Helm repository was not found or the index.yaml is not valid")
|
2022-12-01 01:27:49 +00:00
|
|
|
|
|
|
|
type File struct {
|
|
|
|
APIVersion string `yaml:"apiVersion" json:"apiVersion"`
|
|
|
|
Entries map[string][]Entry `yaml:"entries" json:"entries"`
|
|
|
|
Generated string `yaml:"generated" json:"generated"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Annotations struct {
|
|
|
|
Category string `yaml:"category" json:"category"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Entry struct {
|
|
|
|
Annotations *Annotations `yaml:"annotations" json:"annotations,omitempty"`
|
|
|
|
Created string `yaml:"created" json:"created"`
|
|
|
|
Deprecated bool `yaml:"deprecated" json:"deprecated"`
|
|
|
|
Description string `yaml:"description" json:"description"`
|
|
|
|
Digest string `yaml:"digest" json:"digest"`
|
|
|
|
Home string `yaml:"home" json:"home"`
|
|
|
|
Name string `yaml:"name" json:"name"`
|
|
|
|
Sources []string `yaml:"sources" json:"sources"`
|
|
|
|
Urls []string `yaml:"urls" json:"urls"`
|
|
|
|
Version string `yaml:"version" json:"version"`
|
|
|
|
Icon string `yaml:"icon" json:"icon,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// SearchRepo downloads the `index.yaml` file for specified repo, parses it and returns JSON to caller.
|
|
|
|
// The functionality is similar to that of what `helm search repo [chart] --repo <repo>` CLI runs;
|
|
|
|
// this approach is used instead since the `helm search repo` requires a repo to be added to the global helm cache
|
|
|
|
func (hbpm *helmBinaryPackageManager) SearchRepo(searchRepoOpts options.SearchRepoOptions) ([]byte, error) {
|
|
|
|
if searchRepoOpts.Repo == "" {
|
|
|
|
return nil, errRequiredSearchOptions
|
|
|
|
}
|
|
|
|
|
2023-04-14 02:50:37 +00:00
|
|
|
client := searchRepoOpts.Client
|
2024-01-29 00:03:11 +00:00
|
|
|
|
|
|
|
if client == nil {
|
2023-04-14 02:50:37 +00:00
|
|
|
// The current index.yaml is ~9MB on bitnami.
|
|
|
|
// At a slow @2mbit download = 40s. @100bit = ~1s.
|
|
|
|
// I'm seeing 3 - 4s over wifi.
|
|
|
|
// Give ample time but timeout for now. Can be improved in the future
|
|
|
|
client = &http.Client{
|
2024-01-29 00:03:11 +00:00
|
|
|
Timeout: 300 * time.Second,
|
2023-09-19 06:07:51 +00:00
|
|
|
Transport: http.DefaultTransport,
|
2023-04-14 02:50:37 +00:00
|
|
|
}
|
2022-12-01 01:27:49 +00:00
|
|
|
}
|
|
|
|
|
2024-06-18 18:59:12 +00:00
|
|
|
// Allow redirect behavior to be overridden if specified.
|
2024-02-21 22:35:33 +00:00
|
|
|
if client.CheckRedirect == nil {
|
|
|
|
client.CheckRedirect = defaultCheckRedirect
|
|
|
|
}
|
|
|
|
|
2022-12-01 01:27:49 +00:00
|
|
|
url, err := url.ParseRequestURI(searchRepoOpts.Repo)
|
|
|
|
if err != nil {
|
2024-10-10 15:06:20 +00:00
|
|
|
return nil, errors.Wrap(err, "invalid helm chart URL: "+searchRepoOpts.Repo)
|
2022-12-01 01:27:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
url.Path = path.Join(url.Path, "index.yaml")
|
|
|
|
resp, err := client.Get(url.String())
|
|
|
|
if err != nil {
|
2024-02-21 22:35:33 +00:00
|
|
|
return nil, errInvalidRepoURL
|
2022-12-01 01:27:49 +00:00
|
|
|
}
|
2023-03-27 18:14:16 +00:00
|
|
|
defer resp.Body.Close()
|
2022-12-01 01:27:49 +00:00
|
|
|
|
|
|
|
var file File
|
|
|
|
err = yaml.NewDecoder(resp.Body).Decode(&file)
|
|
|
|
if err != nil {
|
2024-02-21 22:35:33 +00:00
|
|
|
return nil, errInvalidRepoURL
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate index.yaml
|
|
|
|
if file.APIVersion == "" || file.Entries == nil {
|
|
|
|
return nil, errInvalidRepoURL
|
2022-12-01 01:27:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
result, err := json.Marshal(file)
|
|
|
|
if err != nil {
|
2024-02-21 22:35:33 +00:00
|
|
|
return nil, errInvalidRepoURL
|
2022-12-01 01:27:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
2024-02-21 22:35:33 +00:00
|
|
|
|
|
|
|
// defaultCheckRedirect is a default CheckRedirect for helm
|
|
|
|
// We don't allow redirects to URLs not ending in index.yaml
|
|
|
|
// After that we follow the go http client behavior which is to stop
|
|
|
|
// after a maximum of 10 redirects
|
|
|
|
func defaultCheckRedirect(req *http.Request, via []*http.Request) error {
|
|
|
|
// The request url must end in index.yaml
|
|
|
|
if path.Base(req.URL.Path) != "index.yaml" {
|
|
|
|
return errors.New("the request URL must end in index.yaml")
|
|
|
|
}
|
|
|
|
|
|
|
|
// default behavior below
|
|
|
|
if len(via) >= 10 {
|
|
|
|
return errors.New("stopped after 10 redirects")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|