mirror of https://github.com/goharbor/harbor.git
189 lines
6.0 KiB
Go
189 lines
6.0 KiB
Go
// Copyright Project Harbor Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package artifact
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/goharbor/harbor/src/controller/artifact/processor/wasm"
|
|
|
|
"github.com/goharbor/harbor/src/lib/q"
|
|
"github.com/goharbor/harbor/src/pkg"
|
|
|
|
"github.com/docker/distribution/manifest/manifestlist"
|
|
"github.com/docker/distribution/manifest/schema1"
|
|
"github.com/docker/distribution/manifest/schema2"
|
|
"github.com/goharbor/harbor/src/controller/artifact/processor"
|
|
"github.com/goharbor/harbor/src/lib/log"
|
|
"github.com/goharbor/harbor/src/pkg/artifact"
|
|
"github.com/goharbor/harbor/src/pkg/blob"
|
|
"github.com/goharbor/harbor/src/pkg/registry"
|
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
)
|
|
|
|
// Abstractor abstracts the metadata of artifact
|
|
type Abstractor interface {
|
|
// AbstractMetadata abstracts the metadata for the specific artifact type into the artifact model,
|
|
AbstractMetadata(ctx context.Context, artifact *artifact.Artifact) error
|
|
}
|
|
|
|
// NewAbstractor creates a new abstractor
|
|
func NewAbstractor() Abstractor {
|
|
return &abstractor{
|
|
artMgr: pkg.ArtifactMgr,
|
|
blobMgr: blob.Mgr,
|
|
regCli: registry.Cli,
|
|
}
|
|
}
|
|
|
|
type abstractor struct {
|
|
artMgr artifact.Manager
|
|
blobMgr blob.Manager
|
|
regCli registry.Client
|
|
}
|
|
|
|
func (a *abstractor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact) error {
|
|
// read manifest content
|
|
manifest, _, err := a.regCli.PullManifest(artifact.RepositoryName, artifact.Digest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
manifestMediaType, content, err := manifest.Payload()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
artifact.ManifestMediaType = manifestMediaType
|
|
|
|
switch artifact.ManifestMediaType {
|
|
case "", "application/json", schema1.MediaTypeSignedManifest:
|
|
if err := a.abstractManifestV1Metadata(ctx, artifact, content); err != nil {
|
|
return err
|
|
}
|
|
case v1.MediaTypeImageManifest, schema2.MediaTypeManifest:
|
|
if err = a.abstractManifestV2Metadata(artifact, content); err != nil {
|
|
return err
|
|
}
|
|
case v1.MediaTypeImageIndex, manifestlist.MediaTypeManifestList:
|
|
if err = a.abstractIndexMetadata(ctx, artifact, content); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("unsupported manifest media type: %s", artifact.ManifestMediaType)
|
|
}
|
|
return processor.Get(artifact.MediaType).AbstractMetadata(ctx, artifact, content)
|
|
}
|
|
|
|
// the artifact is enveloped by docker manifest v1
|
|
func (a *abstractor) abstractManifestV1Metadata(ctx context.Context, artifact *artifact.Artifact, content []byte) error {
|
|
// unify the media type of v1 manifest to "schema1.MediaTypeSignedManifest"
|
|
artifact.ManifestMediaType = schema1.MediaTypeSignedManifest
|
|
// as no config layer in the docker v1 manifest, use the "schema1.MediaTypeSignedManifest"
|
|
// as the media type of artifact
|
|
artifact.MediaType = schema1.MediaTypeSignedManifest
|
|
|
|
manifest := &schema1.Manifest{}
|
|
if err := json.Unmarshal(content, manifest); err != nil {
|
|
return err
|
|
}
|
|
|
|
var ol q.OrList
|
|
for _, fsLayer := range manifest.FSLayers {
|
|
ol.Values = append(ol.Values, fsLayer.BlobSum.String())
|
|
}
|
|
|
|
// there is no layer size in v1 manifest, compute the artifact size from the blobs
|
|
blobs, err := a.blobMgr.List(ctx, q.New(q.KeyWords{"digest": &ol}))
|
|
if err != nil {
|
|
log.G(ctx).Errorf("failed to get blobs of the artifact %s, error %v", artifact.Digest, err)
|
|
return err
|
|
}
|
|
|
|
artifact.Size = int64(len(content))
|
|
for _, blob := range blobs {
|
|
artifact.Size += blob.Size
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// the artifact is enveloped by OCI manifest or docker manifest v2
|
|
func (a *abstractor) abstractManifestV2Metadata(artifact *artifact.Artifact, content []byte) error {
|
|
manifest := &v1.Manifest{}
|
|
if err := json.Unmarshal(content, manifest); err != nil {
|
|
return err
|
|
}
|
|
// use the "manifest.config.mediatype" as the media type of the artifact
|
|
artifact.MediaType = manifest.Config.MediaType
|
|
|
|
if manifest.Annotations[wasm.AnnotationVariantKey] == wasm.AnnotationVariantValue || manifest.Annotations[wasm.AnnotationHandlerKey] == wasm.AnnotationHandlerValue {
|
|
artifact.MediaType = wasm.MediaType
|
|
}
|
|
|
|
// set size
|
|
artifact.Size = int64(len(content)) + manifest.Config.Size
|
|
for _, layer := range manifest.Layers {
|
|
artifact.Size += layer.Size
|
|
}
|
|
// set annotations
|
|
artifact.Annotations = manifest.Annotations
|
|
return nil
|
|
}
|
|
|
|
// the artifact is enveloped by OCI index or docker manifest list
|
|
func (a *abstractor) abstractIndexMetadata(ctx context.Context, art *artifact.Artifact, content []byte) error {
|
|
// the identity of index is still in progress, we use the manifest mediaType
|
|
// as the media type of artifact
|
|
art.MediaType = art.ManifestMediaType
|
|
|
|
index := &v1.Index{}
|
|
if err := json.Unmarshal(content, index); err != nil {
|
|
return err
|
|
}
|
|
|
|
// set annotations
|
|
art.Annotations = index.Annotations
|
|
|
|
art.Size += int64(len(content))
|
|
// populate the referenced artifacts
|
|
for _, mani := range index.Manifests {
|
|
digest := mani.Digest.String()
|
|
// make sure the child artifact exist
|
|
ar, err := a.artMgr.GetByDigest(ctx, art.RepositoryName, digest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
art.Size += ar.Size
|
|
art.References = append(art.References, &artifact.Reference{
|
|
ChildID: ar.ID,
|
|
ChildDigest: digest,
|
|
Platform: mani.Platform,
|
|
URLs: mani.URLs,
|
|
Annotations: mani.Annotations,
|
|
})
|
|
}
|
|
|
|
// Currently, CNAB put its media type inside the annotations
|
|
// try to parse the artifact media type from the annotations
|
|
if art.Annotations != nil {
|
|
mediaType := art.Annotations["org.opencontainers.artifactType"]
|
|
if len(mediaType) > 0 {
|
|
art.MediaType = mediaType
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|