Add new feature for supporting WebAssembly artifact (#16931)

support wasm

Signed-off-by: ln23415 <ln23415@hotmail.com>
This commit is contained in:
Roooocky 2022-07-08 23:08:28 +08:00 committed by GitHub
parent af802a4416
commit 7e4b26b220
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 328 additions and 0 deletions

BIN
icons/wasm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -18,6 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/goharbor/harbor/src/controller/artifact/processor/wasm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg"
@ -126,6 +127,11 @@ func (a *abstractor) abstractManifestV2Metadata(artifact *artifact.Artifact, con
}
// use the "manifest.config.mediatype" as the media type of the artifact
artifact.MediaType = manifest.Config.MediaType
if manifest.Annotations[wasm.AnnotationVariantKey] == wasm.AnnotationVariantValue || manifest.Annotations[wasm.AnnotationHandlerKey] == wasm.AnnotationHandlerValue {
artifact.MediaType = wasm.MediaType
}
// set size
artifact.Size = int64(len(content)) + manifest.Config.Size
for _, layer := range manifest.Layers {

View File

@ -28,6 +28,7 @@ import (
"github.com/goharbor/harbor/src/controller/artifact/processor/chart"
"github.com/goharbor/harbor/src/controller/artifact/processor/cnab"
"github.com/goharbor/harbor/src/controller/artifact/processor/image"
"github.com/goharbor/harbor/src/controller/artifact/processor/wasm"
"github.com/goharbor/harbor/src/lib/icon"
"github.com/goharbor/harbor/src/controller/artifact/processor"
@ -72,6 +73,7 @@ var (
image.ArtifactTypeImage: icon.DigestOfIconImage,
chart.ArtifactTypeChart: icon.DigestOfIconChart,
cnab.ArtifactTypeCNAB: icon.DigestOfIconCNAB,
wasm.ArtifactTypeWASM: icon.DigestOfIconWASM,
}
)

View File

@ -0,0 +1,135 @@
// 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 wasm
import (
"context"
"encoding/json"
"github.com/goharbor/harbor/src/controller/artifact/processor"
"github.com/goharbor/harbor/src/controller/artifact/processor/base"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/opencontainers/image-spec/specs-go/v1"
)
// const definitions
const (
// ArtifactTypeWASM is the artifact type for image
ArtifactTypeWASM = "WASM"
AdditionTypeBuildHistory = "BUILD_HISTORY"
// AnnotationVariantKey and AnnotationVariantValue is available key-value pair to identify an annotation fashion wasm artifact
AnnotationVariantKey = "module.wasm.image/variant"
AnnotationVariantValue = "compat"
// AnnotationHandlerKey and AnnotationHandlerValue is another available key-value pair to identify an annotation fashion wasm artifact
AnnotationHandlerKey = "run.oci.handler"
AnnotationHandlerValue = "wasm"
MediaType = "application/vnd.wasm.config.v1+json"
)
func init() {
pc := &Processor{}
pc.ManifestProcessor = base.NewManifestProcessor()
mediaTypes := []string{
MediaType,
}
if err := processor.Register(pc, mediaTypes...); err != nil {
log.Errorf("failed to register processor for media type %v: %v", mediaTypes, err)
return
}
}
// Processor processes image with OCI manifest and docker v2 manifest
type Processor struct {
*base.ManifestProcessor
}
func (m *Processor) AbstractMetadata(ctx context.Context, art *artifact.Artifact, manifestBody []byte) error {
art.ExtraAttrs = map[string]interface{}{}
manifest := &v1.Manifest{}
if err := json.Unmarshal(manifestBody, manifest); err != nil {
return err
}
if art.ExtraAttrs == nil {
art.ExtraAttrs = map[string]interface{}{}
}
if manifest.Annotations[AnnotationVariantKey] == AnnotationVariantValue || manifest.Annotations[AnnotationHandlerKey] == AnnotationHandlerValue {
// for annotation way
config := &v1.Image{}
if err := m.UnmarshalConfig(ctx, art.RepositoryName, manifestBody, config); err != nil {
return err
}
art.ExtraAttrs["manifest.config.mediaType"] = manifest.Config.MediaType
art.ExtraAttrs["created"] = config.Created
art.ExtraAttrs["architecture"] = config.Architecture
art.ExtraAttrs["os"] = config.OS
art.ExtraAttrs["config"] = config.Config
// if the author is null, try to get it from labels:
// https://docs.docker.com/engine/reference/builder/#maintainer-deprecated
author := config.Author
if len(author) == 0 && len(config.Config.Labels) > 0 {
author = config.Config.Labels["maintainer"]
}
art.ExtraAttrs["author"] = author
} else {
// for wasm-to-oci way
art.ExtraAttrs["manifest.config.mediaType"] = MediaType
if len(manifest.Layers) > 0 {
art.ExtraAttrs["manifest.layers.mediaType"] = manifest.Layers[0].MediaType
art.ExtraAttrs["org.opencontainers.image.title"] = manifest.Layers[0].Annotations["org.opencontainers.image.title"]
}
}
return nil
}
func (m *Processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) {
if addition != AdditionTypeBuildHistory {
return nil, errors.New(nil).WithCode(errors.BadRequestCode).
WithMessage("addition %s isn't supported for %s(manifest version 2)", addition, ArtifactTypeWASM)
}
mani, _, err := m.RegCli.PullManifest(artifact.RepositoryName, artifact.Digest)
if err != nil {
return nil, err
}
_, content, err := mani.Payload()
if err != nil {
return nil, err
}
config := &v1.Image{}
if err = m.ManifestProcessor.UnmarshalConfig(ctx, artifact.RepositoryName, content, config); err != nil {
return nil, err
}
content, err = json.Marshal(config.History)
if err != nil {
return nil, err
}
return &processor.Addition{
Content: content,
ContentType: "application/json; charset=utf-8",
}, nil
}
func (m *Processor) GetArtifactType(ctx context.Context, artifact *artifact.Artifact) string {
return ArtifactTypeWASM
}
func (m *Processor) ListAdditionTypes(ctx context.Context, artifact *artifact.Artifact) []string {
return []string{AdditionTypeBuildHistory}
}

View File

@ -0,0 +1,180 @@
// 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 wasm
import (
"bytes"
"encoding/json"
"io/ioutil"
"strings"
"testing"
"github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/controller/artifact/processor/base"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/goharbor/harbor/src/testing/pkg/registry"
"github.com/stretchr/testify/suite"
)
var (
// For OCI fashion wasm artifact
oci_manifest = `{
"schemaVersion":2,
"config":{
"mediaType":"application/vnd.wasm.config.v1+json",
"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
"size":2
},
"layers":[
{
"mediaType":"application/vnd.wasm.content.layer.v1+wasm",
"digest":"sha256:d43012458290e4e2a350055bbe4a9f49fd4fb6b51d412089301e63ea4397ab4f",
"size":3951005,
"annotations":{
"org.opencontainers.image.title":"test.wasm"
}
}
]
}`
oci_config = `{}`
// For annotation fashion wasm artifact
annnotated_manifest = `{
"schemaVersion":2,
"mediaType":"application/vnd.oci.image.manifest.v1+json",
"config":{
"mediaType":"application/vnd.oci.image.config.v1+json",
"digest":"sha256:6fd90b7cd05366c82ca32a3ff259e62dcb15b3b5e9672fe7d45609f29b6c1e95",
"size":637
},
"layers":[
{
"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip",
"digest":"sha256:818ab7fdcd8d16270f01795d6aee7af0d1a06c71ce2cd3c1e8d8f946e9475450",
"size":500361
}
],
"annotations":{
"module.wasm.image/variant":"compat",
"org.opencontainers.image.base.digest":"",
"org.opencontainers.image.base.name":""
}
}`
annnotated_config = `{
"created":"2022-03-02T09:02:41.01773982Z",
"architecture":"amd64",
"os":"linux",
"config":{
"Env":[
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd":[
"/sleep.wasm"
],
"Labels":{
"io.buildah.version":"1.25.0-dev"
}
},
"rootfs":{
"type":"layers",
"diff_ids":[
"sha256:65885aa5fd4c157de98241de75669c4bf0d6f17d220c40069de31a572371a80d"
]
},
"history":[
{
"created":"2022-03-02T09:02:41.011039932Z",
"created_by":"/bin/sh -c #(nop) COPY file:9fc00231cd29a2b8f76cfeaa9bc2355a7df54585b51d1f5dc98daf51557614b9 in / ",
"empty_layer":true
},
{
"created":"2022-03-02T09:02:41.043350231Z",
"created_by":"/bin/sh -c #(nop) CMD [\"/sleep.wasm\"]"
}
]
}`
)
type WASMProcessorTestSuite struct {
suite.Suite
processor *Processor
regCli *registry.FakeClient
}
func (m *WASMProcessorTestSuite) SetupTest() {
m.regCli = &registry.FakeClient{}
m.processor = &Processor{}
m.processor.ManifestProcessor = &base.ManifestProcessor{RegCli: m.regCli}
}
func (m *WASMProcessorTestSuite) TestAbstractMetadataForAnnotationFashion() {
artifact := &artifact.Artifact{}
m.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(0, ioutil.NopCloser(bytes.NewReader([]byte(annnotated_config))), nil)
err := m.processor.AbstractMetadata(nil, artifact, []byte(annnotated_manifest))
m.Require().Nil(err)
m.NotNil(artifact.ExtraAttrs["created"])
m.Equal("amd64", artifact.ExtraAttrs["architecture"])
m.Equal("linux", artifact.ExtraAttrs["os"])
m.NotNil(artifact.ExtraAttrs["config"])
m.regCli.AssertExpectations(m.T())
}
func (m *WASMProcessorTestSuite) TestAbstractMetadataForOCIFashion() {
artifact := &artifact.Artifact{}
err := m.processor.AbstractMetadata(nil, artifact, []byte(oci_manifest))
m.Require().Nil(err)
m.NotNil(artifact.ExtraAttrs["org.opencontainers.image.title"])
m.Equal(MediaType, artifact.ExtraAttrs["manifest.config.mediaType"])
m.NotNil(artifact.ExtraAttrs["manifest.layers.mediaType"])
m.regCli.AssertExpectations(m.T())
}
func (m *WASMProcessorTestSuite) TestAbstractAdditionForAnnotationFashion() {
// unknown addition
_, err := m.processor.AbstractAddition(nil, nil, "unknown_addition")
m.True(errors.IsErr(err, errors.BadRequestCode))
// build history
artifact := &artifact.Artifact{}
manifest := schema2.Manifest{}
err = json.Unmarshal([]byte(annnotated_manifest), &manifest)
deserializedManifest, err := schema2.FromStruct(manifest)
m.Require().Nil(err)
m.regCli.On("PullManifest").Return(deserializedManifest, "", nil)
m.regCli.On("PullBlob").Return(0, ioutil.NopCloser(strings.NewReader(annnotated_config)), nil)
addition, err := m.processor.AbstractAddition(nil, artifact, AdditionTypeBuildHistory)
m.Require().Nil(err)
m.Equal("application/json; charset=utf-8", addition.ContentType)
m.Equal(`[{"created":"2022-03-02T09:02:41.011039932Z","created_by":"/bin/sh -c #(nop) COPY file:9fc00231cd29a2b8f76cfeaa9bc2355a7df54585b51d1f5dc98daf51557614b9 in / ","empty_layer":true},{"created":"2022-03-02T09:02:41.043350231Z","created_by":"/bin/sh -c #(nop) CMD [\"/sleep.wasm\"]"}]`, string(addition.Content))
}
func (m *WASMProcessorTestSuite) TestGetArtifactType() {
m.Assert().Equal(ArtifactTypeWASM, m.processor.GetArtifactType(nil, nil))
}
func (m *WASMProcessorTestSuite) TestListAdditionTypes() {
additions := m.processor.ListAdditionTypes(nil, nil)
m.EqualValues([]string{AdditionTypeBuildHistory}, additions)
}
func TestManifestV2ProcessorTestSuite(t *testing.T) {
suite.Run(t, &WASMProcessorTestSuite{})
}

View File

@ -60,6 +60,10 @@ var (
path: "./icons/nydus.png",
resize: false,
},
icon.DigestOfIconWASM: {
path: "./icons/wasm.png",
resize: true,
},
icon.DigestOfIconDefault: {
path: "./icons/default.png",
resize: true,

View File

@ -6,6 +6,7 @@ const (
DigestOfIconChart = "sha256:61cf3a178ff0f75bf08a25d96b75cf7355dc197749a9f128ed3ef34b0df05518"
DigestOfIconCNAB = "sha256:089bdda265c14d8686111402c8ad629e8177a1ceb7dcd0f7f39b6480f623b3bd"
DigestOfIconDefault = "sha256:da834479c923584f4cbcdecc0dac61f32bef1d51e8aae598cf16bd154efab49f"
DigestOfIconWASM = "sha256:badd7693bcaf115be202748241dd0ea6ee3b0524bfab9ac22d1e1c43721afec6"
// ToDo add the accessories images
DigestOfIconAccDefault = ""