mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +01:00
Enhance the global search API to include chart searching result
- add chart search interface method in utility handler - update the search API handler - update the search API UT case Signed-off-by: Steven Zou <szou@vmware.com>
This commit is contained in:
parent
abf67c8de0
commit
a9f2ff2c91
@ -21,15 +21,11 @@ security:
|
|||||||
paths:
|
paths:
|
||||||
/search:
|
/search:
|
||||||
get:
|
get:
|
||||||
summary: Search for projects and repositories
|
summary: Search for projects, repositories and helm charts
|
||||||
description: >
|
description: >
|
||||||
The Search endpoint returns information about the projects and
|
The Search endpoint returns information about the projects ,repositories
|
||||||
repositories
|
and helm charts offered at public status or related to the current logged in user. The
|
||||||
|
response includes the project, repository list and charts in a proper
|
||||||
offered at public status or related to the current logged in user. The
|
|
||||||
|
|
||||||
response includes the project and repository list in a proper
|
|
||||||
|
|
||||||
display order.
|
display order.
|
||||||
parameters:
|
parameters:
|
||||||
- name: q
|
- name: q
|
||||||
@ -3075,16 +3071,21 @@ definitions:
|
|||||||
Search:
|
Search:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
projects:
|
project:
|
||||||
description: Search results of the projects that matched the filter keywords.
|
description: Search results of the projects that matched the filter keywords.
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/Project'
|
$ref: '#/definitions/Project'
|
||||||
repositories:
|
repository:
|
||||||
description: Search results of the repositories that matched the filter keywords.
|
description: Search results of the repositories that matched the filter keywords.
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/SearchRepository'
|
$ref: '#/definitions/SearchRepository'
|
||||||
|
chart:
|
||||||
|
description: Search results of the charts that macthed the filter keywords.
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/SearchResult'
|
||||||
SearchRepository:
|
SearchRepository:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -4481,6 +4482,19 @@ definitions:
|
|||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
description: 'The time offset with the UTC 00:00 in seconds.'
|
description: 'The time offset with the UTC 00:00 in seconds.'
|
||||||
|
SearchResult:
|
||||||
|
type: object
|
||||||
|
description: The chart search result item
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: The chart name with repo name
|
||||||
|
score:
|
||||||
|
type: integer
|
||||||
|
description: The matched level
|
||||||
|
chart:
|
||||||
|
$ref: '#/definitions/ChartVersion'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -185,6 +185,21 @@ func TestResponseRewrite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test the chart searching
|
||||||
|
func TestChartSearching(t *testing.T) {
|
||||||
|
namespaces := []string{"repo1", "repo2"}
|
||||||
|
q := "harbor"
|
||||||
|
|
||||||
|
results, err := mockController.GetRepositoryHandler().SearchChart(q, namespaces)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expect nil error but got '%s'", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) != 2 {
|
||||||
|
t.Fatalf("expect 2 results but got %d", len(results))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clear environments
|
// Clear environments
|
||||||
func TestStopServers(t *testing.T) {
|
func TestStopServers(t *testing.T) {
|
||||||
stopMockServers()
|
stopMockServers()
|
||||||
|
@ -2,17 +2,16 @@ package chartserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
"github.com/goharbor/harbor/src/ui/filter"
|
"github.com/goharbor/harbor/src/ui/filter"
|
||||||
|
"k8s.io/helm/cmd/helm/search"
|
||||||
helm_repo "k8s.io/helm/pkg/repo"
|
helm_repo "k8s.io/helm/pkg/repo"
|
||||||
|
|
||||||
hlog "github.com/goharbor/harbor/src/common/utils/log"
|
hlog "github.com/goharbor/harbor/src/common/utils/log"
|
||||||
@ -20,6 +19,9 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
maxWorkers = 10
|
maxWorkers = 10
|
||||||
|
|
||||||
|
//Keep consistent with 'helm search' command
|
||||||
|
searchMaxScore = 25
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepositoryHandler defines all the handlers to handle the requests related with chart repository
|
// RepositoryHandler defines all the handlers to handle the requests related with chart repository
|
||||||
@ -36,12 +38,6 @@ type RepositoryHandler struct {
|
|||||||
backendServerAddress *url.URL
|
backendServerAddress *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass work to the workers
|
|
||||||
// 'index' is the location of processing namespace/project in the list
|
|
||||||
type workload struct {
|
|
||||||
index uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result returned by worker
|
// Result returned by worker
|
||||||
type processedResult struct {
|
type processedResult struct {
|
||||||
namespace string
|
namespace string
|
||||||
@ -77,122 +73,17 @@ func (rh *RepositoryHandler) GetIndexFile(w http.ResponseWriter, req *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// The final merged index file
|
namespaces := []string{}
|
||||||
mergedIndexFile := &helm_repo.IndexFile{
|
for _, p := range results.Projects {
|
||||||
APIVersion: "v1",
|
namespaces = append(namespaces, p.Name)
|
||||||
Entries: make(map[string]helm_repo.ChartVersions),
|
|
||||||
Generated: time.Now(),
|
|
||||||
PublicKeys: []string{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve index.yaml for repositories
|
mergedIndexFile, err := rh.getIndexYaml(namespaces)
|
||||||
workerPool := make(chan *workload, maxWorkers)
|
|
||||||
// Sync the output results from the retriever
|
|
||||||
resultChan := make(chan *processedResult, 1)
|
|
||||||
// Receive error
|
|
||||||
errorChan := make(chan error, 1)
|
|
||||||
// Signal chan for merging work
|
|
||||||
mergeDone := make(chan struct{}, 1)
|
|
||||||
// Total projects/namespaces
|
|
||||||
total := uint32(results.Total)
|
|
||||||
// Track all the background threads
|
|
||||||
waitGroup := new(sync.WaitGroup)
|
|
||||||
|
|
||||||
// Initialize
|
|
||||||
initialItemCount := maxWorkers
|
|
||||||
if total < maxWorkers {
|
|
||||||
initialItemCount = int(total)
|
|
||||||
}
|
|
||||||
for i := 0; i < initialItemCount; i++ {
|
|
||||||
workerPool <- &workload{uint32(i)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Atomtic index
|
|
||||||
var indexRef uint32
|
|
||||||
atomic.AddUint32(&indexRef, uint32(initialItemCount-1))
|
|
||||||
|
|
||||||
// Start the index files merging thread
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
mergeDone <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for res := range resultChan {
|
|
||||||
rh.mergeIndexFile(res.namespace, mergedIndexFile, res.indexFileOfRepo)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Retrieve the index files for the repositories
|
|
||||||
// and blocking here
|
|
||||||
LOOP:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case work := <-workerPool:
|
|
||||||
if work.index >= total {
|
|
||||||
break LOOP
|
|
||||||
}
|
|
||||||
// Process
|
|
||||||
// New one
|
|
||||||
waitGroup.Add(1)
|
|
||||||
namespace := results.Projects[work.index].Name
|
|
||||||
go func(ns string) {
|
|
||||||
// Return the worker back to the pool
|
|
||||||
defer func() {
|
|
||||||
waitGroup.Done() // done
|
|
||||||
|
|
||||||
// Put one. The invalid index will be treated as a signal to quit loop
|
|
||||||
nextOne := atomic.AddUint32(&indexRef, 1)
|
|
||||||
workerPool <- &workload{nextOne}
|
|
||||||
}()
|
|
||||||
|
|
||||||
indexFile, err := rh.getIndexYamlWithNS(ns)
|
|
||||||
if err != nil {
|
|
||||||
errorChan <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output
|
|
||||||
resultChan <- &processedResult{
|
|
||||||
namespace: ns,
|
|
||||||
indexFileOfRepo: indexFile,
|
|
||||||
}
|
|
||||||
}(namespace)
|
|
||||||
case err = <-errorChan:
|
|
||||||
// Quit earlier
|
|
||||||
break LOOP
|
|
||||||
case <-req.Context().Done():
|
|
||||||
// Quit earlier
|
|
||||||
err = errors.New("request of getting index yaml file is aborted")
|
|
||||||
break LOOP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hold util all the retrieving work are done
|
|
||||||
waitGroup.Wait()
|
|
||||||
|
|
||||||
// close consumer channel
|
|
||||||
close(resultChan)
|
|
||||||
|
|
||||||
// Wait until merging thread quit
|
|
||||||
<-mergeDone
|
|
||||||
|
|
||||||
// All the threads are done
|
|
||||||
// Met an error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteInternalError(w, err)
|
WriteInternalError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove duplicated keys in public key list
|
|
||||||
hash := make(map[string]string)
|
|
||||||
for _, key := range mergedIndexFile.PublicKeys {
|
|
||||||
hash[key] = key
|
|
||||||
}
|
|
||||||
mergedIndexFile.PublicKeys = []string{}
|
|
||||||
for k := range hash {
|
|
||||||
mergedIndexFile.PublicKeys = append(mergedIndexFile.PublicKeys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := yaml.Marshal(mergedIndexFile)
|
bytes, err := yaml.Marshal(mergedIndexFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteInternalError(w, err)
|
WriteInternalError(w, err)
|
||||||
@ -208,6 +99,160 @@ func (rh *RepositoryHandler) DownloadChartObject(w http.ResponseWriter, req *htt
|
|||||||
rh.trafficProxy.ServeHTTP(w, req)
|
rh.trafficProxy.ServeHTTP(w, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 (rh *RepositoryHandler) 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 := rh.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIndexYaml will get the index yaml files for all the namespaces and merge them
|
||||||
|
// as one unified index yaml file.
|
||||||
|
func (rh *RepositoryHandler) getIndexYaml(namespaces []string) (*helm_repo.IndexFile, error) {
|
||||||
|
// The final merged index file
|
||||||
|
mergedIndexFile := &helm_repo.IndexFile{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Entries: make(map[string]helm_repo.ChartVersions),
|
||||||
|
Generated: time.Now(),
|
||||||
|
PublicKeys: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync the output results from the retriever
|
||||||
|
resultChan := make(chan *processedResult, 1)
|
||||||
|
// Receive error
|
||||||
|
errorChan := make(chan error, 1)
|
||||||
|
// Signal chan for merging work
|
||||||
|
mergeDone := make(chan struct{}, 1)
|
||||||
|
// Total projects/namespaces
|
||||||
|
total := len(namespaces)
|
||||||
|
// Initialize
|
||||||
|
initialItemCount := maxWorkers
|
||||||
|
if total < maxWorkers {
|
||||||
|
initialItemCount = total
|
||||||
|
}
|
||||||
|
// Retrieve index.yaml for repositories
|
||||||
|
workerPool := make(chan struct{}, initialItemCount)
|
||||||
|
|
||||||
|
// Add initial tokens to the pool
|
||||||
|
for i := 0; i < initialItemCount; i++ {
|
||||||
|
workerPool <- struct{}{}
|
||||||
|
}
|
||||||
|
// Track all the background threads
|
||||||
|
waitGroup := new(sync.WaitGroup)
|
||||||
|
|
||||||
|
// Start the index files merging thread
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
mergeDone <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for res := range resultChan {
|
||||||
|
rh.mergeIndexFile(res.namespace, mergedIndexFile, res.indexFileOfRepo)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Retrieve the index files for the repositories
|
||||||
|
// and blocking here
|
||||||
|
var err error
|
||||||
|
LOOP:
|
||||||
|
for _, ns := range namespaces {
|
||||||
|
// Check if error has occurred in some goroutines
|
||||||
|
select {
|
||||||
|
case err = <-errorChan:
|
||||||
|
break LOOP
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply one token before processing
|
||||||
|
<-workerPool
|
||||||
|
|
||||||
|
waitGroup.Add(1)
|
||||||
|
go func(ns string) {
|
||||||
|
defer func() {
|
||||||
|
waitGroup.Done() //done
|
||||||
|
//Return the worker back to the pool
|
||||||
|
workerPool <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
indexFile, err := rh.getIndexYamlWithNS(ns)
|
||||||
|
if err != nil {
|
||||||
|
if len(errorChan) == 0 {
|
||||||
|
//Only need one error as failure signal
|
||||||
|
errorChan <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output
|
||||||
|
resultChan <- &processedResult{
|
||||||
|
namespace: ns,
|
||||||
|
indexFileOfRepo: indexFile,
|
||||||
|
}
|
||||||
|
}(ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold util all the retrieving work are done
|
||||||
|
waitGroup.Wait()
|
||||||
|
|
||||||
|
// close merge channel
|
||||||
|
close(resultChan)
|
||||||
|
|
||||||
|
// Wait until merging thread quit
|
||||||
|
<-mergeDone
|
||||||
|
|
||||||
|
// All the threads are done
|
||||||
|
// Make sure error in the chan is read
|
||||||
|
if err == nil && len(errorChan) > 0 {
|
||||||
|
err = <-errorChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Met an error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicated keys in public key list
|
||||||
|
hash := make(map[string]string)
|
||||||
|
for _, key := range mergedIndexFile.PublicKeys {
|
||||||
|
hash[key] = key
|
||||||
|
}
|
||||||
|
mergedIndexFile.PublicKeys = []string{}
|
||||||
|
for k := range hash {
|
||||||
|
mergedIndexFile.PublicKeys = append(mergedIndexFile.PublicKeys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedIndexFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Get the index yaml file under the specified namespace from the backend server
|
// Get the index yaml file under the specified namespace from the backend server
|
||||||
func (rh *RepositoryHandler) getIndexYamlWithNS(namespace string) (*helm_repo.IndexFile, error) {
|
func (rh *RepositoryHandler) getIndexYamlWithNS(namespace string) (*helm_repo.IndexFile, error) {
|
||||||
// Join url path
|
// Join url path
|
||||||
|
@ -25,8 +25,13 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/utils"
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
uiutils "github.com/goharbor/harbor/src/ui/utils"
|
uiutils "github.com/goharbor/harbor/src/ui/utils"
|
||||||
|
"k8s.io/helm/cmd/helm/search"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type chartSearchHandler func(string, []string) ([]*search.Result, error)
|
||||||
|
|
||||||
|
var searchHandler chartSearchHandler
|
||||||
|
|
||||||
// SearchAPI handles requesst to /api/search
|
// SearchAPI handles requesst to /api/search
|
||||||
type SearchAPI struct {
|
type SearchAPI struct {
|
||||||
BaseController
|
BaseController
|
||||||
@ -35,6 +40,7 @@ type SearchAPI struct {
|
|||||||
type searchResult struct {
|
type searchResult struct {
|
||||||
Project []*models.Project `json:"project"`
|
Project []*models.Project `json:"project"`
|
||||||
Repository []map[string]interface{} `json:"repository"`
|
Repository []map[string]interface{} `json:"repository"`
|
||||||
|
Chart []*search.Result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get ...
|
// Get ...
|
||||||
@ -80,7 +86,10 @@ func (s *SearchAPI) Get() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
projectResult := []*models.Project{}
|
projectResult := []*models.Project{}
|
||||||
|
proNames := []string{}
|
||||||
for _, p := range projects {
|
for _, p := range projects {
|
||||||
|
proNames = append(proNames, p.Name)
|
||||||
|
|
||||||
if len(keyword) > 0 && !strings.Contains(p.Name, keyword) {
|
if len(keyword) > 0 && !strings.Contains(p.Name, keyword) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -115,7 +124,21 @@ func (s *SearchAPI) Get() {
|
|||||||
s.CustomAbort(http.StatusInternalServerError, "")
|
s.CustomAbort(http.StatusInternalServerError, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
result := &searchResult{Project: projectResult, Repository: repositoryResult}
|
if searchHandler == nil {
|
||||||
|
searchHandler = chartController.GetRepositoryHandler().SearchChart
|
||||||
|
}
|
||||||
|
|
||||||
|
chartResults, err := searchHandler(keyword, proNames)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to filter charts: %v", err)
|
||||||
|
s.CustomAbort(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &searchResult{
|
||||||
|
Project: projectResult,
|
||||||
|
Repository: repositoryResult,
|
||||||
|
Chart: chartResults,
|
||||||
|
}
|
||||||
s.Data["json"] = result
|
s.Data["json"] = result
|
||||||
s.ServeJSON()
|
s.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@ -20,15 +20,28 @@ import (
|
|||||||
|
|
||||||
"github.com/goharbor/harbor/src/common"
|
"github.com/goharbor/harbor/src/common"
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
"k8s.io/helm/cmd/helm/search"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/dao"
|
"github.com/goharbor/harbor/src/common/dao"
|
||||||
member "github.com/goharbor/harbor/src/common/dao/project"
|
member "github.com/goharbor/harbor/src/common/dao/project"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
helm_repo "k8s.io/helm/pkg/repo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSearch(t *testing.T) {
|
func TestSearch(t *testing.T) {
|
||||||
fmt.Println("Testing Search(SearchGet) API")
|
fmt.Println("Testing Search(SearchGet) API")
|
||||||
|
//Use mock chart search handler
|
||||||
|
searchHandler = func(string, []string) ([]*search.Result, error) {
|
||||||
|
results := []*search.Result{}
|
||||||
|
results = append(results, &search.Result{
|
||||||
|
Name: "library/harbor",
|
||||||
|
Score: 0,
|
||||||
|
Chart: &helm_repo.ChartVersion{},
|
||||||
|
})
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
// create a public project named "search"
|
// create a public project named "search"
|
||||||
projectID1, err := dao.AddProject(models.Project{
|
projectID1, err := dao.AddProject(models.Project{
|
||||||
Name: "search",
|
Name: "search",
|
||||||
@ -164,4 +177,22 @@ func TestSearch(t *testing.T) {
|
|||||||
assert.True(t, exist)
|
assert.True(t, exist)
|
||||||
_, exist = repositories["search-2/hello-world"]
|
_, exist = repositories["search-2/hello-world"]
|
||||||
assert.True(t, exist)
|
assert.True(t, exist)
|
||||||
|
|
||||||
|
//Search chart
|
||||||
|
err = handleAndParse(&testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/search",
|
||||||
|
queryStruct: struct {
|
||||||
|
Keyword string `url:"q"`
|
||||||
|
}{
|
||||||
|
Keyword: "harbor",
|
||||||
|
},
|
||||||
|
credential: sysAdmin,
|
||||||
|
}, result)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 1, len(result.Chart))
|
||||||
|
require.Equal(t, "library/harbor", result.Chart[0].Name)
|
||||||
|
|
||||||
|
//Restore chart search handler
|
||||||
|
searchHandler = nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user