mirror of https://github.com/artifacthub/hub.git
172 lines
4.6 KiB
Go
172 lines
4.6 KiB
Go
package helmplugin
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
|
|
"github.com/Masterminds/semver/v3"
|
|
"github.com/artifacthub/hub/internal/hub"
|
|
"github.com/artifacthub/hub/internal/license"
|
|
"github.com/artifacthub/hub/internal/pkg"
|
|
"github.com/artifacthub/hub/internal/util"
|
|
"github.com/hashicorp/go-multierror"
|
|
"helm.sh/helm/v3/pkg/plugin"
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
var (
|
|
// licenseRE is a regular expression used to locate a license file in the
|
|
// repository.
|
|
licenseRE = regexp.MustCompile(`(?i)license.*`)
|
|
)
|
|
|
|
// TrackerSource is a hub.TrackerSource implementation for Helm plugins
|
|
// repositories.
|
|
type TrackerSource struct {
|
|
i *hub.TrackerSourceInput
|
|
}
|
|
|
|
// NewTrackerSource creates a new TrackerSource instance.
|
|
func NewTrackerSource(i *hub.TrackerSourceInput) *TrackerSource {
|
|
return &TrackerSource{i}
|
|
}
|
|
|
|
// GetPackagesAvailable implements the TrackerSource interface.
|
|
func (s *TrackerSource) GetPackagesAvailable() (map[string]*hub.Package, error) {
|
|
packagesAvailable := make(map[string]*hub.Package)
|
|
|
|
// Walk the path provided looking for available packages
|
|
err := filepath.Walk(s.i.BasePath, func(pkgPath string, info os.FileInfo, err error) error {
|
|
// Return ASAP if context is cancelled
|
|
select {
|
|
case <-s.i.Svc.Ctx.Done():
|
|
return s.i.Svc.Ctx.Err()
|
|
default:
|
|
}
|
|
|
|
// If an error is raised while visiting a path or the path is not a
|
|
// directory, we skip it
|
|
if err != nil || !info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
// Get plugin metadata
|
|
pluginMetadataPath := filepath.Join(pkgPath, plugin.PluginFileName)
|
|
md, err := GetMetadata(pluginMetadataPath)
|
|
if err != nil {
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
s.warn(fmt.Errorf("error getting plugin metadata (path: %s): %w", pluginMetadataPath, err))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Prepare and store package version
|
|
p, err := PreparePackage(s.i.Repository, md, pkgPath)
|
|
if err != nil {
|
|
s.warn(fmt.Errorf("error preparing package %s version %s: %w", md.Name, md.Version, err))
|
|
return nil
|
|
}
|
|
packagesAvailable[pkg.BuildKey(p)] = p
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return packagesAvailable, nil
|
|
}
|
|
|
|
// warn is a helper that sends the error provided to the errors collector and
|
|
// logs it as a warning.
|
|
func (s *TrackerSource) warn(err error) {
|
|
s.i.Svc.Logger.Warn().Err(err).Send()
|
|
s.i.Svc.Ec.Append(s.i.Repository.RepositoryID, err.Error())
|
|
}
|
|
|
|
// GetManifest reads and parses the plugin metadata file.
|
|
func GetMetadata(pkgPath string) (*plugin.Metadata, error) {
|
|
data, err := os.ReadFile(pkgPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading plugin metadata file: %w", err)
|
|
}
|
|
var md *plugin.Metadata
|
|
if err = yaml.Unmarshal(data, &md); err != nil || md == nil {
|
|
return nil, fmt.Errorf("error unmarshaling plugin metadata file: %w", err)
|
|
}
|
|
if err := validateMetadata(md); err != nil {
|
|
return nil, fmt.Errorf("error validating plugin metadata: %w", err)
|
|
}
|
|
return md, nil
|
|
}
|
|
|
|
// validateMetadata checks if the plugin metadata provided is valid.
|
|
func validateMetadata(md *plugin.Metadata) error {
|
|
var errs *multierror.Error
|
|
|
|
if md.Name == "" {
|
|
errs = multierror.Append(errs, errors.New("name not provided"))
|
|
}
|
|
if md.Version == "" {
|
|
errs = multierror.Append(errs, errors.New("version not provided"))
|
|
} else if _, err := semver.NewVersion(md.Version); err != nil {
|
|
errs = multierror.Append(errs, fmt.Errorf("invalid version (semver expected): %w", err))
|
|
}
|
|
if md.Description == "" {
|
|
errs = multierror.Append(errs, errors.New("description not provided"))
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|
|
|
|
// PreparePackage prepares a package version using the plugin metadata and the
|
|
// files in the path provided.
|
|
func PreparePackage(r *hub.Repository, md *plugin.Metadata, pkgPath string) (*hub.Package, error) {
|
|
// Parse version (previously validated)
|
|
sv, _ := semver.NewVersion(md.Version)
|
|
version := sv.String()
|
|
|
|
// Prepare package from metadata
|
|
p := &hub.Package{
|
|
Name: md.Name,
|
|
Version: version,
|
|
Description: md.Description,
|
|
Keywords: []string{
|
|
"helm",
|
|
"helm-plugin",
|
|
},
|
|
Links: []*hub.Link{
|
|
{
|
|
Name: "Source",
|
|
URL: r.URL,
|
|
},
|
|
},
|
|
Repository: r,
|
|
}
|
|
|
|
// Include readme file if available
|
|
readme, err := util.ReadRegularFile(filepath.Join(pkgPath, "README.md"))
|
|
if err == nil {
|
|
p.Readme = string(readme)
|
|
}
|
|
|
|
// Process and include license if available
|
|
files, err := os.ReadDir(pkgPath)
|
|
if err == nil {
|
|
for _, file := range files {
|
|
if licenseRE.Match([]byte(file.Name())) {
|
|
licenseFile, err := util.ReadRegularFile(filepath.Join(pkgPath, file.Name()))
|
|
if err == nil {
|
|
p.License = license.Detect(licenseFile)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return p, nil
|
|
}
|