mirror of
https://github.com/goharbor/harbor.git
synced 2024-06-26 14:55:06 +02:00
Fix failures because front downlowd chart using relative path Signed-off-by: Qian Deng <dengq@vmware.com>
234 lines
6.4 KiB
Go
234 lines
6.4 KiB
Go
package chartserver
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pkg/errors"
|
|
"k8s.io/helm/cmd/helm/search"
|
|
|
|
hlog "github.com/goharbor/harbor/src/common/utils/log"
|
|
"github.com/goharbor/harbor/src/core/config"
|
|
)
|
|
|
|
const (
|
|
maxDeletionThreads = 10
|
|
)
|
|
|
|
// GetCountOfCharts calculates and returns the total count of charts under the specified namespaces.
|
|
// See @ServiceHandler.GetCountOfCharts
|
|
func (c *Controller) GetCountOfCharts(namespaces []string) (uint64, error) {
|
|
if namespaces == nil || len(namespaces) == 0 {
|
|
return 0, nil // Directly return 0 instead of non-nil error
|
|
}
|
|
|
|
indexFile, err := c.getIndexYaml(namespaces)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return (uint64)(len(indexFile.Entries)), nil
|
|
}
|
|
|
|
// DeleteChart deletes all the chart versions of the specified chart under the namespace.
|
|
// See @ServiceHandler.DeleteChart
|
|
func (c *Controller) DeleteChart(namespace, chartName string) error {
|
|
if len(strings.TrimSpace(namespace)) == 0 {
|
|
return errors.New("empty namespace when deleting chart")
|
|
}
|
|
|
|
if len(strings.TrimSpace(chartName)) == 0 {
|
|
return errors.New("empty chart name when deleting chart")
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/%s", c.APIPrefix(namespace), chartName)
|
|
|
|
content, err := c.apiClient.GetContent(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
allVersions, err := c.chartOperator.GetChartVersions(content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Let's delete the versions in parallel
|
|
// The number of goroutine is controlled by the const maxDeletionThreads
|
|
qSize := len(allVersions)
|
|
if qSize > maxDeletionThreads {
|
|
qSize = maxDeletionThreads
|
|
}
|
|
tokenQueue := make(chan struct{}, qSize)
|
|
errChan := make(chan error, 1)
|
|
waitGroup := new(sync.WaitGroup)
|
|
waitGroup.Add(len(allVersions))
|
|
|
|
// Append initial tokens
|
|
for i := 0; i < qSize; i++ {
|
|
tokenQueue <- struct{}{}
|
|
}
|
|
|
|
// Collect errors
|
|
errs := make([]error, 0)
|
|
errWrapper := make(chan error, 1)
|
|
go func() {
|
|
defer func() {
|
|
// pass to the out func
|
|
if len(errs) > 0 {
|
|
errWrapper <- fmt.Errorf("%v", errs)
|
|
}
|
|
close(errWrapper)
|
|
}()
|
|
|
|
for deletionErr := range errChan {
|
|
errs = append(errs, deletionErr)
|
|
}
|
|
}()
|
|
|
|
// Schedule deletion tasks
|
|
for _, deletingVersion := range allVersions {
|
|
// Apply for token first
|
|
// If no available token, pending here
|
|
<-tokenQueue
|
|
|
|
// Got one token
|
|
go func(deletingVersion *ChartVersion) {
|
|
defer func() {
|
|
// return the token back
|
|
tokenQueue <- struct{}{}
|
|
|
|
// done
|
|
waitGroup.Done()
|
|
}()
|
|
|
|
if err := c.DeleteChartVersion(namespace, chartName, deletingVersion.GetVersion()); err != nil {
|
|
errChan <- err
|
|
}
|
|
}(deletingVersion)
|
|
}
|
|
|
|
// Wait all goroutines are done
|
|
waitGroup.Wait()
|
|
// Safe to quit error collection goroutine
|
|
close(errChan)
|
|
|
|
err = <-errWrapper
|
|
|
|
return err
|
|
}
|
|
|
|
// GetChartVersionDetails get the specified version for one chart
|
|
// This handler should return the details of the chart version,
|
|
// maybe including metadata,dependencies and values etc.
|
|
// See @ServiceHandler.GetChartVersionDetails
|
|
func (c *Controller) GetChartVersionDetails(namespace, chartName, version string) (*ChartVersionDetails, error) {
|
|
chartV, err := c.GetChartVersion(namespace, chartName, version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Query cache
|
|
chartDetails := c.chartCache.GetChart(chartV.Digest)
|
|
if chartDetails == nil {
|
|
// NOT hit!!
|
|
content, err := c.getChartVersionContent(namespace, chartV.URLs[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Process bytes and get more details of chart version
|
|
chartDetails, err = c.chartOperator.GetChartDetails(content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
chartDetails.Metadata = chartV
|
|
|
|
// Put it into the cache for next access
|
|
c.chartCache.PutChart(chartDetails)
|
|
} else {
|
|
// Just logged
|
|
hlog.Debugf("Get detailed data from cache for chart: %s:%s (%s)",
|
|
chartDetails.Metadata.Name,
|
|
chartDetails.Metadata.Version,
|
|
chartDetails.Metadata.Digest)
|
|
}
|
|
// The change of prov file will not cause any influence to the digest of chart,
|
|
// and then the digital signature status should be not cached
|
|
//
|
|
// Generate the security report
|
|
// prov file share same endpoint with the chart version
|
|
// Just add .prov suffix to the chart version to form the path of prov file
|
|
// Anyway, there will be a report about the digital signature status
|
|
chartDetails.Security = &SecurityReport{
|
|
Signature: &DigitalSignature{
|
|
Signed: false,
|
|
},
|
|
}
|
|
// Try to get the prov file to confirm if it is exitsing
|
|
provFilePath := fmt.Sprintf("%s.prov", chartV.URLs[0])
|
|
provBytes, err := c.getChartVersionContent(namespace, provFilePath)
|
|
if err == nil && len(provBytes) > 0 {
|
|
chartDetails.Security.Signature.Signed = true
|
|
chartDetails.Security.Signature.Provenance = provFilePath
|
|
} else {
|
|
// Just log it
|
|
hlog.Errorf("Failed to get prov file for chart %s with error: %s, got %d bytes", chartV.Name, err.Error(), len(provBytes))
|
|
}
|
|
|
|
return chartDetails, nil
|
|
}
|
|
|
|
// SearchChart search charts in the specified namespaces with the keyword q.
|
|
// RegExp mode is enabled as default.
|
|
// For each chart, only the latest version will shown in the result list if matched to avoid duplicated entries.
|
|
// Keep consistent with `helm search` command.
|
|
func (c *Controller) SearchChart(q string, namespaces []string) ([]*search.Result, error) {
|
|
if len(q) == 0 || len(namespaces) == 0 {
|
|
// Return empty list
|
|
return []*search.Result{}, nil
|
|
}
|
|
|
|
// Get the merged index yaml file of the namespaces
|
|
ind, err := c.getIndexYaml(namespaces)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build the search index
|
|
index := search.NewIndex()
|
|
// As the repo name is already merged into the index yaml, we use empty repo name.
|
|
// Set 'All' to false to return only one version for each chart.
|
|
index.AddRepo("", ind, false)
|
|
|
|
// Search
|
|
// RegExp is enabled
|
|
results, err := index.Search(q, searchMaxScore, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Sort results.
|
|
search.SortScore(results)
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// Get the content bytes of the chart version
|
|
func (c *Controller) getChartVersionContent(namespace string, subPath string) ([]byte, error) {
|
|
var url string
|
|
if strings.HasPrefix(subPath, "http") {
|
|
extEndpoint, err := config.ExtEndpoint()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "can not get ext endpoint")
|
|
}
|
|
url = strings.TrimPrefix(subPath, fmt.Sprintf("%s/%s", extEndpoint, "chartrepo/"))
|
|
} else {
|
|
url = path.Join(namespace, subPath)
|
|
}
|
|
url = fmt.Sprintf("%s/%s", c.backendServerAddress.String(), url)
|
|
return c.apiClient.GetContent(url)
|
|
}
|