Merge pull request #11033 from ywk253100/200311_artifact

Restructure the packages of artifact
This commit is contained in:
Wenkai Yin(尹文开) 2020-03-12 16:30:04 +08:00 committed by GitHub
commit d6b32e19df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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)
}
}