diff --git a/pkg/common/accessio/blobaccess/compress.go b/pkg/common/accessio/blobaccess/compress.go new file mode 100644 index 0000000000..3270b2b7e5 --- /dev/null +++ b/pkg/common/accessio/blobaccess/compress.go @@ -0,0 +1,188 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package blobaccess + +import ( + "bytes" + "compress/gzip" + "io" + "sync" + + "github.com/opencontainers/go-digest" + + "github.com/open-component-model/ocm/pkg/common/accessio/blobaccess/spi" + compression2 "github.com/open-component-model/ocm/pkg/common/compression" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/mime" +) + +//////////////////////////////////////////////////////////////////////////////// + +type compression struct { + blob BlobAccess +} + +var _ spi.BlobAccessBase = (*compression)(nil) + +func (c *compression) Close() error { + return c.blob.Close() +} + +func (c *compression) Get() ([]byte, error) { + r, err := c.blob.Reader() + if err != nil { + return nil, err + } + defer r.Close() + rr, _, err := compression2.AutoDecompress(r) + if err != nil { + return nil, err + } + buf := bytes.NewBuffer(nil) + + w := gzip.NewWriter(buf) + _, err = io.Copy(w, rr) + w.Close() + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +type reader struct { + wait sync.WaitGroup + io.ReadCloser + err error +} + +func (r *reader) Close() error { + err := r.ReadCloser.Close() + r.wait.Wait() + return errors.Join(err, r.err) +} + +func (c *compression) Reader() (io.ReadCloser, error) { + r, err := c.blob.Reader() + if err != nil { + return nil, err + } + defer r.Close() + rr, _, err := compression2.AutoDecompress(r) + if err != nil { + return nil, err + } + pr, pw := io.Pipe() + cw := gzip.NewWriter(pw) + + outr := &reader{ReadCloser: pr} + outr.wait.Add(1) + + go func() { + _, err := io.Copy(cw, rr) + outr.err = errors.Join(err, cw.Close(), pw.Close()) + outr.wait.Done() + }() + return outr, nil +} + +func (c *compression) Digest() digest.Digest { + return BLOB_UNKNOWN_DIGEST +} + +func (c *compression) MimeType() string { + m := c.blob.MimeType() + if mime.IsGZip(m) { + return m + } + return m + "+gzip" +} + +func (c *compression) DigestKnown() bool { + return false +} + +func (c *compression) Size() int64 { + return BLOB_UNKNOWN_SIZE +} + +func WithCompression(blob BlobAccess) (BlobAccess, error) { + b, err := blob.Dup() + if err != nil { + return nil, err + } + return spi.NewBlobAccessForBase(&compression{ + blob: b, + }), nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type decompression struct { + blob BlobAccess +} + +var _ spi.BlobAccessBase = (*decompression)(nil) + +func (c *decompression) Close() error { + return c.blob.Close() +} + +func (c *decompression) Get() ([]byte, error) { + r, err := c.blob.Reader() + if err != nil { + return nil, err + } + defer r.Close() + rr, _, err := compression2.AutoDecompress(r) + if err != nil { + return nil, err + } + buf := bytes.NewBuffer(nil) + _, err = io.Copy(buf, rr) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (c *decompression) Reader() (io.ReadCloser, error) { + r, err := c.blob.Reader() + if err != nil { + return nil, err + } + defer r.Close() + rr, _, err := compression2.AutoDecompress(r) + return rr, err +} + +func (c *decompression) Digest() digest.Digest { + return BLOB_UNKNOWN_DIGEST +} + +func (c *decompression) MimeType() string { + m := c.blob.MimeType() + if !mime.IsGZip(m) { + return m + } + return m[:len(m)-5] +} + +func (c *decompression) DigestKnown() bool { + return false +} + +func (c *decompression) Size() int64 { + return BLOB_UNKNOWN_SIZE +} + +func WithDecompression(blob BlobAccess) (BlobAccess, error) { + b, err := blob.Dup() + if err != nil { + return nil, err + } + return spi.NewBlobAccessForBase(&decompression{ + blob: b, + }), nil +} diff --git a/pkg/common/accessio/blobaccess/compress_test.go b/pkg/common/accessio/blobaccess/compress_test.go new file mode 100644 index 0000000000..6b2c207f28 --- /dev/null +++ b/pkg/common/accessio/blobaccess/compress_test.go @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package blobaccess_test + +import ( + "bytes" + "compress/gzip" + "io" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/open-component-model/ocm/pkg/common/accessio/blobaccess" + "github.com/open-component-model/ocm/pkg/mime" +) + +var _ = Describe("temp file management", func() { + + Context("compress", func() { + It("compress access", func() { + blob := blobaccess.ForString(mime.MIME_TEXT, "testdata") + defer blob.Close() + + comp := Must(blobaccess.WithCompression(blob)) + defer comp.Close() + + Expect(comp.MimeType()).To(Equal(mime.MIME_TEXT + "+gzip")) + data := Must(comp.Get()) + Expect(len(data)).To(Not(Equal(8))) + + uncomp := Must(io.ReadAll(Must(gzip.NewReader(bytes.NewReader(data))))) + Expect(string(uncomp)).To(Equal("testdata")) + }) + + It("compress reader access", func() { + blob := blobaccess.ForString(mime.MIME_TEXT, "testdata") + defer blob.Close() + + comp := Must(blobaccess.WithCompression(blob)) + defer comp.Close() + + r := Must(comp.Reader()) + data := Must(io.ReadAll(r)) + Expect(len(data)).To(Not(Equal(8))) + + uncomp := Must(io.ReadAll(Must(gzip.NewReader(bytes.NewReader(data))))) + Expect(string(uncomp)).To(Equal("testdata")) + }) + }) + + Context("uncompress", func() { + buf := bytes.NewBuffer(nil) + cw := gzip.NewWriter(buf) + MustBeSuccessful(io.WriteString(cw, "testdata")) + cw.Close() + + It("uncompress access", func() { + blob := blobaccess.ForData(mime.MIME_TEXT+"+gzip", buf.Bytes()) + defer blob.Close() + + comp := Must(blobaccess.WithDecompression(blob)) + defer comp.Close() + Expect(comp.MimeType()).To(Equal(mime.MIME_TEXT)) + + data := Must(comp.Get()) + Expect(string(data)).To(Equal("testdata")) + }) + + It("compress reader access", func() { + blob := blobaccess.ForData(mime.MIME_TEXT+"+gzip", buf.Bytes()) + defer blob.Close() + + comp := Must(blobaccess.WithDecompression(blob)) + defer comp.Close() + + r := Must(comp.Reader()) + data := Must(io.ReadAll(r)) + Expect(string(data)).To(Equal("testdata")) + }) + }) + +}) diff --git a/pkg/common/accessio/blobaccess/spi/utils.go b/pkg/common/accessio/blobaccess/spi/utils.go index 1621a27f75..789d026ba5 100644 --- a/pkg/common/accessio/blobaccess/spi/utils.go +++ b/pkg/common/accessio/blobaccess/spi/utils.go @@ -187,6 +187,10 @@ func (s *staticBlobAccess) Dup() (BlobAccess, error) { return s, nil } +func (s *staticBlobAccess) Close() error { + return nil +} + // ForStaticDataAccess is used for a data access using no closer. // They don't require a finalization and can be used // as long as they exist. Therefore, no ref counting diff --git a/pkg/common/accessio/blobaccess/spi/view.go b/pkg/common/accessio/blobaccess/spi/view.go index 58797f3a3b..078bf1601d 100644 --- a/pkg/common/accessio/blobaccess/spi/view.go +++ b/pkg/common/accessio/blobaccess/spi/view.go @@ -33,6 +33,10 @@ func (b *blobAccessView) base() BlobAccessBase { return b.access } +func (b *blobAccessView) Close() error { + return b.View.Close() +} + func (b *blobAccessView) Validate() error { return utils.ValidateObject(b.access) } @@ -41,9 +45,8 @@ func (b *blobAccessView) Get() (result []byte, err error) { return result, b.Execute(func() error { result, err = b.access.Get() if err != nil { - return fmt.Errorf("unable to get access: %w", err) + return err } - return nil }) } diff --git a/pkg/common/accessio/blobaccess/standard.go b/pkg/common/accessio/blobaccess/standard.go index 4faffa8080..87c0f2de94 100644 --- a/pkg/common/accessio/blobaccess/standard.go +++ b/pkg/common/accessio/blobaccess/standard.go @@ -63,6 +63,12 @@ func (b *blobprovider) Close() error { // as long as the given blob access is not closed. // If required the blob can be closed with the additionally // provided Close method. +// ATTENTION: the underlying BlobAccess wil not be closed +// as long as the provider is not closed, but the BlobProvider +// interface is no io.Closer. +// To be on the safe side, this method should only be called +// with static blob access, featuring a NOP closer without +// anny attached external resources, which should be released. func ProviderForBlobAccess(blob BlobAccess) *blobprovider { return &blobprovider{blob} } diff --git a/pkg/common/accessio/refmgmt/refcloser.go b/pkg/common/accessio/refmgmt/refcloser.go index 7c009cb82a..9588f4d033 100644 --- a/pkg/common/accessio/refmgmt/refcloser.go +++ b/pkg/common/accessio/refmgmt/refcloser.go @@ -68,7 +68,23 @@ type LazyMode interface { Lazy() } +// ToLazy resets the main view flag +// of closer views to enable +// dark release of resources even if the +// first/main view has been closed. +// Otherwise, closing the main view will +// fail, if there are still subsequent views. +func ToLazy[T any](o T, err error) (T, error) { + if err == nil { + Lazy(o) + } + return o, err +} + func Lazy(o interface{}) bool { + if o == nil { + return false + } if l, ok := o.(LazyMode); ok { l.Lazy() return true @@ -125,6 +141,9 @@ func (v *view) Execute(f func() error) error { return f() } +// Release will release the view. +// With releasing the last view +// the underlying object will be closed. func (v *view) Release() error { v.lock.Lock() defer v.lock.Unlock() @@ -135,6 +154,10 @@ func (v *view) Release() error { return v.ref.Unref() } +// Finalize will try to finalize the +// underlying object. This is only +// possible if no further view is +// still pending. func (v *view) Finalize() error { v.lock.Lock() defer v.lock.Unlock() diff --git a/pkg/contexts/ocm/cpi/view.go b/pkg/contexts/ocm/cpi/view.go index 2eb38ac55a..7da21f6ff6 100644 --- a/pkg/contexts/ocm/cpi/view.go +++ b/pkg/contexts/ocm/cpi/view.go @@ -13,6 +13,7 @@ import ( "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" "github.com/open-component-model/ocm/pkg/common/accessio/blobaccess" + "github.com/open-component-model/ocm/pkg/common/accessio/refmgmt" "github.com/open-component-model/ocm/pkg/common/accessio/resource" "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" @@ -150,6 +151,26 @@ func (r *repositoryView) LookupComponent(name string) (acc ComponentAccess, err return acc, err } +func (r *repositoryView) NewVersion(comp, vers string, overrides ...bool) (ComponentVersionAccess, error) { + c, err := refmgmt.ToLazy(r.LookupComponent(comp)) + if err != nil { + return nil, err + } + defer c.Close() + + return c.NewVersion(vers, overrides...) +} + +func (r *repositoryView) AddVersion(cv ComponentVersionAccess, overrides ...bool) error { + c, err := refmgmt.ToLazy(r.LookupComponent(cv.GetName())) + if err != nil { + return err + } + defer c.Close() + + return c.AddVersion(cv, overrides...) +} + //////////////////////////////////////////////////////////////////////////////// type _ComponentAccessView interface { @@ -166,6 +187,8 @@ type ComponentAccessImpl interface { GetName() string IsOwned(access ComponentVersionAccess) bool + + AddVersion(cv ComponentVersionAccess) error } type _ComponentAccessImplBase = resource.ResourceImplBase[ComponentAccess] @@ -245,16 +268,16 @@ func (c *componentAccessView) LookupVersion(version string) (acc ComponentVersio return acc, err } -func (c *componentAccessView) AddVersion(acc ComponentVersionAccess) error { +func (c *componentAccessView) AddVersion(acc ComponentVersionAccess, overrides ...bool) error { if acc.GetName() != c.GetName() { return errors.ErrInvalid("component name", acc.GetName()) } return c.Execute(func() error { - return c.addVersion(acc) + return c.addVersion(acc, overrides...) }) } -func (c *componentAccessView) addVersion(acc ComponentVersionAccess) (ferr error) { +func (c *componentAccessView) addVersion(acc ComponentVersionAccess, overrides ...bool) (ferr error) { var finalize finalizer.Finalizer defer finalize.FinalizeWithErrorPropagation(&ferr) @@ -275,7 +298,7 @@ func (c *componentAccessView) addVersion(acc ComponentVersionAccess) (ferr error // transfer all local blobs into a new owned version. sel = func(spec AccessSpec) bool { return spec.IsLocal(ctx) } - eff, err = c.impl.NewVersion(acc.GetVersion(), true) + eff, err = c.impl.NewVersion(acc.GetVersion(), overrides...) if err != nil { return err } @@ -654,10 +677,7 @@ func (c *componentVersionAccessView) accessMethod(spec AccessSpec) (meth AccessM meth, err = c.impl.AccessMethod(c, spec) if err == nil { if blob := c.getLocalBlob(spec); blob != nil { - meth = &fakeMethod{ - AccessMethod: meth, - blob: blob, - } + meth, err = newFakeMethod(meth, blob) } } } @@ -847,8 +867,51 @@ func (c *componentVersionAccessView) SetSourceBlob(meta *SourceMeta, blob BlobAc } type fakeMethod struct { - AccessMethod `json:",inline"` - blob blobaccess.BlobAccess + spec AccessSpec + local bool + mime string + blob blobaccess.BlobAccess +} + +var _ AccessMethod = (*fakeMethod)(nil) + +func newFakeMethod(m AccessMethod, blob BlobAccess) (AccessMethod, error) { + b, err := blob.Dup() + if err != nil { + return nil, errors.Wrapf(err, "cannot remember blob for access method") + } + f := &fakeMethod{ + spec: m.AccessSpec(), + local: m.IsLocal(), + mime: m.MimeType(), + blob: b, + } + err = m.Close() + if err != nil { + _ = b.Close() + return nil, errors.Wrapf(err, "closing access method") + } + return f, nil +} + +func (f *fakeMethod) MimeType() string { + return f.mime +} + +func (f *fakeMethod) IsLocal() bool { + return f.local +} + +func (f *fakeMethod) GetKind() string { + return f.spec.GetKind() +} + +func (f *fakeMethod) AccessSpec() internal.AccessSpec { + return f.spec +} + +func (f *fakeMethod) Close() error { + return f.blob.Close() } func (f *fakeMethod) Reader() (io.ReadCloser, error) { diff --git a/pkg/contexts/ocm/internal/repository.go b/pkg/contexts/ocm/internal/repository.go index 73c7f5cf8e..63ad2c7b1c 100644 --- a/pkg/contexts/ocm/internal/repository.go +++ b/pkg/contexts/ocm/internal/repository.go @@ -34,8 +34,10 @@ type RepositoryImpl interface { type Repository interface { resource.ResourceView[Repository] - RepositoryImpl + + NewVersion(comp, version string, overrides ...bool) (ComponentVersionAccess, error) + AddVersion(cv ComponentVersionAccess, overrides ...bool) error } // ConsumerIdentityProvider is an interface for object requiring @@ -56,7 +58,6 @@ type ComponentAccessImpl interface { ListVersions() ([]string, error) LookupVersion(version string) (ComponentVersionAccess, error) HasVersion(vers string) (bool, error) - AddVersion(ComponentVersionAccess) error NewVersion(version string, overrides ...bool) (ComponentVersionAccess, error) Close() error @@ -66,6 +67,7 @@ type ComponentAccess interface { resource.ResourceView[ComponentAccess] ComponentAccessImpl + AddVersion(cv ComponentVersionAccess, overrides ...bool) error } // AccessProvider assembled methods provided diff --git a/pkg/contexts/ocm/repositories/composition/repository_test.go b/pkg/contexts/ocm/repositories/composition/repository_test.go index 7048bf7328..4e10b73df2 100644 --- a/pkg/contexts/ocm/repositories/composition/repository_test.go +++ b/pkg/contexts/ocm/repositories/composition/repository_test.go @@ -23,7 +23,7 @@ import ( const COMPONENT = "acme.org/testcomp" const VERSION = "1.0.0" -var _ = Describe("access method", func() { +var _ = Describe("repository", func() { var ctx = ocm.DefaultContext() It("handles cvs", func() { diff --git a/pkg/contexts/ocm/repositories/composition/version.go b/pkg/contexts/ocm/repositories/composition/version.go index ae64b1242c..e5fb7ded02 100644 --- a/pkg/contexts/ocm/repositories/composition/version.go +++ b/pkg/contexts/ocm/repositories/composition/version.go @@ -20,7 +20,7 @@ func NewComponentVersion(ctx cpi.ContextProvider, name, vers string) cpi.Compone panic("wrong composition repo implementation: " + err.Error()) } defer c.Close() - cv, err := c.LookupVersion(vers) + cv, err := c.NewVersion(vers) if err != nil { panic("wrong composition repo implementation: " + err.Error()) } diff --git a/pkg/contexts/ocm/repositories/composition/version_test.go b/pkg/contexts/ocm/repositories/composition/version_test.go index 74f75146bf..6823e39b0d 100644 --- a/pkg/contexts/ocm/repositories/composition/version_test.go +++ b/pkg/contexts/ocm/repositories/composition/version_test.go @@ -1,5 +1,72 @@ -// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. // // SPDX-License-Identifier: Apache-2.0 -package composition +package composition_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/open-component-model/ocm/pkg/common/accessio/blobaccess" + "github.com/open-component-model/ocm/pkg/common/accessio/blobaccess/spi" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + me "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" + "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" + "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/open-component-model/ocm/pkg/mime" +) + +var _ = Describe("version", func() { + var ctx = ocm.DefaultContext() + + It("handles anonymous version", func() { + finalize := finalizer.Finalizer{} + defer Defer(finalize.Finalize) + + nested := finalize.Nested() + + // compose new version + cv := me.NewComponentVersion(ctx, COMPONENT, VERSION) + cv.GetDescriptor().Provider.Name = "acme.org" + nested.Close(cv, "composed version") + + // wrap a non-closer access into a ref counting access to check cleanup + blob := spi.NewBlobAccessForBase(blobaccess.ForString(mime.MIME_TEXT, "testdata")) + nested.Close(blob, "blob") + MustBeSuccessful(cv.SetResourceBlob(ocm.NewResourceMeta("test", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), blob, "", nil)) + + // add version to repository + repo1 := me.NewRepository(ctx) + finalize.Close(repo1, "target repo1") + c := Must(repo1.LookupComponent(COMPONENT)) + finalize.Close(c, "src comp") + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(nested.Finalize()) + + // check result + cv = Must(c.LookupVersion(VERSION)) + nested.Close(cv, "query") + rs := Must(cv.GetResourcesByName("test")) + Expect(len(rs)).To(Equal(1)) + data := Must(ocmutils.GetResourceData(rs[0])) + Expect(string(data)).To(Equal("testdata")) + + // add this version again + repo2 := me.NewRepository(ctx) + finalize.Close(repo2, "target repo2") + MustBeSuccessful(repo2.AddVersion(cv)) + MustBeSuccessful(nested.Finalize()) + + // check result + cv = Must(repo2.LookupComponentVersion(COMPONENT, VERSION)) + finalize.Close(cv, "query") + rs = Must(cv.GetResourcesByName("test")) + Expect(len(rs)).To(Equal(1)) + data = Must(ocmutils.GetResourceData(rs[0])) + Expect(string(data)).To(Equal("testdata")) + }) +}) diff --git a/pkg/contexts/ocm/repositories/virtual/accessmethod_localblob.go b/pkg/contexts/ocm/repositories/virtual/accessmethod_localblob.go index 447b3c621c..ff1e8230f7 100644 --- a/pkg/contexts/ocm/repositories/virtual/accessmethod_localblob.go +++ b/pkg/contexts/ocm/repositories/virtual/accessmethod_localblob.go @@ -11,7 +11,6 @@ import ( "github.com/open-component-model/ocm/pkg/common/accessio/blobaccess" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/errors" ) type localBlobAccessMethod struct { @@ -82,7 +81,6 @@ func (m *localBlobAccessMethod) Get() (data []byte, ferr error) { if ferr != nil { return nil, err } - defer errors.PropagateError(&ferr, b.Close) return blobaccess.BlobData(b) } diff --git a/pkg/contexts/ocm/resourcetypes/data/options.go b/pkg/contexts/ocm/resourcetypes/data/options.go index 76d1d88769..53faf8cda7 100644 --- a/pkg/contexts/ocm/resourcetypes/data/options.go +++ b/pkg/contexts/ocm/resourcetypes/data/options.go @@ -12,9 +12,18 @@ import ( type Option = optionutils.Option[*Options] +type compressionMode string + +const ( + COMPRESSION = compressionMode("compression") + DECOMPRESSION = compressionMode("decompression") + NONE = compressionMode("") +) + type Options struct { rpi.Options - MimeType string + MimeType string + Compression compressionMode } var _ rpi.GeneralOptionsProvider = (*Options)(nil) @@ -51,3 +60,21 @@ func (o mimetype) ApplyTo(opts *Options) { func WithMimeType(mime string) Option { return mimetype{mime} } + +//////////////////////////////////////////////////////////////////////////////// + +type compression struct { + mode compressionMode +} + +func (o compression) ApplyTo(opts *Options) { + opts.Compression = o.mode +} + +func WithCompression() Option { + return compression{COMPRESSION} +} + +func WithDecompression() Option { + return compression{DECOMPRESSION} +} diff --git a/pkg/contexts/ocm/resourcetypes/data/resource.go b/pkg/contexts/ocm/resourcetypes/data/resource.go index 1a975d6fc9..014bf9d30e 100644 --- a/pkg/contexts/ocm/resourcetypes/data/resource.go +++ b/pkg/contexts/ocm/resourcetypes/data/resource.go @@ -16,10 +16,29 @@ import ( func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, blob []byte, opts ...Option) cpi.ArtifactAccess[M] { eff := optionutils.EvalOptions(opts...) - if eff.MimeType == "" { - eff.MimeType = mime.MIME_OCTET + + media := eff.MimeType + if media == "" { + media = mime.MIME_OCTET + } + + var blobprov blobaccess.BlobAccessProvider + switch eff.Compression { + case NONE: + blobprov = blobaccess.ProviderForData(media, blob) + case COMPRESSION: + blob := blobaccess.ForData(media, blob) + defer blob.Close() + blob, _ = blobaccess.WithCompression(blob) + blobprov = blobaccess.ProviderForBlobAccess(blob) + case DECOMPRESSION: + blob := blobaccess.ForData(media, blob) + defer blob.Close() + blob, _ = blobaccess.WithDecompression(blob) + blobprov = blobaccess.ProviderForBlobAccess(blob) } - accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobaccess.ProviderForData(eff.MimeType, blob), eff.Hint, eff.Global) + + accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, eff.Hint, eff.Global) // strange type cast is required by Go compiler, meta has the correct type. return cpi.NewArtifactAccessForProvider(generics.As[*M](meta), accprov) } diff --git a/pkg/contexts/ocm/resourcetypes/file/options.go b/pkg/contexts/ocm/resourcetypes/file/options.go index a230b97713..7a44099017 100644 --- a/pkg/contexts/ocm/resourcetypes/file/options.go +++ b/pkg/contexts/ocm/resourcetypes/file/options.go @@ -14,9 +14,18 @@ import ( type Option = optionutils.Option[*Options] +type compressionMode string + +const ( + COMPRESSION = compressionMode("compression") + DECOMPRESSION = compressionMode("decompression") + NONE = compressionMode("") +) + type Options struct { rpi.Options - FileSystem vfs.FileSystem + FileSystem vfs.FileSystem + Compression compressionMode } var _ rpi.GeneralOptionsProvider = (*Options)(nil) @@ -53,3 +62,21 @@ func (o filesystem) ApplyTo(opts *Options) { func WithFileSystem(fs vfs.FileSystem) Option { return filesystem{fs} } + +//////////////////////////////////////////////////////////////////////////////// + +type compression struct { + mode compressionMode +} + +func (o compression) ApplyTo(opts *Options) { + opts.Compression = o.mode +} + +func WithCompression() Option { + return compression{COMPRESSION} +} + +func WithDecompression() Option { + return compression{DECOMPRESSION} +} diff --git a/pkg/contexts/ocm/resourcetypes/file/resource.go b/pkg/contexts/ocm/resourcetypes/file/resource.go index 40e228e035..10c9e6aa57 100644 --- a/pkg/contexts/ocm/resourcetypes/file/resource.go +++ b/pkg/contexts/ocm/resourcetypes/file/resource.go @@ -26,7 +26,22 @@ func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, media str media = mime.MIME_OCTET } - blobprov := blobaccess.ProviderForFile(media, path, eff.FileSystem) + var blobprov blobaccess.BlobAccessProvider + switch eff.Compression { + case NONE: + blobprov = blobaccess.ProviderForFile(media, path, eff.FileSystem) + case COMPRESSION: + blob := blobaccess.ForFile(media, path, eff.FileSystem) + defer blob.Close() + blob, _ = blobaccess.WithCompression(blob) + blobprov = blobaccess.ProviderForBlobAccess(blob) + case DECOMPRESSION: + blob := blobaccess.ForFile(media, path, eff.FileSystem) + defer blob.Close() + blob, _ = blobaccess.WithDecompression(blob) + blobprov = blobaccess.ProviderForBlobAccess(blob) + } + accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, eff.Hint, eff.Global) // strange type cast is required by Go compiler, meta has the correct type. return cpi.NewArtifactAccessForProvider(generics.As[*M](meta), accprov) diff --git a/pkg/contexts/ocm/resourcetypes/text/options.go b/pkg/contexts/ocm/resourcetypes/text/options.go index 4264683538..363609e860 100644 --- a/pkg/contexts/ocm/resourcetypes/text/options.go +++ b/pkg/contexts/ocm/resourcetypes/text/options.go @@ -14,6 +14,12 @@ type ( Options = data.Options ) +const ( + COMPRESSION = data.COMPRESSION + DECOMPRESSION = data.DECOMPRESSION + NONE = data.NONE +) + //////////////////////////////////////////////////////////////////////////////// // General Options @@ -31,3 +37,11 @@ func WithGlobalAccess(a cpi.AccessSpec) Option { func WithimeType(mime string) Option { return data.WithMimeType(mime) } + +func WithCompression() Option { + return data.WithCompression() +} + +func WithDecompression() Option { + return data.WithDecompression() +} diff --git a/pkg/contexts/ocm/resourcetypes/text/resource.go b/pkg/contexts/ocm/resourcetypes/text/resource.go index 4c68c77993..37a197faff 100644 --- a/pkg/contexts/ocm/resourcetypes/text/resource.go +++ b/pkg/contexts/ocm/resourcetypes/text/resource.go @@ -5,19 +5,14 @@ package text import ( - "github.com/open-component-model/ocm/pkg/common/accessio/blobaccess" "github.com/open-component-model/ocm/pkg/contexts/ocm" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/generics" - "github.com/open-component-model/ocm/pkg/optionutils" + "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes/data" ) func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, blob string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobaccess.ProviderForString(eff.MimeType, blob), eff.Hint, eff.Global) - // strange type cast is required by Go compiler, meta has the correct type. - return cpi.NewArtifactAccessForProvider(generics.As[*M](meta), accprov) + return data.Access(ctx, meta, []byte(blob), opts...) } func ResourceAccess(ctx ocm.Context, media string, meta *ocm.ResourceMeta, blob string, opts ...Option) cpi.ResourceAccess {