diff --git a/docs/swagger.yaml b/docs/swagger.yaml index b024eb2fe..1b271e85b 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -682,11 +682,11 @@ paths: - Products responses: 200: - description: Searched for respositories successfully. + description: If detail is false, the response body is a string array which contains the names of repositories, or the response body contains an object array as described in schema. schema: type: array items: - type: string + $ref: '#/definitions/Repository' headers: X-Total-Count: description: The total count of repositories @@ -741,15 +741,20 @@ paths: type: string required: true description: Relevant repository name. + - name: detail + in: query + type: boolean + required: false + description: If detail is true, the manifests is returned too. tags: - Products responses: 200: - description: Retrieved tags from a relevant repository successfully. + description: If detail is false, the response body is a string array, or the response body contains the manifest informations as described in schema. schema: type: array items: - type: string + $ref: '#/definitions/DetailedTag' 500: description: Unexpected internal errors. /repositories/manifests: @@ -779,7 +784,7 @@ paths: 200: description: Retrieved manifests from a relevant repository successfully. schema: - $ref: '#/definitions/Repository' + $ref: '#/definitions/Manifest' 404: description: Retrieved manifests from a relevant repository not found. 500: @@ -1613,7 +1618,7 @@ definitions: repo_count: type: integer description: The number of the repositories under this project. - Repository: + Manifest: type: object properties: manifest: @@ -2058,3 +2063,45 @@ definitions: hashes: type: object description: The JSON object of the hash of the image. + DetailedTag: + type: object + properties: + tag: + type: string + description: The tag of image. + manifest: + type: object + description: The detail of manifest. + Repository: + type: object + properties: + id: + type: string + description: The ID of repository. + name: + type: string + description: The name of repository. + owner_id: + type: integer + description: The owner ID of repository. + project_id: + type: integer + description: The project ID of repository. + description: + type: string + description: The description of repository. + pull_count: + type: integer + description: The pull count of repository. + star_count: + type: integer + description: The star count of repository. + tags_count: + type: integer + description: The tags count of repository. + creation_time: + type: string + description: The creation time of repository. + update_time: + type: string + description: The update time of repository. \ No newline at end of file diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go index ba663b604..c2c51e2d5 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -498,7 +498,8 @@ func (a testapi) GetRepos(authInfo usrInfo, projectID, } //Get tags of a relevant repository -func (a testapi) GetReposTags(authInfo usrInfo, repoName string) (int, error) { +func (a testapi) GetReposTags(authInfo usrInfo, repoName, + detail string) (int, interface{}, error) { _sling := sling.New().Get(a.basePath) path := "/api/repositories/tags" @@ -507,11 +508,35 @@ func (a testapi) GetReposTags(authInfo usrInfo, repoName string) (int, error) { type QueryParams struct { RepoName string `url:"repo_name"` + Detail string `url:"detail"` } - _sling = _sling.QueryStruct(&QueryParams{RepoName: repoName}) - httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo) - return httpStatusCode, err + _sling = _sling.QueryStruct(&QueryParams{ + RepoName: repoName, + Detail: detail, + }) + httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo) + if err != nil { + return 0, nil, err + } + + if httpStatusCode != http.StatusOK { + return httpStatusCode, body, nil + } + + if detail == "true" || detail == "1" { + result := []detailedTagResp{} + if err := json.Unmarshal(body, &result); err != nil { + return 0, nil, err + } + return http.StatusOK, result, nil + } + + result := []string{} + if err := json.Unmarshal(body, &result); err != nil { + return 0, nil, err + } + return http.StatusOK, result, nil } //Get manifests of a relevant repository diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 2a82ebe77..eb443546c 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -58,6 +58,16 @@ type repoResp struct { UpdateTime time.Time `json:"update_time"` } +type detailedTagResp struct { + Tag string `json:"tag"` + Manifest interface{} `json:"manifest"` +} + +type manifestResp struct { + Manifest interface{} `json:"manifest"` + Config interface{} `json:"config,omitempty" ` +} + // Get ... func (ra *RepositoryAPI) Get() { projectID, err := ra.GetInt64("project_id") @@ -259,6 +269,7 @@ func (ra *RepositoryAPI) GetTags() { if len(repoName) == 0 { ra.CustomAbort(http.StatusBadRequest, "repo_name is nil") } + detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true" projectName, _ := utils.ParseRepository(repoName) project, err := dao.GetProjectByName(projectName) @@ -294,8 +305,34 @@ func (ra *RepositoryAPI) GetTags() { ra.CustomAbort(regErr.StatusCode, regErr.Detail) } - ra.Data["json"] = tags + if !detail { + ra.Data["json"] = tags + ra.ServeJSON() + return + } + + result := []detailedTagResp{} + + for _, tag := range tags { + manifest, err := getManifest(client, tag, "v1") + if err != nil { + if regErr, ok := err.(*registry_error.Error); ok { + ra.CustomAbort(regErr.StatusCode, regErr.Detail) + } + + log.Errorf("failed to get manifest of %s:%s: %v", repoName, tag, err) + ra.CustomAbort(http.StatusInternalServerError, "internal error") + } + + result = append(result, detailedTagResp{ + Tag: tag, + Manifest: manifest.Manifest, + }) + } + + ra.Data["json"] = result ra.ServeJSON() + } func listTag(client *registry.Repository) ([]string, error) { @@ -312,6 +349,8 @@ func listTag(client *registry.Repository) ([]string, error) { regErr.StatusCode == http.StatusNotFound { return tags, nil } + + return nil, err } tags = append(tags, ts...) @@ -362,20 +401,7 @@ func (ra *RepositoryAPI) GetManifests() { ra.CustomAbort(http.StatusInternalServerError, "internal error") } - result := struct { - Manifest interface{} `json:"manifest"` - Config interface{} `json:"config,omitempty" ` - }{} - - mediaTypes := []string{} - switch version { - case "v1": - mediaTypes = append(mediaTypes, schema1.MediaTypeManifest) - case "v2": - mediaTypes = append(mediaTypes, schema2.MediaTypeManifest) - } - - _, mediaType, payload, err := rc.PullManifest(tag, mediaTypes) + manifest, err := getManifest(rc, tag, version) if err != nil { if regErr, ok := err.(*registry_error.Error); ok { ra.CustomAbort(regErr.StatusCode, regErr.Detail) @@ -385,33 +411,50 @@ func (ra *RepositoryAPI) GetManifests() { ra.CustomAbort(http.StatusInternalServerError, "internal error") } + ra.Data["json"] = manifest + ra.ServeJSON() +} + +func getManifest(client *registry.Repository, + tag, version string) (*manifestResp, error) { + result := &manifestResp{} + + mediaTypes := []string{} + switch version { + case "v1": + mediaTypes = append(mediaTypes, schema1.MediaTypeManifest) + case "v2": + mediaTypes = append(mediaTypes, schema2.MediaTypeManifest) + } + + _, mediaType, payload, err := client.PullManifest(tag, mediaTypes) + if err != nil { + return nil, err + } + manifest, _, err := registry.UnMarshal(mediaType, payload) if err != nil { - log.Errorf("an error occurred while parsing manifest of %s:%s: %v", repoName, tag, err) - ra.CustomAbort(http.StatusInternalServerError, "") + return nil, err } result.Manifest = manifest deserializedmanifest, ok := manifest.(*schema2.DeserializedManifest) if ok { - _, data, err := rc.PullBlob(deserializedmanifest.Target().Digest.String()) + _, data, err := client.PullBlob(deserializedmanifest.Target().Digest.String()) if err != nil { - log.Errorf("failed to get config of manifest %s:%s: %v", repoName, tag, err) - ra.CustomAbort(http.StatusInternalServerError, "") + return nil, err } b, err := ioutil.ReadAll(data) if err != nil { - log.Errorf("failed to read config of manifest %s:%s: %v", repoName, tag, err) - ra.CustomAbort(http.StatusInternalServerError, "") + return nil, err } result.Config = string(b) } - ra.Data["json"] = result - ra.ServeJSON() + return result, nil } func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) { diff --git a/src/ui/api/repository_test.go b/src/ui/api/repository_test.go index a556e3955..61ff4abd8 100644 --- a/src/ui/api/repository_test.go +++ b/src/ui/api/repository_test.go @@ -73,43 +73,64 @@ func TestGetRepos(t *testing.T) { } func TestGetReposTags(t *testing.T) { - var httpStatusCode int - var err error - var repoName string assert := assert.New(t) apiTest := newHarborAPI() + repository := "" + detail := "false" + fmt.Println("Testing ReposTags Get API") //-------------------case 1 : response code = 400------------------------// fmt.Println("case 1 : response code = 400,repo_name is nil") - repoName = "" - httpStatusCode, err = apiTest.GetReposTags(*admin, repoName) + + code, _, err := apiTest.GetReposTags(*admin, repository, detail) if err != nil { - t.Error("Error whihle get reposTags by repoName", err.Error()) - t.Log(err) + t.Errorf("failed to get tags of repository %s: %v", repository, err) } else { - assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400") + assert.Equal(int(400), code, "httpStatusCode should be 400") } + //-------------------case 2 : response code = 404------------------------// fmt.Println("case 2 : response code = 404,repo not found") - repoName = "errorRepos" - httpStatusCode, err = apiTest.GetReposTags(*admin, repoName) + repository = "errorRepos" + code, _, err = apiTest.GetReposTags(*admin, repository, detail) if err != nil { - t.Error("Error whihle get reposTags by repoName", err.Error()) - t.Log(err) + t.Errorf("failed to get tags of repository %s: %v", repository, err) } else { - assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404") + assert.Equal(int(404), code, "httpStatusCode should be 404") } //-------------------case 3 : response code = 200------------------------// fmt.Println("case 3 : response code = 200") - repoName = "library/hello-world" - httpStatusCode, err = apiTest.GetReposTags(*admin, repoName) + repository = "library/hello-world" + code, tags, err := apiTest.GetReposTags(*admin, repository, detail) if err != nil { - t.Error("Error whihle get reposTags by repoName", err.Error()) - t.Log(err) + t.Errorf("failed to get tags of repository %s: %v", repository, err) } else { - assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") + assert.Equal(int(200), code, "httpStatusCode should be 200") + if tg, ok := tags.([]string); ok { + assert.Equal(1, len(tg), fmt.Sprintf("there should be only one tag, but now %v", tg)) + assert.Equal(tg[0], "latest", "the tag should be latest") + } else { + t.Error("the tags should be in simple style as the detail is false") + } + } + + //-------------------case 4 : response code = 200------------------------// + fmt.Println("case 4 : response code = 200") + repository = "library/hello-world" + detail = "true" + code, tags, err = apiTest.GetReposTags(*admin, repository, detail) + if err != nil { + t.Errorf("failed to get tags of repository %s: %v", repository, err) + } else { + assert.Equal(int(200), code, "httpStatusCode should be 200") + if tg, ok := tags.([]detailedTagResp); ok { + assert.Equal(1, len(tg), fmt.Sprintf("there should be only one tag, but now %v", tg)) + assert.Equal(tg[0].Tag, "latest", "the tag should be latest") + } else { + t.Error("the tags should be in detail style as the detail is true") + } } fmt.Printf("\n")