Restructure the packages of artifact

1. Introduce a new interface Processor to replace Abstractor and Descriptor
2. Provide the base processors for manifest and index to reduce the duplicate code
3. Move the child artifacts checking out of processor

Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
Wenkai Yin 2020-03-11 16:20:43 +08:00
parent 25b5c3796b
commit 289f04d301
38 changed files with 1258 additions and 1482 deletions

View File

@ -0,0 +1,145 @@
// 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/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/pkg/artifact"
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: artifact.Mgr,
blobFetcher: blob.Fcher,
}
}
type abstractor struct {
artMgr artifact.Manager
blobFetcher blob.Fetcher
}
func (a *abstractor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact) error {
// read manifest content
manifestMediaType, content, err := a.blobFetcher.FetchManifest(artifact.RepositoryName, artifact.Digest)
if err != nil {
return err
}
artifact.ManifestMediaType = manifestMediaType
switch artifact.ManifestMediaType {
case "", "application/json", schema1.MediaTypeSignedManifest:
a.abstractManifestV1Metadata(artifact)
case v1.MediaTypeImageManifest, schema2.MediaTypeManifest:
if err = a.abstractManifestV2Metadata(content, artifact); err != nil {
return err
}
case v1.MediaTypeImageIndex, manifestlist.MediaTypeManifestList:
if err = a.abstractIndexMetadata(ctx, content, artifact); err != nil {
return err
}
default:
return fmt.Errorf("unsupported manifest media type: %s", artifact.ManifestMediaType)
}
return processor.Get(artifact.MediaType).AbstractMetadata(ctx, content, artifact)
}
// the artifact is enveloped by docker manifest v1
func (a *abstractor) abstractManifestV1Metadata(artifact *artifact.Artifact) {
// 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
// there is no layer size in v1 manifest, doesn't set the artifact size
}
// the artifact is enveloped by OCI manifest or docker manifest v2
func (a *abstractor) abstractManifestV2Metadata(content []byte, artifact *artifact.Artifact) 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
// 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, content []byte, art *artifact.Artifact) 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
}

View File

@ -1,129 +0,0 @@
// 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 abstractor
import (
"context"
"encoding/json"
"fmt"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/api/artifact/abstractor/blob"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/opencontainers/image-spec/specs-go/v1"
)
// Abstractor abstracts the specific information for different types of artifacts
type Abstractor interface {
// AbstractMetadata abstracts the metadata for the specific artifact type into the artifact model,
// the metadata can be got from the manifest or other layers referenced by the manifest.
AbstractMetadata(ctx context.Context, artifact *artifact.Artifact) error
// AbstractAddition abstracts the addition of the artifact.
// The additions are different for different artifacts:
// build history for image; values.yaml, readme and dependencies for chart, etc
AbstractAddition(ctx context.Context, artifact *artifact.Artifact, additionType string) (addition *resolver.Addition, err error)
}
// NewAbstractor returns an instance of the default abstractor
func NewAbstractor() Abstractor {
return &abstractor{
blobFetcher: blob.Fcher,
}
}
type abstractor struct {
blobFetcher blob.Fetcher
}
// TODO add white list for supported artifact type
func (a *abstractor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact) error {
// read manifest content
manifestMediaType, content, err := a.blobFetcher.FetchManifest(artifact.RepositoryName, artifact.Digest)
if err != nil {
return err
}
artifact.ManifestMediaType = manifestMediaType
switch artifact.ManifestMediaType {
// docker manifest v1
case "", "application/json", schema1.MediaTypeSignedManifest:
// 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
// there is no layer size in v1 manifest, doesn't set the artifact size
// OCI manifest/docker manifest v2
case v1.MediaTypeImageManifest, schema2.MediaTypeManifest:
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
// 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
// OCI index/docker manifest list
case v1.MediaTypeImageIndex, manifestlist.MediaTypeManifestList:
// the identity of index is still in progress, we use the manifest mediaType
// as the media type of artifact
artifact.MediaType = artifact.ManifestMediaType
index := &v1.Index{}
if err := json.Unmarshal(content, index); err != nil {
return err
}
// the size for image index is meaningless, doesn't set it for image index
// but it is useful for CNAB or other artifacts, set it when needed
// set annotations
artifact.Annotations = index.Annotations
// Currently, CNAB put its media type inside the annotations
// try to parse the artifact media type from the annotations
if artifact.Annotations != nil {
mediaType := artifact.Annotations["org.opencontainers.artifactType"]
if len(mediaType) > 0 {
artifact.MediaType = mediaType
}
}
default:
return fmt.Errorf("unsupported manifest media type: %s", artifact.ManifestMediaType)
}
resolver := resolver.Get(artifact.MediaType)
if resolver != nil {
return resolver.ResolveMetadata(ctx, content, artifact)
}
return nil
}
func (a *abstractor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*resolver.Addition, error) {
resolver := resolver.Get(artifact.MediaType)
if resolver == nil {
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("the resolver for artifact %s not found, cannot get the addition", artifact.Type)
}
return resolver.ResolveAddition(ctx, artifact, addition)
}

View File

@ -1,298 +0,0 @@
// 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 abstractor
import (
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/api/artifact/abstractor/blob"
tresolver "github.com/goharbor/harbor/src/testing/api/artifact/abstractor/resolver"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/suite"
"testing"
)
var (
fakeArtifactType = "FAKE_ARTIFACT"
v1Manifest = `{
"name": "hello-world",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
},
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
},
{
"blobSum": "sha256:cc8567d70002e957612902a8e985ea129d831ebe04057d88fb644857caa45d11"
},
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
}
],
"history": [
{
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
],
"schemaVersion": 1,
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "OD6I:6DRK:JXEJ:KBM4:255X:NSAA:MUSF:E4VM:ZI6W:CUN2:L4Z6:LSF4",
"kty": "EC",
"x": "3gAwX48IQ5oaYQAYSxor6rYYc_6yjuLCjtQ9LUakg4A",
"y": "t72ge6kIA1XOjqjVoEOiPPAURltJFBMGDSQvEGVB010"
},
"alg": "ES256"
},
"signature": "XREm0L8WNn27Ga_iE_vRnTxVMhhYY0Zst_FfkKopg6gWSoTOZTuW4rK0fg_IqnKkEKlbD83tD46LKEGi5aIVFg",
"protected": "eyJmb3JtYXRMZW5ndGgiOjY2MjgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wNC0wOFQxODo1Mjo1OVoifQ"
}
]
}`
v2Manifest = `{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1510,
"digest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 977,
"digest": "sha256:1b930d010525941c1d56ec53b97bd057a67ae1865eebf042686d2a2d18271ced"
}
],
"annotations": {
"com.example.key1": "value1"
}
}`
v2Config = `{
"architecture": "amd64",
"config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/hello"
],
"ArgsEscaped": true,
"Image": "sha256:a6d1aaad8ca65655449a26146699fe9d61240071f6992975be7e720f1cd42440",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"container": "8e2caa5a514bb6d8b4f2a2553e9067498d261a0fd83a96aeaaf303943dff6ff9",
"container_config": {
"Hostname": "8e2caa5a514b",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/hello\"]"
],
"ArgsEscaped": true,
"Image": "sha256:a6d1aaad8ca65655449a26146699fe9d61240071f6992975be7e720f1cd42440",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
}
},
"created": "2019-01-01T01:29:27.650294696Z",
"docker_version": "18.06.1-ce",
"history": [
{
"created": "2019-01-01T01:29:27.416803627Z",
"created_by": "/bin/sh -c #(nop) COPY file:f77490f70ce51da25bd21bfc30cb5e1a24b2b65eb37d4af0c327ddc24f0986a6 in / "
},
{
"created": "2019-01-01T01:29:27.650294696Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/hello\"]",
"empty_layer": true
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:af0b15c8625bb1938f1d7b17081031f649fd14e6b233688eea3c5483994a66a3"
]
}
}`
index = `{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7143,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
],
"annotations": {
"com.example.key1": "value1"
}
}`
)
type abstractorTestSuite struct {
suite.Suite
abstractor Abstractor
fetcher *blob.FakeFetcher
resolver *tresolver.FakeResolver
}
func (a *abstractorTestSuite) SetupTest() {
a.fetcher = &blob.FakeFetcher{}
a.resolver = &tresolver.FakeResolver{}
a.abstractor = &abstractor{
blobFetcher: a.fetcher,
}
}
// docker manifest v1
func (a *abstractorTestSuite) TestAbstractMetadataOfV1Manifest() {
resolver.Register(a.resolver, schema1.MediaTypeSignedManifest)
a.fetcher.On("FetchManifest").Return(schema1.MediaTypeSignedManifest, []byte(v1Manifest), nil)
a.resolver.On("ArtifactType").Return(fakeArtifactType)
a.resolver.On("ResolveMetadata").Return(nil)
artifact := &artifact.Artifact{
ID: 1,
}
err := a.abstractor.AbstractMetadata(nil, artifact)
a.Require().Nil(err)
a.Assert().Equal(int64(1), artifact.ID)
a.Assert().Equal(schema1.MediaTypeSignedManifest, artifact.ManifestMediaType)
a.Assert().Equal(schema1.MediaTypeSignedManifest, artifact.MediaType)
a.Assert().Equal(int64(0), artifact.Size)
}
// docker manifest v2
func (a *abstractorTestSuite) TestAbstractMetadataOfV2Manifest() {
resolver.Register(a.resolver, schema2.MediaTypeImageConfig)
a.fetcher.On("FetchManifest").Return(schema2.MediaTypeManifest, []byte(v2Manifest), nil)
a.resolver.On("ArtifactType").Return(fakeArtifactType)
a.resolver.On("ResolveMetadata").Return(nil)
artifact := &artifact.Artifact{
ID: 1,
}
err := a.abstractor.AbstractMetadata(nil, artifact)
a.Require().Nil(err)
a.Assert().Equal(int64(1), artifact.ID)
a.Assert().Equal(schema2.MediaTypeManifest, artifact.ManifestMediaType)
a.Assert().Equal(schema2.MediaTypeImageConfig, artifact.MediaType)
a.Assert().Equal(int64(3043), artifact.Size)
}
// OCI index
func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() {
resolver.Register(a.resolver, v1.MediaTypeImageIndex)
a.fetcher.On("FetchManifest").Return(v1.MediaTypeImageIndex, []byte(index), nil)
a.resolver.On("ArtifactType").Return(fakeArtifactType)
a.resolver.On("ResolveMetadata").Return(nil)
artifact := &artifact.Artifact{
ID: 1,
}
err := a.abstractor.AbstractMetadata(nil, artifact)
a.Require().Nil(err)
a.Assert().Equal(int64(1), artifact.ID)
a.Assert().Equal(v1.MediaTypeImageIndex, artifact.ManifestMediaType)
a.Assert().Equal(v1.MediaTypeImageIndex, artifact.MediaType)
a.Assert().Equal(int64(0), artifact.Size)
a.Assert().Equal("value1", artifact.Annotations["com.example.key1"])
}
// OCI index
func (a *abstractorTestSuite) TestAbstractMetadataOfUnsupported() {
a.fetcher.On("FetchManifest").Return("unsupported-manifest", []byte{}, nil)
artifact := &artifact.Artifact{
ID: 1,
}
err := a.abstractor.AbstractMetadata(nil, artifact)
a.Require().NotNil(err)
}
func (a *abstractorTestSuite) TestAbstractAddition() {
resolver.Register(a.resolver, v1.MediaTypeImageConfig)
// cannot get the resolver
art := &artifact.Artifact{
MediaType: "unknown",
}
_, err := a.abstractor.AbstractAddition(nil, art, "addition")
a.True(ierror.IsErr(err, ierror.BadRequestCode))
// get the resolver
art = &artifact.Artifact{
MediaType: v1.MediaTypeImageConfig,
}
a.resolver.On("ResolveAddition").Return(nil, nil)
_, err = a.abstractor.AbstractAddition(nil, art, "addition")
a.Require().Nil(err)
}
func TestAbstractorTestSuite(t *testing.T) {
suite.Run(t, &abstractorTestSuite{})
}

View File

@ -1,126 +0,0 @@
// 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 cnab
import (
"context"
"encoding/json"
"github.com/goharbor/harbor/src/api/artifact/abstractor/blob"
resolv "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/descriptor"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
// const definitions
const (
ArtifactTypeCNAB = "CNAB"
mediaType = "application/vnd.cnab.manifest.v1"
)
func init() {
resolver := &resolver{
argMgr: artifact.Mgr,
blobFetcher: blob.Fcher,
}
if err := resolv.Register(resolver, mediaType); err != nil {
log.Errorf("failed to register resolver for media type %s: %v", mediaType, err)
return
}
if err := descriptor.Register(resolver, mediaType); err != nil {
log.Errorf("failed to register descriptor for media type %s: %v", mediaType, err)
return
}
}
type resolver struct {
argMgr artifact.Manager
blobFetcher blob.Fetcher
}
func (r *resolver) ResolveMetadata(ctx context.Context, manifest []byte, art *artifact.Artifact) error {
index := &v1.Index{}
if err := json.Unmarshal(manifest, index); err != nil {
return err
}
cfgManiDgt := ""
// populate the referenced artifacts
for _, mani := range index.Manifests {
digest := mani.Digest.String()
// make sure the child artifact exist
ar, err := r.argMgr.GetByDigest(ctx, art.RepositoryName, digest)
if err != nil {
return err
}
art.References = append(art.References, &artifact.Reference{
ChildID: ar.ID,
ChildDigest: digest,
Platform: mani.Platform,
URLs: mani.URLs,
Annotations: mani.Annotations,
})
// try to get the digest of the manifest that the config layer is referenced by
if mani.Annotations != nil &&
mani.Annotations["io.cnab.manifest.type"] == "config" {
cfgManiDgt = mani.Digest.String()
}
}
if len(cfgManiDgt) == 0 {
return nil
}
// resolve the config of CNAB
// get the manifest that the config layer is referenced by
_, cfgMani, err := r.blobFetcher.FetchManifest(art.RepositoryName, cfgManiDgt)
if err != nil {
return err
}
m := &v1.Manifest{}
if err := json.Unmarshal(cfgMani, m); err != nil {
return err
}
cfgDgt := m.Config.Digest.String()
// get the config layer
cfg, err := r.blobFetcher.FetchLayer(art.RepositoryName, cfgDgt)
if err != nil {
return err
}
metadata := map[string]interface{}{}
if err := json.Unmarshal(cfg, &metadata); err != nil {
return err
}
if art.ExtraAttrs == nil {
art.ExtraAttrs = map[string]interface{}{}
}
for k, v := range metadata {
art.ExtraAttrs[k] = v
}
return nil
}
func (r *resolver) ResolveAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*resolv.Addition, error) {
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("addition %s isn't supported for %s", addition, ArtifactTypeCNAB)
}
func (r *resolver) GetArtifactType() string {
return ArtifactTypeCNAB
}
func (r *resolver) ListAdditionTypes() []string {
return nil
}

View File

@ -1,138 +0,0 @@
// 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 cnab
import (
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/api/artifact/abstractor/blob"
testingartifact "github.com/goharbor/harbor/src/testing/pkg/artifact"
"github.com/stretchr/testify/suite"
"testing"
)
type resolverTestSuite struct {
suite.Suite
resolver *resolver
artMgr *testingartifact.FakeManager
blobFetcher *blob.FakeFetcher
}
func (r *resolverTestSuite) SetupTest() {
r.artMgr = &testingartifact.FakeManager{}
r.blobFetcher = &blob.FakeFetcher{}
r.resolver = &resolver{
argMgr: r.artMgr,
blobFetcher: r.blobFetcher,
}
}
func (r *resolverTestSuite) TestResolveMetadata() {
index := `{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:b9616da7500f8c7c9a5e8d915714cd02d11bcc71ff5b4fd190bb77b1355c8549",
"size": 193,
"annotations": {
"io.cnab.manifest.type": "config"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:a59a4e74d9cc89e4e75dfb2cc7ea5c108e4236ba6231b53081a9e2506d1197b6",
"size": 942,
"annotations": {
"io.cnab.manifest.type": "invocation"
}
}
],
"annotations": {
"io.cnab.keywords": "[\"helloworld\",\"cnab\",\"tutorial\"]",
"io.cnab.runtime_version": "v1.0.0",
"org.opencontainers.artifactType": "application/vnd.cnab.manifest.v1",
"org.opencontainers.image.authors": "[{\"name\":\"Jane Doe\",\"email\":\"jane.doe@example.com\",\"url\":\"https://example.com\"}]",
"org.opencontainers.image.description": "A short description of your bundle",
"org.opencontainers.image.title": "helloworld",
"org.opencontainers.image.version": "0.1.1"
}
}`
manifest := `{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b",
"size": 498
},
"layers": null
}`
config := `{
"description": "A short description of your bundle",
"invocationImages": [
{
"contentDigest": "sha256:a59a4e74d9cc89e4e75dfb2cc7ea5c108e4236ba6231b53081a9e2506d1197b6",
"image": "cnab/helloworld:0.1.1",
"imageType": "docker",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 942
}
],
"keywords": [
"helloworld",
"cnab",
"tutorial"
],
"maintainers": [
{
"email": "jane.doe@example.com",
"name": "Jane Doe",
"url": "https://example.com"
}
],
"name": "helloworld",
"schemaVersion": "v1.0.0",
"version": "0.1.1"
}`
art := &artifact.Artifact{}
r.artMgr.On("GetByDigest").Return(&artifact.Artifact{ID: 1}, nil)
r.blobFetcher.On("FetchManifest").Return("", []byte(manifest), nil)
r.blobFetcher.On("FetchLayer").Return([]byte(config), nil)
err := r.resolver.ResolveMetadata(nil, []byte(index), art)
r.Require().Nil(err)
r.Len(art.References, 2)
r.Equal("0.1.1", art.ExtraAttrs["version"].(string))
r.Equal("helloworld", art.ExtraAttrs["name"].(string))
}
func (r *resolverTestSuite) TestResolveAddition() {
_, err := r.resolver.ResolveAddition(nil, nil, "")
r.Require().NotNil(err)
r.True(ierror.IsErr(err, ierror.BadRequestCode))
}
func (r *resolverTestSuite) TestGetArtifactType() {
r.Assert().Equal(ArtifactTypeCNAB, r.resolver.GetArtifactType())
}
func (r *resolverTestSuite) TestListAdditionTypes() {
r.Nil(r.resolver.ListAdditionTypes())
}
func TestResolverTestSuite(t *testing.T) {
suite.Run(t, &resolverTestSuite{})
}

View File

@ -1,87 +0,0 @@
// 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 image
import (
"context"
"encoding/json"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/descriptor"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
func init() {
rslver := &indexResolver{
artMgr: artifact.Mgr,
}
mediaTypes := []string{
v1.MediaTypeImageIndex,
manifestlist.MediaTypeManifestList,
}
if err := resolver.Register(rslver, mediaTypes...); err != nil {
log.Errorf("failed to register resolver for media type %v: %v", mediaTypes, err)
return
}
if err := descriptor.Register(rslver, mediaTypes...); err != nil {
log.Errorf("failed to register descriptor for media type %v: %v", mediaTypes, err)
return
}
}
// indexResolver resolves artifact with OCI index and docker manifest list
type indexResolver struct {
artMgr artifact.Manager
}
func (i *indexResolver) ResolveMetadata(ctx context.Context, manifest []byte, art *artifact.Artifact) error {
index := &v1.Index{}
if err := json.Unmarshal(manifest, index); err != nil {
return err
}
// populate the referenced artifacts
for _, mani := range index.Manifests {
digest := mani.Digest.String()
// make sure the child artifact exist
ar, err := i.artMgr.GetByDigest(ctx, art.RepositoryName, digest)
if err != nil {
return err
}
art.References = append(art.References, &artifact.Reference{
ChildID: ar.ID,
ChildDigest: digest,
Platform: mani.Platform,
URLs: mani.URLs,
Annotations: mani.Annotations,
})
}
return nil
}
func (i *indexResolver) ResolveAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*resolver.Addition, error) {
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("addition %s isn't supported for %s(index)", addition, ArtifactTypeImage)
}
func (i *indexResolver) GetArtifactType() string {
return ArtifactTypeImage
}
func (i *indexResolver) ListAdditionTypes() []string {
return nil
}

View File

@ -1,150 +0,0 @@
// 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 image
import (
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
arttesting "github.com/goharbor/harbor/src/testing/pkg/artifact"
"github.com/stretchr/testify/suite"
"testing"
)
type indexResolverTestSuite struct {
suite.Suite
resolver *indexResolver
artMgr *arttesting.FakeManager
}
func (i *indexResolverTestSuite) SetupTest() {
i.artMgr = &arttesting.FakeManager{}
i.resolver = &indexResolver{
artMgr: i.artMgr,
}
}
func (i *indexResolverTestSuite) TestResolveMetadata() {
manifest := `{
"manifests": [
{
"digest": "sha256:92c7f9c92844bbbb5d0a101b22f7c2a7949e40f8ea90c8b3bc396879d95e899a",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "amd64",
"os": "linux"
},
"size": 524
},
{
"digest": "sha256:e5785cb0c62cebbed4965129bae371f0589cadd6d84798fb58c2c5f9e237efd9",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v5"
},
"size": 525
},
{
"digest": "sha256:50b8560ad574c779908da71f7ce370c0a2471c098d44d1c8f6b513c5a55eeeb1",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
},
"size": 525
},
{
"digest": "sha256:963612c5503f3f1674f315c67089dee577d8cc6afc18565e0b4183ae355fb343",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
},
"size": 525
},
{
"digest": "sha256:85dc5fbe16214366748ebe9d7cc73bc42d61d19d61fe05f01e317d278c2287ed",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "386",
"os": "linux"
},
"size": 527
},
{
"digest": "sha256:8aaea2a718a29334caeaf225716284ce29dc17418edba98dbe6dafea5afcda16",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "ppc64le",
"os": "linux"
},
"size": 525
},
{
"digest": "sha256:577ad4331d4fac91807308da99ecc107dcc6b2254bc4c7166325fd01113bea2a",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "s390x",
"os": "linux"
},
"size": 525
},
{
"digest": "sha256:351e40a9ab7ca6818dfbf9c967d1dd15599438edc41189e3d4d87eeffba5b8bf",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.17763.914"
},
"size": 1124
}
],
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"schemaVersion": 2
}`
art := &artifact.Artifact{}
i.artMgr.On("GetByDigest").Return(&artifact.Artifact{
ID: 1,
}, nil)
err := i.resolver.ResolveMetadata(nil, []byte(manifest), art)
i.Require().Nil(err)
i.artMgr.AssertExpectations(i.T())
i.Require().Len(art.References, 8)
i.Assert().Equal(int64(1), art.References[0].ChildID)
i.Assert().Equal("amd64", art.References[0].Platform.Architecture)
}
func (i *indexResolverTestSuite) TestResolveAddition() {
_, err := i.resolver.ResolveAddition(nil, nil, AdditionTypeBuildHistory)
i.True(ierror.IsErr(err, ierror.BadRequestCode))
}
func (i *indexResolverTestSuite) TestGetArtifactType() {
i.Assert().Equal(ArtifactTypeImage, i.resolver.GetArtifactType())
}
func (i *indexResolverTestSuite) TestListAdditionTypes() {
additions := i.resolver.ListAdditionTypes()
i.Len(additions, 0)
}
func TestIndexResolverTestSuite(t *testing.T) {
suite.Run(t, &indexResolverTestSuite{})
}

View File

@ -1,62 +0,0 @@
// 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 resolver
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/artifact"
)
var (
registry = map[string]Resolver{}
)
// Resolver resolves the detail information for a specific kind of artifact
type Resolver interface {
// ResolveMetadata receives the manifest content, resolves the metadata
// from the manifest or the layers referenced by the manifest, and populates
// the metadata into the artifact
ResolveMetadata(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error
// ResolveAddition returns the addition of the artifact.
// The additions are different for different artifacts:
// build history for image; values.yaml, readme and dependencies for chart, etc
ResolveAddition(ctx context.Context, artifact *artifact.Artifact, additionType string) (addition *Addition, err error)
}
// Register resolver, one resolver can handle multiple media types for one kind of artifact
func Register(resolver Resolver, mediaTypes ...string) error {
for _, mediaType := range mediaTypes {
_, exist := registry[mediaType]
if exist {
return fmt.Errorf("resolver to handle media type %s already exists", mediaType)
}
registry[mediaType] = resolver
log.Infof("resolver to handle media type %s registered", mediaType)
}
return nil
}
// Get the resolver according to the media type
func Get(mediaType string) Resolver {
return registry[mediaType]
}
// Addition defines the specific addition of different artifacts: build history for image, values.yaml for chart, etc
type Addition struct {
Content []byte // the content of the addition
ContentType string // the content type of the addition, returned as "Content-Type" header in API
}

View File

@ -1,69 +0,0 @@
// 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 resolver
import (
"context"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/stretchr/testify/suite"
"testing"
)
type fakeResolver struct{}
func (f *fakeResolver) ResolveMetadata(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error {
return nil
}
func (f *fakeResolver) ResolveAddition(ctx context.Context, artifact *artifact.Artifact, additionType string) (*Addition, error) {
return nil, nil
}
type resolverTestSuite struct {
suite.Suite
}
func (r *resolverTestSuite) SetupTest() {
registry = map[string]Resolver{}
}
func (r *resolverTestSuite) TestRegister() {
// registry a resolver
mediaType := "fake_media_type"
err := Register(nil, mediaType)
r.Assert().Nil(err)
// try to register a resolver for the existing media type
err = Register(nil, mediaType)
r.Assert().NotNil(err)
}
func (r *resolverTestSuite) TestGet() {
// registry a resolver
mediaType := "fake_media_type"
err := Register(&fakeResolver{}, mediaType)
r.Assert().Nil(err)
// get the resolver
resolver := Get(mediaType)
r.Assert().NotNil(resolver)
// get the not exist resolver
resolver = Get("not_existing_media_type")
r.Assert().Nil(resolver)
}
func TestResolverTestSuite(t *testing.T) {
suite.Run(t, &resolverTestSuite{})
}

View File

@ -0,0 +1,203 @@
// 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 (
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob"
tart "github.com/goharbor/harbor/src/testing/pkg/artifact"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/suite"
"testing"
)
var (
v1Manifest = `{
"name": "hello-world",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
},
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
},
{
"blobSum": "sha256:cc8567d70002e957612902a8e985ea129d831ebe04057d88fb644857caa45d11"
},
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
}
],
"history": [
{
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
}
],
"schemaVersion": 1,
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "OD6I:6DRK:JXEJ:KBM4:255X:NSAA:MUSF:E4VM:ZI6W:CUN2:L4Z6:LSF4",
"kty": "EC",
"x": "3gAwX48IQ5oaYQAYSxor6rYYc_6yjuLCjtQ9LUakg4A",
"y": "t72ge6kIA1XOjqjVoEOiPPAURltJFBMGDSQvEGVB010"
},
"alg": "ES256"
},
"signature": "XREm0L8WNn27Ga_iE_vRnTxVMhhYY0Zst_FfkKopg6gWSoTOZTuW4rK0fg_IqnKkEKlbD83tD46LKEGi5aIVFg",
"protected": "eyJmb3JtYXRMZW5ndGgiOjY2MjgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wNC0wOFQxODo1Mjo1OVoifQ"
}
]
}`
v2Manifest = `{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1510,
"digest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 977,
"digest": "sha256:1b930d010525941c1d56ec53b97bd057a67ae1865eebf042686d2a2d18271ced"
}
],
"annotations": {
"com.example.key1": "value1"
}
}`
index = `{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7143,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
],
"annotations": {
"com.example.key1": "value1"
}
}`
)
type abstractorTestSuite struct {
suite.Suite
argMgr *tart.FakeManager
fetcher *blob.FakeFetcher
abstractor *abstractor
}
func (a *abstractorTestSuite) SetupTest() {
a.fetcher = &blob.FakeFetcher{}
a.argMgr = &tart.FakeManager{}
a.abstractor = &abstractor{
artMgr: a.argMgr,
blobFetcher: a.fetcher,
}
// clear all registered processors
processor.Registry = map[string]processor.Processor{}
}
// docker manifest v1
func (a *abstractorTestSuite) TestAbstractMetadataOfV1Manifest() {
a.fetcher.On("FetchManifest").Return(schema1.MediaTypeSignedManifest, []byte(v1Manifest), nil)
artifact := &artifact.Artifact{
ID: 1,
}
err := a.abstractor.AbstractMetadata(nil, artifact)
a.Require().Nil(err)
a.Assert().Equal(int64(1), artifact.ID)
a.Assert().Equal(schema1.MediaTypeSignedManifest, artifact.ManifestMediaType)
a.Assert().Equal(schema1.MediaTypeSignedManifest, artifact.MediaType)
a.Assert().Equal(int64(0), artifact.Size)
}
// docker manifest v2
func (a *abstractorTestSuite) TestAbstractMetadataOfV2Manifest() {
a.fetcher.On("FetchManifest").Return(schema2.MediaTypeManifest, []byte(v2Manifest), nil)
artifact := &artifact.Artifact{
ID: 1,
}
err := a.abstractor.AbstractMetadata(nil, artifact)
a.Require().Nil(err)
a.Assert().Equal(int64(1), artifact.ID)
a.Assert().Equal(schema2.MediaTypeManifest, artifact.ManifestMediaType)
a.Assert().Equal(schema2.MediaTypeImageConfig, artifact.MediaType)
a.Assert().Equal(int64(3043), artifact.Size)
a.Require().Len(artifact.Annotations, 1)
a.Equal("value1", artifact.Annotations["com.example.key1"])
}
// OCI index
func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() {
a.fetcher.On("FetchManifest").Return(v1.MediaTypeImageIndex, []byte(index), nil)
a.argMgr.On("GetByDigest").Return(&artifact.Artifact{
ID: 2,
Size: 10,
}, nil)
artifact := &artifact.Artifact{
ID: 1,
}
err := a.abstractor.AbstractMetadata(nil, artifact)
a.Require().Nil(err)
a.Assert().Equal(int64(1), artifact.ID)
a.Assert().Equal(v1.MediaTypeImageIndex, artifact.ManifestMediaType)
a.Assert().Equal(v1.MediaTypeImageIndex, artifact.MediaType)
a.Assert().Equal(int64(668), artifact.Size)
a.Require().Len(artifact.Annotations, 1)
a.Assert().Equal("value1", artifact.Annotations["com.example.key1"])
a.Len(artifact.References, 2)
}
// OCI index
func (a *abstractorTestSuite) TestAbstractMetadataOfUnsupported() {
a.fetcher.On("FetchManifest").Return("unsupported-manifest", []byte{}, nil)
artifact := &artifact.Artifact{
ID: 1,
}
err := a.abstractor.AbstractMetadata(nil, artifact)
a.Require().NotNil(err)
}
func TestAbstractorTestSuite(t *testing.T) {
suite.Run(t, &abstractorTestSuite{})
}

View File

@ -19,14 +19,8 @@ import (
"context"
"errors"
"fmt"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/event"
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
"strings"
"time"
"github.com/goharbor/harbor/src/api/artifact/abstractor"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/descriptor"
"github.com/goharbor/harbor/src/api/tag"
"github.com/goharbor/harbor/src/internal"
"github.com/goharbor/harbor/src/internal/orm"
@ -36,16 +30,19 @@ import (
"github.com/goharbor/harbor/src/pkg/immutabletag/match"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"github.com/goharbor/harbor/src/pkg/label"
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/registry"
"github.com/goharbor/harbor/src/pkg/signature"
"github.com/opencontainers/go-digest"
"strings"
"time"
// registry image resolvers
_ "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver/image"
_ "github.com/goharbor/harbor/src/api/artifact/processor/image"
// register chart resolver
_ "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver/chart"
_ "github.com/goharbor/harbor/src/api/artifact/processor/chart"
// register CNAB resolver
_ "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver/cnab"
_ "github.com/goharbor/harbor/src/api/artifact/processor/cnab"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
@ -94,7 +91,7 @@ type Controller interface {
// GetAddition returns the addition of the artifact.
// The addition is different according to the artifact type:
// build history for image; values.yaml, readme and dependencies for chart, etc
GetAddition(ctx context.Context, artifactID int64, additionType string) (addition *resolver.Addition, err error)
GetAddition(ctx context.Context, artifactID int64, additionType string) (addition *processor.Addition, err error)
// AddLabel to the specified artifact
AddLabel(ctx context.Context, artifactID int64, labelID int64) (err error)
// RemoveLabel from the specified artifact
@ -113,9 +110,9 @@ func NewController() Controller {
blobMgr: blob.Mgr,
sigMgr: signature.GetManager(),
labelMgr: label.Mgr,
abstractor: abstractor.NewAbstractor(),
immutableMtr: rule.NewRuleMatcher(),
regCli: registry.Cli,
abstractor: NewAbstractor(),
}
}
@ -129,9 +126,9 @@ type controller struct {
blobMgr blob.Manager
sigMgr signature.Manager
labelMgr label.Manager
abstractor abstractor.Abstractor
immutableMtr match.ImmutableTagMatcher
regCli registry.Client
abstractor Abstractor
}
func (c *controller) Ensure(ctx context.Context, repository, digest string, tags ...string) (bool, int64, error) {
@ -187,7 +184,7 @@ func (c *controller) ensureArtifact(ctx context.Context, repository, digest stri
}
// populate the artifact type
artifact.Type = descriptor.GetArtifactType(artifact.MediaType)
artifact.Type = processor.Get(artifact.MediaType).GetArtifactType()
// create it
// use orm.WithTransaction here to avoid the issue:
@ -473,12 +470,12 @@ func (c *controller) UpdatePullTime(ctx context.Context, artifactID int64, tagID
return c.tagCtl.Update(ctx, tag, "PullTime")
}
func (c *controller) GetAddition(ctx context.Context, artifactID int64, addition string) (*resolver.Addition, error) {
func (c *controller) GetAddition(ctx context.Context, artifactID int64, addition string) (*processor.Addition, error) {
artifact, err := c.artMgr.Get(ctx, artifactID)
if err != nil {
return nil, err
}
return c.abstractor.AbstractAddition(ctx, artifact, addition)
return processor.Get(artifact.MediaType).AbstractAddition(ctx, artifact, addition)
}
func (c *controller) AddLabel(ctx context.Context, artifactID int64, labelID int64) error {
@ -578,7 +575,7 @@ func (c *controller) populateLabels(ctx context.Context, art *Artifact) {
}
func (c *controller) populateAdditionLinks(ctx context.Context, artifact *Artifact) {
types := descriptor.ListAdditionTypes(artifact.MediaType)
types := processor.Get(artifact.MediaType).ListAdditionTypes()
if len(types) > 0 {
version := internal.GetAPIVersion(ctx)
for _, t := range types {

View File

@ -19,8 +19,6 @@ import (
"testing"
"time"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/descriptor"
"github.com/goharbor/harbor/src/api/tag"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/internal"
@ -53,26 +51,6 @@ func (f *fakeAbstractor) AbstractMetadata(ctx context.Context, artifact *artifac
args := f.Called()
return args.Error(0)
}
func (f *fakeAbstractor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, additionType string) (*resolver.Addition, error) {
args := f.Called()
var addition *resolver.Addition
if args.Get(0) != nil {
addition = args.Get(0).(*resolver.Addition)
}
return addition, args.Error(1)
}
type fakeDescriptor struct {
mock.Mock
}
func (f *fakeDescriptor) GetArtifactType() string {
return "IMAGE"
}
func (f *fakeDescriptor) ListAdditionTypes() []string {
return []string{"BUILD_HISTORY"}
}
type controllerTestSuite struct {
suite.Suite
@ -109,7 +87,6 @@ func (c *controllerTestSuite) SetupTest() {
immutableMtr: c.immutableMtr,
regCli: c.regCli,
}
descriptor.Register(&fakeDescriptor{}, "")
}
func (c *controllerTestSuite) TestAssembleArtifact() {
@ -148,11 +125,6 @@ func (c *controllerTestSuite) TestAssembleArtifact() {
c.Require().NotNil(artifact)
c.Equal(art.ID, artifact.ID)
c.Contains(artifact.Tags, tg)
c.Require().NotNil(artifact.AdditionLinks)
c.Require().NotNil(artifact.AdditionLinks["build_history"])
c.False(artifact.AdditionLinks["build_history"].Absolute)
c.Equal("/api/2.0/projects/library/repositories/hello-world/artifacts/sha256:123/additions/build_history",
artifact.AdditionLinks["build_history"].HREF)
c.Contains(artifact.Labels, lb)
// TODO check other fields of option
}
@ -496,10 +468,9 @@ func (c *controllerTestSuite) TestUpdatePullTime() {
}
func (c *controllerTestSuite) TestGetAddition() {
c.artMgr.On("Get").Return(nil, nil)
c.abstractor.On("AbstractAddition").Return(nil, nil)
c.artMgr.On("Get").Return(&artifact.Artifact{}, nil)
_, err := c.ctl.GetAddition(nil, 1, "addition")
c.Require().Nil(err)
c.Require().NotNil(err)
}
func (c *controllerTestSuite) TestAddTo() {

View File

@ -1,84 +0,0 @@
// 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 descriptor
import (
"fmt"
"github.com/goharbor/harbor/src/common/utils/log"
"regexp"
"strings"
)
// ArtifactTypeUnknown defines the type for the unknown artifacts
const ArtifactTypeUnknown = "UNKNOWN"
var (
registry = map[string]Descriptor{}
artifactTypeRegExp = regexp.MustCompile(`^application/vnd\.[^.]*\.(.*)\.config\.[^.]*\+json$`)
)
// Descriptor describes the static information for one kind of media type
type Descriptor interface {
// GetArtifactType returns the type of one kind of artifact specified by media type
GetArtifactType() string
// ListAdditionTypes returns the supported addition types of one kind of artifact specified by media type
ListAdditionTypes() []string
}
// Register descriptor, one descriptor can handle multiple media types for one kind of artifact
func Register(descriptor Descriptor, mediaTypes ...string) error {
for _, mediaType := range mediaTypes {
_, exist := registry[mediaType]
if exist {
return fmt.Errorf("descriptor to handle media type %s already exists", mediaType)
}
registry[mediaType] = descriptor
log.Infof("descriptor to handle media type %s registered", mediaType)
}
return nil
}
// Get the descriptor according to the media type
func Get(mediaType string) Descriptor {
return registry[mediaType]
}
// GetArtifactType gets the artifact type according to the media type
func GetArtifactType(mediaType string) string {
descriptor := Get(mediaType)
if descriptor != nil {
return descriptor.GetArtifactType()
}
// if got no descriptor, try to parse the artifact type based on the media type
return parseArtifactType(mediaType)
}
// ListAdditionTypes lists the supported addition types according to the media type
func ListAdditionTypes(mediaType string) []string {
descriptor := Get(mediaType)
if descriptor != nil {
return descriptor.ListAdditionTypes()
}
return nil
}
func parseArtifactType(mediaType string) string {
strs := artifactTypeRegExp.FindStringSubmatch(mediaType)
if len(strs) == 2 {
return strings.ToUpper(strs[1])
}
// can not get the artifact type from the media type, return unknown
return ArtifactTypeUnknown
}

View File

@ -0,0 +1,48 @@
// 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 base
import (
"context"
"github.com/goharbor/harbor/src/api/artifact/processor"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
)
// IndexProcessor is a base processor to process artifact enveloped by OCI index or docker manifest list
// Currently, it is just a null implementation
type IndexProcessor struct {
}
// AbstractMetadata abstracts metadata of artifact
func (m *IndexProcessor) AbstractMetadata(ctx context.Context, content []byte, artifact *artifact.Artifact) error {
return nil
}
// AbstractAddition abstracts the addition of artifact
func (m *IndexProcessor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) {
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("addition %s isn't supported", addition)
}
// GetArtifactType returns the artifact type
func (m *IndexProcessor) GetArtifactType() string {
return ""
}
// ListAdditionTypes returns the supported addition types
func (m *IndexProcessor) ListAdditionTypes() []string {
return nil
}

View File

@ -0,0 +1,88 @@
// 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 base
import (
"context"
"encoding/json"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/opencontainers/image-spec/specs-go/v1"
)
// NewManifestProcessor creates a new base manifest processor.
// All metadata read from config layer will be populated if specifying no "properties"
func NewManifestProcessor(properties ...string) *ManifestProcessor {
return &ManifestProcessor{
properties: properties,
BlobFetcher: blob.Fcher,
}
}
// ManifestProcessor is a base processor to process artifact enveloped by OCI manifest or docker v2 manifest
type ManifestProcessor struct {
properties []string
BlobFetcher blob.Fetcher
}
// AbstractMetadata abstracts metadata of artifact
func (m *ManifestProcessor) AbstractMetadata(ctx context.Context, content []byte, artifact *artifact.Artifact) error {
// get manifest
manifest := &v1.Manifest{}
if err := json.Unmarshal(content, manifest); err != nil {
return err
}
// get config layer
layer, err := m.BlobFetcher.FetchLayer(artifact.RepositoryName, manifest.Config.Digest.String())
if err != nil {
return err
}
// parse metadata from config layer
metadata := map[string]interface{}{}
if err := json.Unmarshal(layer, &metadata); err != nil {
return err
}
// if no properties specified, populate all metadata into the ExtraAttrs
if len(m.properties) == 0 {
artifact.ExtraAttrs = metadata
return nil
}
if artifact.ExtraAttrs == nil {
artifact.ExtraAttrs = map[string]interface{}{}
}
for _, property := range m.properties {
artifact.ExtraAttrs[property] = metadata[property]
}
return nil
}
// AbstractAddition abstracts the addition of artifact
func (m *ManifestProcessor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) {
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("addition %s isn't supported", addition)
}
// GetArtifactType returns the artifact type
func (m *ManifestProcessor) GetArtifactType() string {
return ""
}
// ListAdditionTypes returns the supported addition types
func (m *ManifestProcessor) ListAdditionTypes() []string {
return nil
}

View File

@ -0,0 +1,154 @@
// 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 base
import (
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob"
"github.com/stretchr/testify/suite"
"testing"
)
const (
manifest = `{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1510,
"digest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 977,
"digest": "sha256:1b930d010525941c1d56ec53b97bd057a67ae1865eebf042686d2a2d18271ced"
}
]
}`
config = `{
"architecture": "amd64",
"config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/hello"
],
"ArgsEscaped": true,
"Image": "sha256:a6d1aaad8ca65655449a26146699fe9d61240071f6992975be7e720f1cd42440",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"container": "8e2caa5a514bb6d8b4f2a2553e9067498d261a0fd83a96aeaaf303943dff6ff9",
"container_config": {
"Hostname": "8e2caa5a514b",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/hello\"]"
],
"ArgsEscaped": true,
"Image": "sha256:a6d1aaad8ca65655449a26146699fe9d61240071f6992975be7e720f1cd42440",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
}
},
"created": "2019-01-01T01:29:27.650294696Z",
"docker_version": "18.06.1-ce",
"history": [
{
"created": "2019-01-01T01:29:27.416803627Z",
"created_by": "/bin/sh -c #(nop) COPY file:f77490f70ce51da25bd21bfc30cb5e1a24b2b65eb37d4af0c327ddc24f0986a6 in / "
},
{
"created": "2019-01-01T01:29:27.650294696Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/hello\"]",
"empty_layer": true
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:af0b15c8625bb1938f1d7b17081031f649fd14e6b233688eea3c5483994a66a3"
]
}
}`
)
type manifestTestSuite struct {
suite.Suite
processor *ManifestProcessor
blobFetcher *blob.FakeFetcher
}
func (m *manifestTestSuite) SetupTest() {
m.blobFetcher = &blob.FakeFetcher{}
m.processor = &ManifestProcessor{
BlobFetcher: m.blobFetcher,
}
}
func (m *manifestTestSuite) TestAbstractMetadata() {
// abstract all properties
art := &artifact.Artifact{}
m.blobFetcher.On("FetchLayer").Return([]byte(config), nil)
m.processor.AbstractMetadata(nil, []byte(manifest), art)
m.Len(art.ExtraAttrs, 9)
// reset the mock
m.SetupTest()
// abstract the specified properties
m.processor.properties = []string{"os"}
art = &artifact.Artifact{}
m.blobFetcher.On("FetchLayer").Return([]byte(config), nil)
m.processor.AbstractMetadata(nil, []byte(manifest), art)
m.Require().Len(art.ExtraAttrs, 1)
m.Equal("linux", art.ExtraAttrs["os"])
}
func TestManifestSuite(t *testing.T) {
suite.Run(t, &manifestTestSuite{})
}

View File

@ -17,9 +17,9 @@ package chart
import (
"context"
"encoding/json"
"github.com/goharbor/harbor/src/api/artifact/abstractor/blob"
resolv "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/descriptor"
ps "github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/base"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
@ -34,61 +34,36 @@ const (
AdditionTypeValues = "VALUES.YAML"
AdditionTypeReadme = "README.MD"
AdditionTypeDependencies = "DEPENDENCIES"
// TODO import it from helm chart repository
mediaType = "application/vnd.cncf.helm.config.v1+json"
)
func init() {
resolver := &resolver{
pc := &processor{
blobFetcher: blob.Fcher,
chartOperator: chart.Optr,
}
if err := resolv.Register(resolver, mediaType); err != nil {
log.Errorf("failed to register resolver for media type %s: %v", mediaType, err)
return
}
if err := descriptor.Register(resolver, mediaType); err != nil {
log.Errorf("failed to register descriptor for media type %s: %v", mediaType, err)
pc.ManifestProcessor = base.NewManifestProcessor()
if err := ps.Register(pc, mediaType); err != nil {
log.Errorf("failed to register processor for media type %s: %v", mediaType, err)
return
}
}
type resolver struct {
type processor struct {
*base.ManifestProcessor
blobFetcher blob.Fetcher
chartOperator chart.Operator
}
func (r *resolver) ResolveMetadata(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error {
m := &v1.Manifest{}
if err := json.Unmarshal(manifest, m); err != nil {
return err
}
digest := m.Config.Digest.String()
layer, err := r.blobFetcher.FetchLayer(artifact.RepositoryName, digest)
if err != nil {
return err
}
metadata := map[string]interface{}{}
if err := json.Unmarshal(layer, &metadata); err != nil {
return err
}
if artifact.ExtraAttrs == nil {
artifact.ExtraAttrs = map[string]interface{}{}
}
for k, v := range metadata {
artifact.ExtraAttrs[k] = v
}
return nil
}
func (r *resolver) ResolveAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*resolv.Addition, error) {
func (p *processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*ps.Addition, error) {
if addition != AdditionTypeValues && addition != AdditionTypeReadme && addition != AdditionTypeDependencies {
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("addition %s isn't supported for %s", addition, ArtifactTypeChart)
}
_, content, err := r.blobFetcher.FetchManifest(artifact.RepositoryName, artifact.Digest)
_, content, err := p.blobFetcher.FetchManifest(artifact.RepositoryName, artifact.Digest)
if err != nil {
return nil, err
}
@ -101,11 +76,11 @@ func (r *resolver) ResolveAddition(ctx context.Context, artifact *artifact.Artif
// chart do have two layers, one is config, we should resolve the other one.
layerDgst := layer.Digest.String()
if layerDgst != manifest.Config.Digest.String() {
content, err = r.blobFetcher.FetchLayer(artifact.RepositoryName, layerDgst)
content, err = p.blobFetcher.FetchLayer(artifact.RepositoryName, layerDgst)
if err != nil {
return nil, err
}
chartDetails, err := r.chartOperator.GetDetails(content)
chartDetails, err := p.chartOperator.GetDetails(content)
if err != nil {
return nil, err
}
@ -128,7 +103,7 @@ func (r *resolver) ResolveAddition(ctx context.Context, artifact *artifact.Artif
additionContentType = "application/json; charset=utf-8"
}
return &resolv.Addition{
return &ps.Addition{
Content: additionContent,
ContentType: additionContentType,
}, nil
@ -137,10 +112,10 @@ func (r *resolver) ResolveAddition(ctx context.Context, artifact *artifact.Artif
return nil, nil
}
func (r *resolver) GetArtifactType() string {
func (p *processor) GetArtifactType() string {
return ArtifactTypeChart
}
func (r *resolver) ListAdditionTypes() []string {
func (p *processor) ListAdditionTypes() []string {
return []string{AdditionTypeValues, AdditionTypeReadme, AdditionTypeDependencies}
}

View File

@ -18,87 +18,34 @@ import (
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
chartserver "github.com/goharbor/harbor/src/pkg/chart"
"github.com/goharbor/harbor/src/testing/api/artifact/abstractor/blob"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/testing/pkg/chart"
"github.com/stretchr/testify/suite"
helm_chart "helm.sh/helm/v3/pkg/chart"
"testing"
)
type resolverTestSuite struct {
type processorTestSuite struct {
suite.Suite
resolver *resolver
processor *processor
blobFetcher *blob.FakeFetcher
chartOptr *chart.FakeOpertaor
}
func (r *resolverTestSuite) SetupTest() {
r.blobFetcher = &blob.FakeFetcher{}
r.chartOptr = &chart.FakeOpertaor{}
r.resolver = &resolver{
blobFetcher: r.blobFetcher,
chartOperator: r.chartOptr,
func (p *processorTestSuite) SetupTest() {
p.blobFetcher = &blob.FakeFetcher{}
p.chartOptr = &chart.FakeOpertaor{}
p.processor = &processor{
blobFetcher: p.blobFetcher,
chartOperator: p.chartOptr,
}
}
func (r *resolverTestSuite) TestResolveMetadata() {
content := `{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.cncf.helm.config.v1+json",
"digest": "sha256:c87983b066bd08616c6135832363ed42784d66386814694b237f5608213be325",
"size": 542
},
"layers": [
{
"mediaType": "application/vnd.cncf.helm.chart.content.layer.v1+tar",
"digest": "sha256:0f8c0650d55f5e00d11d7462381c340454a3b9e517e15a0187011dc305690541",
"size": 28776
}
]
}`
config := `{
"name": "harbor",
"home": "https://goharbor.io",
"sources": [
"https://github.com/goharbor/harbor",
"https://github.com/goharbor/harbor-helm"
],
"version": "1.1.2",
"description": "An open source trusted cloud native registry that stores, signs, and scans content",
"keywords": [
"docker",
"registry",
"harbor"
],
"maintainers": [
{
"name": "Jesse Hu",
"email": "huh@vmware.com"
},
{
"name": "paulczar",
"email": "username.taken@gmail.com"
}
],
"icon": "https://raw.githubusercontent.com/goharbor/harbor/master/docs/img/harbor_logo.png",
"apiVersion": "v1",
"appVersion": "1.8.2"
}`
artifact := &artifact.Artifact{}
r.blobFetcher.On("FetchLayer").Return([]byte(config), nil)
err := r.resolver.ResolveMetadata(nil, []byte(content), artifact)
r.Require().Nil(err)
r.blobFetcher.AssertExpectations(r.T())
r.Assert().Equal("1.1.2", artifact.ExtraAttrs["version"].(string))
r.Assert().Equal("1.8.2", artifact.ExtraAttrs["appVersion"].(string))
}
func (r *resolverTestSuite) TestResolveAddition() {
func (p *processorTestSuite) TestAbstractAddition() {
// unknown addition
_, err := r.resolver.ResolveAddition(nil, nil, "unknown_addition")
r.True(ierror.IsErr(err, ierror.BadRequestCode))
_, err := p.processor.AbstractAddition(nil, nil, "unknown_addition")
p.True(ierror.IsErr(err, ierror.BadRequestCode))
chartManifest := `{"schemaVersion":2,"config":{"mediaType":"application/vnd.cncf.helm.config.v1+json","digest":"sha256:76a59ebef39013bf7b57e411629b569a5175590024f31eeaaa577a0f8da9e523","size":528},"layers":[{"mediaType":"application/tar+gzip","digest":"sha256:0bd64cfb958b68c71b46597e22185a41e784dc96e04090bc7d2a480b704c3b65","size":12607}]}`
@ -151,38 +98,38 @@ func (r *resolverTestSuite) TestResolveAddition() {
}
artifact := &artifact.Artifact{}
r.blobFetcher.On("FetchManifest").Return("", []byte(chartManifest), nil)
r.blobFetcher.On("FetchLayer").Return([]byte(chartYaml), nil)
r.chartOptr.On("GetDetails").Return(chartDetails, nil)
p.blobFetcher.On("FetchManifest").Return("", []byte(chartManifest), nil)
p.blobFetcher.On("FetchLayer").Return([]byte(chartYaml), nil)
p.chartOptr.On("GetDetails").Return(chartDetails, nil)
// values.yaml
addition, err := r.resolver.ResolveAddition(nil, artifact, AdditionTypeValues)
r.Require().Nil(err)
r.Equal("text/plain; charset=utf-8", addition.ContentType)
r.Equal(`image:\n ## Bitnami MongoDB registry\n ##\n registry: docker.io\n ## Bitnami MongoDB image name\n ##\n repository: bitnami/mongodb\n ## Bitnami MongoDB image tag\n ## ref: https://hub.docker.com/r/bitnami/mongodb/tags/\n`, string(addition.Content))
addition, err := p.processor.AbstractAddition(nil, artifact, AdditionTypeValues)
p.Require().Nil(err)
p.Equal("text/plain; charset=utf-8", addition.ContentType)
p.Equal(`image:\n ## Bitnami MongoDB registry\n ##\n registry: docker.io\n ## Bitnami MongoDB image name\n ##\n repository: bitnami/mongodb\n ## Bitnami MongoDB image tag\n ## ref: https://hub.docker.com/r/bitnami/mongodb/tags/\n`, string(addition.Content))
// README.md
addition, err = r.resolver.ResolveAddition(nil, artifact, AdditionTypeReadme)
r.Require().Nil(err)
r.Equal("text/markdown; charset=utf-8", addition.ContentType)
r.Equal(`This chart bootstraps a [Redis](https://github.com/bitnami/bitnami-docker-redis) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.`, string(addition.Content))
addition, err = p.processor.AbstractAddition(nil, artifact, AdditionTypeReadme)
p.Require().Nil(err)
p.Equal("text/markdown; charset=utf-8", addition.ContentType)
p.Equal(`This chart bootstraps a [Redis](https://github.com/bitnami/bitnami-docker-redis) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.`, string(addition.Content))
// README.md
addition, err = r.resolver.ResolveAddition(nil, artifact, AdditionTypeDependencies)
r.Require().Nil(err)
r.Equal("application/json; charset=utf-8", addition.ContentType)
r.Equal(`[{"name":"harbor","version":"v1.10","repository":"github.com/goharbor"}]`, string(addition.Content))
addition, err = p.processor.AbstractAddition(nil, artifact, AdditionTypeDependencies)
p.Require().Nil(err)
p.Equal("application/json; charset=utf-8", addition.ContentType)
p.Equal(`[{"name":"harbor","version":"v1.10","repository":"github.com/goharbor"}]`, string(addition.Content))
}
func (r *resolverTestSuite) TestGetArtifactType() {
r.Assert().Equal(ArtifactTypeChart, r.resolver.GetArtifactType())
func (p *processorTestSuite) TestGetArtifactType() {
p.Assert().Equal(ArtifactTypeChart, p.processor.GetArtifactType())
}
func (r *resolverTestSuite) TestListAdditionTypes() {
additions := r.resolver.ListAdditionTypes()
r.EqualValues([]string{AdditionTypeValues, AdditionTypeReadme, AdditionTypeDependencies}, additions)
func (p *processorTestSuite) TestListAdditionTypes() {
additions := p.processor.ListAdditionTypes()
p.EqualValues([]string{AdditionTypeValues, AdditionTypeReadme, AdditionTypeDependencies}, additions)
}
func TestResolverTestSuite(t *testing.T) {
suite.Run(t, &resolverTestSuite{})
func TestProcessorTestSuite(t *testing.T) {
suite.Run(t, &processorTestSuite{})
}

View File

@ -0,0 +1,75 @@
// 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 cnab
import (
"context"
ps "github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/base"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/artifact"
)
// const definitions
const (
ArtifactTypeCNAB = "CNAB"
mediaType = "application/vnd.cnab.manifest.v1"
)
func init() {
pc := &processor{
blobFetcher: blob.Fcher,
manifestProcessor: base.NewManifestProcessor(),
}
pc.IndexProcessor = &base.IndexProcessor{}
if err := ps.Register(pc, mediaType); err != nil {
log.Errorf("failed to register processor for media type %s: %v", mediaType, err)
return
}
}
type processor struct {
*base.IndexProcessor
manifestProcessor *base.ManifestProcessor
blobFetcher blob.Fetcher
}
func (p *processor) AbstractMetadata(ctx context.Context, manifest []byte, art *artifact.Artifact) error {
cfgManiDgt := ""
// try to get the digest of the manifest that the config layer is referenced by
for _, reference := range art.References {
if reference.Annotations != nil &&
reference.Annotations["io.cnab.manifest.type"] == "config" {
cfgManiDgt = reference.ChildDigest
}
}
if len(cfgManiDgt) == 0 {
return nil
}
// get the manifest that the config layer is referenced by
_, cfgMani, err := p.blobFetcher.FetchManifest(art.RepositoryName, cfgManiDgt)
if err != nil {
return err
}
// abstract the metadata from config layer
return p.manifestProcessor.AbstractMetadata(ctx, cfgMani, art)
}
func (p *processor) GetArtifactType() string {
return ArtifactTypeCNAB
}

View File

@ -0,0 +1,104 @@
// 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 cnab
import (
"github.com/goharbor/harbor/src/api/artifact/processor/base"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob"
"github.com/stretchr/testify/suite"
"testing"
)
type processorTestSuite struct {
suite.Suite
processor *processor
blobFetcher *blob.FakeFetcher
}
func (p *processorTestSuite) SetupTest() {
p.blobFetcher = &blob.FakeFetcher{}
p.processor = &processor{
blobFetcher: p.blobFetcher,
manifestProcessor: &base.ManifestProcessor{
BlobFetcher: p.blobFetcher,
},
}
}
func (p *processorTestSuite) TestAbstractMetadata() {
manifest := `{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b",
"size": 498
},
"layers": null
}`
config := `{
"description": "A short description of your bundle",
"invocationImages": [
{
"contentDigest": "sha256:a59a4e74d9cc89e4e75dfb2cc7ea5c108e4236ba6231b53081a9e2506d1197b6",
"image": "cnab/helloworld:0.1.1",
"imageType": "docker",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 942
}
],
"keywords": [
"helloworld",
"cnab",
"tutorial"
],
"maintainers": [
{
"email": "jane.doe@example.com",
"name": "Jane Doe",
"url": "https://example.com"
}
],
"name": "helloworld",
"schemaVersion": "v1.0.0",
"version": "0.1.1"
}`
art := &artifact.Artifact{
References: []*artifact.Reference{
{
ChildDigest: "sha256:b9616da7500f8c7c9a5e8d915714cd02d11bcc71ff5b4fd190bb77b1355c8549",
Annotations: map[string]string{
"io.cnab.manifest.type": "config",
},
},
},
}
p.blobFetcher.On("FetchManifest").Return("", []byte(manifest), nil)
p.blobFetcher.On("FetchLayer").Return([]byte(config), nil)
err := p.processor.AbstractMetadata(nil, nil, art)
p.Require().Nil(err)
p.Len(art.ExtraAttrs, 7)
p.Equal("0.1.1", art.ExtraAttrs["version"].(string))
p.Equal("helloworld", art.ExtraAttrs["name"].(string))
}
func (p *processorTestSuite) TestGetArtifactType() {
p.Assert().Equal(ArtifactTypeCNAB, p.processor.GetArtifactType())
}
func TestProcessorTestSuite(t *testing.T) {
suite.Run(t, &processorTestSuite{})
}

View File

@ -0,0 +1,59 @@
// 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 processor
import (
"context"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
"regexp"
"strings"
)
// ArtifactTypeUnknown defines the type for the unknown artifacts
const ArtifactTypeUnknown = "UNKNOWN"
var (
artifactTypeRegExp = regexp.MustCompile(`^application/vnd\.[^.]*\.(.*)\.config\.[^.]*\+json$`)
)
// the default processor to process artifact
// currently, it only tries to parse the artifact type from media type
type defaultProcessor struct {
mediaType string
}
func (d *defaultProcessor) GetArtifactType() string {
// try to parse the type from the media type
strs := artifactTypeRegExp.FindStringSubmatch(d.mediaType)
if len(strs) == 2 {
return strings.ToUpper(strs[1])
}
// can not get the artifact type from the media type, return unknown
return ArtifactTypeUnknown
}
func (d *defaultProcessor) ListAdditionTypes() []string {
return nil
}
func (d *defaultProcessor) AbstractMetadata(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error {
// do nothing currently
// we can extend this function to abstract the metadata in the future if needed
return nil
}
func (d *defaultProcessor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*Addition, error) {
// return error directly
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("the processor for artifact %s not found, cannot get the addition", artifact.Type)
}

View File

@ -12,39 +12,44 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package descriptor
package processor
import (
"github.com/stretchr/testify/suite"
"testing"
)
type descriptorTestSuite struct {
type defaultProcessorTestSuite struct {
suite.Suite
}
func (d *descriptorTestSuite) TestParseArtifactType() {
func (d *defaultProcessorTestSuite) TestGetArtifactType() {
mediaType := ""
typee := parseArtifactType(mediaType)
processor := &defaultProcessor{mediaType: mediaType}
typee := processor.GetArtifactType()
d.Equal(ArtifactTypeUnknown, typee)
mediaType = "unknown"
typee = parseArtifactType(mediaType)
processor = &defaultProcessor{mediaType: mediaType}
typee = processor.GetArtifactType()
d.Equal(ArtifactTypeUnknown, typee)
mediaType = "application/vnd.oci.image.config.v1+json"
typee = parseArtifactType(mediaType)
processor = &defaultProcessor{mediaType: mediaType}
typee = processor.GetArtifactType()
d.Equal("IMAGE", typee)
mediaType = "application/vnd.cncf.helm.chart.config.v1+json"
typee = parseArtifactType(mediaType)
processor = &defaultProcessor{mediaType: mediaType}
typee = processor.GetArtifactType()
d.Equal("HELM.CHART", typee)
mediaType = "application/vnd.sylabs.sif.config.v1+json"
typee = parseArtifactType(mediaType)
processor = &defaultProcessor{mediaType: mediaType}
typee = processor.GetArtifactType()
d.Equal("SIF", typee)
}
func TestDescriptorTestSuite(t *testing.T) {
suite.Run(t, &descriptorTestSuite{})
func TestDefaultProcessorTestSuite(t *testing.T) {
suite.Run(t, &defaultProcessorTestSuite{})
}

View File

@ -0,0 +1,45 @@
// 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 image
import (
"github.com/docker/distribution/manifest/manifestlist"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/base"
"github.com/goharbor/harbor/src/common/utils/log"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
func init() {
mediaTypes := []string{
v1.MediaTypeImageIndex,
manifestlist.MediaTypeManifestList,
}
pc := &indexProcessor{}
pc.IndexProcessor = &base.IndexProcessor{}
if err := processor.Register(pc, mediaTypes...); err != nil {
log.Errorf("failed to register processor for media type %v: %v", mediaTypes, err)
return
}
}
// indexProcessor processes image with OCI index and docker manifest list
type indexProcessor struct {
*base.IndexProcessor
}
func (i *indexProcessor) GetArtifactType() string {
return ArtifactTypeImage
}

View File

@ -0,0 +1,37 @@
// 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 image
import (
"github.com/stretchr/testify/suite"
"testing"
)
type indexProcessTestSuite struct {
suite.Suite
processor *indexProcessor
}
func (i *indexProcessTestSuite) SetupTest() {
i.processor = &indexProcessor{}
}
func (i *indexProcessTestSuite) TestGetArtifactType() {
i.Assert().Equal(ArtifactTypeImage, i.processor.GetArtifactType())
}
func TestIndexProcessTestSuite(t *testing.T) {
suite.Run(t, &indexProcessTestSuite{})
}

View File

@ -18,32 +18,27 @@ import (
"context"
"encoding/json"
"github.com/docker/distribution/manifest/schema1"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/descriptor"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
)
func init() {
rslver := &manifestV1Resolver{}
if err := resolver.Register(rslver, schema1.MediaTypeSignedManifest); err != nil {
log.Errorf("failed to register resolver for media type %s: %v", schema1.MediaTypeSignedManifest, err)
return
}
if err := descriptor.Register(rslver, schema1.MediaTypeSignedManifest); err != nil {
log.Errorf("failed to register descriptor for media type %s: %v", schema1.MediaTypeSignedManifest, err)
pc := &manifestV1Processor{}
if err := processor.Register(pc, schema1.MediaTypeSignedManifest); err != nil {
log.Errorf("failed to register processor for media type %s: %v", schema1.MediaTypeSignedManifest, err)
return
}
}
// manifestV1Resolver resolve artifact with docker v1 manifest
type manifestV1Resolver struct {
// manifestV1Processor processes image with docker v1 manifest
type manifestV1Processor struct {
}
func (m *manifestV1Resolver) ResolveMetadata(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error {
func (m *manifestV1Processor) AbstractMetadata(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error {
mani := &schema1.Manifest{}
if err := json.Unmarshal([]byte(manifest), mani); err != nil {
if err := json.Unmarshal(manifest, mani); err != nil {
return err
}
if artifact.ExtraAttrs == nil {
@ -53,15 +48,15 @@ func (m *manifestV1Resolver) ResolveMetadata(ctx context.Context, manifest []byt
return nil
}
func (m *manifestV1Resolver) ResolveAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*resolver.Addition, error) {
func (m *manifestV1Processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) {
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("addition %s isn't supported for %s(manifest version 1)", addition, ArtifactTypeImage)
}
func (m *manifestV1Resolver) GetArtifactType() string {
func (m *manifestV1Processor) GetArtifactType() string {
return ArtifactTypeImage
}
func (m *manifestV1Resolver) ListAdditionTypes() []string {
func (m *manifestV1Processor) ListAdditionTypes() []string {
return nil
}

View File

@ -21,17 +21,17 @@ import (
"testing"
)
type manifestV1ResolverTestSuite struct {
type manifestV1ProcessorTestSuite struct {
suite.Suite
resolver *manifestV1Resolver
processor *manifestV1Processor
}
func (m *manifestV1ResolverTestSuite) SetupSuite() {
m.resolver = &manifestV1Resolver{}
func (m *manifestV1ProcessorTestSuite) SetupSuite() {
m.processor = &manifestV1Processor{}
}
func (m *manifestV1ResolverTestSuite) TestResolveMetadata() {
func (m *manifestV1ProcessorTestSuite) TestAbstractMetadata() {
manifest := `{
"name": "hello-world",
"tag": "latest",
@ -78,25 +78,25 @@ func (m *manifestV1ResolverTestSuite) TestResolveMetadata() {
}
`
artifact := &artifact.Artifact{}
err := m.resolver.ResolveMetadata(nil, []byte(manifest), artifact)
err := m.processor.AbstractMetadata(nil, []byte(manifest), artifact)
m.Require().Nil(err)
m.Assert().Equal("amd64", artifact.ExtraAttrs["architecture"].(string))
}
func (m *manifestV1ResolverTestSuite) TestResolveAddition() {
_, err := m.resolver.ResolveAddition(nil, nil, AdditionTypeBuildHistory)
func (m *manifestV1ProcessorTestSuite) TestAbstractAddition() {
_, err := m.processor.AbstractAddition(nil, nil, AdditionTypeBuildHistory)
m.True(ierror.IsErr(err, ierror.BadRequestCode))
}
func (m *manifestV1ResolverTestSuite) TestGetArtifactType() {
m.Assert().Equal(ArtifactTypeImage, m.resolver.GetArtifactType())
func (m *manifestV1ProcessorTestSuite) TestGetArtifactType() {
m.Assert().Equal(ArtifactTypeImage, m.processor.GetArtifactType())
}
func (m *manifestV1ResolverTestSuite) TestListAdditionTypes() {
additions := m.resolver.ListAdditionTypes()
func (m *manifestV1ProcessorTestSuite) TestListAdditionTypes() {
additions := m.processor.ListAdditionTypes()
m.Len(additions, 0)
}
func TestManifestV1ResolverTestSuite(t *testing.T) {
suite.Run(t, &manifestV1ResolverTestSuite{})
func TestManifestV1ProcessorTestSuite(t *testing.T) {
suite.Run(t, &manifestV1ProcessorTestSuite{})
}

View File

@ -18,9 +18,9 @@ import (
"context"
"encoding/json"
"github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/api/artifact/abstractor/blob"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/descriptor"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/base"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
@ -35,53 +35,27 @@ const (
)
func init() {
rslver := &manifestV2Resolver{
pc := &manifestV2Processor{
blobFetcher: blob.Fcher,
}
pc.ManifestProcessor = base.NewManifestProcessor("created", "author", "architecture", "os")
mediaTypes := []string{
v1.MediaTypeImageConfig,
schema2.MediaTypeImageConfig,
}
if err := resolver.Register(rslver, mediaTypes...); err != nil {
log.Errorf("failed to register resolver for media type %v: %v", mediaTypes, err)
return
}
if err := descriptor.Register(rslver, mediaTypes...); err != nil {
log.Errorf("failed to register descriptor for media type %v: %v", mediaTypes, err)
if err := processor.Register(pc, mediaTypes...); err != nil {
log.Errorf("failed to register processor for media type %v: %v", mediaTypes, err)
return
}
}
// manifestV2Resolver resolve artifact with OCI manifest and docker v2 manifest
type manifestV2Resolver struct {
// manifestV2Processor processes image with OCI manifest and docker v2 manifest
type manifestV2Processor struct {
*base.ManifestProcessor
blobFetcher blob.Fetcher
}
func (m *manifestV2Resolver) ResolveMetadata(ctx context.Context, content []byte, artifact *artifact.Artifact) error {
manifest := &v1.Manifest{}
if err := json.Unmarshal(content, manifest); err != nil {
return err
}
digest := manifest.Config.Digest.String()
layer, err := m.blobFetcher.FetchLayer(artifact.RepositoryName, digest)
if err != nil {
return err
}
image := &v1.Image{}
if err := json.Unmarshal(layer, image); err != nil {
return err
}
if artifact.ExtraAttrs == nil {
artifact.ExtraAttrs = map[string]interface{}{}
}
artifact.ExtraAttrs["created"] = image.Created
artifact.ExtraAttrs["author"] = image.Author
artifact.ExtraAttrs["architecture"] = image.Architecture
artifact.ExtraAttrs["os"] = image.OS
return nil
}
func (m *manifestV2Resolver) ResolveAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*resolver.Addition, error) {
func (m *manifestV2Processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) {
if addition != AdditionTypeBuildHistory {
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("addition %s isn't supported for %s(manifest version 2)", addition, ArtifactTypeImage)
@ -106,16 +80,16 @@ func (m *manifestV2Resolver) ResolveAddition(ctx context.Context, artifact *arti
if err != nil {
return nil, err
}
return &resolver.Addition{
return &processor.Addition{
Content: content,
ContentType: "application/json; charset=utf-8",
}, nil
}
func (m *manifestV2Resolver) GetArtifactType() string {
func (m *manifestV2Processor) GetArtifactType() string {
return ArtifactTypeImage
}
func (m *manifestV2Resolver) ListAdditionTypes() []string {
func (m *manifestV2Processor) ListAdditionTypes() []string {
return []string{AdditionTypeBuildHistory}
}

View File

@ -17,7 +17,7 @@ package image
import (
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/api/artifact/abstractor/blob"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob"
"github.com/stretchr/testify/suite"
"testing"
)
@ -118,54 +118,43 @@ var (
}`
)
type manifestV2ResolverTestSuite struct {
type manifestV2ProcessorTestSuite struct {
suite.Suite
resolver *manifestV2Resolver
processor *manifestV2Processor
blobFetcher *blob.FakeFetcher
}
func (m *manifestV2ResolverTestSuite) SetupTest() {
func (m *manifestV2ProcessorTestSuite) SetupTest() {
m.blobFetcher = &blob.FakeFetcher{}
m.resolver = &manifestV2Resolver{
m.processor = &manifestV2Processor{
blobFetcher: m.blobFetcher,
}
}
func (m *manifestV2ResolverTestSuite) TestResolveMetadata() {
artifact := &artifact.Artifact{}
m.blobFetcher.On("FetchLayer").Return([]byte(config), nil)
err := m.resolver.ResolveMetadata(nil, []byte(manifest), artifact)
m.Require().Nil(err)
m.blobFetcher.AssertExpectations(m.T())
m.Assert().Equal("amd64", artifact.ExtraAttrs["architecture"].(string))
m.Assert().Equal("linux", artifact.ExtraAttrs["os"].(string))
}
func (m *manifestV2ResolverTestSuite) TestResolveAddition() {
func (m *manifestV2ProcessorTestSuite) TestAbstractAddition() {
// unknown addition
_, err := m.resolver.ResolveAddition(nil, nil, "unknown_addition")
_, err := m.processor.AbstractAddition(nil, nil, "unknown_addition")
m.True(ierror.IsErr(err, ierror.BadRequestCode))
// build history
artifact := &artifact.Artifact{}
m.blobFetcher.On("FetchManifest").Return("", []byte(manifest), nil)
m.blobFetcher.On("FetchLayer").Return([]byte(config), nil)
addition, err := m.resolver.ResolveAddition(nil, artifact, AdditionTypeBuildHistory)
addition, err := m.processor.AbstractAddition(nil, artifact, AdditionTypeBuildHistory)
m.Require().Nil(err)
m.Equal("application/json; charset=utf-8", addition.ContentType)
m.Equal(`[{"created":"2019-01-01T01:29:27.416803627Z","created_by":"/bin/sh -c #(nop) COPY file:f77490f70ce51da25bd21bfc30cb5e1a24b2b65eb37d4af0c327ddc24f0986a6 in / "},{"created":"2019-01-01T01:29:27.650294696Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]`, string(addition.Content))
}
func (m *manifestV2ResolverTestSuite) TestGetArtifactType() {
m.Assert().Equal(ArtifactTypeImage, m.resolver.GetArtifactType())
func (m *manifestV2ProcessorTestSuite) TestGetArtifactType() {
m.Assert().Equal(ArtifactTypeImage, m.processor.GetArtifactType())
}
func (m *manifestV2ResolverTestSuite) TestListAdditionTypes() {
additions := m.resolver.ListAdditionTypes()
func (m *manifestV2ProcessorTestSuite) TestListAdditionTypes() {
additions := m.processor.ListAdditionTypes()
m.EqualValues([]string{AdditionTypeBuildHistory}, additions)
}
func TestManifestV2ResolverTestSuite(t *testing.T) {
suite.Run(t, &manifestV2ResolverTestSuite{})
func TestManifestV2ProcessorTestSuite(t *testing.T) {
suite.Run(t, &manifestV2ProcessorTestSuite{})
}

View File

@ -0,0 +1,72 @@
// 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 processor
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/artifact"
)
var (
// Registry for registered artifact processors
Registry = map[string]Processor{}
)
// Addition defines the specific addition of different artifacts: build history for image, values.yaml for chart, etc
type Addition struct {
Content []byte // the content of the addition
ContentType string // the content type of the addition, returned as "Content-Type" header in API
}
// Processor processes specified artifact
type Processor interface {
// GetArtifactType returns the type of one kind of artifact specified by media type
GetArtifactType() string
// ListAdditionTypes returns the supported addition types of one kind of artifact specified by media type
ListAdditionTypes() []string
// AbstractMetadata abstracts the metadata for the specific artifact type into the artifact model,
// the metadata can be got from the manifest or other layers referenced by the manifest.
AbstractMetadata(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error
// AbstractAddition abstracts the addition of the artifact.
// The additions are different for different artifacts:
// build history for image; values.yaml, readme and dependencies for chart, etc
AbstractAddition(ctx context.Context, artifact *artifact.Artifact, additionType string) (addition *Addition, err error)
}
// Register artifact processor, one processor can process multiple media types for one kind of artifact
func Register(processor Processor, mediaTypes ...string) error {
for _, mediaType := range mediaTypes {
_, exist := Registry[mediaType]
if exist {
return fmt.Errorf("the processor to process media type %s already exists", mediaType)
}
Registry[mediaType] = processor
log.Infof("the processor to process media type %s registered", mediaType)
}
return nil
}
// Get the artifact processor according to the media type
func Get(mediaType string) Processor {
processor := Registry[mediaType]
// no registered processor found, use the default one
if processor == nil {
log.Debugf("the processor for media type %s not found, use the default one", mediaType)
processor = &defaultProcessor{mediaType: mediaType}
}
return processor
}

View File

@ -0,0 +1,79 @@
// 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 processor
import (
"context"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/stretchr/testify/suite"
"testing"
)
type fakeProcessor struct{}
func (f *fakeProcessor) GetArtifactType() string {
return ""
}
func (f *fakeProcessor) ListAdditionTypes() []string {
return nil
}
func (f *fakeProcessor) AbstractMetadata(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error {
return nil
}
func (f *fakeProcessor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, additionType string) (*Addition, error) {
return nil, nil
}
type processorTestSuite struct {
suite.Suite
}
func (p *processorTestSuite) SetupTest() {
Registry = map[string]Processor{}
}
func (p *processorTestSuite) TestRegister() {
// success
mediaType := "fake_media_type"
err := Register(nil, mediaType)
p.Require().Nil(err)
// conflict
err = Register(nil, mediaType)
p.Require().NotNil(err)
}
func (p *processorTestSuite) TestGet() {
// register a processor
mediaType := "fake_media_type"
err := Register(&fakeProcessor{}, mediaType)
p.Require().Nil(err)
// get the processor
processor := Get(mediaType)
p.Require().NotNil(processor)
_, ok := processor.(*fakeProcessor)
p.True(ok)
// get the not existing processor
processor = Get("not_existing_media_type")
p.Require().NotNil(processor)
_, ok = processor.(*defaultProcessor)
p.True(ok)
}
func TestProcessorTestSuite(t *testing.T) {
suite.Run(t, &processorTestSuite{})
}

View File

@ -16,7 +16,7 @@ package migration
import (
"context"
"github.com/goharbor/harbor/src/api/artifact/abstractor"
art "github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/project"
@ -25,7 +25,7 @@ import (
)
func upgradeData(ctx context.Context) error {
abstractor := abstractor.NewAbstractor()
abstractor := art.NewAbstractor()
pros, err := project.Mgr.List()
if err != nil {
return err
@ -69,7 +69,7 @@ func upgradeData(ctx context.Context) error {
return setDataVersion(ctx, 30)
}
func abstract(ctx context.Context, abstractor abstractor.Abstractor, art *artifact.Artifact) error {
func abstract(ctx context.Context, abstractor art.Abstractor, art *artifact.Artifact) error {
// abstract the children
for _, reference := range art.References {
child, err := artifact.Mgr.Get(ctx, reference.ChildID)

View File

@ -25,7 +25,7 @@ import (
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/event"
"github.com/goharbor/harbor/src/api/repository"
"github.com/goharbor/harbor/src/api/scan"
@ -301,7 +301,7 @@ func (a *artifactAPI) GetAddition(ctx context.Context, params operation.GetAddit
return a.SendError(ctx, err)
}
var addition *resolver.Addition
var addition *processor.Addition
if params.Addition == vulnerabilitiesAddition {
addition, err = resolveVulnerabilitiesAddition(ctx, artifact)

View File

@ -22,7 +22,7 @@ import (
"reflect"
"github.com/goharbor/harbor/src/api/artifact"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/scan"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/scan/report"
@ -37,7 +37,7 @@ func boolValue(v *bool) bool {
return false
}
func resolveVulnerabilitiesAddition(ctx context.Context, artifact *artifact.Artifact) (*resolver.Addition, error) {
func resolveVulnerabilitiesAddition(ctx context.Context, artifact *artifact.Artifact) (*processor.Addition, error) {
art := &v1.Artifact{
NamespaceID: artifact.ProjectID,
Repository: artifact.RepositoryName,
@ -67,7 +67,7 @@ func resolveVulnerabilitiesAddition(ctx context.Context, artifact *artifact.Arti
content, _ := json.Marshal(vulnerabilities)
return &resolver.Addition{
return &processor.Addition{
Content: content,
ContentType: "application/json",
}, nil

View File

@ -1,43 +0,0 @@
// 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 resolver
import (
"context"
"github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/stretchr/testify/mock"
)
// FakeResolver is a fake resolver that implement the src/api/artifact/abstractor/resolver.Resolver interface
type FakeResolver struct {
mock.Mock
}
// ResolveMetadata ...
func (f *FakeResolver) ResolveMetadata(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error {
args := f.Called()
return args.Error(0)
}
// ResolveAddition ...
func (f *FakeResolver) ResolveAddition(ctx context.Context, artifact *artifact.Artifact, additionType string) (*resolver.Addition, error) {
args := f.Called()
var addition *resolver.Addition
if args.Get(0) != nil {
addition = args.Get(0).(*resolver.Addition)
}
return addition, args.Error(1)
}

View File

@ -9,9 +9,9 @@ import (
mock "github.com/stretchr/testify/mock"
q "github.com/goharbor/harbor/src/pkg/q"
processor "github.com/goharbor/harbor/src/api/artifact/processor"
resolver "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver"
q "github.com/goharbor/harbor/src/pkg/q"
time "time"
)
@ -150,15 +150,15 @@ func (_m *Controller) Get(ctx context.Context, id int64, option *artifact.Option
}
// GetAddition provides a mock function with given fields: ctx, artifactID, additionType
func (_m *Controller) GetAddition(ctx context.Context, artifactID int64, additionType string) (*resolver.Addition, error) {
func (_m *Controller) GetAddition(ctx context.Context, artifactID int64, additionType string) (*processor.Addition, error) {
ret := _m.Called(ctx, artifactID, additionType)
var r0 *resolver.Addition
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *resolver.Addition); ok {
var r0 *processor.Addition
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *processor.Addition); ok {
r0 = rf(ctx, artifactID, additionType)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*resolver.Addition)
r0 = ret.Get(0).(*processor.Addition)
}
}