mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 05:19:39 +02:00
refactor(helm): helm binary to sdk refactor [r8s-229] (#463)
Co-authored-by: stevensbkang <skan070@gmail.com>
This commit is contained in:
parent
0d25f3f430
commit
b5961d79f8
56 changed files with 2222 additions and 819 deletions
351
pkg/libhelm/sdk/search_repo.go
Normal file
351
pkg/libhelm/sdk/search_repo.go
Normal file
|
@ -0,0 +1,351 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/pkg/libhelm/options"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/segmentio/encoding/json"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
"helm.sh/helm/v3/pkg/getter"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
)
|
||||
|
||||
var (
|
||||
errRequiredSearchOptions = errors.New("repo is required")
|
||||
errInvalidRepoURL = errors.New("the request failed since either the Helm repository was not found or the index.yaml is not valid")
|
||||
)
|
||||
|
||||
type RepoIndex struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Entries map[string][]ChartInfo `json:"entries"`
|
||||
Generated string `json:"generated"`
|
||||
}
|
||||
|
||||
// SearchRepo downloads the `index.yaml` file for specified repo, parses it and returns JSON to caller.
|
||||
func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoOptions) ([]byte, error) {
|
||||
// Validate input options
|
||||
if err := validateSearchRepoOptions(searchRepoOpts); err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Str("repo", searchRepoOpts.Repo).
|
||||
Err(err).
|
||||
Msg("Missing required search repo options")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("context", "HelmClient").
|
||||
Str("repo", searchRepoOpts.Repo).
|
||||
Msg("Searching repository")
|
||||
|
||||
// Parse and validate the repository URL
|
||||
repoURL, err := parseRepoURL(searchRepoOpts.Repo)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Str("repo", searchRepoOpts.Repo).
|
||||
Err(err).
|
||||
Msg("Invalid repository URL")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set up Helm CLI environment
|
||||
repoSettings := cli.New()
|
||||
|
||||
// Ensure all required Helm directories exist
|
||||
if err := ensureHelmDirectoriesExist(repoSettings); err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Err(err).
|
||||
Msg("Failed to ensure Helm directories exist")
|
||||
return nil, errors.Wrap(err, "failed to ensure Helm directories exist")
|
||||
}
|
||||
|
||||
// Download the index file and update repository configuration
|
||||
indexPath, err := downloadRepoIndex(repoURL.String(), repoSettings, searchRepoOpts.Repo)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Str("repo_url", repoURL.String()).
|
||||
Err(err).
|
||||
Msg("Failed to download repository index")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load and parse the index file
|
||||
log.Debug().
|
||||
Str("context", "HelmClient").
|
||||
Str("index_path", indexPath).
|
||||
Msg("Loading index file")
|
||||
|
||||
indexFile, err := loadIndexFile(indexPath)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Str("index_path", indexPath).
|
||||
Err(err).
|
||||
Msg("Failed to load index file")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert the index file to our response format
|
||||
result, err := convertIndexToResponse(indexFile)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "HelmClient").
|
||||
Err(err).
|
||||
Msg("Failed to convert index to response format")
|
||||
return nil, errors.Wrap(err, "failed to convert index to response format")
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("context", "HelmClient").
|
||||
Str("repo", searchRepoOpts.Repo).
|
||||
Int("entries_count", len(indexFile.Entries)).
|
||||
Msg("Successfully searched repository")
|
||||
|
||||
return json.Marshal(result)
|
||||
}
|
||||
|
||||
// validateSearchRepoOptions validates the required search repository options.
|
||||
func validateSearchRepoOptions(opts options.SearchRepoOptions) error {
|
||||
if opts.Repo == "" {
|
||||
return errRequiredSearchOptions
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseRepoURL parses and validates the repository URL.
|
||||
func parseRepoURL(repoURL string) (*url.URL, error) {
|
||||
parsedURL, err := url.ParseRequestURI(repoURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid helm chart URL: "+repoURL)
|
||||
}
|
||||
return parsedURL, nil
|
||||
}
|
||||
|
||||
// downloadRepoIndex downloads the index.yaml file from the repository and updates
|
||||
// the repository configuration.
|
||||
func downloadRepoIndex(repoURLString string, repoSettings *cli.EnvSettings, repoName string) (string, error) {
|
||||
log.Debug().
|
||||
Str("context", "helm_sdk_repo_index").
|
||||
Str("repo_url", repoURLString).
|
||||
Str("repo_name", repoName).
|
||||
Msg("Creating chart repository object")
|
||||
|
||||
// Create chart repository object
|
||||
rep, err := repo.NewChartRepository(
|
||||
&repo.Entry{
|
||||
URL: repoURLString,
|
||||
},
|
||||
getter.All(repoSettings),
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "helm_sdk_repo_index").
|
||||
Str("repo_url", repoURLString).
|
||||
Err(err).
|
||||
Msg("Failed to create chart repository object")
|
||||
return "", errInvalidRepoURL
|
||||
}
|
||||
|
||||
// Load repository configuration file
|
||||
f, err := repo.LoadFile(repoSettings.RepositoryConfig)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "helm_sdk_repo_index").
|
||||
Str("repo_config", repoSettings.RepositoryConfig).
|
||||
Err(err).
|
||||
Msg("Failed to load repo config")
|
||||
return "", errors.Wrap(err, "failed to load repo config")
|
||||
}
|
||||
|
||||
// Download the index file
|
||||
log.Debug().
|
||||
Str("context", "helm_sdk_repo_index").
|
||||
Str("repo_url", repoURLString).
|
||||
Msg("Downloading index file")
|
||||
|
||||
indexPath, err := rep.DownloadIndexFile()
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("context", "helm_sdk_repo_index").
|
||||
Str("repo_url", repoURLString).
|
||||
Err(err).
|
||||
Msg("Failed to download index file")
|
||||
return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURLString)
|
||||
}
|
||||
|
||||
// Update repository configuration
|
||||
c := repo.Entry{
|
||||
Name: repoName,
|
||||
URL: repoURLString,
|
||||
}
|
||||
f.Update(&c)
|
||||
|
||||
// Write updated configuration
|
||||
repoFile := repoSettings.RepositoryConfig
|
||||
if err := f.WriteFile(repoFile, 0644); err != nil {
|
||||
log.Error().
|
||||
Str("context", "helm_sdk_repo_index").
|
||||
Str("repo_file", repoSettings.RepositoryConfig).
|
||||
Err(err).
|
||||
Msg("Failed to write repository configuration")
|
||||
return "", errors.Wrap(err, "failed to write repository configuration")
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("context", "helm_sdk_repo_index").
|
||||
Str("index_path", indexPath).
|
||||
Msg("Successfully downloaded index file")
|
||||
|
||||
return indexPath, nil
|
||||
}
|
||||
|
||||
// loadIndexFile loads the index file from the given path.
|
||||
func loadIndexFile(indexPath string) (*repo.IndexFile, error) {
|
||||
indexFile, err := repo.LoadIndexFile(indexPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to load downloaded index file: %s", indexPath)
|
||||
}
|
||||
return indexFile, nil
|
||||
}
|
||||
|
||||
// convertIndexToResponse converts the Helm index file to our response format.
|
||||
func convertIndexToResponse(indexFile *repo.IndexFile) (RepoIndex, error) {
|
||||
result := RepoIndex{
|
||||
APIVersion: indexFile.APIVersion,
|
||||
Entries: make(map[string][]ChartInfo),
|
||||
Generated: indexFile.Generated.String(),
|
||||
}
|
||||
|
||||
// Convert Helm SDK types to our response types
|
||||
for name, charts := range indexFile.Entries {
|
||||
result.Entries[name] = convertChartsToChartInfo(charts)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// convertChartsToChartInfo converts Helm chart entries to ChartInfo objects.
|
||||
func convertChartsToChartInfo(charts []*repo.ChartVersion) []ChartInfo {
|
||||
chartInfos := make([]ChartInfo, len(charts))
|
||||
for i, chart := range charts {
|
||||
chartInfos[i] = ChartInfo{
|
||||
Name: chart.Name,
|
||||
Version: chart.Version,
|
||||
AppVersion: chart.AppVersion,
|
||||
Description: chart.Description,
|
||||
Deprecated: chart.Deprecated,
|
||||
Created: chart.Created.String(),
|
||||
Digest: chart.Digest,
|
||||
Home: chart.Home,
|
||||
Sources: chart.Sources,
|
||||
URLs: chart.URLs,
|
||||
Icon: chart.Icon,
|
||||
Annotations: chart.Annotations,
|
||||
}
|
||||
}
|
||||
return chartInfos
|
||||
}
|
||||
|
||||
// ChartInfo represents a Helm chart in the repository index
|
||||
type ChartInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
AppVersion string `json:"appVersion"`
|
||||
Description string `json:"description"`
|
||||
Deprecated bool `json:"deprecated"`
|
||||
Created string `json:"created"`
|
||||
Digest string `json:"digest"`
|
||||
Home string `json:"home"`
|
||||
Sources []string `json:"sources"`
|
||||
URLs []string `json:"urls"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Annotations any `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// ensureHelmDirectoriesExist checks and creates required Helm directories if they don't exist
|
||||
func ensureHelmDirectoriesExist(settings *cli.EnvSettings) error {
|
||||
log.Debug().
|
||||
Str("context", "helm_sdk_dirs").
|
||||
Msg("Ensuring Helm directories exist")
|
||||
|
||||
// List of directories to ensure exist
|
||||
directories := []string{
|
||||
filepath.Dir(settings.RepositoryConfig), // Repository config directory
|
||||
settings.RepositoryCache, // Repository cache directory
|
||||
filepath.Dir(settings.RegistryConfig), // Registry config directory
|
||||
settings.PluginsDirectory, // Plugins directory
|
||||
}
|
||||
|
||||
// Create each directory if it doesn't exist
|
||||
for _, dir := range directories {
|
||||
if dir == "" {
|
||||
continue // Skip empty paths
|
||||
}
|
||||
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
log.Error().
|
||||
Str("context", "helm_sdk_dirs").
|
||||
Str("directory", dir).
|
||||
Err(err).
|
||||
Msg("Failed to create directory")
|
||||
return errors.Wrapf(err, "failed to create directory: %s", dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure registry config file exists
|
||||
if settings.RegistryConfig != "" {
|
||||
if _, err := os.Stat(settings.RegistryConfig); os.IsNotExist(err) {
|
||||
// Create the directory if it doesn't exist
|
||||
dir := filepath.Dir(settings.RegistryConfig)
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
log.Error().
|
||||
Str("context", "helm_sdk_dirs").
|
||||
Str("directory", dir).
|
||||
Err(err).
|
||||
Msg("Failed to create directory")
|
||||
return errors.Wrapf(err, "failed to create directory: %s", dir)
|
||||
}
|
||||
|
||||
// Create an empty registry config file
|
||||
if _, err := os.Create(settings.RegistryConfig); err != nil {
|
||||
log.Error().
|
||||
Str("context", "helm_sdk_dirs").
|
||||
Str("file", settings.RegistryConfig).
|
||||
Err(err).
|
||||
Msg("Failed to create registry config file")
|
||||
return errors.Wrapf(err, "failed to create registry config file: %s", settings.RegistryConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure repository config file exists
|
||||
if settings.RepositoryConfig != "" {
|
||||
if _, err := os.Stat(settings.RepositoryConfig); os.IsNotExist(err) {
|
||||
// Create an empty repository config file with default yaml structure
|
||||
f := repo.NewFile()
|
||||
if err := f.WriteFile(settings.RepositoryConfig, 0644); err != nil {
|
||||
log.Error().
|
||||
Str("context", "helm_sdk_dirs").
|
||||
Str("file", settings.RepositoryConfig).
|
||||
Err(err).
|
||||
Msg("Failed to create repository config file")
|
||||
return errors.Wrapf(err, "failed to create repository config file: %s", settings.RepositoryConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("context", "helm_sdk_dirs").
|
||||
Msg("Successfully ensured all Helm directories exist")
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue