mirror of https://github.com/goharbor/harbor.git
Restructure the packages of artifact
1. Introduce a new interface Processor to replace Abstractor and Descriptor 2. Provide the base processors for manifest and index to reduce the duplicate code 3. Move the child artifacts checking out of processor Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
25b5c3796b
commit
289f04d301
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue