mirror of https://github.com/artifacthub/hub.git
258 lines
7.0 KiB
Go
258 lines
7.0 KiB
Go
package tekton
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/Masterminds/semver/v3"
|
|
"github.com/artifacthub/hub/internal/hub"
|
|
"github.com/artifacthub/hub/internal/pkg"
|
|
"github.com/artifacthub/hub/internal/repo"
|
|
"github.com/artifacthub/hub/internal/tracker/source"
|
|
"github.com/ghodss/yaml"
|
|
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
|
|
)
|
|
|
|
const (
|
|
changesAnnotation = "artifacthub.io/changes"
|
|
licenseAnnotation = "artifacthub.io/license"
|
|
linksAnnotation = "artifacthub.io/links"
|
|
maintainersAnnotation = "artifacthub.io/maintainers"
|
|
providerAnnotation = "artifacthub.io/provider"
|
|
recommendationsAnnotation = "artifacthub.io/recommendations"
|
|
)
|
|
|
|
// TrackerSource is a hub.TrackerSource implementation for Tekton 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 package manifest
|
|
manifest, manifestRaw, err := getManifest(pkgPath)
|
|
if err != nil {
|
|
s.warn(fmt.Errorf("error getting package manifest: %w", err))
|
|
return nil
|
|
}
|
|
if manifest == nil {
|
|
// Package manifest not found, not a package path
|
|
return nil
|
|
}
|
|
|
|
// Parse and validate version
|
|
versionLabel := manifest.Labels["app.kubernetes.io/version"]
|
|
sv, err := semver.NewVersion(versionLabel)
|
|
if err != nil {
|
|
s.warn(fmt.Errorf("invalid package (%s) version (%s): %w", manifest.Name, versionLabel, err))
|
|
return nil
|
|
}
|
|
|
|
// Prepare and store package version
|
|
p, err := s.preparePackage(s.i.Repository, manifest, manifestRaw, pkgPath, sv.String())
|
|
if err != nil {
|
|
s.warn(fmt.Errorf("error preparing package: %w", err))
|
|
return nil
|
|
}
|
|
packagesAvailable[pkg.BuildKey(p)] = p
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return packagesAvailable, nil
|
|
}
|
|
|
|
// preparePackage prepares a package version using the package manifest and the
|
|
// files in the path provided.
|
|
func (s *TrackerSource) preparePackage(
|
|
r *hub.Repository,
|
|
manifest *v1beta1.Task,
|
|
manifestRaw []byte,
|
|
pkgPath,
|
|
version string,
|
|
) (*hub.Package, error) {
|
|
// Prepare content and source urls
|
|
var repoBaseURL, pkgsPath, provider string
|
|
matches := repo.GitRepoURLRE.FindStringSubmatch(r.URL)
|
|
if len(matches) >= 3 {
|
|
repoBaseURL = matches[1]
|
|
provider = matches[2]
|
|
}
|
|
if len(matches) == 4 {
|
|
pkgsPath = strings.TrimSuffix(matches[3], "/")
|
|
}
|
|
var blobPath, rawPath string
|
|
switch provider {
|
|
case "github":
|
|
blobPath = "blob"
|
|
rawPath = "raw"
|
|
case "gitlab":
|
|
blobPath = "-/blob"
|
|
rawPath = "-/raw"
|
|
}
|
|
branch := repo.GetBranch(r)
|
|
pkgVersionPath := strings.TrimPrefix(pkgPath, s.i.BasePath)
|
|
contentURL := fmt.Sprintf("%s/%s/%s/%s%s/%s.yaml",
|
|
repoBaseURL, rawPath, branch, pkgsPath, pkgVersionPath, manifest.Name)
|
|
sourceURL := fmt.Sprintf("%s/%s/%s/%s%s/%s.yaml",
|
|
repoBaseURL, blobPath, branch, pkgsPath, pkgVersionPath, manifest.Name)
|
|
|
|
// Prepare keywords
|
|
keywords := []string{
|
|
"tekton",
|
|
"task",
|
|
}
|
|
tags := strings.Split(manifest.Annotations["tekton.dev/tags"], ",")
|
|
for _, tag := range tags {
|
|
keywords = append(keywords, strings.TrimSpace(tag))
|
|
}
|
|
|
|
// Prepare package from manifest
|
|
p := &hub.Package{
|
|
Name: manifest.Name,
|
|
Version: version,
|
|
DisplayName: manifest.Annotations["tekton.dev/displayName"],
|
|
Description: manifest.Spec.Description,
|
|
Keywords: keywords,
|
|
ContentURL: contentURL,
|
|
Repository: r,
|
|
Links: []*hub.Link{
|
|
{
|
|
Name: "source",
|
|
URL: sourceURL,
|
|
},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"manifestRaw": string(manifestRaw),
|
|
"pipelines.minVersion": manifest.Annotations["tekton.dev/pipelines.minVersion"],
|
|
},
|
|
}
|
|
|
|
// Include readme file if available
|
|
readme, err := ioutil.ReadFile(filepath.Join(pkgPath, "README.md"))
|
|
if err == nil {
|
|
p.Readme = string(readme)
|
|
}
|
|
|
|
// Enrich package with information from annotations
|
|
if err := enrichPackageFromAnnotations(p, manifest.Annotations); err != nil {
|
|
return nil, fmt.Errorf("error enriching package %s version %s: %w", manifest.Name, version, err)
|
|
}
|
|
|
|
return p, 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 package manifest.
|
|
func getManifest(pkgPath string) (*v1beta1.Task, []byte, error) {
|
|
// Locate manifest file
|
|
matches, err := filepath.Glob(filepath.Join(pkgPath, "*.yaml"))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error locating manifest file: %w", err)
|
|
}
|
|
if len(matches) != 1 {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// Process matches, returning the first valid resource manifest found
|
|
for _, match := range matches {
|
|
// Read and parse manifest file
|
|
manifestData, err := ioutil.ReadFile(match)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
manifest := &v1beta1.Task{}
|
|
if err = yaml.Unmarshal(manifestData, &manifest); err != nil {
|
|
continue
|
|
}
|
|
if manifest.Kind != "Task" && manifest.Kind != "ClusterTask" {
|
|
continue
|
|
}
|
|
return manifest, manifestData, nil
|
|
}
|
|
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// enrichPackageFromAnnotations adds some extra information to the package from
|
|
// the provided annotations.
|
|
func enrichPackageFromAnnotations(p *hub.Package, annotations map[string]string) error {
|
|
// Changes
|
|
if v, ok := annotations[changesAnnotation]; ok {
|
|
changes, err := source.ParseChangesAnnotation(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.Changes = changes
|
|
}
|
|
|
|
// License
|
|
p.License = annotations[licenseAnnotation]
|
|
|
|
// Links
|
|
if v, ok := annotations[linksAnnotation]; ok {
|
|
var links []*hub.Link
|
|
if err := yaml.Unmarshal([]byte(v), &links); err != nil {
|
|
return fmt.Errorf("invalid links value: %s", v)
|
|
}
|
|
p.Links = append(p.Links, links...)
|
|
}
|
|
|
|
// Maintainers
|
|
if v, ok := annotations[maintainersAnnotation]; ok {
|
|
var maintainers []*hub.Maintainer
|
|
if err := yaml.Unmarshal([]byte(v), &maintainers); err != nil {
|
|
return fmt.Errorf("invalid maintainers value: %s", v)
|
|
}
|
|
p.Maintainers = maintainers
|
|
}
|
|
|
|
// Provider
|
|
p.Provider = annotations[providerAnnotation]
|
|
|
|
// Recommendations
|
|
if v, ok := annotations[recommendationsAnnotation]; ok {
|
|
var recommendations []*hub.Recommendation
|
|
if err := yaml.Unmarshal([]byte(v), &recommendations); err != nil {
|
|
return fmt.Errorf("invalid recommendations value: %s", v)
|
|
}
|
|
p.Recommendations = recommendations
|
|
}
|
|
|
|
return nil
|
|
}
|