mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-23 00:57:44 +01:00
Merge pull request #1557 from ywk253100/170309_get_tag
Refactor getting tags API to return more info
This commit is contained in:
commit
39f786dbbc
@ -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.
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user