mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-28 13:15:33 +01:00
Add new feature for supporting WebAssembly artifact (#16931)
support wasm Signed-off-by: ln23415 <ln23415@hotmail.com>
This commit is contained in:
parent
af802a4416
commit
7e4b26b220
BIN
icons/wasm.png
Normal file
BIN
icons/wasm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
@ -18,6 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/goharbor/harbor/src/controller/artifact/processor/wasm"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/lib/q"
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
"github.com/goharbor/harbor/src/pkg"
|
"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
|
// use the "manifest.config.mediatype" as the media type of the artifact
|
||||||
artifact.MediaType = manifest.Config.MediaType
|
artifact.MediaType = manifest.Config.MediaType
|
||||||
|
|
||||||
|
if manifest.Annotations[wasm.AnnotationVariantKey] == wasm.AnnotationVariantValue || manifest.Annotations[wasm.AnnotationHandlerKey] == wasm.AnnotationHandlerValue {
|
||||||
|
artifact.MediaType = wasm.MediaType
|
||||||
|
}
|
||||||
|
|
||||||
// set size
|
// set size
|
||||||
artifact.Size = int64(len(content)) + manifest.Config.Size
|
artifact.Size = int64(len(content)) + manifest.Config.Size
|
||||||
for _, layer := range manifest.Layers {
|
for _, layer := range manifest.Layers {
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/controller/artifact/processor/chart"
|
"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/cnab"
|
||||||
"github.com/goharbor/harbor/src/controller/artifact/processor/image"
|
"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/lib/icon"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/controller/artifact/processor"
|
"github.com/goharbor/harbor/src/controller/artifact/processor"
|
||||||
@ -72,6 +73,7 @@ var (
|
|||||||
image.ArtifactTypeImage: icon.DigestOfIconImage,
|
image.ArtifactTypeImage: icon.DigestOfIconImage,
|
||||||
chart.ArtifactTypeChart: icon.DigestOfIconChart,
|
chart.ArtifactTypeChart: icon.DigestOfIconChart,
|
||||||
cnab.ArtifactTypeCNAB: icon.DigestOfIconCNAB,
|
cnab.ArtifactTypeCNAB: icon.DigestOfIconCNAB,
|
||||||
|
wasm.ArtifactTypeWASM: icon.DigestOfIconWASM,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
135
src/controller/artifact/processor/wasm/wasm.go
Normal file
135
src/controller/artifact/processor/wasm/wasm.go
Normal 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}
|
||||||
|
}
|
180
src/controller/artifact/processor/wasm/wasm_test.go
Normal file
180
src/controller/artifact/processor/wasm/wasm_test.go
Normal 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 = ®istry.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{})
|
||||||
|
}
|
@ -60,6 +60,10 @@ var (
|
|||||||
path: "./icons/nydus.png",
|
path: "./icons/nydus.png",
|
||||||
resize: false,
|
resize: false,
|
||||||
},
|
},
|
||||||
|
icon.DigestOfIconWASM: {
|
||||||
|
path: "./icons/wasm.png",
|
||||||
|
resize: true,
|
||||||
|
},
|
||||||
icon.DigestOfIconDefault: {
|
icon.DigestOfIconDefault: {
|
||||||
path: "./icons/default.png",
|
path: "./icons/default.png",
|
||||||
resize: true,
|
resize: true,
|
||||||
|
@ -6,6 +6,7 @@ const (
|
|||||||
DigestOfIconChart = "sha256:61cf3a178ff0f75bf08a25d96b75cf7355dc197749a9f128ed3ef34b0df05518"
|
DigestOfIconChart = "sha256:61cf3a178ff0f75bf08a25d96b75cf7355dc197749a9f128ed3ef34b0df05518"
|
||||||
DigestOfIconCNAB = "sha256:089bdda265c14d8686111402c8ad629e8177a1ceb7dcd0f7f39b6480f623b3bd"
|
DigestOfIconCNAB = "sha256:089bdda265c14d8686111402c8ad629e8177a1ceb7dcd0f7f39b6480f623b3bd"
|
||||||
DigestOfIconDefault = "sha256:da834479c923584f4cbcdecc0dac61f32bef1d51e8aae598cf16bd154efab49f"
|
DigestOfIconDefault = "sha256:da834479c923584f4cbcdecc0dac61f32bef1d51e8aae598cf16bd154efab49f"
|
||||||
|
DigestOfIconWASM = "sha256:badd7693bcaf115be202748241dd0ea6ee3b0524bfab9ac22d1e1c43721afec6"
|
||||||
|
|
||||||
// ToDo add the accessories images
|
// ToDo add the accessories images
|
||||||
DigestOfIconAccDefault = ""
|
DigestOfIconAccDefault = ""
|
||||||
|
Loading…
Reference in New Issue
Block a user