From c8802f05ade9fd22f88ae803a20d22d8bfc4ec35 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Thu, 9 Mar 2017 14:48:25 +0800 Subject: [PATCH 1/2] refactor get tag API --- src/ui/api/repository.go | 75 +++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 2a82ebe77..85815acb8 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -58,6 +58,11 @@ type repoResp struct { UpdateTime time.Time `json:"update_time"` } +type detailedTagResp struct { + Tag string `json:"tag"` + Manifest interface{} `json:"manifest"` +} + // Get ... func (ra *RepositoryAPI) Get() { projectID, err := ra.GetInt64("project_id") @@ -259,6 +264,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 +300,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, + }) + } + + ra.Data["json"] = result ra.ServeJSON() + } func listTag(client *registry.Repository) ([]string, error) { @@ -312,6 +344,8 @@ func listTag(client *registry.Repository) ([]string, error) { regErr.StatusCode == http.StatusNotFound { return tags, nil } + + return nil, err } tags = append(tags, ts...) @@ -362,6 +396,22 @@ func (ra *RepositoryAPI) GetManifests() { ra.CustomAbort(http.StatusInternalServerError, "internal error") } + manifest, err := getManifest(rc, tag, version) + if err != nil { + if regErr, ok := err.(*registry_error.Error); ok { + ra.CustomAbort(regErr.StatusCode, regErr.Detail) + } + + log.Errorf("error occurred while getting manifest of %s:%s: %v", repoName, tag, err) + ra.CustomAbort(http.StatusInternalServerError, "internal error") + } + + ra.Data["json"] = manifest + ra.ServeJSON() +} + +func getManifest(client *registry.Repository, + tag, version string) (interface{}, error) { result := struct { Manifest interface{} `json:"manifest"` Config interface{} `json:"config,omitempty" ` @@ -375,43 +425,34 @@ func (ra *RepositoryAPI) GetManifests() { mediaTypes = append(mediaTypes, schema2.MediaTypeManifest) } - _, mediaType, payload, err := rc.PullManifest(tag, mediaTypes) + _, mediaType, payload, err := client.PullManifest(tag, mediaTypes) if err != nil { - if regErr, ok := err.(*registry_error.Error); ok { - ra.CustomAbort(regErr.StatusCode, regErr.Detail) - } - - log.Errorf("error occurred while getting manifest of %s:%s: %v", repoName, tag, err) - ra.CustomAbort(http.StatusInternalServerError, "internal error") + 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) { From 3cb53011eb3e1712d8b681d63e36e5546f5ce530 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Thu, 9 Mar 2017 17:30:00 +0800 Subject: [PATCH 2/2] refactor get tags API to return more info --- docs/swagger.yaml | 59 +++++++++++++++++++++++++++++++---- src/ui/api/harborapi_test.go | 34 +++++++++++++++++--- src/ui/api/repository.go | 14 +++++---- src/ui/api/repository_test.go | 57 ++++++++++++++++++++++----------- 4 files changed, 130 insertions(+), 34 deletions(-) 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 57cc8ea11..c54297c54 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -7,6 +7,7 @@ import ( "fmt" "io/ioutil" "log" + "net/http" "net/http/httptest" "path/filepath" "runtime" @@ -474,7 +475,8 @@ func (a testapi) GetRepos(authInfo usrInfo, projectID, detail string) (int, erro } //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" @@ -483,11 +485,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 85815acb8..eb443546c 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -63,6 +63,11 @@ type detailedTagResp struct { 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") @@ -321,7 +326,7 @@ func (ra *RepositoryAPI) GetTags() { result = append(result, detailedTagResp{ Tag: tag, - Manifest: manifest, + Manifest: manifest.Manifest, }) } @@ -411,11 +416,8 @@ func (ra *RepositoryAPI) GetManifests() { } func getManifest(client *registry.Repository, - tag, version string) (interface{}, error) { - result := struct { - Manifest interface{} `json:"manifest"` - Config interface{} `json:"config,omitempty" ` - }{} + tag, version string) (*manifestResp, error) { + result := &manifestResp{} mediaTypes := []string{} switch version { diff --git a/src/ui/api/repository_test.go b/src/ui/api/repository_test.go index a473bdadb..f21ef6776 100644 --- a/src/ui/api/repository_test.go +++ b/src/ui/api/repository_test.go @@ -52,43 +52,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")