mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-05 01:59:44 +01:00
Merge pull request #2355 from ywk253100/170522_repo
Return signatures in GET tags API
This commit is contained in:
commit
924a62df7e
@ -2195,12 +2195,30 @@ definitions:
|
||||
DetailedTag:
|
||||
type: object
|
||||
properties:
|
||||
tag:
|
||||
digest:
|
||||
type: string
|
||||
description: The tag of image.
|
||||
manifest:
|
||||
description: The digest of the tag.
|
||||
name:
|
||||
type: string
|
||||
description: The name of the tag.
|
||||
architecture:
|
||||
type: string
|
||||
description: The architecture of the image.
|
||||
os:
|
||||
type: string
|
||||
description: The os of the image.
|
||||
docker_version:
|
||||
type: string
|
||||
description: The version of docker which builds the image.
|
||||
author:
|
||||
type: string
|
||||
description: The author of the image.
|
||||
created:
|
||||
type: string
|
||||
description: The build time of the image.
|
||||
signature:
|
||||
type: object
|
||||
description: The detail of manifest.
|
||||
description: The signature of image, defined by RepoSignature. If it is null, the image is unsigned.
|
||||
Repository:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -15,6 +15,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@ -51,9 +52,19 @@ type repoResp struct {
|
||||
UpdateTime time.Time `json:"update_time"`
|
||||
}
|
||||
|
||||
type tag struct {
|
||||
Digest string `json:"digest"`
|
||||
Name string `json:"name"`
|
||||
Architecture string `json:"architecture"`
|
||||
OS string `json:"os"`
|
||||
DockerVersion string `json:"docker_version"`
|
||||
Author string `json:"author"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
type tagResp struct {
|
||||
Tag string `json:"tag"`
|
||||
Manifest interface{} `json:"manifest"`
|
||||
tag
|
||||
Signature *notary.Target `json:"signature"`
|
||||
}
|
||||
|
||||
type manifestResp struct {
|
||||
@ -205,22 +216,13 @@ func (ra *RepositoryAPI) Delete() {
|
||||
}
|
||||
|
||||
if config.WithNotary() {
|
||||
var digest string
|
||||
signedTags := make(map[string]struct{})
|
||||
targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(),
|
||||
ra.SecurityCtx.GetUsername(), repoName)
|
||||
signedTags, err := getSignatures(ra.SecurityCtx.GetUsername(), repoName)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get Notary targets for repository: %s, error: %v", repoName, err)
|
||||
log.Warningf("Failed to check signature status of repository: %s for deletion, there maybe orphaned targets in Notary.", repoName)
|
||||
}
|
||||
for _, tgt := range targets {
|
||||
digest, err = notary.DigestFromTarget(tgt)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get disgest from target, error: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
signedTags[digest] = struct{}{}
|
||||
ra.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get signatures for repository %s: %v", repoName, err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, t := range tags {
|
||||
digest, _, err := rc.ManifestExist(t)
|
||||
if err != nil {
|
||||
@ -319,41 +321,113 @@ func (ra *RepositoryAPI) GetTags() {
|
||||
log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
tags, err := listTag(client)
|
||||
|
||||
// get tags
|
||||
tags, err := getDetailedTags(client)
|
||||
if err != nil {
|
||||
regErr, ok := err.(*registry_error.Error)
|
||||
if !ok {
|
||||
log.Errorf("error occurred while listing tags of %s: %v", repoName, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||
ra.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to list tags of repository %s: %v", repoName, err))
|
||||
return
|
||||
}
|
||||
|
||||
ra.CustomAbort(regErr.StatusCode, regErr.Detail)
|
||||
ra.RenderError(regErr.StatusCode, regErr.Detail)
|
||||
return
|
||||
}
|
||||
|
||||
result := []tagResp{}
|
||||
|
||||
for _, tag := range tags {
|
||||
manifest, err := getManifest(client, tag, "v1")
|
||||
// get signatures
|
||||
signatures := map[string]*notary.Target{}
|
||||
if config.WithNotary() {
|
||||
signatures, err = getSignatures(repoName, ra.SecurityCtx.GetUsername())
|
||||
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")
|
||||
ra.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get signatures of repository %s: %v", repoName, err))
|
||||
return
|
||||
}
|
||||
|
||||
result = append(result, tagResp{
|
||||
Tag: tag,
|
||||
Manifest: manifest.Manifest,
|
||||
})
|
||||
}
|
||||
|
||||
ra.Data["json"] = result
|
||||
// assemble the response
|
||||
tagResps := []*tagResp{}
|
||||
for _, tag := range tags {
|
||||
tagResp := &tagResp{
|
||||
tag: *tag,
|
||||
}
|
||||
|
||||
// compare both digest and tag
|
||||
if signature, ok := signatures[tag.Digest]; ok {
|
||||
if tag.Name == signature.Tag {
|
||||
tagResp.Signature = signature
|
||||
}
|
||||
}
|
||||
|
||||
tagResps = append(tagResps, tagResp)
|
||||
}
|
||||
|
||||
ra.Data["json"] = tagResps
|
||||
ra.ServeJSON()
|
||||
}
|
||||
|
||||
func listTag(client *registry.Repository) ([]string, error) {
|
||||
// get tags of the repository, read manifest for every tag
|
||||
// and assemble necessary attrs(os, architecture, etc.) into
|
||||
// one struct
|
||||
func getDetailedTags(client *registry.Repository) ([]*tag, error) {
|
||||
ts, err := getSimpleTags(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := []*tag{}
|
||||
|
||||
for _, t := range ts {
|
||||
// the ignored manifest can be used to calculate the image size
|
||||
digest, _, config, err := getV2Manifest(client, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag := &tag{}
|
||||
if err = json.Unmarshal(config, tag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag.Name = t
|
||||
tag.Digest = digest
|
||||
|
||||
list = append(list, tag)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// get v2 manifest of tag, returns digest, manifest,
|
||||
// manifest config and error. The manifest config contains
|
||||
// architecture, os, author, etc.
|
||||
func getV2Manifest(client *registry.Repository, tag string) (
|
||||
string, *schema2.DeserializedManifest, []byte, error) {
|
||||
digest, _, payload, err := client.PullManifest(tag, []string{schema2.MediaTypeManifest})
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
manifest := &schema2.DeserializedManifest{}
|
||||
if err = manifest.UnmarshalJSON(payload); err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
_, reader, err := client.PullBlob(manifest.Target().Digest.String())
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
config, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
return digest, manifest, config, nil
|
||||
}
|
||||
|
||||
// return tag name list for the repository
|
||||
func getSimpleTags(client *registry.Repository) ([]string, error) {
|
||||
tags := []string{}
|
||||
|
||||
ts, err := client.ListTag()
|
||||
@ -542,3 +616,22 @@ func (ra *RepositoryAPI) GetSignatures() {
|
||||
ra.Data["json"] = targets
|
||||
ra.ServeJSON()
|
||||
}
|
||||
|
||||
func getSignatures(repository, username string) (map[string]*notary.Target, error) {
|
||||
targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(),
|
||||
username, repository)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signatures := map[string]*notary.Target{}
|
||||
for _, tgt := range targets {
|
||||
digest, err := notary.DigestFromTarget(tgt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signatures[digest] = &tgt
|
||||
}
|
||||
|
||||
return signatures, nil
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ func TestGetReposTags(t *testing.T) {
|
||||
assert.Equal(int(200), code, "httpStatusCode should be 200")
|
||||
if tg, ok := tags.([]tagResp); 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")
|
||||
assert.Equal(tg[0].Name, "latest", "the tag should be latest")
|
||||
} else {
|
||||
t.Error("unexpected response")
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ func getTags(repository string) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags, err := listTag(client)
|
||||
tags, err := getSimpleTags(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user