Merge pull request #1557 from ywk253100/170309_get_tag

Refactor getting tags API to return more info
This commit is contained in:
Wenkai Yin 2017-03-10 15:22:25 +08:00 committed by GitHub
commit 39f786dbbc
4 changed files with 188 additions and 52 deletions

View File

@ -682,11 +682,11 @@ paths:
- Products - Products
responses: responses:
200: 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: schema:
type: array type: array
items: items:
type: string $ref: '#/definitions/Repository'
headers: headers:
X-Total-Count: X-Total-Count:
description: The total count of repositories description: The total count of repositories
@ -741,15 +741,20 @@ paths:
type: string type: string
required: true required: true
description: Relevant repository name. description: Relevant repository name.
- name: detail
in: query
type: boolean
required: false
description: If detail is true, the manifests is returned too.
tags: tags:
- Products - Products
responses: responses:
200: 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: schema:
type: array type: array
items: items:
type: string $ref: '#/definitions/DetailedTag'
500: 500:
description: Unexpected internal errors. description: Unexpected internal errors.
/repositories/manifests: /repositories/manifests:
@ -779,7 +784,7 @@ paths:
200: 200:
description: Retrieved manifests from a relevant repository successfully. description: Retrieved manifests from a relevant repository successfully.
schema: schema:
$ref: '#/definitions/Repository' $ref: '#/definitions/Manifest'
404: 404:
description: Retrieved manifests from a relevant repository not found. description: Retrieved manifests from a relevant repository not found.
500: 500:
@ -1613,7 +1618,7 @@ definitions:
repo_count: repo_count:
type: integer type: integer
description: The number of the repositories under this project. description: The number of the repositories under this project.
Repository: Manifest:
type: object type: object
properties: properties:
manifest: manifest:
@ -2058,3 +2063,45 @@ definitions:
hashes: hashes:
type: object type: object
description: The JSON object of the hash of the image. 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.

View File

@ -498,7 +498,8 @@ func (a testapi) GetRepos(authInfo usrInfo, projectID,
} }
//Get tags of a relevant repository //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) _sling := sling.New().Get(a.basePath)
path := "/api/repositories/tags" path := "/api/repositories/tags"
@ -507,11 +508,35 @@ func (a testapi) GetReposTags(authInfo usrInfo, repoName string) (int, error) {
type QueryParams struct { type QueryParams struct {
RepoName string `url:"repo_name"` RepoName string `url:"repo_name"`
Detail string `url:"detail"`
} }
_sling = _sling.QueryStruct(&QueryParams{RepoName: repoName}) _sling = _sling.QueryStruct(&QueryParams{
httpStatusCode, _, err := request(_sling, jsonAcceptHeader, authInfo) RepoName: repoName,
return httpStatusCode, err 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 //Get manifests of a relevant repository

View File

@ -58,6 +58,16 @@ type repoResp struct {
UpdateTime time.Time `json:"update_time"` 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 ... // Get ...
func (ra *RepositoryAPI) Get() { func (ra *RepositoryAPI) Get() {
projectID, err := ra.GetInt64("project_id") projectID, err := ra.GetInt64("project_id")
@ -259,6 +269,7 @@ func (ra *RepositoryAPI) GetTags() {
if len(repoName) == 0 { if len(repoName) == 0 {
ra.CustomAbort(http.StatusBadRequest, "repo_name is nil") ra.CustomAbort(http.StatusBadRequest, "repo_name is nil")
} }
detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true"
projectName, _ := utils.ParseRepository(repoName) projectName, _ := utils.ParseRepository(repoName)
project, err := dao.GetProjectByName(projectName) project, err := dao.GetProjectByName(projectName)
@ -294,8 +305,34 @@ func (ra *RepositoryAPI) GetTags() {
ra.CustomAbort(regErr.StatusCode, regErr.Detail) 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() ra.ServeJSON()
} }
func listTag(client *registry.Repository) ([]string, error) { func listTag(client *registry.Repository) ([]string, error) {
@ -312,6 +349,8 @@ func listTag(client *registry.Repository) ([]string, error) {
regErr.StatusCode == http.StatusNotFound { regErr.StatusCode == http.StatusNotFound {
return tags, nil return tags, nil
} }
return nil, err
} }
tags = append(tags, ts...) tags = append(tags, ts...)
@ -362,20 +401,7 @@ func (ra *RepositoryAPI) GetManifests() {
ra.CustomAbort(http.StatusInternalServerError, "internal error") ra.CustomAbort(http.StatusInternalServerError, "internal error")
} }
result := struct { manifest, err := getManifest(rc, tag, version)
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)
if err != nil { if err != nil {
if regErr, ok := err.(*registry_error.Error); ok { if regErr, ok := err.(*registry_error.Error); ok {
ra.CustomAbort(regErr.StatusCode, regErr.Detail) ra.CustomAbort(regErr.StatusCode, regErr.Detail)
@ -385,33 +411,50 @@ func (ra *RepositoryAPI) GetManifests() {
ra.CustomAbort(http.StatusInternalServerError, "internal error") 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) manifest, _, err := registry.UnMarshal(mediaType, payload)
if err != nil { if err != nil {
log.Errorf("an error occurred while parsing manifest of %s:%s: %v", repoName, tag, err) return nil, err
ra.CustomAbort(http.StatusInternalServerError, "")
} }
result.Manifest = manifest result.Manifest = manifest
deserializedmanifest, ok := manifest.(*schema2.DeserializedManifest) deserializedmanifest, ok := manifest.(*schema2.DeserializedManifest)
if ok { if ok {
_, data, err := rc.PullBlob(deserializedmanifest.Target().Digest.String()) _, data, err := client.PullBlob(deserializedmanifest.Target().Digest.String())
if err != nil { if err != nil {
log.Errorf("failed to get config of manifest %s:%s: %v", repoName, tag, err) return nil, err
ra.CustomAbort(http.StatusInternalServerError, "")
} }
b, err := ioutil.ReadAll(data) b, err := ioutil.ReadAll(data)
if err != nil { if err != nil {
log.Errorf("failed to read config of manifest %s:%s: %v", repoName, tag, err) return nil, err
ra.CustomAbort(http.StatusInternalServerError, "")
} }
result.Config = string(b) result.Config = string(b)
} }
ra.Data["json"] = result return result, nil
ra.ServeJSON()
} }
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) { func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {

View File

@ -73,43 +73,64 @@ func TestGetRepos(t *testing.T) {
} }
func TestGetReposTags(t *testing.T) { func TestGetReposTags(t *testing.T) {
var httpStatusCode int
var err error
var repoName string
assert := assert.New(t) assert := assert.New(t)
apiTest := newHarborAPI() apiTest := newHarborAPI()
repository := ""
detail := "false"
fmt.Println("Testing ReposTags Get API") fmt.Println("Testing ReposTags Get API")
//-------------------case 1 : response code = 400------------------------// //-------------------case 1 : response code = 400------------------------//
fmt.Println("case 1 : response code = 400,repo_name is nil") 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 { if err != nil {
t.Error("Error whihle get reposTags by repoName", err.Error()) t.Errorf("failed to get tags of repository %s: %v", repository, err)
t.Log(err)
} else { } else {
assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400") assert.Equal(int(400), code, "httpStatusCode should be 400")
} }
//-------------------case 2 : response code = 404------------------------// //-------------------case 2 : response code = 404------------------------//
fmt.Println("case 2 : response code = 404,repo not found") fmt.Println("case 2 : response code = 404,repo not found")
repoName = "errorRepos" repository = "errorRepos"
httpStatusCode, err = apiTest.GetReposTags(*admin, repoName) code, _, err = apiTest.GetReposTags(*admin, repository, detail)
if err != nil { if err != nil {
t.Error("Error whihle get reposTags by repoName", err.Error()) t.Errorf("failed to get tags of repository %s: %v", repository, err)
t.Log(err)
} else { } else {
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404") assert.Equal(int(404), code, "httpStatusCode should be 404")
} }
//-------------------case 3 : response code = 200------------------------// //-------------------case 3 : response code = 200------------------------//
fmt.Println("case 3 : response code = 200") fmt.Println("case 3 : response code = 200")
repoName = "library/hello-world" repository = "library/hello-world"
httpStatusCode, err = apiTest.GetReposTags(*admin, repoName) code, tags, err := apiTest.GetReposTags(*admin, repository, detail)
if err != nil { if err != nil {
t.Error("Error whihle get reposTags by repoName", err.Error()) t.Errorf("failed to get tags of repository %s: %v", repository, err)
t.Log(err)
} else { } 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") fmt.Printf("\n")