From d6a6f67596c5c0280f3ddbe92e4f531242b3e7a6 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Mon, 6 Mar 2017 17:12:09 +0800 Subject: [PATCH] refactor search API to return more info --- docs/swagger.yaml | 22 +++------ src/common/dao/project.go | 11 +++-- src/common/dao/repository.go | 3 +- src/ui/api/repository.go | 30 ++++++++---- src/ui/api/search.go | 83 ++++++++++++++++++++++++++------- src/ui/api/search_test.go | 4 +- tests/apitests/apilib/search.go | 10 ++-- 7 files changed, 110 insertions(+), 53 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index dcbf73c6b..bd10ed2c9 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1483,26 +1483,12 @@ definitions: description: Search results of the projects that matched the filter keywords. type: array items: - $ref: '#/definitions/SearchProject' + $ref: '#/definitions/Project' repositories: description: Search results of the repositories that matched the filter keywords. type: array items: $ref: '#/definitions/SearchRepository' - SearchProject: - type: object - properties: - id: - type: integer - format: int64 - description: The ID of project - name: - type: string - description: The name of the project - public: - type: integer - format: int - description: The flag to indicate the publicity of the project (1 is public, 0 is non-public) SearchRepository: type: object properties: @@ -1518,6 +1504,12 @@ definitions: repository_name: type: string description: The name of the repository + pull_count: + type: integer + description: The count how many times the repository is pulled + tags_count: + type: integer + description: The count of tags in the repository ProjectReq: type: object properties: diff --git a/src/common/dao/project.go b/src/common/dao/project.go index d013560fd..9f476dd0c 100644 --- a/src/common/dao/project.go +++ b/src/common/dao/project.go @@ -168,10 +168,15 @@ func ToggleProjectPublicity(projectID int64, publicity int) error { // 2. the prject is public or the user is a member of the project func SearchProjects(userID int) ([]models.Project, error) { o := GetOrmer() - sql := `select distinct p.project_id, p.name, p.public + + sql := + `select distinct p.project_id, p.name, p.public, + p.owner_id, p.creation_time, p.update_time, pm.role role from project p - left join project_member pm on p.project_id = pm.project_id - where (pm.user_id = ? or p.public = 1) and p.deleted = 0` + left join project_member pm + on p.project_id = pm.project_id + where (pm.user_id = ? or p.public = 1) + and p.deleted = 0 ` var projects []models.Project diff --git a/src/common/dao/repository.go b/src/common/dao/repository.go index 8acd4242f..e7cab6459 100644 --- a/src/common/dao/repository.go +++ b/src/common/dao/repository.go @@ -50,7 +50,8 @@ func GetRepositoryByName(name string) (*models.RepoRecord, error) { func GetAllRepositories() ([]models.RepoRecord, error) { o := GetOrmer() var repos []models.RepoRecord - _, err := o.QueryTable("repository").All(&repos) + _, err := o.QueryTable("repository"). + OrderBy("Name").All(&repos) return repos, err } diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 94aa302ee..ee913b3fb 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -234,36 +234,46 @@ func (ra *RepositoryAPI) GetTags() { } } - rc, err := ra.initRepositoryClient(repoName) + client, err := ra.initRepositoryClient(repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) ra.CustomAbort(http.StatusInternalServerError, "internal error") } - - tags := []string{} - - ts, err := rc.ListTag() + tags, err := listTag(client) if err != nil { regErr, ok := err.(*registry_error.Error) if !ok { log.Errorf("error occurred while listing tags of %s: %v", repoName, err) ra.CustomAbort(http.StatusInternalServerError, "internal error") } + + ra.CustomAbort(regErr.StatusCode, regErr.Detail) + } + + ra.Data["json"] = tags + ra.ServeJSON() +} + +func listTag(client *registry.Repository) ([]string, error) { + tags := []string{} + + ts, err := client.ListTag() + if err != nil { // TODO remove the logic if the bug of registry is fixed // It's a workaround for a bug of registry: when listing tags of // a repository which is being pushed, a "NAME_UNKNOWN" error will // been returned, while the catalog API can list this repository. - if regErr.StatusCode != http.StatusNotFound { - ra.CustomAbort(regErr.StatusCode, regErr.Detail) + + if regErr, ok := err.(*registry_error.Error); ok && + regErr.StatusCode == http.StatusNotFound { + return tags, nil } } tags = append(tags, ts...) - sort.Strings(tags) - ra.Data["json"] = tags - ra.ServeJSON() + return tags, nil } // GetManifests handles GET /api/repositories/manifests diff --git a/src/ui/api/search.go b/src/ui/api/search.go index aae80a2dc..ce3201c90 100644 --- a/src/ui/api/search.go +++ b/src/ui/api/search.go @@ -20,12 +20,13 @@ import ( "sort" "strings" + "github.com/vmware/harbor/src/common/api" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" - "github.com/vmware/harbor/src/ui/service/cache" "github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/common/utils/log" - "github.com/vmware/harbor/src/common/api" + "github.com/vmware/harbor/src/ui/config" + "github.com/vmware/harbor/src/ui/service/cache" ) // SearchAPI handles requesst to /api/search @@ -34,7 +35,7 @@ type SearchAPI struct { } type searchResult struct { - Project []map[string]interface{} `json:"project"` + Project []models.Project `json:"project"` Repository []map[string]interface{} `json:"repository"` } @@ -71,58 +72,104 @@ func (s *SearchAPI) Get() { projectSorter := &models.ProjectSorter{Projects: projects} sort.Sort(projectSorter) - projectResult := []map[string]interface{}{} + projectResult := []models.Project{} for _, p := range projects { match := true if len(keyword) > 0 && !strings.Contains(p.Name, keyword) { match = false } if match { - entry := make(map[string]interface{}) - entry["id"] = p.ProjectID - entry["name"] = p.Name - entry["public"] = p.Public - projectResult = append(projectResult, entry) + if userID != dao.NonExistUserID { + if isSysAdmin { + p.Role = models.PROJECTADMIN + } + if p.Role == models.PROJECTADMIN { + p.Togglable = true + } + } + + repos, err := dao.GetRepositoryByProjectName(p.Name) + if err != nil { + log.Errorf("failed to get repositories of project %s: %v", p.Name, err) + s.CustomAbort(http.StatusInternalServerError, "") + } + + p.RepoCount = len(repos) + + projectResult = append(projectResult, p) } } - repositories, err := cache.GetRepoFromCache() + repositoryResult, err := filterRepositories(projects, keyword) if err != nil { - log.Errorf("failed to list repositories: %v", err) + log.Errorf("failed to filter repositories: %v", err) s.CustomAbort(http.StatusInternalServerError, "") } - sort.Strings(repositories) - repositoryResult := filterRepositories(repositories, projects, keyword) result := &searchResult{Project: projectResult, Repository: repositoryResult} s.Data["json"] = result s.ServeJSON() } -func filterRepositories(repositories []string, projects []models.Project, keyword string) []map[string]interface{} { +func filterRepositories(projects []models.Project, keyword string) ( + []map[string]interface{}, error) { + + repositories, err := dao.GetAllRepositories() + if err != nil { + return nil, err + } + i, j := 0, 0 result := []map[string]interface{}{} for i < len(repositories) && j < len(projects) { r := repositories[i] - p, _ := utils.ParseRepository(r) + p, _ := utils.ParseRepository(r.Name) d := strings.Compare(p, projects[j].Name) if d < 0 { i++ continue } else if d == 0 { i++ - if len(keyword) != 0 && !strings.Contains(r, keyword) { + if len(keyword) != 0 && !strings.Contains(r.Name, keyword) { continue } entry := make(map[string]interface{}) - entry["repository_name"] = r + entry["repository_name"] = r.Name entry["project_name"] = projects[j].Name entry["project_id"] = projects[j].ProjectID entry["project_public"] = projects[j].Public + entry["pull_count"] = r.PullCount + + tags, err := getTags(r.Name) + if err != nil { + return nil, err + } + entry["tags_count"] = len(tags) + result = append(result, entry) } else { j++ } } - return result + return result, nil +} + +func getTags(repository string) ([]string, error) { + url, err := config.RegistryURL() + if err != nil { + return nil, err + } + + client, err := cache.NewRepositoryClient(url, true, + "admin", repository, "repository", repository, "pull") + if err != nil { + return nil, err + } + + tags, err := listTag(client) + if err != nil { + return nil, err + } + + return tags, nil } diff --git a/src/ui/api/search_test.go b/src/ui/api/search_test.go index ac05f74f2..ee5442a88 100644 --- a/src/ui/api/search_test.go +++ b/src/ui/api/search_test.go @@ -22,9 +22,9 @@ func TestSearch(t *testing.T) { t.Log(err) } else { assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") - assert.Equal(int64(1), result.Projects[0].Id, "Project id should be equal") + assert.Equal(int64(1), result.Projects[0].ProjectID, "Project id should be equal") assert.Equal("library", result.Projects[0].Name, "Project name should be library") - assert.Equal(int32(1), result.Projects[0].Public, "Project public status should be 1 (true)") + assert.Equal(1, result.Projects[0].Public, "Project public status should be 1 (true)") } //--------case 2 : Response Code = 200, sysAdmin and search repo--------// diff --git a/tests/apitests/apilib/search.go b/tests/apitests/apilib/search.go index d12388469..447763d9b 100644 --- a/tests/apitests/apilib/search.go +++ b/tests/apitests/apilib/search.go @@ -1,10 +1,10 @@ -/* +/* * Harbor API * * These APIs provide services for manipulating Harbor project. * * OpenAPI spec version: 0.3.0 - * + * * Generated by: https://github.com/swagger-api/swagger-codegen.git * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,12 +22,14 @@ package apilib -import() +import ( + "github.com/vmware/harbor/src/common/models" +) type Search struct { // Search results of the projects that matched the filter keywords. - Projects []SearchProject `json:"project,omitempty"` + Projects []models.Project `json:"project,omitempty"` // Search results of the repositories that matched the filter keywords. Repositories []SearchRepository `json:"repository,omitempty"`