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
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.

View File

@ -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

View File

@ -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) {

View File

@ -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")