From 5ffd6d1a144fb617c4773bf4908fa3337afd0d87 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 23 Apr 2024 14:17:03 +0200 Subject: [PATCH 1/2] Rework object finalization (#703) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description ## What type of PR is this? (check all applicable) - [ ] 🍕 Feature - [ ] 🐛 Bug Fix - [ ] 📝 Documentation Update - [ ] 🎨 Style - [ ] 🧑‍💻 Code Refactor - [ ] 🔥 Performance Improvements - [ ] ✅ Test - [ ] 🤖 Build - [ ] 🔁 CI - [ ] 📦 Chore (Release) - [ ] ⏩ Revert ## Related Tickets & Documents - Related Issue # (issue) - Closes # (issue) - Fixes # (issue) > Remove if not applicable ## Screenshots ## Added tests? - [ ] 👍 yes - [ ] 🙅 no, because they aren't needed - [ ] 🙋 no, because I need help - [ ] Separate ticket for tests # (issue/pr) Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration ## Added to documentation? - [ ] 📜 README.md - [ ] 🙅 no documentation needed ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --- pkg/contexts/clictx/internal/context.go | 99 ++++++++---- pkg/contexts/config/config/context_test.go | 46 +++++- pkg/contexts/config/config/dummy_test.go | 11 ++ pkg/contexts/config/context_test.go | 2 + pkg/contexts/config/cpi/interface.go | 4 + pkg/contexts/config/internal/context.go | 31 +++- pkg/contexts/config/internal/updater.go | 35 +++- .../credentials/internal/builder_test.go | 6 +- pkg/contexts/credentials/internal/context.go | 33 +++- .../repositories/dockerconfig/repo_test.go | 2 + .../repositories/dockerconfig/repository.go | 3 +- .../datacontext/attrs/rootcertsattr/config.go | 2 +- .../datacontext/context-refcount-model.png | Bin 0 -> 259864 bytes pkg/contexts/datacontext/context.go | 61 ++++--- pkg/contexts/datacontext/context_test.go | 27 ++++ pkg/contexts/datacontext/cpi.go | 152 +++++++++++++++--- pkg/contexts/datacontext/gc_test.go | 58 ++++++- pkg/contexts/oci/internal/builder_test.go | 2 +- pkg/contexts/oci/internal/context.go | 35 ++-- pkg/contexts/ocm/attrs/signingattr/config.go | 5 +- .../handlers/generic/ocirepo/blobhandler.go | 1 + pkg/contexts/ocm/internal/builder_test.go | 8 +- pkg/contexts/ocm/internal/context.go | 35 ++-- pkg/contexts/ocm/internal/resolver.go | 27 +++- .../ocm/repositories/comparch/repository.go | 4 +- .../repositories/composition/repository.go | 8 +- .../repositories/genericocireg/repository.go | 4 +- .../ocm/repositories/virtual/repository.go | 5 +- pkg/finalizer/object.go | 2 +- pkg/refmgmt/finalized/doc.go | 33 ++++ pkg/refmgmt/finalized/finalized_test.go | 146 +++++++++++++++++ pkg/refmgmt/finalized/finalizedref.go | 48 ++++++ pkg/refmgmt/finalized/suite_test.go | 17 ++ 33 files changed, 795 insertions(+), 157 deletions(-) create mode 100755 pkg/contexts/datacontext/context-refcount-model.png create mode 100644 pkg/contexts/datacontext/context_test.go create mode 100644 pkg/refmgmt/finalized/doc.go create mode 100644 pkg/refmgmt/finalized/finalized_test.go create mode 100644 pkg/refmgmt/finalized/finalizedref.go create mode 100644 pkg/refmgmt/finalized/suite_test.go diff --git a/pkg/contexts/clictx/internal/context.go b/pkg/contexts/clictx/internal/context.go index ef30d76158..d6a3cfa70c 100644 --- a/pkg/contexts/clictx/internal/context.go +++ b/pkg/contexts/clictx/internal/context.go @@ -24,6 +24,7 @@ import ( ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/out" + "github.com/open-component-model/ocm/pkg/utils" ) const CONTEXT_TYPE = "ocm.cmd" + datacontext.OCM_CONTEXT_SUFFIX @@ -47,15 +48,18 @@ func (f *FileSystem) ApplyOption(options accessio.Options) error { return nil } +type ContextProvider interface { + CLIContext() Context +} + type Context interface { datacontext.Context - - AttributesContext() datacontext.AttributesContext - - ConfigContext() config.Context - CredentialsContext() credentials.Context - OCIContext() oci.Context - OCMContext() ocm.Context + ContextProvider + datacontext.ContextProvider + config.ContextProvider + credentials.ContextProvider + oci.ContextProvider + ocm.ContextProvider FileSystem() *FileSystem @@ -91,13 +95,14 @@ func DefinedForContext(ctx context.Context) (Context, bool) { //////////////////////////////////////////////////////////////////////////////// +type _InternalContext = datacontext.InternalContext + type _context struct { - datacontext.Context + _InternalContext updater cfgcpi.Updater sharedAttributes datacontext.AttributesContext - config config.Context credentials credentials.Context oci *_oci ocm *_ocm @@ -105,7 +110,29 @@ type _context struct { out out.Context } -var _ Context = &_context{} +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) + +// gcWrapper is used as garbage collectable +// wrapper for a context implementation +// to establish a runtime finalizer. +type gcWrapper struct { + datacontext.GCWrapper + *_context +} + +func newView(c *_context, ref ...bool) Context { + if utils.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + +func (w *gcWrapper) SetContext(c *_context) { + w._context = c +} func newContext(shared datacontext.AttributesContext, ocmctx ocm.Context, outctx out.Context, fs vfs.FileSystem, delegates datacontext.Delegates) Context { if outctx == nil { @@ -115,19 +142,27 @@ func newContext(shared datacontext.AttributesContext, ocmctx ocm.Context, outctx shared = ocmctx.AttributesContext() } c := &_context{ - sharedAttributes: shared, - credentials: ocmctx.CredentialsContext(), - config: ocmctx.CredentialsContext().ConfigContext(), + sharedAttributes: datacontext.PersistentContextRef(shared), + credentials: datacontext.PersistentContextRef(ocmctx.CredentialsContext()), out: outctx, } - c.Context = datacontext.NewContextBase(c, CONTEXT_TYPE, key, ocmctx.GetAttributes(), delegates) - c.updater = cfgcpi.NewUpdater(ocmctx.CredentialsContext().ConfigContext(), c) + c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, ocmctx.GetAttributes(), delegates) + c.updater = cfgcpi.NewUpdater(datacontext.PersistentContextRef(ocmctx.CredentialsContext().ConfigContext()), c) + ocmctx = datacontext.PersistentContextRef(ocmctx) c.oci = newOCI(c, ocmctx) c.ocm = newOCM(c, ocmctx) if fs != nil { vfsattr.Set(c.AttributesContext(), fs) } - return c + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) +} + +func (c *_context) CLIContext() Context { + return newView(c) } func (c *_context) Update() error { @@ -139,7 +174,7 @@ func (c *_context) AttributesContext() datacontext.AttributesContext { } func (c *_context) ConfigContext() config.Context { - return c.config + return c.updater.GetContext() } func (c *_context) CredentialsContext() credentials.Context { @@ -155,7 +190,7 @@ func (c *_context) OCMContext() ocm.Context { } func (c *_context) FileSystem() *FileSystem { - return &FileSystem{vfsattr.Get(c)} + return &FileSystem{vfsattr.Get(c.CLIContext())} } func (c *_context) OCI() OCI { @@ -185,7 +220,7 @@ func (c *_context) StdIn() io.Reader { func (c *_context) WithStdIO(r io.Reader, o io.Writer, e io.Writer) Context { return &_view{ - Context: c, + Context: c.CLIContext(), out: out.NewFor(out.WithStdIO(c.out, r, o, e)), } } @@ -211,7 +246,7 @@ func (c *_view) StdIn() io.Reader { func (c *_view) WithStdIO(r io.Reader, o io.Writer, e io.Writer) Context { return &_view{ - Context: c.Context, + Context: c.CLIContext(), out: out.NewFor(out.WithStdIO(c.out, r, o, e)), } } @@ -222,16 +257,16 @@ func (c *_view) WithStdIO(r io.Reader, o io.Writer, e io.Writer) Context { // _ocm uses ocm and ctfocm type _oci struct { - *_context + cli *_context ctx oci.Context repos map[string]oci.RepositorySpec } func newOCI(ctx *_context, ocmctx ocm.Context) *_oci { return &_oci{ - _context: ctx, - ctx: ocmctx.OCIContext(), - repos: map[string]oci.RepositorySpec{}, + cli: ctx, + ctx: ocmctx.OCIContext(), + repos: map[string]oci.RepositorySpec{}, } } @@ -240,29 +275,29 @@ func (c *_oci) Context() oci.Context { } func (c *_oci) OpenCTF(path string) (oci.Repository, error) { - ok, err := vfs.Exists(c.FileSystem(), path) + ok, err := vfs.Exists(c.cli.FileSystem(), path) if err != nil { return nil, err } if !ok { return nil, errors.ErrNotFound("file", path) } - return ctfoci.Open(c.ctx, accessobj.ACC_WRITABLE, path, 0, accessio.PathFileSystem(c.FileSystem())) + return ctfoci.Open(c.ctx, accessobj.ACC_WRITABLE, path, 0, accessio.PathFileSystem(c.cli.FileSystem())) } //////////////////////////////////////////////////////////////////////////////// type _ocm struct { - *_context + cli *_context ctx ocm.Context repos map[string]ocm.RepositorySpec } func newOCM(ctx *_context, ocmctx ocm.Context) *_ocm { return &_ocm{ - _context: ctx, - ctx: ocmctx, - repos: map[string]ocm.RepositorySpec{}, + cli: ctx, + ctx: ocmctx, + repos: map[string]ocm.RepositorySpec{}, } } @@ -271,12 +306,12 @@ func (c *_ocm) Context() ocm.Context { } func (c *_ocm) OpenCTF(path string) (ocm.Repository, error) { - ok, err := vfs.Exists(c.FileSystem(), path) + ok, err := vfs.Exists(c.cli.FileSystem(), path) if err != nil { return nil, err } if !ok { return nil, errors.ErrNotFound("file", path) } - return ctfocm.Open(c.ctx, accessobj.ACC_WRITABLE, path, 0, c) + return ctfocm.Open(c.ctx, accessobj.ACC_WRITABLE, path, 0, c.cli.FileSystem()) } diff --git a/pkg/contexts/config/config/context_test.go b/pkg/contexts/config/config/context_test.go index 161f87edaf..7fffc81209 100644 --- a/pkg/contexts/config/config/context_test.go +++ b/pkg/contexts/config/config/context_test.go @@ -7,17 +7,27 @@ package config_test import ( "os" "reflect" + "runtime" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" + "github.com/open-component-model/ocm/pkg/generics" "sigs.k8s.io/yaml" "github.com/open-component-model/ocm/pkg/contexts/config" local "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/testutils" + . "github.com/open-component-model/ocm/pkg/testutils" ) +func CheckRefs(ctx config.Context, n int) { + runtime.GC() + time.Sleep(time.Second) + Expect(datacontext.GetContextRefCount(ctx)).To(Equal(n)) // all temp refs have been finalized +} + var _ = Describe("generic config handling", func() { var scheme config.ConfigTypeScheme @@ -41,6 +51,8 @@ var _ = Describe("generic config handling", func() { Expect(err).To(Succeed()) Expect(config.IsGeneric(result)).To(BeFalse()) Expect(reflect.TypeOf(result).String()).To(Equal("*config.Config")) + + CheckRefs(cfgctx, 1) }) It("it applies to existing context", func() { @@ -57,6 +69,8 @@ var _ = Describe("generic config handling", func() { Expect(len(cfgs)).To(Equal(3)) Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) + + CheckRefs(cfgctx, 1) }) It("it applies nested to existing context", func() { @@ -73,6 +87,8 @@ var _ = Describe("generic config handling", func() { Expect(len(cfgs)).To(Equal(4)) Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) + + CheckRefs(cfgctx, 1) }) It("it applies unknown type to existing context", func() { @@ -82,7 +98,7 @@ var _ = Describe("generic config handling", func() { err = cfgctx.ApplyConfig(cfg, "testconfig") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(testutils.StringEqualWithContext("testconfig: config apply errors: {config entry 0--testconfig: config type \"Dummy\" is unknown, config entry 1--testconfig: config type \"Dummy\" is unknown}")) + Expect(err.Error()).To(StringEqualWithContext("testconfig: config apply errors: {config entry 0--testconfig: config type \"Dummy\" is unknown, config entry 1--testconfig: config type \"Dummy\" is unknown}")) gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) Expect(gen).To(Equal(int64(3))) Expect(len(cfgs)).To(Equal(3)) @@ -90,6 +106,8 @@ var _ = Describe("generic config handling", func() { RegisterAt(scheme) d := newDummy(cfgctx) Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) + + CheckRefs(cfgctx, 1) }) It("it applies composed config to existing context", func() { @@ -110,6 +128,8 @@ var _ = Describe("generic config handling", func() { Expect(len(cfgs)).To(Equal(3)) Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) + + CheckRefs(cfgctx, 1) }) It("it applies composed config set to existing context", func() { @@ -138,5 +158,27 @@ var _ = Describe("generic config handling", func() { Expect(gen).To(Equal(int64(3))) Expect(len(cfgs)).To(Equal(3)) Expect(d.getApplied()).To(Equal([]*Config{NewConfig("", "bob"), NewConfig("alice", "")})) + + CheckRefs(cfgctx, 1) + }) + + It("it applies compig to storing target", func() { + RegisterAt(scheme) + d := newDummy(cfgctx) + + cfg := NewConfig("alice", "") + + err := cfgctx.ApplyConfig(cfg, "testconfig") + Expect(err).To(Succeed()) + + Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", "")})) + + target := dummyTarget{} + MustBeSuccessful(cfgctx.ApplyTo(0, &target)) + Expect(target.used).NotTo(BeNil()) + Expect(target.used.GetId()).To(Equal(cfgctx.GetId())) + + CheckRefs(cfgctx, generics.Conditional(datacontext.MULTI_REF, 2, 1)) // config context stored in target with separate ref + target.used.GetId() }) }) diff --git a/pkg/contexts/config/config/dummy_test.go b/pkg/contexts/config/config/dummy_test.go index 26d30fd226..117056a0e6 100644 --- a/pkg/contexts/config/config/dummy_test.go +++ b/pkg/contexts/config/config/dummy_test.go @@ -50,11 +50,22 @@ func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { d.applied = append(d.applied, a) return nil } + c, ok := target.(*dummyTarget) + if ok { + c.used = ctx + return nil + } return cpi.ErrNoContext(DummyType) } //////////////////////////////////////////////////////////////////////////////// +type dummyTarget struct { + used config.Context +} + +//////////////////////////////////////////////////////////////////////////////// + func newDummy(ctx config.Context) *dummyContext { d := &dummyContext{ config: ctx, diff --git a/pkg/contexts/config/context_test.go b/pkg/contexts/config/context_test.go index 4292864c48..b1ee3fcb2a 100644 --- a/pkg/contexts/config/context_test.go +++ b/pkg/contexts/config/context_test.go @@ -10,6 +10,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/contexts/config" "github.com/open-component-model/ocm/pkg/errors" @@ -23,6 +24,7 @@ var _ = Describe("config handling", func() { BeforeEach(func() { scheme = config.NewConfigTypeScheme() cfgctx = config.WithConfigTypeScheme(scheme).New() + Expect(cfgctx.AttributesContext().GetId()).NotTo(BeIdenticalTo(datacontext.DefaultContext.GetId())) }) It("can deserialize unknown", func() { diff --git a/pkg/contexts/config/cpi/interface.go b/pkg/contexts/config/cpi/interface.go index f5351a649d..d2ce4d6db2 100644 --- a/pkg/contexts/config/cpi/interface.go +++ b/pkg/contexts/config/cpi/interface.go @@ -62,6 +62,10 @@ func NewUpdater(ctx Context, target interface{}) Updater { return internal.NewUpdater(ctx, target) } +func NewUpdaterForFactory[T any](ctx Context, f func() T) Updater { + return internal.NewUpdaterForFactory(ctx, f) +} + //////////////////////////////////////////////////////////////////////////////// func ErrNoContext(name string) error { diff --git a/pkg/contexts/config/internal/context.go b/pkg/contexts/config/internal/context.go index a4bc566445..ea23e652ac 100644 --- a/pkg/contexts/config/internal/context.go +++ b/pkg/contexts/config/internal/context.go @@ -12,6 +12,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/runtime" + "github.com/open-component-model/ocm/pkg/utils" ) // OCM_CONFIG_TYPE_SUFFIX is the standard suffix used for configuration @@ -130,7 +131,10 @@ type _context struct { description string } -var _ Context = &_context{} +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) // gcWrapper is used as garbage collectable // wrapper for a context implementation @@ -140,6 +144,13 @@ type gcWrapper struct { *_context } +func newView(c *_context, ref ...bool) Context { + if utils.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + func (w *gcWrapper) SetContext(c *_context) { w._context = c } @@ -153,14 +164,18 @@ func newContext(shared datacontext.AttributesContext, reposcheme ConfigTypeSchem }, } c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, shared.GetAttributes(), delegates) - c.updater = NewUpdater(c, c) - datacontext.AssureUpdater(shared, NewUpdater(c, shared)) + c.updater = NewUpdaterForFactory(c, c.ConfigContext) // provide target as new view to internal context + datacontext.AssureUpdater(shared, NewUpdater(c, datacontext.PersistentContextRef(shared))) - return datacontext.FinalizedContext[gcWrapper](c) + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) } func (c *_context) ConfigContext() Context { - return c + return newView(c) } func (c *_context) Update() error { @@ -177,7 +192,7 @@ func (c *_context) WithInfo(desc string) Context { if c.description != "" { desc = desc + "--" + c.description } - return &_context{c.coreContext, desc} + return newView(&_context{c.coreContext, desc}) } func (c *_context) AttributesContext() datacontext.AttributesContext { @@ -203,7 +218,9 @@ func (c *_context) GetConfigForData(data []byte, unmarshaler runtime.Unmarshaler func (c *_context) ApplyConfig(spec Config, desc string) error { var unknown error - spec = (&AppliedConfig{config: spec}).eval(c) + + // use temporary view for outbound calls + spec = (&AppliedConfig{config: spec}).eval(newView(c)) if IsGeneric(spec) { unknown = errors.ErrUnknown(KIND_CONFIGTYPE, spec.GetType()) } diff --git a/pkg/contexts/config/internal/updater.go b/pkg/contexts/config/internal/updater.go index ce76a0024e..170710ca5a 100644 --- a/pkg/contexts/config/internal/updater.go +++ b/pkg/contexts/config/internal/updater.go @@ -26,29 +26,52 @@ type Updater interface { RUnlock() } +// updater implements the Updater interface. +// It must be prepared to work with an internal config context +// representation. Therefore, it must pass a self reference +// of the context to outbound calls. type updater struct { sync.RWMutex ctx Context - target interface{} + targetFunc func() interface{} lastGeneration int64 inupdate bool } +// TargetFunction can be used to map any type specific factory function +// to a target function returning a formal interface{} type. +func TargetFunction[T any](f func() T) func() interface{} { + return func() interface{} { return f() } +} + // NewUpdater create a configuration updater for a configuration target // based on a dedicated configuration context. func NewUpdater(ctx Context, target interface{}) Updater { + var targetFunc func() interface{} + if f, ok := target.(func() interface{}); ok { + targetFunc = f + } else { + targetFunc = func() interface{} { return target } + } + return &updater{ + ctx: ctx, + targetFunc: targetFunc, + } +} + +func NewUpdaterForFactory[T any](ctx Context, t func() T) Updater { return &updater{ - ctx: ctx, - target: target, + ctx: ctx, + targetFunc: TargetFunction(t), } } func (u *updater) GetContext() Context { - return u.ctx + return u.ctx.ConfigContext() } func (u *updater) GetTarget() interface{} { - return u.target + return u.targetFunc() } func (u *updater) State() (int64, bool) { @@ -66,7 +89,7 @@ func (u *updater) Update() error { u.inupdate = true u.Unlock() - gen, err := u.ctx.ApplyTo(u.lastGeneration, u.target) + gen, err := u.ctx.ApplyTo(u.lastGeneration, u.GetTarget()) u.Lock() defer u.Unlock() diff --git a/pkg/contexts/credentials/internal/builder_test.go b/pkg/contexts/credentials/internal/builder_test.go index f748bdebe7..b128be5a93 100644 --- a/pkg/contexts/credentials/internal/builder_test.go +++ b/pkg/contexts/credentials/internal/builder_test.go @@ -21,7 +21,7 @@ var _ = Describe("builder test", func() { Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(ctx.ConfigContext()).To(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.ConfigContext().GetId()).To(BeIdenticalTo(config.DefaultContext().GetId())) }) It("creates defaulted", func() { @@ -31,7 +31,7 @@ var _ = Describe("builder test", func() { Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.ConfigContext().GetId()).NotTo(BeIdenticalTo(config.DefaultContext().GetType())) Expect(ctx.ConfigContext().ConfigTypes()).To(BeIdenticalTo(config.DefaultContext().ConfigTypes())) }) @@ -43,7 +43,7 @@ var _ = Describe("builder test", func() { Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) Expect(ctx.RepositoryTypes().KnownTypeNames()).To(Equal(local.DefaultRepositoryTypeScheme.KnownTypeNames())) - Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.ConfigContext().GetId()).NotTo(BeIdenticalTo(config.DefaultContext().GetId())) Expect(ctx.ConfigContext().ConfigTypes()).NotTo(BeIdenticalTo(config.DefaultContext().ConfigTypes())) Expect(ctx.ConfigContext().ConfigTypes().KnownTypeNames()).To(Equal(config.DefaultContext().ConfigTypes().KnownTypeNames())) }) diff --git a/pkg/contexts/credentials/internal/context.go b/pkg/contexts/credentials/internal/context.go index e6d3e952c0..a2d1074437 100644 --- a/pkg/contexts/credentials/internal/context.go +++ b/pkg/contexts/credentials/internal/context.go @@ -14,6 +14,7 @@ import ( "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/finalizer" "github.com/open-component-model/ocm/pkg/runtime" + "github.com/open-component-model/ocm/pkg/utils" ) // CONTEXT_TYPE is the global type for a credential context. @@ -102,7 +103,10 @@ type _context struct { consumerProviders *consumerProviderRegistry } -var _ Context = &_context{} +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) // gcWrapper is used as garbage collectable // wrapper for a context implementation @@ -112,24 +116,35 @@ type gcWrapper struct { *_context } +func newView(c *_context, ref ...bool) Context { + if utils.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + func (w *gcWrapper) SetContext(c *_context) { w._context = c } func newContext(configctx config.Context, reposcheme RepositoryTypeScheme, consumerMatchers IdentityMatcherRegistry, delegates datacontext.Delegates) Context { c := &_context{ - sharedattributes: configctx.AttributesContext(), + sharedattributes: datacontext.PersistentContextRef(configctx.AttributesContext()), knownRepositoryTypes: reposcheme, consumerIdentityMatchers: consumerMatchers, consumerProviders: newConsumerProviderRegistry(), } c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, configctx.GetAttributes(), delegates) - c.updater = cfgcpi.NewUpdater(configctx, c) - return datacontext.FinalizedContext[gcWrapper](c) + c.updater = cfgcpi.NewUpdaterForFactory(datacontext.PersistentContextRef(configctx), c.CredentialsContext) + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) } func (c *_context) CredentialsContext() Context { - return c + return newView(c) } func (c *_context) Update() error { @@ -157,12 +172,13 @@ func (c *_context) RepositorySpecForConfig(data []byte, unmarshaler runtime.Unma } func (c *_context) RepositoryForSpec(spec RepositorySpec, creds ...CredentialsSource) (Repository, error) { - cred, err := CredentialsChain(creds).Credentials(c) + out := newView(c) + cred, err := CredentialsChain(creds).Credentials(out) if err != nil { return nil, err } c.Update() - return spec.Repository(c, cred) + return spec.Repository(out, cred) } func (c *_context) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...CredentialsSource) (Repository, error) { @@ -174,7 +190,8 @@ func (c *_context) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarsha } func (c *_context) CredentialsForSpec(spec CredentialsSpec, creds ...CredentialsSource) (Credentials, error) { - repospec := spec.GetRepositorySpec(c) + out := newView(c) + repospec := spec.GetRepositorySpec(out) repo, err := c.RepositoryForSpec(repospec, creds...) if err != nil { return nil, err diff --git a/pkg/contexts/credentials/repositories/dockerconfig/repo_test.go b/pkg/contexts/credentials/repositories/dockerconfig/repo_test.go index d4cd772560..7a42e69f0d 100644 --- a/pkg/contexts/credentials/repositories/dockerconfig/repo_test.go +++ b/pkg/contexts/credentials/repositories/dockerconfig/repo_test.go @@ -13,6 +13,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" . "github.com/open-component-model/ocm/pkg/testutils" "github.com/open-component-model/ocm/pkg/common" @@ -165,6 +166,7 @@ var _ = Describe("docker config", func() { ctx.GetType() Expect(r.Get()).To(BeNil()) + Expect(datacontext.GetContextRefCount(ctx)).To(Equal(1)) ctx = nil runtime.GC() time.Sleep(time.Second) diff --git a/pkg/contexts/credentials/repositories/dockerconfig/repository.go b/pkg/contexts/credentials/repositories/dockerconfig/repository.go index 756df304c4..79d2aea161 100644 --- a/pkg/contexts/credentials/repositories/dockerconfig/repository.go +++ b/pkg/contexts/credentials/repositories/dockerconfig/repository.go @@ -17,6 +17,7 @@ import ( "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/finalizer" "github.com/open-component-model/ocm/pkg/utils" @@ -33,7 +34,7 @@ type Repository struct { func NewRepository(ctx cpi.Context, path string, data []byte, propagate bool) (*Repository, error) { r := &Repository{ - ctx: ctx, + ctx: datacontext.InternalContextRef(ctx), propagate: propagate, path: path, data: data, diff --git a/pkg/contexts/datacontext/attrs/rootcertsattr/config.go b/pkg/contexts/datacontext/attrs/rootcertsattr/config.go index 9fcf7d1238..d562553964 100644 --- a/pkg/contexts/datacontext/attrs/rootcertsattr/config.go +++ b/pkg/contexts/datacontext/attrs/rootcertsattr/config.go @@ -62,7 +62,7 @@ func (a *Config) AddRootCertificate(chain signutils.GenericCertificateChain) err func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { if t, ok := target.(Context); ok { - if t.AttributesContext() == t { // apply only to root context + if t.AttributesContext().IsAttributesContext() { // apply only to root context return errors.Wrapf(a.ApplyToAttribute(Get(t)), "applying config to certattr failed") } } diff --git a/pkg/contexts/datacontext/context-refcount-model.png b/pkg/contexts/datacontext/context-refcount-model.png new file mode 100755 index 0000000000000000000000000000000000000000..89d79e12c8eff5153706e700db0eb5ba399337b6 GIT binary patch literal 259864 zcmeFYeO%J_`akZ@&N=OzeK&V=`c#&><+je5r#WTJ6w%Xa+E&dW1&6 zv&dB>+Hk!1BdVyk!)1PV5&4vJcYNYH?i7u$4J0oHYraWFMdWnc={<_OKWfG}JoNjX zo1Pa*zsu$_GP_U7=U2}C=GXH$KFM8CTm0tOv14AxcV`N@^w*6yarw?y%<*0HF72yb z;hlu>xwP=r&H@g?|7?4<=+|chzDFxo)D&<0Zy%lzkMUQQ#n@~5jy0dnqRuZqeEr$w zr_+N&aXv|*@!sUm`j)L?=v3Qlz4I0yE_wdUO#D8<-45cSUzLbCH*?+cqgS~h`+Zga z?V~Jo1rRx9k^|m23r8jAEI;`v&+@t^>sEA#VFAH^lf%XJ0MYavH>7euf)p3va>>GN^n*|*|4 ze6+pW<5BXjr}jt43L-xX^)T|q#m{WR+5B1W$rQqr9KS_HCR^b$tNLYBSyZo;m8!sTO|sTETx3ea|O;(L2^n|6e2ewa0n8 z)~zk=Lq#0F(e;2(UsU=X#@cMiV&01+iiF{>Xf*@XHEqi6wFv|1ifu(mQt@!Joom>p zX>T!D;@DYsXKgRp$)?O|NVd%D%?nqA=OJ+#tx$d`_WePGTYz#hV&6;J&h~}dx_Y1HiU97-SFWwAJG(=9-C{igrA$i2jK(1ANg3n* z5$pTw)dhG#RPMaZcOl69as3|XmDvmh+xOXlz*;?x4I>qxYQ@}3RT|uEz7OUc=1$z6 zD;&g%$=cbS@I*ZR2+tVCaq%BcRyi}vU!Hxx0^?TNhHF>CubLjF-*4}P_A=w65M#Vr zX(w7J+_|p!YQ*~7dA$dKf?s}ce9!kbXvL>V63a~Cxoa5b!TqE9KrOCd#si}b$$nH~ zh37bRNcw{x7AoiWOk(ClK@k+ml_X(gJOxTOJx(EYG3UNun4uuvFqYIt2^7Af6}K#| z=LuyJ+-_(8Nz%+ZCtpTiuMx&7bff(u0UUXnUClF!#{YuaSZU0toh(lacLYajgp6Wr ze=(|V%=G#KGnIFhotwzy#YBlkANW<>2~Kwn!6J(woE7l{b57o{(vj5)##Z;$V;Wwi6iI|&jCg6@^Wy1DAy_Z7ofZRh?M z@`rWB1qq!OP+6=l`jPBWp1e)A;XD7NMm_3(yf>@?h`Yy)Cw=}e2)HHbF_+FZFi;Eqere7r^@jF9w z{bONRu6r&Mn(~|hsR?@?(1vKe!E?&(>Cw#n{3~Zb3|b6vNA^3(5AD-ZwD<{ zd_{z0TZbuIn6Y@JZ73s{@@8@qu{4KVrypsM7bxUm>gL<(gS2QY1!#&5v%>T0x`tL@ zG@X*MAA_~IH|?u7?4Q@H8+9R%YU+HeVq{rZA58(T6)-T#HoLj6R=&_6BOPsuFJ4k+ zD>;+xsC0EY4KJJ9Ey}_h1u#gP@Tz&ntz(lO>K$lO=9rDi4GMz|V49w?4N>2~&J-on zhVEF}f_=yf(6fZ7_7`bNPh_$x;un1a0s^hbcNwnD5i~H9{RbSj>_1 zY33(~6Hp3#qGDT^M8%q&P-Ya}mh!>d%yA7=mVtUhYnsz z&{Y#r^*HuE3CiV^=605fn3sc$=Pc)xDcsnw21@FIixI|ZUN$e4v<9z<%A?jNkrvS8 zuSBX0ZzRmsW_nKc*JuTqeXjC$VV1bLU(nUtn!hro6S_sm!j3 z30GsvF%R~dcWW|&Vc&oQU2kDgLQ^1`(rm6Mjd{psJIL9lAf>XPY`IE*pK#gp{Y1~R z--RpVW~xh9d}=(et1tZXwICxb%!o>~a@c+S?R}b#UY%rJWbB3pWAM#8&|bxjYXlR6 zpx<}V-Lqq{oNqiRhnm^!*>&veFe_!6#F%}-yD={jt|lasUTBbnz{D)tYWbIJoKDIj z%#lA=Ct2$!Du;qRhm%vYnQy5P;`}ft!!$SeaGs@!YsbSVKLt6Cc%xAg_bb{p+S9Wg zc0+CX^v5}Pbw1zRzfG09uzPnBJTV+*Y_rs@A*7P*1WjaWb`D-6itRjmjnx?KEXW*F zcja~RBI#@D&Z6rSH_YnB?ztUGg@sVuGn4$5x3<2|l|< zKvGl}2i5h8^E0leyGLwt%0)X&!s6eH6jL43UoO-HFJinRxQSiAx@k%&Dam9uXX6HY z?lho9P%szU^`=k+t<5c$yO`JI*;l)gi1FgiNc%Q9n_W;!=SHr_m^?Kas#3jFvnIL5 zt~V{@a=mq_YE5ke_poG>veq`h^PRk=CMvVlO4{l=r`Wh$upgbE z2mY_V_)SHco)f+7@W2kYnqvClfnm${;UQmk8rShgC1>O@rDYc6VR>E=gW6}81|ym8 zJ${XzOx=OMxFS(A_i@A}LjijF2MeXl-o}%NgfigZ;ntV|FLGZB?< z!_0^D4xuLfkgET%0|QL#rc(>6Ryv#gw&i9>9fHneQHW!(XNDc@W1>ZNG1hlR8a37w zb78=IgRHk+ERdv+sqM^Uy{il#LYeM)Xj(V#rLRf};y zj+Rq^Z$s7ADrYU$DSsRfr&MrRi%PC^Z_F69nv<5BFfplMQ~2wwHNoDTHk^up$_|E! zld*}WLP)x-k(D<9XMkk!YxwLw5vgGUde zQ@)#1p2>i%I$oEY*p?vm$S&;X9o;(n{IxxyZ%NhuqzLosQ(z=jLa zT*`;|xy!K&=c?$=QP7z>j})a-{*@+uAE+Gdc!2PXoRqihFWW7re7Jw1Uc;j#0p(fl z`E84Wj6nl)Un;4VJ!{WgyiH05rxj+^@CvA1D05F)EeXQvo4BrXRaA_Tkm#8z!!4Fg z7g94!>V;EE=6=~968F0r>jB~R>7g5UCh*5o1_9~}* zYx(T=t``L{L3NxB^z*UFmt6sL*X#~zhGTfWz^Nh%l5$E=RxOYDpvVfvwY){^quPyR zU2`(tWy(1q3Izt;Dsl%q)9W_C=$#r#AAl+xUGC&POS@pNXN@4F6iY`hS+XKcawVOz zR(H?h5w08q@|X@@TrmjY>Lo26USt%Ef(tHI-gc$?{Z_%}%Opn|sGhleNtNG}$ni2_ ztx`yGWGZYS%h>Dg3RlRu+qbnX+L<;I$x(PhKCj?+Ue(kO>k0cA$`XNFIHjwc8`?^=Z=B?)@puE~=n4|v%ygn7~~t#)l?k~yc71@xrQ zda~=Y@txecD8;Ie>4DZHJnnC6P<0!eZh0Zifid??o49SA2+jMc=G(T6v6tmBy38!9 zTG=CiXkU0fTrIs%IKDxMM45YAEye9)>b=cq8jq+dE2;^IfXBInpSisNlbX{iF>mo|#tOU0nVy5msS134CL>mM zR9?hrSV^L`aWzVAHG?E|8gdT=qB(swY@a4WWrI-0GVGJ2f@=af1lQ#^O{G)})PRhA z*qAYd%bp+x2wo=g-j@IAR~?QgeCv7i$Wl_*CH1~;;sy4OWjgvtLVt5qrBWAh)n^N? zpXlGc<)RkR5Lk&(;0>S2lThXC+Ormt!bZQgp!Gb~yRxLTc(@jfst`zB@p*W3Wj)%5 zFkg+$!jE@^(=WunFLn-Z;VsMzM+;oUV9&*0^++eJ(M{O=0p(Qhg*-F|r%IkxUdd*A zEHg^3;gh8vK-_M}aXh1X!bFo(-5%S;0ZQ-~#uNuOpA)*QM~Nile)*{DZxi~!SF@OV zI{|2Hv(&P$096DJ)Wclo0ek)FW4jQqm?vGg35j!Z&uhmDeLJ~9Fc?4w?M2LpHuuQ3 z2o+I<5gE{e%1o>|m~E`XI;GjZ_{_}Hnkj^13p5)QMxtM;*y0H3kcrg7kwIU6Hzc8d zfFj|c&V#D$OW#Sh4va+0>}v#0^)!<-HhCu@oY4jP0GI>x9gwxnU&5n!)}yZUi-ZU5 zeS0Oz`LfuqBzZI1lXxkXj5j|uU3l~xLCNJX8gf_r<9m}YV?wKs1}}bIU-aBH$r(iG z*FyDKUF3$`jg_krE!+)>bG{b>FZ4AM<7%LP*GJQgAnpW-1SrIHR_H{jp#xb*7OF>H6$+tOxmXy_{UHfguWq9r(nqXEpvm%tKd(ZK!nF0>~#Q2u#dG^Mk z2iHf!uz~%o42pH;3PoGZ9K`n41#WW`z8h;{S&5k=+_Irzp797bsLy1<7R|Zjqn0lq z%1W#g;elssE>Q2yH%XGa(?4{b=p z*nGK7i}iV)s3WizZRu1oT{`6;<6aGq%cSfQSOlw1>_d_C-08=+UNedIVKw7Xc7H-c9yLJt<#J$m)RaV z#6q>B|Fq}g(6E_YOy2+SW(L%HJFCU>MC*J-Me%DvGZuphpxs zhnU8b|A&3@tdyiSa3yDWq4f#vbDw^0ok8EHhvgn=&}yq2DzWK`B*SIy6bu@5X>xLe zbZ#yzvX4xYoW)Bt7o)QT*|ec*Y{cK=kUM1d0hm%z61dCu4dX`4@?Mw#F3v ztLcuvKo4xS7aiXuC_Kj4YJGjUlH`8LP7_Ene2?b)tKdhVdG?GAvL37>Uo57(K*Sj% z1+)D4VkP8rTN$&&rIIx?@OUVr4fmS~sAD9B@(n$rXWC8`EXe4b!%gb5YtjaRLxS|4 z0#bXj;|uaH3ek-d48%iGu*EG7WO1mT>yMv=8ntjdOS6>RF#wloT0OgH z_ydy|@6W|bIZZsGLYZ#67UpoB!`sEeQAE1J-UyeN8ZpkJ$*PYmlcM@7rYCR$Gq-8l z{sThfJ9))BS@ugRbo2=dN;`Iw$N1GXAA?-DV=So)mAy6J#sR=px73O#8UB1YC#ZD* z^lE8mFb#vlBZ#@bN-Tx~LmI~HX^hrb42`K~Rfmi5)UN7OYe+TA4=Zk$v?YN*he%C^ z4qq>pA{KK1roaZ`J({SNxgYE+11AAK%2zwr0DM#_J)**l-$vfodh&P+;m*f9NgL@* z4=|B>gjdjpBRle`JL}JltcYms|$IMTp8kE82E@NX1QXZx2AFu@CxkJIdbb5K;|-2Oz827ps?Kf zZDdxw6@mY}hzF)^43*qa;Kdz<5{9?Ku=0`g6E+T62f!cEAkY@eP-bP>bf4b03O(I_ zvc8d}vbDQ;^_I3;$4qg∋3eOb8evd#Wt+Z)C3NQ=EPuJ6!BEYSmarPtRTC=D zNlAOU9|EpRC~#c6AgNCth2hn@pbaep7MB1t!cVHPFB|&VTntk51^*Y2F`$nbt|9Qx zjWuYHe`y&!ZC-Ae?*5J**yNxen;CrKdl!S{Cd-}d{97xkjX~>oliyfix~@+UT7myH zbZh8W=dO4@n>iSvd{`$&fzYg{9jp_>F_0Ms^ZkT1_dFbBa=T;kXKi0JB3_}aE-enN zJFBU#t*bbr32LRkCSi^3CX>6$hdRrj)DP$&s z`Tpe%pwS9vAX-hI;`RQ;3{@YA1Fh~?8YbC2oHR`|6HK~;Fdyv7lG?@K>#=5DJcrFT z8m`Y*!nFFDUNHSYPm()mZWCQ%m`(J1?h09|ITnA>;iw-oAV2+Fgqs9G$aC5mK8s*G7zw(n-R@px{A9Ug0E$^i1r5x^X+Kga zBDp*^p9D;Y+U+Zuc^-q^HDW7twC*pepc@698!j1wx(=zAbLSo4t`owNG_WkXGxaIS z`i!ZcWJf;NVg384B|{M>(56&YVM%KaO+f8)Kg{l`Gs7CV*)X10q;&|ST%cr~(Lhha zD{Pq~HJ-;227oMKJd#kIt30Rz?cWxJrdjK(e3_;-Sh{)|S2EYpT}s2L#A#Cprr0q9 z!P~%0BjL%o0~T)fV7h27x9Vd#MWx(L?!>sV9dVU9ia+j_;D1Zo7Ap=uGV%m2Ysjh8 z!}te=kcqY`vy2GMhoFq>#O+BRI1JMo2YK0me;r!d!*99t1Tde=Hvzz0k>-2tGso+9 zqvMY0txY}Lf(hiPzNSLvEzUW*zJc!hu4R#KCN{-$Vp6i<>PUinGx#@I-z%t(g#{u7 z?Ma^Pj$w?-)l-x%SM)s@)}hRs@Y97fb?#8Ae^%Fb67&VuEcprs<)sNhCb_H%F4`+b zk*HU=njK^&-v4peEZyU%T$q@wL0N~bT|I}p{KJ6BI>Y={ytxr{aD!p~iYV7qmsDA= zg1&UD+%hRac`pWeVLUqH3zU;FG(zEt7VXYut3KKzU$Gs7d~bA+o2|Gm5R_aZ2&K`* zm8ifnPe=}{C!U-xUwur7hSoMn_I!wal+>|p|aOe%){jxLwcJoX67=+ z@s(zExH5et>KyO)l?2MxY<#J(0@5HX*p>B zble=pH}rOa>9Sy~YM2043cjK%+|Fz?-`q2*wB2eObr7`8aD#1c809l}JZ-4vM9;$l zv}f}Hs9Iw35VI!tK-Fpe&x0DhPQ1T9k&ZXT$vmo_qEfI#AdiMqkWmWulZ7L`-aq+a zFdq1E0$(U=W5M$6r~8qwNC&itR5)I(R@fPPh6@6hO?p=`{qs=$iJqwlP#0HRgAqc1 zZ$K^(!vYftYT*&b`G-A?hP@as`EwtOjRJ!*9cgtF^P+=mjKa)a4RV~*Swk{;Vz^`V zBF0e{M#;Ur&>y2#8XUABDGir47;LO4W5=X3c;+ZEHoJbJv@)mGu60@J znj{$WS`-zCc&PUw(_xd+$`b~(1Y*dVXmR$KlC>t$ya~qOa6tu9=28nyO01;SM9*g} zjUViS2mH#SdUk~lcdB+`q7F<##0+C&kOM~WzR6mf)(iQbTQ=+|SIDle$Di5Co@l(6 zVX96F87fz@^ywUBruDu3t)}e6(ETC74Ac3Cm63hZcOO1Gio#DmJl)s@b|d`-O)OPL z!ExqzJ3$yTKa4wC&bUlwV4dP@p%K=Aj2aMg*n$GE5RIlpRO>4Q3*Ix3H71?1SEtz5 zs$mzsIHl{CAR2zqfe68+-GQVNA`Xk*YH&&)~V}dGh0#s z%!aDFT^)%Xr$j)_(?~98XsAvUVonh_o zq4rg$b=CG-V)1kDzrv}5up*ez)hgUTWW!?Zt7$cLr@07jn!DDPa8sLTE$cYKT8@}&oBb1g|H8Eu z)V6O)*3BTd#5n^TCp)J6UATL0k_ZBgy~1S7P^5C2zJXt5#f+SQQ8RYBUh+mSegE5G zq_dQGqgq;4+Y~tu7$bJ+QCE+ynkJJis_oa&i;~W(>oIeTq|lN|Z@*d(pW~-F)>*DP1LK#+U?w&$MZxLjGlN zeK!rwUdz_AlT4ucA8Y zDVI!oyQM3f@f}nwmv()_lgoJ(*M%fu-hoM$y>a$-b)EkCdda^gNd+xvdW4suLNzJC|Y=@pH|>Qfck+k?HgiAg^#aF zmzU#r69py5!hY`IZY(+WNs?VMqcO{9CSs{d9Av`J!Gv6&v#`h`^?m` z#_rp-YeZ2k`GgKIPPL5Wat^AT5OY(@m#{U`u?!I35G8L%8I!swnIq>c^oW zjk)IG57El&{pxCwdO{aMCl+HQIcU*Hv_LLh-yhBs*nh}a)MR8xSz@N{HlYvADOI}j ze*u&J+}#df31|_lr&gkEr&k+~P0E0B1oNkffJ}W5?0rn-RZu7Iif%kr|G=EZz9;)Kk};hkSoPCtAye+^u-uUQ+F>P+f`X~a&}X; zDLWJ(adn2_R%7~?WCERhfVG%uzJLnoS2k~**aIA!HE7i=`s_gZd zY0lg5Q@LD9>nrBs4}eQTg=Kr29r5D4on2|Woz30oWYNP)6eUANa!Gq^%x?ka5ZC)L z2eUcvN$9br0H#HkI%_B8mTH>gbg(*Ok?q3GG9w>rW`FyuXWqrjSD*-?>k_P>+e;7p zji}Rf?D&l3*&lX0GD*28E0z%Y#^M=-$!MuAAJST`qnO1HepuWFxML-EE5NV>qlgcZ zs>98vvPJHj7Q+!p%q?MIDIIg|>ggkr&M7iP2n#0}CT+Dj_h+iS8*iM0r0XFmmP0;3 z)ze(L?d|!M!B%ZvzF>W>0AY$07UM&VVlaulH7fn`FY1B=$S|3v^?|a!iFi2Edpjg1 zg?tfu6*7m3NxS_3dTG>Wz)7EE=%qmz;zq{J+Cu9OGoG0pR;*)q15aWR=H<(c*L0NN z)1+Tj4+w~RBlM4UWMN(4;==a8fKc;g_y$@pwI?W^)H`<{miq!BbamHto8(cQypW@b zGtV!OeAP(T5u71DS~15Pc3Ne`#eBoITH2Kk`wrBWd9OWYb(N=`?@gDdIfLo0aqXhS z`{P#`eHmin0?WFl0LwG*#S+bwtIr6yctio+Su3x;cXVtH{)%r7uW+QGd{Q$yQ!!O2 zVAh+LLoFcSc(Ea(>-*m-OOctAu5F{w2aK&+&YIxYjLIm!OE)I7)p!gIZ_I`-!!lPYB9NvA)P76ML_Z!4w z#OuH50}6EgnJ%{Il+fb#_OJ-oT$kqgr{9(_z^J^nz37q0Pfi~4F8*_U-sNJW_pY${ z-_EZ%wd3nQMZ9zK`5o-aRqXq)@5cs)y*S*s>Oumq)2Ezb;NI+7!72<@7Owrb*WWE} z!tDwA!;{yqjL@Q)G6c@@wB-W6tYf}>`}v|r(FH()d{3|ZKmGo4EAGDcKO#Ioj6DFS zo7r)AtsLum#Jw@|pFdYuFng2X9Wbzq;OaL6^0YekKF&=Fi{^i_08lT`?rkY#*e&7 zeAY0*Vyu{d9y=Dw44wC)_1pEQpL+VPA~oWf9JKt-TH~?J519E~&kfM`HJxpnKDBE9 z3{NdN3DG_#&wj~%>nSlGWpmF4L}9XQ$b~GD>mRqhw!)V$x>^Rdq2;MX zn%tvzBhvauaO+RNv18%B(P@vM{cQV&pN(kj=o7Bmj5V@M ze^H$6=Lmm?#_E=yj`xg579E}YZ$wvj+*zwg%`Wf@FMn=NdGzTkxDRIC|5!QS4#_9q zjM{4)F_g_nCmiY22x{_Esp5EOiu|`eI5 zIe}o9oB6#qhy3GbkB-WP8qcUvg>| zj{HBL9nR(($?te>!1DK=!^!#|z`Y0*&h`b(Ng3=q0?CJv%OD>lwtcu=vl|h(-!^sM z?r=SLCBpp}132~uRo~9Vt0>PN&r`P(0%l6Ufh-9W6zoT(ydEPd`68P5v(j zQG9MyYEdt1)n;yU)YLck*7~FW_9^+Jqu|ur{)^EPl+VB2bpRX|wAY`oghxmqc&N|)753|mpDS(-g42YHmVYzk~ zI2E#f?QRT;YnTO_uVzv1P4BT2I2EKvdVCL4# zya;u{WsQMF@Ow$ zx*v-X?BreE)YphIw%&*l@^jzlz9UsXrOPQ);F_fPP&%uG#p_eO(LFl*D9`%3ou}LF zWzjJSuPrsI$Bf^)l^ie4+2K zif7#my_&3R7GYc|Bmvet&ez1X3J+0}>yTP=Eq|{Ty^+VS@49A3FjhS6-lD@+#Y66KZMC*L9(s9bM-8OyXldhS2-@JqyFEaQRI~`_kim33RjY79`n64Y zg%1l{&)&s^HZHV!Uw35-+31h z;;TfG>ZXu{rFI=k%k&;r$sA*3d0#77b@ZG>>bYbEvH=Pe6v@PGNv0fykDgS#_+vf4 zTg9(GZD?0DvMSawc(hJfnmhudJ(eLBdwqv;Uy!+XmN9A@#OSeCyB>Tj5AzJX%G$awDe5x!6)KMlhzX)PsA-)5?4N#Ax@iOPM>w> zz;XDS8S7l81BukaH|ET&S+eWVS9*B&48h|~vF~lmR$ucnwwsh8Wc_wRcRKu&cE*bh zqGh)R95VX3V{gp9^1+*(@6QEHvrBj>a9q`T3><=D3d_kg)jMuR5yCgFYtG{q_HtB2EBJ#B{DbY3=h+VtH(P!}9G^dDIw zkU0U#Us)9sih^(u-A^Q6(-fmlD^GtgaxStv7@2#vQNWdcoI%O(Ua75MH*<68SW0I$ zD_xu7N<2wAm0bb8(&Th1S^>HBG>+Tc;Lhl?oO_#~iMrVgokMl1ZnCSh`TiV-F`XZ% zsqq`{T#03@qO_oVJkFc}yib`XFWB^!b$*D~M%$P>U!zohl#vdPnCeRUnOBc5W39b4 z7f;%eX5P_v(@bc!)Jfea<3BZf*<@=?9xkc-_VU366lb_z%MG8p`?#&-Ou|ibV#q>B zu;XCP=&@sSW&I^Wf>qeP34hf9sE}aPyOuaA;6}jG34qJ#BGgi@sw?X&a{E@VK}5kq zWpiAVRc94}df4QsxFF;jlq==UFtJnK&}WYt=&fWImG-e%2$CZH&>=v0q}wf%{R?1$ zC`<&Gphg$(1E0O6w4HuAoN*bj*88oxn6dLgRu~Kn0S7rT>E3r5ym_C$V5Ed)tIIGz znjOrf21z#o-#T{;&j$(;%d+v_JXh>fm?nWs|9U1JANtQvKFjr=!p!Fi zzLvR;7mR~g4J@gc`69@%3p&HDJE5WWbw2ksIDRo>;vluJq%u-86gPl@ku<~460H30 z+ZnnPgiAAd5+dclH$bk8R(ZFnWS$<|48r}zWFMzo3$I-h`z+|ael7|b1x z62pb{gSccIv^GIVNF=KW3LZZ$$@)8%Sc1+zU>I<6@?B}9B3QWTB4BQ>WVU?_$m!w4 zOr*oFNm*K5coZX)+0q8X&^zF(*lW7lH-PDIxCM#T!JsLWFk_3z0MrIke48*DCL5!1 zEeO-?+J72w4%LTb9LB&CY3M`k8&pFb#9EC+@K9kG&72Hd_Po0{mvn(YzPr$9xuaBG z9}c4jW&oPEivLs1SmlcAJY!Tizj5kC)DgBW2-Ee_PxRgUZa)9}^C+isfd~bqNihuX z=b!7baE_o2z^XI#i$fzkKM!0?jHkPj4DWi#!(lMCy%gj zAbj8mTA$1##^R~P)9&%p-iwz|o`b4GN#qh-aS~dk;u;nn0Iph1nw*Sw@1V{T&`sTH zCI1K&EGb*v1^E0f#~$cRnv_guv_e*}{pES`8v(3{$+A{ELasfTPCcS2H@~r9R%SI#y#u)jcxxGm zlF}R0J}lA@9f^}9!nBKNW08?=;_hs!P`PbV5UJK=lvfp`jD>!fiITGcSzxX1JWaj9 z+!d|pKgDyYV}^}>7bFD}%zo_*z6~vV9ED+WUoWEt5N2b$8>_(G5@W5?@_8n=eFYHwqrM{Zu_BQ%tCgZ|^1z{i$>@Mx0%@tljZdO(CiA zc-NMjf)#?WunU*B#m}q@{6Nt>mEd@kW{eDTz6vx3mEQ;}I^Qiz)q+K8iB0u3VNL?a z6;5tM+nj0gkPx;5ttXiME~sHbHN7xyZj_8p`!LxyFcK!CH&6ph%kKDn5Z))e$1=Pq zcV!?W!s)Wucbx7g_M$D>D>9r!o~-G2O}bn}L3J3&3njR6o<#F)8>>G#xA2Yl8j0hx zOX`N`^1%@=&+*hv4sdCSzH4@ArG9_2t*yzTEQ=q?46b1p>|$QwUR9+3wr!_;bP2w>Sf^*t5FzAs}A3`#~FU(F%RUAN%VK9E5iwqa+HCnDIS@lKxLseYM zZQHfQn{dUo${9`=d0uy3k`Hq}zM9K5!eGW_I|R%}KqqdqkNufm0{G6E=8g+o7;Nq{ zCe;=>_1Vl|panw>c76#`bS4Aup37GMs)OkTMk6;|EbatXQd(IF-rpp_(I$cSFBYyv zr6f+C==nrme{BB?bf=6Fi19N}W?9UEmdEk!?QJQdzWi%>>a4NTem*>dexNT^LBKPl zW@55i$Z*A70g^zJ_ju78lC_?T=q^=Z*0B)~_KF*OW$ve-=pzLx@GpdXWaB9Pe2!_K zRhCM{Cz=~w$7ds_q*p>-TJ)EeObXuJo-Pk@9!rF42=_Y(iY~4;3BlOVEmX57>SQRd zj*+q?dSi6O5*OPsi(%|9+2GlBP=7X2lkKmV5Q4!;*N;rtWB!1l%lu?@`NYTiCcv-H zn=ix30iuS9<)eeLzG?p6D=I!5T#xSexmYI@uYS`)2=X#v9Z2*OC5M<{5M@AbS&h1A@Vi z&ALo21}l4A8hLLsAbms$tnt}P=a265Lhb3uVBLWSMOgyyVLOw&3i%%suG`8{ezg&0 zoObW7+C+PU_0+g+hD(1fM!ene%p=)@EfZ7U(k^l*((xg~_Dos?E$$7_&e+kJ7`%HJ z>-!UhiiR5wUbq}?wogo+*K;X&x>j#lye53oREGPTN_--jk1&1fXItZDE=!WsZe1Cg zlSI$Aoj#)}R0+p!8{&zoTj~&=`s{4Jq3b)p20Uw)zbb;BQ*W8*c_(@D(#y$XGR~iT ze=3C^RBfqZOAiSaU-d(!DY1YHtz{Zfjv22BZlY3fzV3ly3`=+;Lq+g8n9LAF} zsfLa-!4wxzAvJS5{sCPvTDy{(nyr4z(o4l2KrPy~Ibw}gD7W$#4c5vRs{5-4dx9xH zS#F7~^bb)T|?cEiqz~Cfx+;kilr~sAJ zInPg^0{6A$UDgCs1x4GQ^fX9VdNC;^J6`w|mYXYj!y?COgT3~{bS;3@tW!6rb-O`L zuogXZY05OUiS}jjEemV=)5maG_B$Ik3S2dHqMzmW&hH>EegP8b*)-&sA2G%|ueOVX z{b_=QTD^mZf*+_C9zjnh4UNh|`8_+jS}g_pYa2B6n`#;+9S%Lf$i6 z0^+k*)EdtP2WEKk-1}^qlJ0BZdKd^{S|d@d)aa|PuVnlcY@6j9h{CvR8U4f|nYNG6U22q5zqjpDAht zx#?O&p+!IzMbP{~JEu}>e-2t^(d9OOI}bd*-r+|PoPu;cpi$R(n$UVgAL0V{FV4@8 zY!vwN-;6w(LF1=cJEK~z432akn#lB2@wrc}Esix?_TAqW*VwTg9RCbGnOw#mKQS=| z2;^_U_E^h;14~6k{(c@5vk0I?ZYH#;vX1fB8#4kY=T?u)&%&0+lUy!ch@ks6zq6SC z3KiwTq3ynwY+vq^D5O_oq?= zUrEZ3-!aP2eM|pzzL7THb_0HMi?-4b!vZK2W>lVR78@+1FdYgEx;F8+8)50$(;4tj z-Z0tTktPc0iBWl3jjZ0$0u`ZkZpMaeeuBpTw4_k0>Ak!X^cW{o8_tc5qS-19bMCo3 ze?b$B*$M@=$p%fWZPZMldn0VO0Ccd-;(ku0OlrBB2r(7D=1WUvsnP^peMS39sbuwJ zuZDTxau(2MO(Y?v1ezs8#V#(Fw(kSEA?uf1?4su-3b+V=?aO10b6lcLSr?dLK5-uv z945UUF?1&ds$yq49{xbA?Kx?P3s>v8f-0O|kV`XQ&<9<1-7v2?ie^Z_Z}sEUHS%Wr zxf5-!@j+Kc=2A9|O~$+9+gsWyu#^_DB3(Xxfa^T%c0;Tu zj>-Y7;%#Kx^t7CjN}dEvO`+r{jN-hSoBve-*)>3@w=(|+t*ucoPkYNY1 zvD3JD(s1f!UT9g@1q^BYP}i<(R0=*{-shehq(=m0=&q25VH~W4BkxKDZFTG_IWrc_ zv&D?ij^1lSZ=ny?6d!GGF$;TQN*AMvU)`85PCwDt9xu+G#9IF&Yb{i5+|VuELKc0veCo#& z8tKP2y&CwTV^>}HU@XCUeyWE>PY?VuDPMjEP?=bUU5*hFpLkoT(X?way`(P;+kY=O zyPmG-oNZ6{%BiZ2z-yvyijT=J5M1v0Pm=4sH5)uVCn;_2k8&)TftXwb4 z^35&fQA0wwnHwAbs$4p75-0K}RyxY%0TK&Q0RUq}H>pSgCLSZ3P=?g+?2gC+J$ z$z_zY1|}XueroXrLN-KgJN9Vy_LeDmNj+`v?6=gooRHE;$|xjMlEH)q+}HHWZQ-0V z!!CSjjLlrbyM$sC3NC@r$ZTo5(78>WGVkpfzDY>q z3~RPdwi)8bB;BAJl!HsQdYOp~@^P|fE?SO^Q=;xXnevqjA0t8@DgOv(#R`t>NY$=8I_bqit&dTk^uZcvox#?prW|J`5VN0)^f&y3ILfjz`V;6FOH)~%Z z>8o{sVwVDfHLR)i*^Y|u4rT-Oe#j+O;^Rkx(!Pyi4IuDbU-V1 zF}bmdNS=KoX4*Yxp&oX93SXpYS8<(%_*C8kyGR!M33+i;o<-xYU5=7R-&yY6>*Ej5 zHz5ZY2c`bZ$^{#y@i$L^@}t~h0b{7?;uPll&ND$^V)8FG^Se&S878L;`R_&eKn~8B z^!8Zj4A0rh_@k;R9dnl-x}FMJT1kOB?*=BgFW3p=wzH&^xWjAu^Ap{=zFvP`9wnQ|LmdiXOBEH!s)i6CBeS&?b zeb4v(@qPcl|JD9}#^hP<=f1D|y00rZalng9Wa@uxNc!b>{p7tv>$1xiYZY;g>+P|Y zI(XQQFS~RGe}-?n$a&k51?KFRz$P6AJpPivG!!=SWAyx#-C-PzTPD7Jm&qgA6B9cM z;-OLy1>kDw#AQ1#H75X%NvN0-l^V^}HKZOw*h6rR5_HV!$O;*Ema7Tu)*eh-J_lIJ zAbeG&AH(fTSI`BfA?GPnYNF$aHx#3lnXQ_}DnliICO&c`sCv7x=Z65#4kGOMqO$Xu z`{6>*!9*~k*iPSEHzh;muX`Lg6(Melnr1wP!hD507EF;Jj;Us7rE ztf9r1r3N7RUI@nF{)5OJ2AJ}ZJG}4QXXxqOElB4LdVEH>d@z#xqsT{k)A~!C`@E02 zolq^1G1r zL*i;aSwLJ<5&L=@L9G-+nl5cK3}r6fDd<&XE?&&uNYuP%6_XCkow9zFHEZ>YQr+@1 zX2_RMM1HJ0g{zV!=}bi0vcp#y%_y4%Nc*acUX)~jp}kL~$)CE-I|nLTsN4>UFl}D0 zf{`_v?c>n73`;P+@pWHiy_(*GsDwn+V$?AevbTQOou^4~pO=Mq4c81gfSpd&$zD{o z2q=4KyTV;BuW=`m3~N7w>tct7fN=j=K3snVygJo%a{WwiaE`*y7ZM>_4~A{0`wD>$ zOw3kr_8_Fici`fIRKdWSo%?4=cBPYEXQi+DnlJK=U86MEtN5r`i!3e(omuN z%9juEByZnI#C-zr*>N%?LLpvCB(BONS8TK~L|!V(L9YkDd!iMBZA?@x7XBI?{qI!R zfw*tZ{i%oeI6ClDVA)?|AnrddFFZbm!kNaAELog?AcNE2_A22ZS<8Jx5+AEUv3~A8 zdG^GkDsA+xbw#%eeO7j;f^JL&WD*`?AdN0we{W(-bsB1DIrYtWXiMY{e$GwsmW{9W z-dl(p(9;-|F{Vq#c-k<~1vt}6UzNwKzP6zPePor&xmQU*AU)wk^JmKF&vX+R zM*kA_ufWNtEzB*KtPdCVYZ&Oj+w%M2n=D=*3=-~V& zmij96RdP|^G?{QqQxo>L8{oG$xYkp!?;w4fT1BLlf(^|R3+(RojS{s#c_;YV&x{Zd zRk4P6sl*i5ISLCZQFdzvJHJLw+DWRn|>Ry4MU_9xtO9jRfn2(pC< zfdko$xz8*bk(IL_8c4qe_ac<%@6*qHcTRmC^lQ^3vv0V%EbKd_l5wE3@Z>4qwg#Y? zPn2~rDaP?`llVV|jgM+BVUIuIdRw4|9e?=%V0wjO>e-vp#A)R{mdp7E5M|(EA%1I> zF%7VAU3VW3(=?~2p)=(oT|NP4%~a&1sl<52{qiqWE;%@;bKPl+ocZk{U0lIwB^fQ% zUW-zP7NZrWsa^Eh`VM&%l8WUG$^OYKqFL$`GK2eTi3*3|5XwBYg%0sdj}XUC9AtW+1)HoAfD>#C zkCVQyn1=QCP_4IZ8qusol(RKK&2b%>Gr71Hvq~JS2bihZU`9<{WjST~_ktYGsL#j6J0PL zKiJ{82M@Ox>Jek2*@J%vr-!?mwJ3{i>gH--CHpXHX5DmrP9S@gD8OxPD}`*lh2hVpzLU2*ZjEH%OQ!iHr_jY3u9TXsg`e7V%R z@~!HgzU313af|{q?%sx*>7mBB$=0QDU;ZrhFnhSh*!YURdm+pZB;Q#&8|Hl>h zz8BE4*A*L&s2d?0wzrl9hbXVV@M)?Ya)A-@tB?4c;aI(UAPjd*nKmzb6jXEsye1ET z`jMdTC820p4!c@DUV5{}KHU^T`mgV9_F(c#D2gu(H#I9!FK9w3`&N2OgKV#JHcFPo zI_-^LKKSztxX<3sXBTSEp|gLAv;B~ZTZ80qM_7e+*Zp%48+i{)hc9h{+H#Zsts~XR z_Nq6}PhRgV6L?{M0{MU7Qnvjt7U+jMc`DKG3*!a}`5*DDUl ze}13N*l79*(CGl%y@{#KYP|VBzN+Kd+?6=a#l5V?vXi1x+UAFv4HL_O_a(uYp!z0r zWz_tG=6^rgZb_$yPy>I$xg6pt=Z8O4;5G_po9`xDr6Jnub}cP?W^F42gcSM6wtwy3 z_Z;TM*GXm1{<47c!BHO3kr_T`6PtV2vkMW~cZv57gTA+q+@aGAmECQ90m&q#0kvj! znmzqLK20-9x2~DQRXa%C`vs@z{N-N)+8LJNT3>bCO(5F~@hKDrE>y&={_z~IGc+u{ zsVqPCRsE@^D9P)Lh5)ML$+%{!H|7}h`(1D+TUKL@dAc~M_?;x_-!UEQllRDZjFs{4 z>TR+N@TVe&bbq&+8#wwuzJ1cO`;X!{TlRu#W&fnu*653`$u7jG^Uj{0{X)gUCJ?!U zSMga&{O`Y)e%G^+*SwPcxwPH>%^$w^%{Gw}jg-K$D=O+$;w9U3iM_7*Un}#%X*jGS z`kRoz3#Va?p1l~uDeobfnMmPv^25@-8NL0b%ogM7KbYUGnfY15Jji#kJs=`C~fBii4z~LJhGV7-E!~*?dCrM34%ityK4?%?? zQg;h4REMMz{9k+_(`nX!9zxapI|XHwhEJg!o9f@clG$F_1`Yva>C+tJlYVH>*QdqH z$2=t6kxFQP=1s(;>2R0$;t4D9b3rUnc$JmG2f|^yan%qBLX7PTo zUBHbh9*CrlvI-r28XSICw_87#wzpaYj|3~)=rp_6rxU0zP*aT3z0GUZZmE1z#r(O_ z`Fc8qJ`@7`=T*vl)jhr>OKrM0zD9;Ka?!4Nl&E$xN8wUZu1qlNgNu&7@bnSWileW^ zJI$Cj*=eA(((Xd)WKM%h@!!=U(hX-pgQvUGj+aj-FgTJ<7SoURM0_1_ok8>jTvEWg zTT@ik1RxR}Z&Po$Be~YD3tC<9Hz-B-6YK4ebxFa|;8M%H#(BjAx@~?n0;fTNJ=da% z^fyw8j36VsV2G&wtFg6!f>8U-&JjCxe*%BkuOwf2+t!;D6A}{!2b!sU6>I@B%6Q6t zwA^g#$^n^N3p4Ch^>{IX)`dn?WtlHkOV0H~%}fm-6O1w>|M@Cp@1c}sc#5N5l}^oT zTSCr_@YagXrP(jO=f3n1!|;dLE>u&&YUK z`v}c?NkqLRXE>QlN!ubd>XUQHoCN@rH;jL?4kuA1G8sa>F zqoidS#|d@H*yM6^K8FA%!#uYLxc2y;ZPFZ4S2BpmI?Q z$PNg`5GSf-W}1fzWTD-LhrOnW*1biN@oZ3qlDBQ70qM%uIgyvK?$^|j(4^{U1>I8v zI)?D3P`hj84`M&`wplV{a@&(rWUWI11{%YGoXm3=Fe=ve=+yeYpHC7*Vs3|SbPZVB zh;KngAsYtq8cL)QuKepP^&+Nx@E-Pdt-tl?)3Ar@yyD){PDz!ucOXrF#b1$5hT(5@ zssxE8*|=n0=lBNYl&5i9w_BQI>pg~elN)7A-K%|OM2cU-OOY(~^>6vK1S{tw2avJD z((YI^K`hg8X-)}mmaP)>t4o-2dj61ixbUHbbgvmeR&2w~0#lA9TCvNr1D;hq?f#=q z7;8NhufOd&k~?p|AH%{o4n1_|L{jE#{zY3^4%b6hD6T66j)aw;^-hjwb<^N-c44Hv zrqQk=Bh&AE%ZLQx&3logKkMdB9+Wb&9%;Hufu`U1bVIw~W6TJXbSorHxqX)FTL0^+ zfb{BS@D6}TWjgJ5KOeX;p>ZiGLelPf3mmwgsP^pjLdB3Qz(x^huXi$aU%Qt{WJiNw zCS}T&;EYbI86fF}3`PXBgHjUB+&po(rl@Cg&VOVV@)Hz}es?WZnldyHF69aw zP!)MVF(!78C{1-2HO-=L__mdQG~tVmuxB&YGo+Uk*#gsG5v-K=&$n?sV2SKE`RaE+ z(aKk2(4A4NaBi+D%ru%DYJ^$(G}+WEX<>T1sS7v6&BG}wO_LC!TGA2{%Cg3AR{a() z-7dD~rYSHL!WQ|V3XqY`J!P4Z8dv$p+7{C3UL$YlW1;w8F*gt60Qv@LFlS*c*`Vk&awQyUox`~%Hv z%_A(cEH)L0XXF%8{}Q=lV67)D%)ByQZdZ(HDY}fN8vJnmdHDF966jR;4o2A(!2IG` z!#Exd6hkw*2uNY7sw7d zCIhgyKVU^6<_p9ecWPIJ8dfS(U=ra2*$*mE}#SbvMV8)`NeC zR`#g3gG)tJD!U`Z)?I)A0MX({ngGtq0|+lm`w~fe4}uWL@p(gO?S{*yg$<|#Wttc* zrSe20yOigu7y6*wS%c>HIB^KMz-&F^h$Ti&$0Q6?$l(N2O@-?wxxR#(R{{9=!kwPx z-cSg3JcI9*yh&Sr(xxb%67qEXq>zW`6hmm#Q#+(#Gw6?e*sutgDp}@Z%@; zBWPV!`-V%rHW@VQ`3tcMoE#ka)l1kJ7<)U=@WUKB-oANi^i8BTJN{lPpiw}a8>;T$ zWJ>a)vP7qUtf`}~#Jj^hjhRFL^xiBBl(G-CibqEFV@}8x)Hv9#jX*}ek{~-(nbe~H zI(M1@+%)RmhN{kdMaK?y9~WEzy6>Yva2P2|Av7NWO4qu{iRIq1+_Wtv9| zWU^5twgiYT*@+uhOkrmM9cuZ~>tf@{oAr!LEr7JuccXNey@-QZptW6k8F@ld)m{+0Nx9b* zpw01DbCT}v*u9EVhQnw07{KJNYm36Jy%u?2&6;JV=4?i~+aDThTlkeuI<}1_lw1TM=&aq&Rkl;(14Pxmo%Q@+Ldg{M8YHl^tK3 zzWTmaNFv%H`|aH_x|?dmd^@=LxHe^-FJR*?MV>4EgXNPfH`o*9VP(9iK&i+V!(P_Y zr?-g1gE9KtT+^V;|Gr(IgYcg<_kJ*zqAZiq?Ju?9i%S9!EWjaN{^4sN)m;r*ad~pK za?*JK+ZgKErswvv?;7J+Bx8kYO*8JMw_!S}l1R=vJ~kc9InXEW9r8ep9+{ymVKM>Q zUxmfmiD&0cTv@!0BTB=f*(AF?uUo(AwTI;~S>GE0s zLGkz7NWqQ>rI>H`l+TMh{SrQDYH?-ijvVD+E7-mAbn%3I{r8ii1j^xZYh`_NAnIC*}Ic9IJzMh=S8Y`8pHqvy0hcgl;$ZTDv+7nc+U|>hr;# zV*#~vGY|E68;6uL$JTT)VpxRcanj>Ifc8>NqK?!_y?4kb2!D%#SYC<}IX;b)3mbi@ zXEhjeeN{)!UX1lE4mn6q;|b$OMHfPCR}*!M6{;={HtaSMKMk9~8=+{2+0>A6pSnAvZ z>z~CF+%@6*M)$(W6BTVs4`Prvdn7rOELLsF+9}D5zGK?S(5&!nBaXvUeVceX zgNT22!JUHLIGC1R)C_v1CTAnmMd*tGvNafYtMGM3jN8-e7m6>D!7T>2#{hdRZZ9)&WiV0q)o?OZ1k_3;QBcnk#W_38GwW z{^>(;!ftUEi?Z{W{p0?izv|((o$2=X|6`+;5%8E}zx@x-krg}Rjn6I?gyl+r#Hs?F zw)WpHGiBYZ{|acnd4uO>fzY~KAd21KSZ`upojo{|EFVu-+Wl=HiCZq@WepoR!$2Ff zmpOn08q@@lE`#dK@W%#1Ts5J^8cz1i0nm%r1f=L^+La=N=0kNaLUS8&;=6EP60$9UT)6>=2 z)Aa4)5vZC;`MtQgF@18Z-U*q#S&s;SIJpZ`>9~IxmR(7b0cbu)b1r$a)A7>{@bY-BI{-v`Gkyw#vU5iI8>BXwx2KotSLFQEs@Dt$vS~xI<)tw5CAjm8G2|7Tt@zd!FuV68{ztHScK9qN z&BPfqULBOJ5=kX_=FjpNhyg0P25`M({sLF{vsU&EVP4RKS@}#vO&R5B%V-G7MrkVxg`8j|Idl= zjtujF{|WpvTsdY%BY)-%2n_Oo6!x>OsKcEi7d7g#Hi5hLCi zCf}ZezMhOB+WC^45*cpncvGlt4yXhtmyD?lH32qi8fc*vBs+;GfZm!e%z6pfs<3f_ z`%)p1kY=oB^lrJqqnEbj{8Ehn)a5;^Pr5AN(clu5i6R-*tRFM3i=-(19KQ;{(dW_G0Mbi z3padypEysn{D?dsWq&Ad=Jv~Eg=V`#x-Qr!&PgEe3hmDy8j{6&RWf&_0uCL| z-XJ1zN+onMQ2M8vyN_|=t16f@7+UGNkT%p}BbPG1WI1ni+|?%@m@WeOKA5Q7Z=8^4 zB2>t{vl1XXnJ1@>kM__GdCfowL*n?V-h(VHn6}{Nfvc$WL0~m^FeM8^^Xa9|1AxG` zQ0|ol<)pKccW(xb`)ZVF#Y3sdC8r?kD_E~Kj3wp#mw z2`oY(MwQoB9^{#cH`^ZpwMbgol@P}#K+^GKl@&SK>T*|vlCIBNn!O$eG3pd-Cw6_a z`M2{&S6+#=YHQJ{@>{N89ZGGu8DP^Iz>+JC8-okSp@7j@qEa_7s2^hA(VU^i0DFK& zbFIp*pruz<`+AyF0vcjSmBC2c%lYYc{KweFRPx@4Q5FX)p2eJR4#kEug=mrPn%z@) zcB{3HfdjO+L?;HYrOs9y3*FQ&?a{Ps7dZRR{k%((QlUdfCuHBD0Aq$n9o)P=PrmA^ z?Y-Gcv4|! zq8@J4%M;@ISLI{rKw*D2juW}~7B_|_y-rwXhM?{1?_TYW)fcVR1U|lF;dFa+rxNZ1 ztDKF7wxSZQRJmgl+DYWp=bW~jL2sp9n#H(p`h{#0n8qe|VEX)*`%k~cb%XxyEE=t9 zBLuM2`mHwAC==J|WW25}c2cbQLpUX_XdR-y-BX$o$+kS;s~gAfoDObY5Ee9smVJOt zE+37(RdbvHCCzRXmzQuQZ}p#-wi%p9cve8f$2gt}-TgJOa?`ZNGyA~(7hWgSYJbDf zT`a}TTdY|OX8qbJ&`P|LIssG{{*7*qM8SELhnW}w+*pLi#f%K>%>1r$lXU3=M8C6@f4Bv=AA zbfiztF%{O^C&B^*ME4O?+H9_-Cs`3#+Q(a7Fi>{gnF)NkgpG|%L+i;)6`HtaLaDV= zRWug}52zhi-pfz&*?i>R{Z!}U&q3*E=(!y##4;s@`c{!gv_AOCEVoW8t3hIjC(^7{ z5p8mX?X%(?20d zrlmP2W#wrDs0Z7SHfO#vQ#-s1w>Tf--bVRQX{Bdtg}^NhmQuORtK;6--a^QF-t{rl ztQ*?<-7S(D?UvoiSt;;Gn+4RJ3oL=_1Uh-hfhdpfXT%I_=af~NUL)O%#poWm?*+`b zp2eqTQPD|M)SVs-U{nE)E@UJH>`2;_keL%afCn0y8cTcMzySj-es4_W2jM_K2F(V=_gw&;+#JKMIh46b62~E?jUT$xJ zq$kNbV(4x%rh}C#_W4iFL@=L{H#EW}A{1DKl9UQ)zZjp3eCL~+SLhv(;X|o^nRFj? z(JR(p$w{qHyYg%g+3PpUtUmvR0Mj(p&CxkGs<`XNek#NR`m%PImYprur>V|mIj$j; zF=#zOyVpXfhB)hhAp{(J?ULyk8<>uO!2Vi!)pxbo_K0*nvbKes8n7!yKCO(z25fxG zcnL|qU69-<509t7MfWET z&iE0HPw5b~Dt5pmaVg12@;*La2N>hRlS7LscnDsTMpam9TXi#RmUfFIf9M{a^jAd< zX^f}2y16f1ZO%s~P1xUgZ}w+@hN;xvHSExHP!`_9hY8D#o)-rO{h9)x;v!(=sM}}m zrNK$3lPY6`;_R}9G%?GvjbZ80S1un0CP)U#>McPPT3@cGJd(-x1CctkkF~3|HA+7D zb&VKbD{{D%t3U`?Zmb*M!9a@31tF9o#*`8#j%3lgzX|DZ9GDmnMmYLfqa4LpieiqV zeeowIQws?uqd?Kxttb~&WpbX#H0J4q=H8&?f{Zd8YHvYR<7#ub8}{i}OxpAMPF9MY z#yH2AjHH%Zrblr3vNJ2atf};C0&UKZQ$Ok5lS~vGo7<@YL5psEMHx-6UIH{?Fy=`_ zI+@A95Hr{a{>8E@X+i9Xt01qQc1nQ>K{Y@U9_)$$^3`~qWOSr~*Nl>eaMmby_Op}U zG*qzbt=?U5nqW^r=FQWsxZVLgYcGJ9JT!C!)oG~m67x)rSNKFRISu=9=@g1L1vunS z&u(r5kU-w@x9M)z#q%N5f06|bICaJU0W-m`Y1O+0S)O|jn($>eRjR2A0GVSpMfN`y zQ_31t_1WU2REtD!Uv_=w)%T{yNOPolXv;rAjzc$Qki(L3O~O~CCDd+`6t#FITMFNh zL_mPrDSWbKU4&<3rVCp{ZVpg-u8F%Q2>BrZcK3%2Qg@^jbJykG+ItX$s%i<&ETEH9 z0)l)2co4%c%}~D<1JS+h#Uy%dUyfUYN);sdz0EMiqLjoHaGZ3(^b$UQ?3_S>cDv+L z;*03}#6&Vjl35er5qm9TQgJ9i^4z=(M$*hfu`we7F(8nSrUY#B?UsmA?Rja+^vb4j z*bdOx3U)RGXccK_uZ7uMeE6(^Y*AZ3T%sEu4<3?oUc-4C&`IRCE%$zf!?~ zS0diRdP=wuLXy0|^T`Gz-H9=$TNjQq2B@R;=cCmX(HPJ%<>ydhA+?O+cYu~gBd0q` zsFY%6j787hsxL4!`*UZLgm;bbYK#Q>dp`mmw}LB>VwD?_8Y*+kuZ2adat;M z8Q*n1YD-HZDdrp7@u9r(!n16}tQ3%-D`>AEv7;GkoF)zjT&93nj`4EaP3E1CWO=g1 z)}6t6Q7rf2xTY><0CN`+KeYVe9LNimjkLP<)k%R?aQe@{B{JhJIb0dA;XX3Wsh`FE ztO{cLQWbaL=+9qPZZl1xXKcVkG;R;p^InE?f!NozG@ip;qOGp20a4=r(l7xITP>*) zrv)_lE|~*gCgBc%I?Q_;uY%inzY&bbG2Ny~z-NU__^?tVys914xn#gT ztc{s$U@-4MSn{Q#z>!87NZQY}0CCO7hE-6C$TErrD2r>|aX``B-5Gt$56C(suk?{8 zSY@rMDd|BhrG&C^YG~*bgSV3v>BD!=y@QbYl#kY585j5tBc+-tVF7u6iRBINE&QB1 zK}Wq@wf+s1(mm;#C+JLJLrMMd?tx3gWihswOq!rQ5fA}T;f+;r?x0l#bD+yW_RDIe zcl(+(K4)cGkC7`j-y^? ztbSE!-0_$`UPO{kRb_hSpAE(krJrxS7lD?k=@SU$q~X$V!kLO}{dSS!P76+ZSBrM< z1N|m^2^SE>bw`oLGBhotgacH%MK?>=J_3a4YEJ}F8_jnn_6Mf707URe7H7Qz+qm=+ z*rv#^PzAnkusIH*{w!A#%%2tN1P|%1Bmm)W&B&3{A|r%+lJz9etE*GL7vX5D!4fW_ zrnfyokh&3|VF;Q%zT$)q3nn-xb>JZAJfTabX5|&#A6eJW=`<^^i=;vsQ%(hqY4c=& zZOSkWC%xV8yXrf0B+;2hFl7|$*Yn2(iQ%W0-v`!p9`Q$T+^tr^5W;aBXbt6}yceoP z{G(wUht!Tr+T(ZSE7L(kc@LaS5A~F#RyL&*XgFYqlt^R?O0|DyxyzT``Zks3GVn_3 zeGB2vTW&5%G{&Fv&WWb5jRq`M8TUUfR+?4_gi-Og6s)2MalDk6Kz_&t)9pp*LLwKu zY;O0WX=?xG{%mxBYlT|PD>T+V27`u6X`U0^&ZI8~D>UUO&x=1?Ci2$5$hmQWrS>Jl zp7uYt7S(1sK@C=Rl_@zQ8~;cdL+d6gjGVt1i9`}bnYk>7oG1~rY4p_f^+U=XEFK45+K5eLbw=H&SFlL)mp236QN-rHfK;6{E6{yfQZ<#2 zzzKD;vWevm6*p>wgVg@LiL|M3{u5)L>!q3P(gmDnH#EZb=qImv1L_B-AyDZ)e;~In z>^=!goUi(=xI3V`f4gy1)L#SpF=K-sY5ds`6%33Fu>T6=p}fw8p^jpM{U;`c*B^iD zCVZCZLb_W^Q7=i0eSn)PyGJWyKj6tG+cb7|*SDdd#7u{44Fod>g+AyJlV-q-S%1B6 zYpI82Ie)Fx1Hn)2!osIF{Zj>LrM}bKLQ`i0A+rcl%(4Z$i9?`u8emmz!Oz5 zwLx-T1|+vnlz3TN*M*!D0mXZpdnHfHjxxFr6D%OUvGJhFs?k15F_K)CW~if#us&QN z^X9ILVtWVQ#xU-1>V_E}kgXpFW^Uq#<2|M86&aPlMyLv0exB(P#P|t$-$*@QRmPk} zCv4nc_RE;@ZD7|d0`IQ5k75IYrQb23{GJ^-y&xTH1_9p^pMFgl29-$rfKo8=Qf%;@ z>=xK<*920a;1XD!gS+gGhX!%kSiM_#k^@q9aG`k1z>z`LU?;lSO6PaYq0Tl`Z^$Ne zbo+hlj{DVc>6pdyL43q@gw2p3W5;TnypE(q<2aK~Fb%r6hMTIOgS%Oz?_!ho=&-50 zExRGJ0=7Y@iDnrB78!#X%Z99WzXX0k@>ij-QE16m1yiy^3A~AI!N54mn~pz_jXmzs z&?T!iWevbT@{*DK5sr(H|K)1|OAb~y+gvSDozcZDjTcl59trE{zr>yjxdz-fZ&|fL%@i!yO$sl z^7g~=x(1cqLF{Y=zJ_-{rsgH5+A;A03hiWm0D_YPdX(Pmi|aT^WU7(IeZ?B!1XfLU zbTBR6ve%}x8J#zvQmH_0_nJ-#vdM)UL9^JpHWN?2tMlF~mbSURVO(+qjPKxbI$g7; z0!kO{j&x4*aWZu)g81ZtXOo8g{duHRCTEX9wS!?dGNdnSmtI(0Wr7Cv04O zF$qW89jd+smcM07+HzqRUdk~{LY*1LOuDe^?f@FN<)TpSz#+4g3r>$RmgI@M4rbkI zxXVf4P%KEca{SPa(_Sr_EV|H_1B@oJ#n(%Hrhvv!jaG|x2{Fn{j^QiA%8|w#fu_7l zM)3NddUt0o6A0P8BVMg!kGKP4_)f3Hfq8zcVaxMRzezWMI6HQ5UCt+#aC*gdu|1DQI=o|*(u>qneG zS0{q0Ky&5TNHjXvB)uDgR4=tFso?dmR!9oXtX6A#4TKD15on6ljCR56!TQ30pt4ya zg=vsyinovrLx^O69pltJQntyaiNmCEt>78r*g>7UncvM%kO8L$lxb2}WE94|Ak&l? zT!;m*DHz?-WpHUU{*C76lU&(ckl+s*P$zk=ug+F%yQD~ZRy!x6T=S&s!;d8?wn@L{ z$*<-s{ROE52PF3W#;Vt{ABrpnWN-p zB3Qsdc*Ku%!v|r0;tLKc$AcipLwa!cE(dyUmNnctak`|<@W8~xD*c2&Dxj2C(QGw{ z-&*F9FK#!gdK`lVoHD;fE3gJ_%|@}OM#Yq!;?nhkuvX3S=TXj~0kCMh1){Y~hAN=Y zG~i{V`<qm(g(QL8Gimx*O2saqt`#x51Hp5iK9&MX zSi+FEtEVp*TB0={VyX7I+WmrG+_(-B2efrR$Bm3k148y}05O;%Z>R(ZO4g=EMtgQ7 zqPc=nGDctcl}7C1cJt(98P90{{v&ZD`L6bDgae7oW^|Rtaq2IECfI|N1U`3!xsk%s zgDxP5(SsA7o3LZbpjA`50aDGf5i%9@z%n4}$CjO3-DL5abju91rQag z6TWT~T*p~09xs2RyMSY*3AUChhSP!49i05w9js&8*r`h3@PcE7Bo3ISS(+O_MC6%g zk3d&=c4K{{cj&SG^Y6DO%Ly#R?qITdSI))l`cp&h1<%b*fizzPcVmZC%0AA5W~DQL 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

jqWan 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 MkAua3`P)DK7hlrGkN^Mx literal 0 HcmV?d00001 diff --git a/pkg/contexts/datacontext/context.go b/pkg/contexts/datacontext/context.go index 1cf0a7fe19..2af9fb34d9 100644 --- a/pkg/contexts/datacontext/context.go +++ b/pkg/contexts/datacontext/context.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "reflect" - runtime2 "runtime" "sync" "github.com/mandelsoft/logging" @@ -19,6 +18,7 @@ import ( "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/finalizer" ocmlog "github.com/open-component-model/ocm/pkg/logging" + "github.com/open-component-model/ocm/pkg/refmgmt" "github.com/open-component-model/ocm/pkg/runtime" "github.com/open-component-model/ocm/pkg/utils" ) @@ -47,6 +47,8 @@ const ( MODE_INITIAL ) +const MULTI_REF = false + func (m BuilderMode) String() string { switch m { case MODE_SHARED: @@ -123,6 +125,8 @@ type Context interface { ContextProvider Delegates + IsIdenticalTo(Context) bool + // GetType returns the context type GetType() string GetId() ContextIdentity @@ -137,7 +141,7 @@ type InternalContext interface { Context finalizer.RecorderProvider GetKey() interface{} - Cleanup() error + GetAllocatable() refmgmt.Allocatable } //////////////////////////////////////////////////////////////////////////////// @@ -216,40 +220,21 @@ type gcWrapper struct { *_context } -func (w *gcWrapper) SetContext(c *_context) { - w._context = c -} - -// var _ Context = gcWrapper{} - -// AttributesContext must be defined at wrapper as special -// case for a root context. -// Unfortunately GO generics do not accept this these for -// FinalizedContext anymore because of this -// pointer receiver, therefore we have to copy and specialize -// the complete stuff. -func (w *gcWrapper) AttributesContext() AttributesContext { - if w.updater != nil { - w.updater.Update() +func newView(c *_context, ref ...bool) AttributesContext { + if utils.Optional(ref...) { + return FinalizedContext[gcWrapper](c) } - return w + return c } -func finalizedContext(c *_context) AttributesContext { - var v gcWrapper - p := &v - p.SetContext(c) - p.setSelf(p, c.GetKey()) // prepare for generic bind operation - runtime2.SetFinalizer(&v, lfi) - Debug(p, "create context", "id", c.GetId()) - return p +func (w *gcWrapper) SetContext(c *_context) { + w._context = c } -func lfi(c *gcWrapper) { - err := c.Cleanup() - c.GetRecorder().Record(c.GetId()) - Debug(c, "cleanup context", "error", err) -} +var ( + _ Context = (*_context)(nil) + _ ViewCreator[AttributesContext] = (*_context)(nil) +) // New provides a root attribute context. func New(parentAttrs ...Attributes) AttributesContext { @@ -265,8 +250,18 @@ func newWithActions(mode BuilderMode, parentAttrs Attributes, actions handlers.R c.contextBase = newContextBase(c, CONTEXT_TYPE, key, parentAttrs, &c.updater, ComposeDelegates(logging.NewWithBase(ocmlog.Context()), handlers.NewRegistry(nil, actions)), ) - // return SetupContext(mode, FinalizedContext[gcWrapper](c)) // see above - return SetupContext(mode, finalizedContext(c)) + return SetupContext(mode, c.CreateView()) // see above +} + +func (c *_context) CreateView() AttributesContext { + return newView(c, true) +} + +func (c *_context) AttributesContext() AttributesContext { + if c.updater != nil { + c.updater.Update() + } + return newView(c) } func (c *_context) IsAttributesContext() bool { diff --git a/pkg/contexts/datacontext/context_test.go b/pkg/contexts/datacontext/context_test.go new file mode 100644 index 0000000000..ecb1593e1d --- /dev/null +++ b/pkg/contexts/datacontext/context_test.go @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package datacontext_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + me "github.com/open-component-model/ocm/pkg/contexts/datacontext" +) + +var _ = Describe("area test", func() { + It("can be garbage collected", func() { + // ocmlog.Context().AddRule(logging.NewConditionRule(logging.DebugLevel, me.Realm)) + + ctx := me.New() + Expect(ctx.IsIdenticalTo(ctx)).To(BeTrue()) + + ctx2 := ctx.AttributesContext() + Expect(ctx.IsIdenticalTo(ctx2)).To(BeTrue()) + + ctx3 := me.New() + Expect(ctx.IsIdenticalTo(ctx3)).To(BeFalse()) + }) +}) diff --git a/pkg/contexts/datacontext/cpi.go b/pkg/contexts/datacontext/cpi.go index 3bb83318be..4d372330ad 100644 --- a/pkg/contexts/datacontext/cpi.go +++ b/pkg/contexts/datacontext/cpi.go @@ -7,13 +7,15 @@ package datacontext import ( "context" "fmt" - runtime2 "runtime" "github.com/mandelsoft/logging" + "github.com/modern-go/reflect2" "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/handlers" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/open-component-model/ocm/pkg/refmgmt" + "github.com/open-component-model/ocm/pkg/refmgmt/finalized" ) // NewContextBase creates a context base implementation supporting @@ -29,22 +31,38 @@ func NewContextBase(eff Context, typ string, key interface{}, parentAttrs Attrib // for a context wrapper handling garbage collection. // It also handles the BindTo interface for a context. type GCWrapper struct { - self Context + ref *finalized.FinalizedRef + self Context // reference to wrapper + ctx Context // reference to internal context key interface{} } +var _ provider = (*GCWrapper)(nil) // wrapper provides access to internal context ref + // setSelf is not public to enforce // the usage of this GCWrapper type in context // specific garbage collection wrappers. // It is enforced by the // finalizableContextWrapper interface. -func (w *GCWrapper) setSelf(self Context, key interface{}) { +func (w *GCWrapper) setSelf(a refmgmt.Allocatable, self Context, ictx Context, key interface{}) { + if a != nil { + w.ref, _ = finalized.NewPlainFinalizedView(a) + } w.self = self + w.ctx = ictx w.key = key } +func (w *GCWrapper) IsPersistent() bool { + return true +} + +func (w *GCWrapper) GetInternalContext() Context { + return w.ctx +} + func init() { // linter complains about unused method. - (&GCWrapper{}).setSelf(nil, nil) + (&GCWrapper{}).setSelf(nil, nil, nil, nil) } // BindTo makes the Context reachable via the resulting context.Context. @@ -53,6 +71,88 @@ func (b GCWrapper) BindTo(ctx context.Context) context.Context { return context.WithValue(ctx, b.key, b.self) } +func (w GCWrapper) getAllocatable() refmgmt.Allocatable { + return w.ref.GetAllocatable() +} + +type view interface { + getAllocatable() refmgmt.Allocatable +} + +type viewI interface { + GetAllocatable() refmgmt.Allocatable +} + +func GetContextRefCount(ctx Context) int { + switch a := ctx.(type) { + case view: + if m, ok := a.getAllocatable().(refmgmt.RefMgmt); ok { + return m.RefCount() + } + case viewI: + if m, ok := a.GetAllocatable().(refmgmt.RefMgmt); ok { + return m.RefCount() + } + } + return -1 +} + +type persistent interface { + IsPersistent() bool +} + +type provider interface { + GetInternalContext() Context +} + +type ViewCreator[C Context] interface { + CreateView() C +} + +func IsPersistentContextRef(ctx Context) bool { + if p, ok := ctx.(persistent); ok { + return p.IsPersistent() + } + return false +} + +// PersistentContextRef ensures a persistent context ref to the given +// context to avoid an automatic cleanup of the context, which is +// executed if all persistent refs are gone. +// If you want to keep context related objects longer than your used +// context reference, you should keep a persistent ref. This +// could be the one provided by context creation, or by retrieving +// an explicit one using this function. +func PersistentContextRef[C Context](ctx C) C { + if IsPersistentContextRef(ctx) { + return ctx + } + var c interface{} = ctx + for { + if p, ok := c.(provider); ok { + c = p.GetInternalContext() + } else { + break + } + } + return c.(ViewCreator[C]).CreateView() +} + +func InternalContextRef[C Context](ctx C) C { + if IsPersistentContextRef(ctx) { + var c interface{} = ctx + for { + if p, ok := c.(provider); ok { + c = p.GetInternalContext() + } else { + break + } + } + return c.(C) + } + return ctx +} + // finalizableContextWrapper is the interface for // a context wrapper used to establish a garbage collectable // runtime finalizer. @@ -62,7 +162,8 @@ type finalizableContextWrapper[C InternalContext, P any] interface { InternalContext SetContext(C) - setSelf(Context, interface{}) + provider + setSelf(refmgmt.Allocatable, Context, Context, interface{}) *P } @@ -81,24 +182,17 @@ func FinalizedContext[W Context, C InternalContext, P finalizableContextWrapper[ var v W p := (P)(&v) p.SetContext(c) - p.setSelf(p, c.GetKey()) // prepare for generic bind operation - runtime2.SetFinalizer(p, fi[W, C, P]) - Debug(p, "create context", "id", c.GetId()) + p.setSelf(c.GetAllocatable(), p, c, c.GetKey()) // prepare for generic bind operation return p } -func fi[W Context, C InternalContext, P finalizableContextWrapper[C, W]](c P) { - err := c.Cleanup() - c.GetRecorder().Record(c.GetId()) - Debug(c, "cleanup context", "error", err) -} - type contextBase struct { - ctxtype string - id ContextIdentity - key interface{} - effective Context - attributes *_attributes + ctxtype string + allocatable refmgmt.Allocatable + id ContextIdentity + key interface{} + effective Context + attributes *_attributes delegates finalizer *finalizer.Finalizer @@ -120,9 +214,22 @@ func newContextBase(eff Context, typ string, key interface{}, parentAttrs Attrib delegates: delegates, recorder: recorder, } + c.allocatable = refmgmt.NewAllocatable(c.cleanup, true) + Debug(c, "create context", "id", c.GetId()) return c } +func (c *contextBase) IsIdenticalTo(ctx Context) bool { + if reflect2.IsNil(ctx) { + return false + } + return c.GetId() == ctx.GetId() +} + +func (c *contextBase) GetAllocatable() refmgmt.Allocatable { + return c.allocatable +} + func (c *contextBase) BindTo(ctx context.Context) context.Context { panic("should never be called") } @@ -151,6 +258,13 @@ func (c *contextBase) GetRecorder() *finalizer.RuntimeFinalizationRecoder { return c.recorder } +func (c *contextBase) cleanup() error { + if c.recorder != nil { + c.recorder.Record(c.id) + } + return c.Cleanup() +} + func (c *contextBase) Cleanup() error { list := errors.ErrListf("cleanup %s", c.id) list.Addf(nil, c.finalizer.Finalize(), "finalizers") diff --git a/pkg/contexts/datacontext/gc_test.go b/pkg/contexts/datacontext/gc_test.go index ffae3b33c3..a3c833961f 100644 --- a/pkg/contexts/datacontext/gc_test.go +++ b/pkg/contexts/datacontext/gc_test.go @@ -10,16 +10,34 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/open-component-model/ocm/pkg/generics" me "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/finalizer" ) var _ = Describe("area test", func() { - It("can be garbage collected", func() { + It("can be garbage collectede", func() { + ctx := me.New() + r := finalizer.GetRuntimeFinalizationRecorder(ctx) + id := ctx.GetId() + Expect(me.GetContextRefCount(ctx)).To(Equal(1)) + ctx = nil + runtime.GC() + time.Sleep(time.Second) + Expect(r.Get()).To(ConsistOf(id)) + + }) + + It("provides second reference", func() { // ocmlog.Context().AddRule(logging.NewConditionRule(logging.DebugLevel, me.Realm)) + multiRefs := generics.Conditional(me.MULTI_REF, 2, 1) ctx := me.New() + Expect(me.GetContextRefCount(ctx)).To(Equal(1)) + + actx := ctx.AttributesContext() + Expect(me.GetContextRefCount(ctx)).To(Equal(multiRefs)) r := finalizer.GetRuntimeFinalizationRecorder(ctx) Expect(r).NotTo(BeNil()) @@ -29,6 +47,14 @@ var _ = Describe("area test", func() { ctx.GetType() Expect(r.Get()).To(BeNil()) + actx.GetType() + actx = nil + runtime.GC() + time.Sleep(time.Second) + ctx.GetType() + Expect(r.Get()).To(BeNil()) + Expect(me.GetContextRefCount(ctx)).To(Equal(1)) + ctx = nil for i := 0; i < 100; i++ { runtime.GC() @@ -37,4 +63,34 @@ var _ = Describe("area test", func() { Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) }) + + It("creates views", func() { + ctx := me.New() + r := finalizer.GetRuntimeFinalizationRecorder(ctx) + + Expect(me.GetContextRefCount(ctx)).To(Equal(1)) + Expect(me.IsPersistentContextRef(ctx)).To(BeTrue()) + + view := me.PersistentContextRef(ctx) + Expect(me.GetContextRefCount(view)).To(Equal(1)) // reuse persistent ref + Expect(me.IsPersistentContextRef(view)).To(BeTrue()) + + non := view.AttributesContext() + Expect(me.IsPersistentContextRef(non)).To(BeFalse()) + + view2 := me.PersistentContextRef(non) + Expect(me.GetContextRefCount(view2)).To(Equal(2)) // create new view + Expect(me.IsPersistentContextRef(view2)).To(BeTrue()) + + Expect(ctx.IsIdenticalTo(view)).To(BeTrue()) + Expect(ctx.IsIdenticalTo(view2)).To(BeTrue()) + + ctx = nil + view = nil + view2 = nil + + runtime.GC() + time.Sleep(time.Second) + Expect(len(r.Get())).To(Equal(1)) // ref non is not persistent + }) }) diff --git a/pkg/contexts/oci/internal/builder_test.go b/pkg/contexts/oci/internal/builder_test.go index 3b4cffc107..2240f5bd48 100644 --- a/pkg/contexts/oci/internal/builder_test.go +++ b/pkg/contexts/oci/internal/builder_test.go @@ -22,7 +22,7 @@ var _ = Describe("builder test", func() { Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(ctx.ConfigContext()).To(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.ConfigContext().GetId()).To(BeIdenticalTo(config.DefaultContext().GetId())) Expect(ctx.CredentialsContext()).To(BeIdenticalTo(credentials.DefaultContext())) }) diff --git a/pkg/contexts/oci/internal/context.go b/pkg/contexts/oci/internal/context.go index da67e49200..6013260894 100644 --- a/pkg/contexts/oci/internal/context.go +++ b/pkg/contexts/oci/internal/context.go @@ -14,6 +14,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/runtime" + "github.com/open-component-model/ocm/pkg/utils" ) const CONTEXT_TYPE = "oci" + datacontext.OCM_CONTEXT_SUFFIX @@ -77,15 +78,17 @@ type _context struct { _InternalContext updater cfgcpi.Updater - sharedattributes datacontext.AttributesContext - credentials credentials.Context + credentials credentials.Context knownRepositoryTypes RepositoryTypeScheme specHandlers RepositorySpecHandlers aliases map[string]RepositorySpec } -var _ Context = &_context{} +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) // gcWrapper is used as garbage collectable // wrapper for a context implementation @@ -95,25 +98,35 @@ type gcWrapper struct { *_context } +func newView(c *_context, ref ...bool) Context { + if utils.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + func (w *gcWrapper) SetContext(c *_context) { w._context = c } func newContext(credctx credentials.Context, reposcheme RepositoryTypeScheme, specHandlers RepositorySpecHandlers, delegates datacontext.Delegates) Context { c := &_context{ - sharedattributes: credctx.AttributesContext(), - credentials: credctx, + credentials: datacontext.PersistentContextRef(credctx), knownRepositoryTypes: reposcheme, specHandlers: specHandlers, aliases: map[string]RepositorySpec{}, } c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, credctx.ConfigContext().GetAttributes(), delegates) - c.updater = cfgcpi.NewUpdater(credctx.ConfigContext(), c) - return datacontext.FinalizedContext[gcWrapper](c) + c.updater = cfgcpi.NewUpdaterForFactory(credctx.ConfigContext(), c.OCIContext) + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) } func (c *_context) OCIContext() Context { - return c + return newView(c) } func (c *_context) Update() error { @@ -121,7 +134,7 @@ func (c *_context) Update() error { } func (c *_context) AttributesContext() datacontext.AttributesContext { - return c.sharedattributes + return c.credentials.AttributesContext() } func (c *_context) ConfigContext() config.Context { @@ -141,7 +154,7 @@ func (c *_context) RepositorySpecHandlers() RepositorySpecHandlers { } func (c *_context) MapUniformRepositorySpec(u *UniformRepositorySpec) (RepositorySpec, error) { - return c.specHandlers.MapUniformRepositorySpec(c, u) + return c.specHandlers.MapUniformRepositorySpec(c.OCIContext(), u) } func (c *_context) RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { @@ -153,7 +166,7 @@ func (c *_context) RepositoryForSpec(spec RepositorySpec, creds ...credentials.C if err != nil { return nil, err } - return spec.Repository(c, cred) + return spec.Repository(c.OCIContext(), cred) } func (c *_context) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) { diff --git a/pkg/contexts/ocm/attrs/signingattr/config.go b/pkg/contexts/ocm/attrs/signingattr/config.go index e531e5cb5b..8eff58ab8b 100644 --- a/pkg/contexts/ocm/attrs/signingattr/config.go +++ b/pkg/contexts/ocm/attrs/signingattr/config.go @@ -210,10 +210,7 @@ func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { t, ok := target.(Context) if !ok { if t, ok := target.(datacontext.AttributesContext); ok { - // datacontext.Context is implemented by all context types. - // Therefore, we have to check for the root context, this is the one - // identical to the attributes context of a context. - if t.AttributesContext() == t { + if t.AttributesContext().IsAttributesContext() { return errors.Wrapf(a.ApplyToRootCertsAttr(rootcertsattr.Get(t)), "applying config to certattr failed") } } diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/blobhandler.go index ca073b5987..6dd9bc9641 100644 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/blobhandler.go +++ b/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/blobhandler.go @@ -61,6 +61,7 @@ func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, g return nil, err } + // this section is for logging, only target, err := json.Marshal(repo.GetSpecification()) if err != nil { return nil, errors.Wrapf(err, "cannot marshal target specification") diff --git a/pkg/contexts/ocm/internal/builder_test.go b/pkg/contexts/ocm/internal/builder_test.go index 5350affe3c..98b133c2c2 100644 --- a/pkg/contexts/ocm/internal/builder_test.go +++ b/pkg/contexts/ocm/internal/builder_test.go @@ -29,11 +29,9 @@ var _ = Describe("builder test", func() { Expect(ctx.BlobHandlers()).To(BeIdenticalTo(local.DefaultBlobHandlerRegistry)) Expect(ctx.BlobDigesters()).To(BeIdenticalTo(local.DefaultBlobDigesterRegistry)) - Expect(ctx.ConfigContext()).To(BeIdenticalTo(config.DefaultContext())) - - Expect(ctx.CredentialsContext()).To(BeIdenticalTo(credentials.DefaultContext())) - - Expect(ctx.OCIContext()).To(BeIdenticalTo(oci.DefaultContext())) + Expect(ctx.ConfigContext().GetId()).To(BeIdenticalTo(config.DefaultContext().GetId())) + Expect(ctx.CredentialsContext().GetId()).To(BeIdenticalTo(credentials.DefaultContext().GetId())) + Expect(ctx.OCIContext().GetId()).To(BeIdenticalTo(oci.DefaultContext().GetId())) }) It("creates defaulted", func() { diff --git a/pkg/contexts/ocm/internal/context.go b/pkg/contexts/ocm/internal/context.go index 5e395afad2..f09de688b8 100644 --- a/pkg/contexts/ocm/internal/context.go +++ b/pkg/contexts/ocm/internal/context.go @@ -21,6 +21,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" "github.com/open-component-model/ocm/pkg/runtime" + "github.com/open-component-model/ocm/pkg/utils" ) const CONTEXT_TYPE = "ocm" + datacontext.OCM_CONTEXT_SUFFIX @@ -127,9 +128,8 @@ type _context struct { _InternalContext updater cfgcpi.Updater - sharedattributes datacontext.AttributesContext - credctx credentials.Context - ocictx oci.Context + credctx credentials.Context + ocictx oci.Context knownRepositoryTypes RepositoryTypeScheme knownAccessTypes AccessTypeScheme @@ -141,7 +141,10 @@ type _context struct { resolver *resolver } -var _ Context = &_context{} +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) // gcWrapper is used as garbage collectable // wrapper for a context implementation @@ -151,15 +154,21 @@ type gcWrapper struct { *_context } +func newView(c *_context, ref ...bool) Context { + if utils.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + func (w *gcWrapper) SetContext(c *_context) { w._context = c } func newContext(credctx credentials.Context, ocictx oci.Context, reposcheme RepositoryTypeScheme, accessscheme AccessTypeScheme, specHandlers RepositorySpecHandlers, blobHandlers BlobHandlerRegistry, blobDigesters BlobDigesterRegistry, repodel RepositoryDelegationRegistry, delegates datacontext.Delegates) Context { c := &_context{ - sharedattributes: credctx.AttributesContext(), - credctx: credctx, - ocictx: ocictx, + credctx: datacontext.PersistentContextRef(credctx), + ocictx: datacontext.PersistentContextRef(ocictx), specHandlers: specHandlers, blobHandlers: blobHandlers, blobDigesters: blobDigesters, @@ -172,17 +181,21 @@ func newContext(credctx credentials.Context, ocictx oci.Context, reposcheme Repo c.knownRepositoryTypes = NewRepositoryTypeScheme(&delegatingDecoder{ctx: c, delegate: repodel}, reposcheme) } c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, credctx.GetAttributes(), delegates) - c.updater = cfgcpi.NewUpdater(credctx.ConfigContext(), c) + c.updater = cfgcpi.NewUpdaterForFactory(credctx.ConfigContext(), c.OCMContext) c.resolver = &resolver{ ctx: c, MatchingResolver: NewMatchingResolver(c), } c.Finalizer().With(c.resolver.Finalize) - return datacontext.FinalizedContext[gcWrapper](c) + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) } func (c *_context) OCMContext() Context { - return c + return newView(c) } func (c *_context) Update() error { @@ -190,7 +203,7 @@ func (c *_context) Update() error { } func (c *_context) AttributesContext() datacontext.AttributesContext { - return c.sharedattributes + return c.credctx.AttributesContext() } func (c *_context) ConfigContext() config.Context { diff --git a/pkg/contexts/ocm/internal/resolver.go b/pkg/contexts/ocm/internal/resolver.go index ad32729535..85665b2855 100644 --- a/pkg/contexts/ocm/internal/resolver.go +++ b/pkg/contexts/ocm/internal/resolver.go @@ -55,6 +55,13 @@ func NewRepositoryCache() *RepositoryCache { } } +func (c *RepositoryCache) Reset() { + c.lock.Lock() + defer c.lock.Unlock() + + c.repositories = map[datacontext.ObjectKey]Repository{} +} + func (c *RepositoryCache) LookupRepository(ctx Context, spec RepositorySpec) (Repository, bool, error) { spec, err := ctx.RepositoryTypes().Convert(spec) if err != nil { @@ -104,9 +111,17 @@ func (r *ResolverRule) Match(name string) bool { return r.prefix == "" || r.prefix == name || strings.HasPrefix(name, r.prefix+"/") } +// MatchingResolver hosts rule to match component version names. +// Matched names will be mapped to a specification for repository +// which should be used to look up the component version. +// Therefore, it keeps a reference to the context to use. +// +// ATTENTION: Because such an object is used by the context +// implementation, the context must be kept as ContextProvider +// to provide context views to outbound calls. type MatchingResolver struct { lock sync.Mutex - ctx Context + ctx ContextProvider finalize finalizer.Finalizer cache *RepositoryCache rules []*ResolverRule @@ -115,17 +130,20 @@ type MatchingResolver struct { func NewMatchingResolver(ctx ContextProvider, rules ...*ResolverRule) *MatchingResolver { return &MatchingResolver{ lock: sync.Mutex{}, - ctx: ctx.OCMContext(), + ctx: ctx, cache: NewRepositoryCache(), rules: nil, } } func (r *MatchingResolver) OCMContext() Context { - return r.ctx + return r.ctx.OCMContext() } func (r *MatchingResolver) Finalize() error { + r.lock.Lock() + defer r.lock.Unlock() + defer r.cache.Reset() return r.finalize.Finalize() } @@ -154,9 +172,10 @@ func (r *MatchingResolver) LookupComponentVersion(name string, version string) ( r.lock.Lock() defer r.lock.Unlock() + ctx := r.ctx.OCMContext() for _, rule := range r.rules { if rule.Match(name) { - repo, cached, err := r.cache.LookupRepository(r.ctx, rule.spec) + repo, cached, err := r.cache.LookupRepository(ctx, rule.spec) if err != nil { return nil, err } diff --git a/pkg/contexts/ocm/repositories/comparch/repository.go b/pkg/contexts/ocm/repositories/comparch/repository.go index 56ae0d1078..c99c9c0d98 100644 --- a/pkg/contexts/ocm/repositories/comparch/repository.go +++ b/pkg/contexts/ocm/repositories/comparch/repository.go @@ -12,6 +12,7 @@ import ( "github.com/open-component-model/ocm/pkg/blobaccess" "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localfsblob" @@ -33,7 +34,8 @@ type RepositoryImpl struct { var _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) -func NewRepository(ctx cpi.Context, s *RepositorySpec) (cpi.Repository, error) { +func NewRepository(ctxp cpi.ContextProvider, s *RepositorySpec) (cpi.Repository, error) { + ctx := datacontext.InternalContextRef(ctxp.OCMContext()) if s.GetPathFileSystem() == nil { s.SetPathFileSystem(vfsattr.Get(ctx)) } diff --git a/pkg/contexts/ocm/repositories/composition/repository.go b/pkg/contexts/ocm/repositories/composition/repository.go index aa39d52042..a199ebd4f9 100644 --- a/pkg/contexts/ocm/repositories/composition/repository.go +++ b/pkg/contexts/ocm/repositories/composition/repository.go @@ -12,6 +12,7 @@ import ( "github.com/open-component-model/ocm/pkg/blobaccess" "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" @@ -22,18 +23,19 @@ import ( //////////////////////////////////////////////////////////////////////////////// -func NewRepository(ctx cpi.ContextProvider, names ...string) cpi.Repository { +func NewRepository(ctxp cpi.ContextProvider, names ...string) cpi.Repository { var repositories *Repositories + ctx := datacontext.InternalContextRef(ctxp.OCMContext()) name := utils.Optional(names...) if name != "" { - repositories = ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories).(*Repositories) + repositories = ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories).(*Repositories) if repo := repositories.GetRepository(name); repo != nil { repo, _ = repo.Dup() return repo } } - repo := virtual.NewRepository(ctx.OCMContext(), NewAccess()) + repo := virtual.NewRepository(ctx, NewAccess()) if repositories != nil { repositories.SetRepository(name, repo) repo, _ = repo.Dup() diff --git a/pkg/contexts/ocm/repositories/genericocireg/repository.go b/pkg/contexts/ocm/repositories/genericocireg/repository.go index 3cb94399b8..37c46ae8a7 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/repository.go +++ b/pkg/contexts/ocm/repositories/genericocireg/repository.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/contexts/oci" ocicpi "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" @@ -53,7 +54,8 @@ var ( _ credentials.ConsumerIdentityProvider = (*RepositoryImpl)(nil) ) -func NewRepository(ctx cpi.Context, meta *ComponentRepositoryMeta, ocirepo oci.Repository) cpi.Repository { +func NewRepository(ctxp cpi.ContextProvider, meta *ComponentRepositoryMeta, ocirepo oci.Repository) cpi.Repository { + ctx := datacontext.InternalContextRef(ctxp.OCMContext()) impl := &RepositoryImpl{ ctx: ctx, meta: *DefaultComponentRepositoryMeta(meta), diff --git a/pkg/contexts/ocm/repositories/virtual/repository.go b/pkg/contexts/ocm/repositories/virtual/repository.go index fe3cc2eaa0..dafbe5457d 100644 --- a/pkg/contexts/ocm/repositories/virtual/repository.go +++ b/pkg/contexts/ocm/repositories/virtual/repository.go @@ -5,6 +5,7 @@ package virtual import ( + "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" ) @@ -18,9 +19,9 @@ type RepositoryImpl struct { var _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) -func NewRepository(ctx cpi.Context, acc Access) cpi.Repository { +func NewRepository(ctxp cpi.ContextProvider, acc Access) cpi.Repository { impl := &RepositoryImpl{ - ctx: ctx, + ctx: datacontext.InternalContextRef(ctxp.OCMContext()), access: acc, } return repocpi.NewRepository(impl, "OCM repo[Simple]") diff --git a/pkg/finalizer/object.go b/pkg/finalizer/object.go index 0cd95cfc46..d092914916 100644 --- a/pkg/finalizer/object.go +++ b/pkg/finalizer/object.go @@ -94,7 +94,7 @@ func fi(o *RuntimeFinalizer) { o.finalize() } -func NewRuntimeFinalizer(id ObjectIdentity, r *RuntimeFinalizationRecoder) *RuntimeFinalizer { +func NewRuntimeFinalizer(id ObjectIdentity, r *RuntimeFinalizationRecoder, cleanup ...func() error) *RuntimeFinalizer { f := &RuntimeFinalizer{ id: id, recorder: r, diff --git a/pkg/refmgmt/finalized/doc.go b/pkg/refmgmt/finalized/doc.go new file mode 100644 index 0000000000..13b1e31d54 --- /dev/null +++ b/pkg/refmgmt/finalized/doc.go @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +// Package finalized provided a view management for a backend object, +// which is based on Go Garbage Collection and runtime finalizers. +// Finalization is not possible in Go, if an object is involved in +// a reference cycle. In such a case the complete cycle is not +// garbage collected at all. +// +// If some kind of finalization is required together with cyclic +// object dependencies, the cleanup of the object can therefore not +// be done with runtime finalizers. +// We separate a garbage collectable view object, which holds a +// reference to a backend object featuring cycles. +// The view object uses reference counting for its backend +// together with runtime finalization. Therefore, it does not require +// a Close method. If the view is garbage collected it releases its +// reference to the backend object. If the last view vanished +// the cleanup method for the backend object is called. +// +// The object functionality is exposed via an interface, only, which +// is also implemented by the vies by embedding a pointer to the backend +// object. +// +// If the backend object requires a cycle by holding local objects +// requiring a reference to the object, this can be done +// by NOT using view objects for this cycle, but the backend object +// itself. If the local object wants to pass the backend object to some +// outgoing call, it MUST wrap the backend object again into a new view. +// Therefore, objects involved in the cycle MUST be prepared to handle +// such outgoing references. +package finalized diff --git a/pkg/refmgmt/finalized/finalized_test.go b/pkg/refmgmt/finalized/finalized_test.go new file mode 100644 index 0000000000..09d9dce5a9 --- /dev/null +++ b/pkg/refmgmt/finalized/finalized_test.go @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package finalized_test + +import ( + "runtime" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/open-component-model/ocm/pkg/refmgmt" + "github.com/open-component-model/ocm/pkg/refmgmt/finalized" +) + +type Interface interface { + GetId() finalizer.ObjectIdentity + GetSelf() Interface + + GetRefId() finalizer.ObjectIdentity +} + +type object struct { + refmgmt.Allocatable + recorder *finalizer.RuntimeFinalizationRecoder + name finalizer.ObjectIdentity +} + +func (o *object) GetId() finalizer.ObjectIdentity { + return o.name +} + +func (o *object) GetSelf() Interface { + v, _ := newView(o) + return v +} + +func (o *object) cleanup() error { + o.recorder.Record(finalizer.ObjectIdentity(o.name)) + return nil +} + +type view struct { + ref *finalized.FinalizedRef + *object +} + +var _ Interface = (*view)(nil) + +func newView(o *object) (Interface, error) { + ref, err := finalized.NewFinalizedView(o.Allocatable, finalizer.NewObjectIdentity("test"), o.recorder) + if err != nil { + return nil, err + } + return &view{ + ref: ref, + object: o, + }, nil +} + +func (v *view) GetRefId() finalizer.ObjectIdentity { + return v.ref.GetRefId() +} + +func New(name string, rec *finalizer.RuntimeFinalizationRecoder) Interface { + o := &object{ + name: finalizer.ObjectIdentity(name), + recorder: rec, + } + o.Allocatable = refmgmt.NewAllocatable(o.cleanup, true) + + v, _ := newView(o) + return v +} + +//////////////////////////////////////////////////////////////////////////////// + +var _ = Describe("finalized ref", func() { + var rec *finalizer.RuntimeFinalizationRecoder + + BeforeEach(func() { + rec = &finalizer.RuntimeFinalizationRecoder{} + }) + + It("cleanup ref and object", func() { + o := New("test", rec) + orefid := o.GetRefId() + id := o.GetId() + + o = nil + + runtime.GC() + time.Sleep(time.Second) + + Expect(rec.Get()).To(ConsistOf( + id, + orefid, + )) + }) + + It("cleanup ref after ref and then object", func() { + o := New("test", rec) + o2 := o + orefoid := o.GetRefId() + Expect(o2.GetRefId()).To(Equal(orefoid)) + + id := o.GetId() + r := o.GetSelf() + rrefid := r.GetRefId() + + Expect(r.GetId()).To(Equal(id)) + Expect(orefoid).NotTo(Equal(rrefid)) + + o.GetId() + o = nil + runtime.GC() + time.Sleep(time.Second) + + Expect(rec.Get()).To(ConsistOf()) + + r.GetId() + r = nil + runtime.GC() + time.Sleep(time.Second) + + Expect(rec.Get()).To(ConsistOf( + rrefid, + )) + + o2.GetId() + o2 = nil + runtime.GC() + time.Sleep(time.Second) + + Expect(rec.Get()).To(ContainElements( + orefoid, + rrefid, + id, + )) + + }) + +}) diff --git a/pkg/refmgmt/finalized/finalizedref.go b/pkg/refmgmt/finalized/finalizedref.go new file mode 100644 index 0000000000..c6262fb157 --- /dev/null +++ b/pkg/refmgmt/finalized/finalizedref.go @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package finalized + +import ( + "runtime" + + "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/open-component-model/ocm/pkg/refmgmt" +) + +type FinalizedRef struct { + allocatable refmgmt.Allocatable + id finalizer.ObjectIdentity + recorder *finalizer.RuntimeFinalizationRecoder +} + +func NewPlainFinalizedView(allocatable refmgmt.Allocatable) (*FinalizedRef, error) { + return NewFinalizedView(allocatable, "", nil) +} + +func NewFinalizedView(allocatable refmgmt.Allocatable, id finalizer.ObjectIdentity, rec *finalizer.RuntimeFinalizationRecoder) (*FinalizedRef, error) { + err := allocatable.Ref() + if err != nil { + return nil, err + } + v := &FinalizedRef{allocatable, id, rec} + + runtime.SetFinalizer(v, cleanup) + return v, nil +} + +func (v *FinalizedRef) GetAllocatable() refmgmt.Allocatable { + return v.allocatable +} + +func (v *FinalizedRef) GetRefId() finalizer.ObjectIdentity { + return v.id +} + +func cleanup(v *FinalizedRef) { + v.allocatable.Unref() + if v.recorder != nil { + v.recorder.Record(v.id) + } +} diff --git a/pkg/refmgmt/finalized/suite_test.go b/pkg/refmgmt/finalized/suite_test.go new file mode 100644 index 0000000000..46c70a1d28 --- /dev/null +++ b/pkg/refmgmt/finalized/suite_test.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package finalized_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Finalized Object Test Suite") +} From bcf07413333b284483888c4c5e17cb19934eacc9 Mon Sep 17 00:00:00 2001 From: Fabian Burth Date: Tue, 23 Apr 2024 14:34:53 +0200 Subject: [PATCH 2/2] enable http registries as ocm repositories (#676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description So far, it was not possible for the ocm tooling to communicate with OCI registries with `http`, but only with `https`. The reason therefore was primarily that the scheme was not considered in the parsing and it defaulted to `https`. This PR extends the code, especially the parsing rules, to be able to also handle `http`. Furthermore, it was not possible to specify `localhost` in OCI commands (e.g. `ocm get artifact`). This PR also extends the code to be able to handle `localhost`. Consequently, the following specifications are now possible: For OCI commands (such as ocm get artifact): `http://example.com/repo:version` `http://localhost:8080/repo:version` `localhost:8080/repo:version`(The port, thus `:8080` is required, here. So, `localhost/repo:version` is not possible, since e.g. `localhost/example:1.0.0` is also a valid dockerhub artifact reference and would therefore be ambiguous.) `localhost//repo:version` (The scheme, thus `http://` can be omitted, if the host is separated from the repo with an unambiguous double slash (//).) `http://localhost:8080//repo:version` (A combination of the previous two formats is also possible, although kind of unintuitive.) For OCM commands (such as ocm transfer): `OCIRegistry::http://localhost:80/test//github.com/example` (The type, thus `OCIRegistry::` is required) `OCIRegistry::localhost:80/test//github.com/example` (Works as well without the scheme, but will default to `https`) and the variations with an actual domain `OCIRegistry::http://example.com:80/test//github.com/example` `http://example.com:80/test//github.com/example` `example.com:80/test//github.com/example` (will default to `https`) `example.com/test//github.com/example` (will default to `https`) ... Furthermore, since using http might potentially be insecure and should only be used in test scenarios, the user should get a warning. To enable this, a corresponding log message was introduced on log level warn and the general default log level for the cli tool was changed from error to warn. Short types for repositories that were expected to work (e.g. oci) are now registered with corresponding repository and repository spec handlers. `make generate` also checks whether the examples are running. This failed on mac, since the component of the 01-getting-started did not contain an arm64 darwin executable due to a mistake in its build. This is now fixed, so after the next release `make generate` will be callable on mac again. Until then, one can call e.g. go generate ./cmds/ocm/... manually. ## What type of PR is this? (check all applicable) - [X] 🍕 Feature - [ ] 🐛 Bug Fix - [ ] 📝 Documentation Update - [ ] 🎨 Style - [ ] 🧑‍💻 Code Refactor - [ ] 🔥 Performance Improvements - [ ] ✅ Test - [ ] 🤖 Build - [ ] 🔁 CI - [ ] 📦 Chore (Release) - [ ] ⏩ Revert ## Added tests? - [X] 👍 yes - [ ] 🙅 no, because they aren't needed - [ ] 🙋 no, because I need help - [ ] Separate ticket for tests # (issue/pr) ## Added to documentation? Adjusted the existing ocm references and oci references cli documentations and added additional examples for the most common use cases. ## Checklist: - [X] My code follows the style guidelines of this project - [X] I have performed a self-review of my code - [X] I have commented my code, particularly in hard-to-understand areas - [X] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [X] I have added tests that prove my fix is effective or that my feature works - [X] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Uwe Krueger --- cmds/ocm/app/app.go | 4 +- cmds/ocm/app/app_test.go | 4 +- cmds/ocm/topics/oci/refs/topic.go | 109 ++- cmds/ocm/topics/ocm/refs/topic.go | 69 +- components/ocmcli/Makefile | 2 +- components/ocmcli/resources.yaml | 2 +- .../plugin_accessmethod_compose.md | 8 + docs/pluginreference/plugin_descriptor.md | 8 + .../plugin_valueset_compose.md | 8 + docs/reference/ocm.md | 4 +- docs/reference/ocm_add_routingslips.md | 2 + docs/reference/ocm_bootstrap_configuration.md | 2 + docs/reference/ocm_bootstrap_package.md | 2 + docs/reference/ocm_check_componentversions.md | 2 + docs/reference/ocm_describe_artifacts.md | 1 + docs/reference/ocm_describe_package.md | 2 + docs/reference/ocm_download_artifacts.md | 1 + docs/reference/ocm_download_cli.md | 2 + .../ocm_download_componentversions.md | 2 + docs/reference/ocm_download_resources.md | 2 + docs/reference/ocm_get_artifacts.md | 1 + docs/reference/ocm_get_componentversions.md | 2 + docs/reference/ocm_get_references.md | 2 + docs/reference/ocm_get_resources.md | 2 + docs/reference/ocm_get_routingslips.md | 2 + docs/reference/ocm_get_sources.md | 2 + docs/reference/ocm_hash_componentversions.md | 2 + docs/reference/ocm_install_plugins.md | 2 + docs/reference/ocm_oci-references.md | 109 ++- docs/reference/ocm_ocm-references.md | 55 +- docs/reference/ocm_show_tags.md | 1 + docs/reference/ocm_show_versions.md | 2 + docs/reference/ocm_sign_componentversions.md | 2 + docs/reference/ocm_transfer_artifacts.md | 1 + .../ocm_transfer_componentversions.md | 2 + .../reference/ocm_verify_componentversions.md | 2 + pkg/cobrautils/logopts/options.go | 2 +- pkg/contexts/oci/grammar/grammar.go | 51 +- pkg/contexts/oci/internal/context.go | 1 + pkg/contexts/oci/internal/uniform.go | 35 +- pkg/contexts/oci/ref.go | 65 +- pkg/contexts/oci/ref_test.go | 661 +++++++++++++++++- pkg/contexts/oci/repositories/ctf/uniform.go | 1 + .../oci/repositories/ocireg/repository.go | 40 +- .../oci/repositories/ocireg/uniform.go | 21 +- .../handlers/oci/ocirepo/blobhandler.go | 13 +- pkg/contexts/ocm/grammar/grammar.go | 38 +- pkg/contexts/ocm/grammar/grammar_test.go | 25 +- pkg/contexts/ocm/internal/uniform.go | 12 +- pkg/contexts/ocm/ref.go | 129 +++- pkg/contexts/ocm/ref_test.go | 248 ++++++- pkg/contexts/ocm/repositories/ctf/uniform.go | 2 +- .../ocm/repositories/genericocireg/uniform.go | 24 +- pkg/runtime/utils.go | 18 + 54 files changed, 1613 insertions(+), 198 deletions(-) diff --git a/cmds/ocm/app/app.go b/cmds/ocm/app/app.go index 71af0e4282..3f479b79c9 100644 --- a/cmds/ocm/app/app.go +++ b/cmds/ocm/app/app.go @@ -95,9 +95,9 @@ type CLIOptions struct { } var desc = ` -The Open Component Model command line client support the work with OCM +The Open Component Model command line client supports the work with OCM artifacts, like Component Archives, Common Transport Archive, -Component Repositories, and component versions. +Component Repositories, and Component Versions. Additionally it provides some limited support for the docker daemon, OCI artifacts and registries. diff --git a/cmds/ocm/app/app_test.go b/cmds/ocm/app/app_test.go index 1980b7faf9..4d19586ea8 100644 --- a/cmds/ocm/app/app_test.go +++ b/cmds/ocm/app/app_test.go @@ -72,7 +72,9 @@ var _ = Describe("Test Environment", func() { buf := bytes.NewBuffer(nil) Expect(env.CatchOutput(buf).ExecuteModified(addTestCommands, "logtest")).To(Succeed()) Expect(log.String()).To(StringEqualTrimmedWithContext(` +V[2] warn realm ocm realm test ERROR error realm ocm realm test +V[2] ctxwarn realm ocm realm test ERROR ctxerror realm ocm realm test `)) }) @@ -139,7 +141,7 @@ ERROR ctxerror realm ocm realm test fmt.Printf("%s\n", string(data)) // {"level":"error","msg":"error","realm":"test","time":"2024-03-27 09:54:19"} // {"level":"error","msg":"ctxerror","realm":"test","time":"2024-03-27 09:54:19"} - Expect(len(string(data))).To(Equal(155)) + Expect(len(string(data))).To(Equal(312)) }) It("sets attr from file", func() { diff --git a/cmds/ocm/topics/oci/refs/topic.go b/cmds/ocm/topics/oci/refs/topic.go index 74bc6ba8ca..3664c59110 100644 --- a/cmds/ocm/topics/oci/refs/topic.go +++ b/cmds/ocm/topics/oci/refs/topic.go @@ -18,7 +18,21 @@ func New(ctx clictx.Context) *cobra.Command { Use: "oci-references", Short: "notation for OCI references", Example: ` -ghcr.io/mandelsoft/cnudie:1.0.0 ++ctf+directory::./ocm/ctf//ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 + +oci::{"baseUrl": "ghcr.io"}//open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 + +oci::https://ghcr.io/open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 +oci::https://ghcr.io//open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 + +oci::http://localhost:8080/ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 +oci::http://localhost:8080//ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 + +ubuntu:24.04 +ubuntu + +tensorflow/tensorflow:2.15.0 +tensorflow/tensorflow `, Long: ` The command line client supports a special notation scheme for specifying @@ -29,26 +43,97 @@ images are possible:

[+][<type>::][./][<file path>//<repository>[:<tag>][@<digest>]
- or -
[<type>::][<json repo spec>//]<repository>[:<tag>][@<digest>]
- or -
[<type>::][<scheme>:://]<domain>[:<port>/]<repository>[:<tag>][@<digest>]
- or +
+ +or + +
+
[+][<type>::][<json repo spec>//]<repository>[:<tag>][@<digest>]
+
+ +Notice that if you specify the <type> in the beginning of this +notation AND in the <json repo spec>, the types have to match +(but there is no reason to specify the type in both places). + +or + +
+
[+][<type>::][<scheme>://]<domain>[:<port>][/]/<repository>[:<tag>][@<digest>]
+
+ +Notice that this notation optionally also allows a double slash to +seperate <domain>[:<port>] and <repository>. While it is +not necessary for unambiguous parsing here, it is supported for +consistency with the other notations. + +or + +
+
[+][<type>::][<scheme>://]<host>:<port>/<repository>[:<tag>][@<digest>]
+
+ +Notice that <port> is required in this notation. Without <port>, +this notation would be ambiguous with the docker library notation +mentioned below. + +or + +
+
[+][<type>::][<scheme>://]<host>[:<port>]//<repository>[:<tag>][@<digest>]
+
+ +Notice the double slash (//) before the <repository>. This serves as +a clear separator between <host>[:<port>] and <repository>. +Thus, with this notation, the port is optional and can therefore be +omitted without creating ambiguity with the docker library notation +mentioned below. + +or + +
<docker library>[:<tag>][@<digest>]
- or +
+ +or + +
<docker repository>/<docker image>[:<tag>][@<digest>]
+--- + Besides dedicated artifacts it is also possible to denote registries as a whole:
-
[+][<type>::][<scheme>:://]<domain>[:<port>]
- or -
[+][<type>::]<json repo spec>
- or -
[+][<type>::][./]<file path>
+
[+][<type>::][./]<file path>
+
+ +or + +
+
[+][<type>::]<json repo spec>
+
+ +Notice that if you specify the <type> in the beginning of this +notation AND in the <json repo spec>, the types have to match +(but there is no reason to specify the type in both places). + +or + +
+
[+][<type>::][<scheme>://]<domain>[:<port>]
+
+ +or + +
+
[+][<type>::][<scheme>://]<host>[:<port>]
+ +Notice that <port> is optional in this notation since this cannot be +an image reference and therefore cannot be ambiguous with the docker +library notation. ` + FileBasedUsage(), } } diff --git a/cmds/ocm/topics/ocm/refs/topic.go b/cmds/ocm/topics/ocm/refs/topic.go index 5bb6f499ad..41cac75876 100644 --- a/cmds/ocm/topics/ocm/refs/topic.go +++ b/cmds/ocm/topics/ocm/refs/topic.go @@ -16,9 +16,27 @@ func New(ctx clictx.Context) *cobra.Command { Use: "ocm-references", Short: "notation for OCM references", Example: ` -ghcr.io/mandelsoft/cnudie//github.com/mandelsoft/pause:1.0.0 +Complete Component Reference Specifications (including all optional arguments): -ctf+tgz::./ctf ++ctf+directory::./ocm/ctf//ocm.software/ocmcli:0.7.0 + +oci::{"baseUrl":"ghcr.io","componentNameMapping":"urlPath","subPath":"open-component-model"}//ocm.software/ocmcli.0.7.0 + +oci::https://ghcr.io:443/open-component-model//ocm.software/ocmcli:0.7.0 + +oci::http://localhost:8080/local-component-repository//ocm.software/ocmcli:0.7.0 + +--- + +Short-Hand Component Reference Specifications (omitting optional arguments): + +./ocm/ctf//ocm.software/ocmcli:0.7.0 + +ghcr.io/open-component-model//ocm.software/ocmcli:0.7.0 + +localhost:8080/local-component-repository//ocm.software/ocmcli:0.7.0 (defaulting to https) + +http://localhost:8080/local-component-repository//ocm.software/ocmcli:0.7.0 `, Long: ` The command line client supports a special notation scheme for specifying @@ -27,24 +45,53 @@ references to any registry supported by the OCM toolset that can host OCM components:
-
[+][<type>::][./][<file path>//<component id>[:<version>]
- or -
[+][<type>::]<domain>[:<port>][/<repository prefix>]//<component id>[:<version]
- or -
[<type>::][<json repo spec>//]<component id>[:<version>]
+
[+][<type>::][./]<file path>//<component id>[:<version>]
+
+ +or +
+
[+][<type>::][<json repo spec>//]<component id>[:<version>]
+or + +
+
[+][<type>::][<scheme>://]<domain>[:<port>][/<repository prefix>]//<component id>[:<version]
+
+ +or + +
+
[+][<type>::][<scheme>://]<host>[:<port>][/<repository prefix>]//<component id>[:<version]
+
+ +--- + Besides dedicated components it is also possible to denote repositories as a whole:
-
[+][<type>::][<scheme>:://]<domain>[:<port>][/<repository prefix>]
- or -
[+][<type>::]<json repo spec>
- or
[+][<type>::][./]<file path>
+ +or + +
+
[+][<type>::]<json repo spec>
+
+ +or + +
+
[+][<type>::][<scheme>://]<domain>[:<port>][/<repository prefix>]
+
+ +or + +
+
[+][<type>::][<scheme>://]<host>[:<port>][/<repository prefix>]
+
` + topicocirefs.FileBasedUsage(), } } diff --git a/components/ocmcli/Makefile b/components/ocmcli/Makefile index 694f6b7269..30db9447fc 100644 --- a/components/ocmcli/Makefile +++ b/components/ocmcli/Makefile @@ -20,7 +20,7 @@ PLATFORM_ARCH := $(shell go env GOARCH) CMDSRCS=$(shell find $(REPO_ROOT)/cmds/$(CMD) -type f) Makefile OCMSRCS=$(shell find $(REPO_ROOT)/pkg -type f) $(REPO_ROOT)/go.* -ATTRIBUTES = VERSION="$(VERSION)" NAME="$(NAME)" COMMIT="$(COMMIT)" IMAGE="$(IMAGE):$(VERSION)" PLATFORMS="$(IMAGE_PLATFORMS)" GEN="$(GEN)" MULTI=$(MULTI) +ATTRIBUTES = VERSION="$(VERSION)" NAME="$(NAME)" COMMIT="$(COMMIT)" IMAGE="$(IMAGE):$(VERSION)" PLATFORMS="$(PLATFORMS)" IMAGE_PLATFORMS="$(IMAGE_PLATFORMS)" GEN="$(GEN)" MULTI=$(MULTI) ifeq ($(MULTI),true) FLAGSUF = .multi diff --git a/components/ocmcli/resources.yaml b/components/ocmcli/resources.yaml index 312ab25ff4..ac152b9b83 100644 --- a/components/ocmcli/resources.yaml +++ b/components/ocmcli/resources.yaml @@ -23,7 +23,7 @@ helper: input: type: (( bool(values.MULTI) ? "dockermulti" :"docker" )) repository: (( index(values.IMAGE, ":") >= 0 ? substr(values.IMAGE,0,index(values.IMAGE,":")) :values.IMAGE )) - variants: (( bool(values.MULTI) ? map[split(" ", values.PLATFORMS)|v|-> values.IMAGE "-" replace(v,"/","-")] :~~ )) + variants: (( bool(values.MULTI) ? map[split(" ", values.IMAGE_PLATFORMS)|v|-> values.IMAGE "-" replace(v,"/","-")] :~~ )) path: (( !bool(values.MULTI) ? values.IMAGE :~~ )) diff --git a/docs/pluginreference/plugin_accessmethod_compose.md b/docs/pluginreference/plugin_accessmethod_compose.md index 58bd57eff4..5ffbea5834 100644 --- a/docs/pluginreference/plugin_accessmethod_compose.md +++ b/docs/pluginreference/plugin_accessmethod_compose.md @@ -40,28 +40,36 @@ The following predefined option types can be used: - accessRegistry: [*string*] registry base URL - accessRepository: [*string*] repository URL - accessVersion: [*string*] version for access specification + - body: [*string*] body of a http request - bucket: [*string*] bucket name - comment: [*string*] comment field value - commit: [*string*] git commit id - digest: [*string*] blob digest - globalAccess: [*map[string]YAML*] access specification for global access + - header: [*string:string,string*] http headers - hint: [*string*] (repository) hint for local artifacts - mediaType: [*string*] media type for artifact blob representation + - noredirect: [*bool*] http redirect behavior - reference: [*string*] reference name - region: [*string*] region name - size: [*int*] blob size + - url: [*string*] artifact or server url + - verb: [*string*] http request method The following predefined value types are supported: - YAML: JSON or YAML document string + - []byte: byte value - []string: list of string values - bool: boolean flag - int: integer value - map[string]YAML: JSON or YAML map - string: string value + - string:string,string: string map defined by dedicated assignment of comma separated strings - string=YAML: string map with arbitrary values defined by dedicated assignments - string=string: string map defined by dedicated assignments + - string=string,string: string map defined by dedicated assignment of comma separated strings ### SEE ALSO diff --git a/docs/pluginreference/plugin_descriptor.md b/docs/pluginreference/plugin_descriptor.md index 2700827888..f04a2169f4 100644 --- a/docs/pluginreference/plugin_descriptor.md +++ b/docs/pluginreference/plugin_descriptor.md @@ -125,28 +125,36 @@ The following predefined option types can be used: - accessRegistry: [*string*] registry base URL - accessRepository: [*string*] repository URL - accessVersion: [*string*] version for access specification + - body: [*string*] body of a http request - bucket: [*string*] bucket name - comment: [*string*] comment field value - commit: [*string*] git commit id - digest: [*string*] blob digest - globalAccess: [*map[string]YAML*] access specification for global access + - header: [*string:string,string*] http headers - hint: [*string*] (repository) hint for local artifacts - mediaType: [*string*] media type for artifact blob representation + - noredirect: [*bool*] http redirect behavior - reference: [*string*] reference name - region: [*string*] region name - size: [*int*] blob size + - url: [*string*] artifact or server url + - verb: [*string*] http request method The following predefined value types are supported: - YAML: JSON or YAML document string + - []byte: byte value - []string: list of string values - bool: boolean flag - int: integer value - map[string]YAML: JSON or YAML map - string: string value + - string:string,string: string map defined by dedicated assignment of comma separated strings - string=YAML: string map with arbitrary values defined by dedicated assignments - string=string: string map defined by dedicated assignments + - string=string,string: string map defined by dedicated assignment of comma separated strings #### Uploader Descriptor diff --git a/docs/pluginreference/plugin_valueset_compose.md b/docs/pluginreference/plugin_valueset_compose.md index d7466f9d9f..0ad1025ed4 100644 --- a/docs/pluginreference/plugin_valueset_compose.md +++ b/docs/pluginreference/plugin_valueset_compose.md @@ -40,28 +40,36 @@ The following predefined option types can be used: - accessRegistry: [*string*] registry base URL - accessRepository: [*string*] repository URL - accessVersion: [*string*] version for access specification + - body: [*string*] body of a http request - bucket: [*string*] bucket name - comment: [*string*] comment field value - commit: [*string*] git commit id - digest: [*string*] blob digest - globalAccess: [*map[string]YAML*] access specification for global access + - header: [*string:string,string*] http headers - hint: [*string*] (repository) hint for local artifacts - mediaType: [*string*] media type for artifact blob representation + - noredirect: [*bool*] http redirect behavior - reference: [*string*] reference name - region: [*string*] region name - size: [*int*] blob size + - url: [*string*] artifact or server url + - verb: [*string*] http request method The following predefined value types are supported: - YAML: JSON or YAML document string + - []byte: byte value - []string: list of string values - bool: boolean flag - int: integer value - map[string]YAML: JSON or YAML map - string: string value + - string:string,string: string map defined by dedicated assignment of comma separated strings - string=YAML: string map with arbitrary values defined by dedicated assignments - string=string: string map defined by dedicated assignments + - string=string,string: string map defined by dedicated assignment of comma separated strings ### SEE ALSO diff --git a/docs/reference/ocm.md b/docs/reference/ocm.md index 3046ee8c66..d2e3a12be2 100644 --- a/docs/reference/ocm.md +++ b/docs/reference/ocm.md @@ -29,9 +29,9 @@ ocm [] ... ### Description -The Open Component Model command line client support the work with OCM +The Open Component Model command line client supports the work with OCM artifacts, like Component Archives, Common Transport Archive, -Component Repositories, and component versions. +Component Repositories, and Component Versions. Additionally it provides some limited support for the docker daemon, OCI artifacts and registries. diff --git a/docs/reference/ocm_add_routingslips.md b/docs/reference/ocm_add_routingslips.md index ce8478ffa4..dd10010bf6 100644 --- a/docs/reference/ocm_add_routingslips.md +++ b/docs/reference/ocm_add_routingslips.md @@ -97,10 +97,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_bootstrap_configuration.md b/docs/reference/ocm_bootstrap_configuration.md index e7609ec4cb..142e8d2202 100644 --- a/docs/reference/ocm_bootstrap_configuration.md +++ b/docs/reference/ocm_bootstrap_configuration.md @@ -78,10 +78,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_bootstrap_package.md b/docs/reference/ocm_bootstrap_package.md index ed44d538b5..bef6e13a14 100644 --- a/docs/reference/ocm_bootstrap_package.md +++ b/docs/reference/ocm_bootstrap_package.md @@ -159,10 +159,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_check_componentversions.md b/docs/reference/ocm_check_componentversions.md index 66af32da98..db42cf0858 100644 --- a/docs/reference/ocm_check_componentversions.md +++ b/docs/reference/ocm_check_componentversions.md @@ -66,10 +66,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_describe_artifacts.md b/docs/reference/ocm_describe_artifacts.md index 218a74ba85..5c5728682a 100644 --- a/docs/reference/ocm_describe_artifacts.md +++ b/docs/reference/ocm_describe_artifacts.md @@ -60,6 +60,7 @@ linked library can be used: - DockerDaemon: v1 - Empty: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_describe_package.md b/docs/reference/ocm_describe_package.md index 60eb15f23a..1790bd2a77 100644 --- a/docs/reference/ocm_describe_package.md +++ b/docs/reference/ocm_describe_package.md @@ -69,10 +69,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_download_artifacts.md b/docs/reference/ocm_download_artifacts.md index c2a2e85abe..101dea7314 100644 --- a/docs/reference/ocm_download_artifacts.md +++ b/docs/reference/ocm_download_artifacts.md @@ -62,6 +62,7 @@ linked library can be used: - DockerDaemon: v1 - Empty: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_download_cli.md b/docs/reference/ocm_download_cli.md index fc7a0ee462..01dc462c08 100644 --- a/docs/reference/ocm_download_cli.md +++ b/docs/reference/ocm_download_cli.md @@ -76,10 +76,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_download_componentversions.md b/docs/reference/ocm_download_componentversions.md index 82487f63b6..de66681659 100644 --- a/docs/reference/ocm_download_componentversions.md +++ b/docs/reference/ocm_download_componentversions.md @@ -65,10 +65,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_download_resources.md b/docs/reference/ocm_download_resources.md index b48beb3071..2754454a4f 100644 --- a/docs/reference/ocm_download_resources.md +++ b/docs/reference/ocm_download_resources.md @@ -101,10 +101,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_get_artifacts.md b/docs/reference/ocm_get_artifacts.md index 2eb9447cb9..a1f370c2c9 100644 --- a/docs/reference/ocm_get_artifacts.md +++ b/docs/reference/ocm_get_artifacts.md @@ -60,6 +60,7 @@ linked library can be used: - DockerDaemon: v1 - Empty: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_get_componentversions.md b/docs/reference/ocm_get_componentversions.md index 3f33d7d644..8d225c75b9 100644 --- a/docs/reference/ocm_get_componentversions.md +++ b/docs/reference/ocm_get_componentversions.md @@ -75,10 +75,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_get_references.md b/docs/reference/ocm_get_references.md index aaf24e7728..0807414767 100644 --- a/docs/reference/ocm_get_references.md +++ b/docs/reference/ocm_get_references.md @@ -76,10 +76,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_get_resources.md b/docs/reference/ocm_get_resources.md index cf9ef134d3..8abddf2f05 100644 --- a/docs/reference/ocm_get_resources.md +++ b/docs/reference/ocm_get_resources.md @@ -76,10 +76,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_get_routingslips.md b/docs/reference/ocm_get_routingslips.md index 518d9d3c0c..d0adb0de32 100644 --- a/docs/reference/ocm_get_routingslips.md +++ b/docs/reference/ocm_get_routingslips.md @@ -75,10 +75,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_get_sources.md b/docs/reference/ocm_get_sources.md index f3bc6f3c1f..51b4bfe545 100644 --- a/docs/reference/ocm_get_sources.md +++ b/docs/reference/ocm_get_sources.md @@ -76,10 +76,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_hash_componentversions.md b/docs/reference/ocm_hash_componentversions.md index 7999825f90..f9fd49fc13 100644 --- a/docs/reference/ocm_hash_componentversions.md +++ b/docs/reference/ocm_hash_componentversions.md @@ -109,10 +109,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_install_plugins.md b/docs/reference/ocm_install_plugins.md index 5e27180a96..72777db8db 100644 --- a/docs/reference/ocm_install_plugins.md +++ b/docs/reference/ocm_install_plugins.md @@ -68,10 +68,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_oci-references.md b/docs/reference/ocm_oci-references.md index c29d9b2404..b317192bfa 100644 --- a/docs/reference/ocm_oci-references.md +++ b/docs/reference/ocm_oci-references.md @@ -11,27 +11,98 @@ images are possible:
[+][<type>::][./][<file path>//<repository>[:<tag>][@<digest>]
- or -
[<type>::][<json repo spec>//]<repository>[:<tag>][@<digest>]
- or -
[<type>::][<scheme>:://]<domain>[:<port>/]<repository>[:<tag>][@<digest>]
- or +
+ +or + +
+
[+][<type>::][<json repo spec>//]<repository>[:<tag>][@<digest>]
+
+ +Notice that if you specify the <type> in the beginning of this +notation AND in the <json repo spec>, the types have to match +(but there is no reason to specify the type in both places). + +or + +
+
[+][<type>::][<scheme>://]<domain>[:<port>][/]/<repository>[:<tag>][@<digest>]
+
+ +Notice that this notation optionally also allows a double slash to +seperate <domain>[:<port>] and <repository>. While it is +not necessary for unambiguous parsing here, it is supported for +consistency with the other notations. + +or + +
+
[+][<type>::][<scheme>://]<host>:<port>/<repository>[:<tag>][@<digest>]
+
+ +Notice that <port> is required in this notation. Without <port>, +this notation would be ambiguous with the docker library notation +mentioned below. + +or + +
+
[+][<type>::][<scheme>://]<host>[:<port>]//<repository>[:<tag>][@<digest>]
+
+ +Notice the double slash (//) before the <repository>. This serves as +a clear separator between <host>[:<port>] and <repository>. +Thus, with this notation, the port is optional and can therefore be +omitted without creating ambiguity with the docker library notation +mentioned below. + +or + +
<docker library>[:<tag>][@<digest>]
- or +
+ +or + +
<docker repository>/<docker image>[:<tag>][@<digest>]
+--- + Besides dedicated artifacts it is also possible to denote registries as a whole:
-
[+][<type>::][<scheme>:://]<domain>[:<port>]
- or -
[+][<type>::]<json repo spec>
- or -
[+][<type>::][./]<file path>
+
[+][<type>::][./]<file path>
+
+ +or + +
+
[+][<type>::]<json repo spec>
+
+ +Notice that if you specify the <type> in the beginning of this +notation AND in the <json repo spec>, the types have to match +(but there is no reason to specify the type in both places). + +or + +
+
[+][<type>::][<scheme>://]<domain>[:<port>]
+
+ +or + +
+
[+][<type>::][<scheme>://]<host>[:<port>]
+Notice that <port> is optional in this notation since this cannot be +an image reference and therefore cannot be ambiguous with the docker +library notation. + The optional + is used for file based implementations (Common Transport Format) to indicate the creation of a not yet existing file. @@ -42,7 +113,21 @@ character. The following formats are supported: directory, ta ### Examples ``` -ghcr.io/mandelsoft/cnudie:1.0.0 ++ctf+directory::./ocm/ctf//ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 + +oci::{"baseUrl": "ghcr.io"}//open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 + +oci::https://ghcr.io/open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 +oci::https://ghcr.io//open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 + +oci::http://localhost:8080/ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 +oci::http://localhost:8080//ocm.software/ocmcli/ocmcli-image:0.7.0@sha256:29c842be1ef1da67f6a1c07a3a3a8eb101bbcc4c80f174b87d147b341bca9625 + +ubuntu:24.04 +ubuntu + +tensorflow/tensorflow:2.15.0 +tensorflow/tensorflow ``` ### SEE ALSO diff --git a/docs/reference/ocm_ocm-references.md b/docs/reference/ocm_ocm-references.md index 28e2e37c64..9b486b596e 100644 --- a/docs/reference/ocm_ocm-references.md +++ b/docs/reference/ocm_ocm-references.md @@ -9,25 +9,54 @@ references to any registry supported by the OCM toolset that can host OCM components:
-
[+][<type>::][./][<file path>//<component id>[:<version>]
- or -
[+][<type>::]<domain>[:<port>][/<repository prefix>]//<component id>[:<version]
- or -
[<type>::][<json repo spec>//]<component id>[:<version>]
+
[+][<type>::][./]<file path>//<component id>[:<version>]
+
+ +or + +
+
[+][<type>::][<json repo spec>//]<component id>[:<version>]
+
+ +or + +
+
[+][<type>::][<scheme>://]<domain>[:<port>][/<repository prefix>]//<component id>[:<version]
+
+or + +
+
[+][<type>::][<scheme>://]<host>[:<port>][/<repository prefix>]//<component id>[:<version]
+--- + Besides dedicated components it is also possible to denote repositories as a whole:
-
[+][<type>::][<scheme>:://]<domain>[:<port>][/<repository prefix>]
- or -
[+][<type>::]<json repo spec>
- or
[+][<type>::][./]<file path>
+or + +
+
[+][<type>::]<json repo spec>
+
+ +or + +
+
[+][<type>::][<scheme>://]<domain>[:<port>][/<repository prefix>]
+
+ +or + +
+
[+][<type>::][<scheme>://]<host>[:<port>][/<repository prefix>]
+
+ The optional + is used for file based implementations (Common Transport Format) to indicate the creation of a not yet existing file. @@ -38,9 +67,13 @@ character. The following formats are supported: directory, ta ### Examples ``` -ghcr.io/mandelsoft/cnudie//github.com/mandelsoft/pause:1.0.0 ++ctf+directory::./ocm/ctf//ocm.software/ocmcli:0.7.0 + +oci::{"baseUrl":"ghcr.io","componentNameMapping":"urlPath","subPath":"open-component-model"}//ocm.software/ocmcli.0.7.0 + +oci::https://ghcr.io:443/open-component-model//ocm.software/ocmcli:0.7.0 -ctf+tgz::./ctf +oci::http://localhost:8080/local-component-repository//ocm.software/ocmcli:0.7.0 ``` ### SEE ALSO diff --git a/docs/reference/ocm_show_tags.md b/docs/reference/ocm_show_tags.md index 886ee7a49f..3fd0a36d29 100644 --- a/docs/reference/ocm_show_tags.md +++ b/docs/reference/ocm_show_tags.md @@ -52,6 +52,7 @@ linked library can be used: - DockerDaemon: v1 - Empty: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_show_versions.md b/docs/reference/ocm_show_versions.md index 344a20baff..309f09b1e0 100644 --- a/docs/reference/ocm_show_versions.md +++ b/docs/reference/ocm_show_versions.md @@ -62,10 +62,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_sign_componentversions.md b/docs/reference/ocm_sign_componentversions.md index f349615e29..a19b73c505 100644 --- a/docs/reference/ocm_sign_componentversions.md +++ b/docs/reference/ocm_sign_componentversions.md @@ -84,10 +84,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_transfer_artifacts.md b/docs/reference/ocm_transfer_artifacts.md index b650382015..3a57c7b4f2 100644 --- a/docs/reference/ocm_transfer_artifacts.md +++ b/docs/reference/ocm_transfer_artifacts.md @@ -71,6 +71,7 @@ linked library can be used: - DockerDaemon: v1 - Empty: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_transfer_componentversions.md b/docs/reference/ocm_transfer_componentversions.md index cbbae63fe8..5e75f7db5b 100644 --- a/docs/reference/ocm_transfer_componentversions.md +++ b/docs/reference/ocm_transfer_componentversions.md @@ -87,10 +87,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/docs/reference/ocm_verify_componentversions.md b/docs/reference/ocm_verify_componentversions.md index e34ae26685..08997e38ef 100644 --- a/docs/reference/ocm_verify_componentversions.md +++ b/docs/reference/ocm_verify_componentversions.md @@ -81,10 +81,12 @@ linked library can be used: Dedicated OCM repository types: - ComponentArchive: v1 + - ca: v1 OCI Repository types (using standard component repository to OCI mapping): - CommonTransportFormat: v1 - OCIRegistry: v1 + - ctf: v1 - oci: v1 - ociRegistry diff --git a/pkg/cobrautils/logopts/options.go b/pkg/cobrautils/logopts/options.go index 85058baa07..5af9483f5f 100644 --- a/pkg/cobrautils/logopts/options.go +++ b/pkg/cobrautils/logopts/options.go @@ -87,7 +87,7 @@ func (o *Options) Configure(ctx ocm.Context, logctx logging.Context) error { } logctx.SetDefaultLevel(l) } else { - logctx.SetDefaultLevel(logging.ErrorLevel) + logctx.SetDefaultLevel(logging.WarnLevel) } logcfg := &config.Config{DefaultLevel: logging.LevelName(logctx.GetDefaultLevel())} diff --git a/pkg/contexts/oci/grammar/grammar.go b/pkg/contexts/oci/grammar/grammar.go index e949574f11..09990802b6 100644 --- a/pkg/contexts/oci/grammar/grammar.go +++ b/pkg/contexts/oci/grammar/grammar.go @@ -50,14 +50,14 @@ var ( // repository name components. RepositorySeparatorRegexp = Literal(RepositorySeparator) - // alphaNumericRegexp defines the alpha numeric atom, typically a + // AlphaNumericRegexp defines the alpha numeric atom, typically a // component of names. This only allows lower case characters and digits. AlphaNumericRegexp = Match(`[a-z0-9]+`) - // separatorRegexp defines the separators allowed to be embedded in name + // SeparatorRegexp defines the separators allowed to be embedded in name // components. This allow one period, one or two underscore and multiple // dashes. - separatorRegexp = Match(`(?:[._]|__|[-]*)`) + SeparatorRegexp = Match(`(?:[._]|__|[-]*)`) // dockerOrgSeparatorRegexp defines the separators allowed to be // embedded in a docker organization name. @@ -76,7 +76,7 @@ var ( // separated by one period, one or two underscore and multiple dashes. NameComponentRegexp = Sequence( AlphaNumericRegexp, - Optional(Repeated(separatorRegexp, AlphaNumericRegexp))) + Optional(Repeated(SeparatorRegexp, AlphaNumericRegexp))) // DomainComponentRegexp restricts the registry domain component of a // repository name to start with a component as defined by DomainPortRegexp @@ -85,6 +85,10 @@ var ( IPRegexp = Sequence(Match("[0-9]+"), Literal(`.`), Match("[0-9]+"), Literal(`.`), Match("[0-9]+"), Literal(`.`), Match("[0-9]+")) + SchemeRegexp = Sequence(Capture(Match("[a-z]+")), Literal(`://`)) + + AnchoredSchemedRegexp = Anchored(Optional(SchemeRegexp), Capture(Match(".*"))) + // DomainRegexp defines the structure of potential domain components // that may be part of image names. This is purposely a subset of what is // allowed by DNS to ensure backwards compatibility with Docker image @@ -101,11 +105,48 @@ var ( DomainRegexp, Optional(Literal(`:`), Match(`[0-9]+`))) + // SchemeDomainPortRegexp defines the structure of potential domain components + // that may be part of image names. This is purposely a subset of what is + // allowed by DNS to ensure backwards compatibility with Docker image + // names followed by an optional port part. + SchemeDomainPortRegexp = Sequence( + Optional(SchemeRegexp), + Capture(DomainPortRegexp)) + // HostPortRegexp describes a non-DNS simple hostname like localhost. HostPortRegexp = Sequence( Or(DomainComponentRegexp, IPRegexp), Optional(Literal(`:`), Match(`[0-9]+`))) + // SchemedHostPortRegexp describes a non-DNS simple hostname with scheme like https://localhost. + SchemedHostPortRegexp = Sequence( + SchemeRegexp, + Capture(HostPortRegexp)) + + SchemeHostPortRegexp = Sequence( + Optional(SchemeRegexp), + Capture(ReqHostPortRegexp)) + + // SchemedHostPortArtifactRegexp describes a non-DNS simple hostname with scheme like https://localhost/repository:1.0.0 with the scheme being required. + AnchoredTypedSchemedHostPortArtifactRegexp = Anchored(Sequence( + Optional(Capture(TypeRegexp), Literal("::")), + Optional(SchemeRegexp), + Capture(Or(HostPortRegexp, DomainPortRegexp)), + Literal("/"), + Literal("/"), + CapturedArtifactVersionRegexp)) + + ReqHostPortRegexp = Sequence( + Or(DomainComponentRegexp, IPRegexp), + Literal(`:`), Match(`[0-9]+`)) + + AnchoredTypedOptSchemedReqHostReqPortArtifactRegexp = Anchored( + Optional(Capture(TypeRegexp), Literal("::")), + Optional(SchemeRegexp), + Capture(ReqHostPortRegexp), + Match(RepositorySeparator), + Optional(CapturedArtifactVersionRegexp)) + PathRegexp = Sequence( Optional(Literal("/")), Match(`[a-zA-Z0-9-_.]+(?:/[a-zA-Z0-9-_.]+)+`)) @@ -124,7 +165,7 @@ var ( // DigestRegexp matches valid digests. DigestRegexp = Match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`) - // RepositoryRegexp is the format of a repository ppart of references. + // RepositoryRegexp is the format of a repository part of references. RepositoryRegexp = Sequence( NameComponentRegexp, Optional(Repeated(RepositorySeparatorRegexp, NameComponentRegexp))) diff --git a/pkg/contexts/oci/internal/context.go b/pkg/contexts/oci/internal/context.go index 6013260894..2fd458db76 100644 --- a/pkg/contexts/oci/internal/context.go +++ b/pkg/contexts/oci/internal/context.go @@ -38,6 +38,7 @@ type Context interface { RepositoryForSpec(spec RepositorySpec, creds ...credentials.CredentialsSource) (Repository, error) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) + RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) GetAlias(name string) RepositorySpec SetAlias(name string, spec RepositorySpec) diff --git a/pkg/contexts/oci/internal/uniform.go b/pkg/contexts/oci/internal/uniform.go index 24a8f39ad8..ad4a204bb5 100644 --- a/pkg/contexts/oci/internal/uniform.go +++ b/pkg/contexts/oci/internal/uniform.go @@ -11,9 +11,11 @@ import ( "strings" "sync" + "github.com/containerd/containerd/reference" "github.com/sirupsen/logrus" "golang.org/x/exp/slices" + "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/runtime" ) @@ -84,21 +86,34 @@ func (u *UniformRepositorySpec) RepositoryRef() string { return fmt.Sprintf("%s%s://%s", t, u.Scheme, u.Host) } +func (u *UniformRepositorySpec) SetType(typ string) { + t, _ := grammar.SplitTypeSpec(typ) + u.Type = t + u.TypeHint = typ +} + func (u *UniformRepositorySpec) String() string { return u.RepositoryRef() } func UniformRepositorySpecForHostURL(typ string, host string) *UniformRepositorySpec { - scheme := "" - parsed, err := url.Parse(host) + s := "" + h := host + var parsed *url.URL + ref, err := reference.Parse(host) + if err == nil { + parsed, err = url.Parse("https://" + ref.Locator) + } else { + parsed, err = url.Parse(host) + } if err == nil { - host = parsed.Host - scheme = parsed.Scheme + s = parsed.Scheme + h = parsed.Host } u := &UniformRepositorySpec{ Type: typ, - Scheme: scheme, - Host: host, + Scheme: s, + Host: h, } return u } @@ -168,6 +183,14 @@ func (s *specHandlers) MapUniformRepositorySpec(ctx Context, u *UniformRepositor defer s.lock.RUnlock() deferr := errors.ErrNotSupported("uniform repository ref", u.String()) + if u.Info != "" && string(u.Info[0]) == "{" && u.Host == "" && u.Scheme == "" { + data, err := runtime.CompleteSpecWithType(u.Type, []byte(u.Info)) + if err != nil { + return nil, err + } + return ctx.RepositorySpecForConfig(data, runtime.DefaultJSONEncoding) + } + if u.Type == "" { if u.Info != "" { spec := ctx.GetAlias(u.Info) diff --git a/pkg/contexts/oci/ref.go b/pkg/contexts/oci/ref.go index 24500d5faf..764058884a 100644 --- a/pkg/contexts/oci/ref.go +++ b/pkg/contexts/oci/ref.go @@ -23,37 +23,30 @@ const ( KIND_ARETEFACT_REFERENCE = "artifact reference" ) -// ParseRepo parses a standard oci repository reference into a internal representation. +// ParseRepo parses a standard oci repository reference into an internal representation. func ParseRepo(ref string) (UniformRepositorySpec, error) { create := false if strings.HasPrefix(ref, "+") { create = true ref = ref[1:] } + uspec := UniformRepositorySpec{} match := grammar.AnchoredRegistryRegexp.FindSubmatch([]byte(ref)) if match == nil { match = grammar.AnchoredGenericRegistryRegexp.FindSubmatch([]byte(ref)) if match == nil { - return UniformRepositorySpec{}, errors.ErrInvalid(KIND_OCI_REFERENCE, ref) + return uspec, errors.ErrInvalid(KIND_OCI_REFERENCE, ref) } - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return UniformRepositorySpec{ - Type: t, - TypeHint: h, - Info: string(match[2]), - CreateIfMissing: create, - }, nil + uspec.SetType(string(match[1])) + uspec.Info = string(match[2]) + uspec.CreateIfMissing = create + return uspec, nil } - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: string(match[2]), - Host: string(match[3]), - CreateIfMissing: create, - }, nil + uspec.SetType(string(match[1])) + uspec.Scheme = string(match[2]) + uspec.Host = string(match[3]) + uspec.CreateIfMissing = create + return uspec, nil } // RefSpec is a go internal representation of an oci reference. @@ -87,10 +80,30 @@ func ParseRef(ref string) (RefSpec, error) { } spec := RefSpec{UniformRepositorySpec: UniformRepositorySpec{CreateIfMissing: create}} + match := grammar.AnchoredTypedSchemedHostPortArtifactRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Scheme = string(match[2]) + spec.Host = string(match[3]) + spec.Repository = string(match[4]) + spec.Tag = pointer(match[5]) + spec.Digest = dig(match[6]) + return spec, nil + } - match := grammar.FileReferenceRegexp.FindSubmatch([]byte(ref)) + match = grammar.AnchoredTypedOptSchemedReqHostReqPortArtifactRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Scheme = string(match[2]) + spec.Host = string(match[3]) + spec.Repository = string(match[4]) + spec.Tag = pointer(match[5]) + spec.Digest = dig(match[6]) + return spec, nil + } + match = grammar.FileReferenceRegexp.FindSubmatch([]byte(ref)) if match != nil { - spec.Type = string(match[1]) + spec.SetType(string(match[1])) spec.Info = string(match[2]) spec.Repository = string(match[3]) spec.Tag = pointer(match[4]) @@ -124,7 +137,7 @@ func ParseRef(ref string) (RefSpec, error) { } match = grammar.TypedReferenceRegexp.FindSubmatch([]byte(ref)) if match != nil { - spec.Type = string(match[1]) + spec.SetType(string(match[1])) spec.Scheme = string(match[2]) spec.Host = string(match[3]) spec.Repository = string(match[4]) @@ -134,7 +147,7 @@ func ParseRef(ref string) (RefSpec, error) { } match = grammar.TypedURIRegexp.FindSubmatch([]byte(ref)) if match != nil { - spec.Type = string(match[1]) + spec.SetType(string(match[1])) spec.Scheme = string(match[2]) spec.Host = string(match[3]) spec.Repository = string(match[4]) @@ -144,7 +157,7 @@ func ParseRef(ref string) (RefSpec, error) { } match = grammar.TypedGenericReferenceRegexp.FindSubmatch([]byte(ref)) if match != nil { - spec.Type = string(match[1]) + spec.SetType(string(match[1])) spec.Info = string(match[2]) spec.Repository = string(match[3]) spec.Tag = pointer(match[4]) @@ -153,7 +166,7 @@ func ParseRef(ref string) (RefSpec, error) { } match = grammar.AnchoredRegistryRegexp.FindSubmatch([]byte(ref)) if match != nil { - spec.Type = string(match[1]) + spec.SetType(string(match[1])) spec.Info = string(match[2]) spec.Repository = string(match[3]) spec.Tag = pointer(match[4]) @@ -163,7 +176,7 @@ func ParseRef(ref string) (RefSpec, error) { match = grammar.AnchoredGenericRegistryRegexp.FindSubmatch([]byte(ref)) if match != nil { - spec.Type = string(match[1]) + spec.SetType(string(match[1])) spec.Info = string(match[2]) match = grammar.ErrorCheckRegexp.FindSubmatch([]byte(ref)) diff --git a/pkg/contexts/oci/ref_test.go b/pkg/contexts/oci/ref_test.go index 3dc52f38b2..b0c8d1977f 100644 --- a/pkg/contexts/oci/ref_test.go +++ b/pkg/contexts/oci/ref_test.go @@ -5,14 +5,85 @@ package oci_test import ( + "strings" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/testutils" - "github.com/opencontainers/go-digest" + godigest "github.com/opencontainers/go-digest" + "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/common/accessobj" "github.com/open-component-model/ocm/pkg/contexts/oci" + "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" + "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" + "github.com/open-component-model/ocm/pkg/runtime" ) +func Type(t string) string { + if t == "" { + return t + } + return t + "::" +} +func FileFormat(t, f string) string { + if t == "" { + return f + } + if f == "" { + return t + } + return t + "+" + f +} +func FileType(t, f string) string { + if t != "" { + return t + } else { + return f + } +} +func Scheme(s string) string { + if s == "" { + return s + } + return s + "://" +} +func Sub(t string) string { + if t == "" { + return t + } + return "/" + t +} +func Vers(t, d string) string { + if t == "" && d == "" { + return "" + } + if t == "" { + return "@" + d + } + if d == "" { + return ":" + t + } + return ":" + t + "@" + d +} + +func Dig(b []byte) *godigest.Digest { + if len(b) == 0 { + return nil + } + s := godigest.Digest(b) + return &s +} + +func Pointer(b []byte) *string { + if len(b) == 0 { + return nil + } + s := string(b) + return &s +} + func CheckRef(ref string, exp *oci.RefSpec) { spec, err := oci.ParseRef(ref) if exp == nil { @@ -34,12 +105,460 @@ func CheckRepo(ref string, exp *oci.UniformRepositorySpec) { } var _ = Describe("ref parsing", func() { - digest := digest.Digest("sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a") + digest := godigest.Digest("sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a") tag := "v1" ghcr := oci.UniformRepositorySpec{Host: "ghcr.io"} docker := oci.UniformRepositorySpec{Host: "docker.io"} + Context("parse file path refs", func() { + t := "ctf" + p := "file/path" + r := "github.com/mandelsoft/ocm" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + Context("[+][::][./][//[:]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, up := range []string{p, "./" + p} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := cm + Type(FileFormat(ut, uf)) + up + "//" + r + Vers(uv, ud) + ut, uf, uv, up, ud := ut, uf, uv, up, ud + + // tests parsing of all permutations of + // [+][::][./][//[:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: up, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + } + }) + }) + + Context("parse domain refs", func() { + t := "oci" + h := "ghcr.io" + r := "github.com/mandelsoft/ocm" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + // Notice that the file formats (directory, tar, tgz) CAN BE PARSED in this notation, BUT for non file based + // implementations like oci, this information is not used. + Context("[+][::][://][:][/]/[:][@]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, ush := range []string{"", "http", "https"} { + for _, uh := range []string{h, h + ":3030"} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + for _, sep := range []string{"/", "//"} { + ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + sep + r + Vers(uv, ud) + ut, uf, ush, uh, uv, ud := ut, uf, ush, uh, uv, ud + + // tests parsing of all permutations of + // [::][://][:][/]/[:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: ush, + Host: uh, + Info: "", + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + } + } + } + }) + + It("repository creation from parsed repo", func() { + ctx := oci.New() + aliasreg := ocireg.NewRepositorySpec("http://ghcr.io") + ctx.SetAlias("myalias", aliasreg) + repo := Must(oci.ParseRef("myalias//repository:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&repo.UniformRepositorySpec)) + Expect(spec).To(Equal(aliasreg)) + }) + }) + + Context("parse host port refs", func() { + t := "oci" + h := "localhost" + r := "github.com/mandelsoft/ocm" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + // localhost (with and without port) (and other host names) are a special case since these are not formally + // valid domains + // the combination of this test and the test below test parsing of all permutations of + // [::][://]:/[:][@] + Context("[+][::][://]:/[:][@]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, ush := range []string{"", "http", "https"} { + for _, uh := range []string{h + ":3030"} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + "/" + r + Vers(uv, ud) + ut, uf, ush, uh, uv, ud := ut, uf, ush, uh, uv, ud + + // tests parsing of all permutations of + // [::][://]:/[:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: ush, + Host: uh, + Info: "", + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + } + } + }) + Context("[+][::][://][:]//[:][@]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, ush := range []string{"", "http", "https"} { + for _, uh := range []string{h, h + ":3030"} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + "//" + r + Vers(uv, ud) + ut, uf, ush, uh, uv, ud := ut, uf, ush, uh, uv, ud + + // tests parsing of all permutations of + // [::][://][:]//[:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: ush, + Host: uh, + Info: "", + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + } + } + }) + }) + + Context("parse json repo spec refs", func() { + t := "oci" + h := "ghcr.io" + r := "github.com/mandelsoft/ocm" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + repospec := ocireg.NewRepositorySpec(h) + jsonrepospec := string(Must(runtime.DefaultJSONEncoding.Marshal(repospec))) + + // Notice that the file formats (directory, tar, tgz) CAN BE PARSED in this notation, BUT for non file based + // implementations like oci, this information is not used. + Context("[+][::][//][:][@]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := cm + Type(FileFormat(ut, uf)) + jsonrepospec + "//" + r + Vers(uv, ud) + ut, uf, uv, ud := ut, uf, uv, ud + + // tests parsing of all permutations of + // [::][//][:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: jsonrepospec, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + }) + }) + + Context("parse docker library refs", func() { + // h := "docker.io" + r := "ubuntu" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + Context("[:][@]", func() { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := r + Vers(uv, ud) + uv, ud := uv, ud + + // tests parsing of all permutations of + // [:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "", + Scheme: "", + Host: "docker.io", + Info: "", + CreateIfMissing: false, + TypeHint: "", + }, + ArtSpec: oci.ArtSpec{ + Repository: "library/" + r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + }) + }) + + Context("parse docker repository refs", func() { + // h := "docker.io" + r := "docker-repo/ubuntu" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + Context("/[:][@]", func() { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := r + Vers(uv, ud) + uv, ud := uv, ud + + // tests parsing of all permutations of + // [:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "", + Scheme: "", + Host: "docker.io", + Info: "", + CreateIfMissing: false, + TypeHint: "", + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + }) + }) + + Context("parse file path repos", func() { + t := "ctf" + p := "file/path" + + Context("[+][::][./][", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, up := range []string{p, "./" + p} { + ref := cm + Type(FileFormat(ut, uf)) + up + ut, uf, up := ut, uf, up + // tests parsing of all permutations of + // [+][::][./][//[:][@] + It("parses ref "+ref, func() { + CheckRepo(ref, &oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: up, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }) + }) + } + } + } + } + }) + }) + + Context("parse domain repos", func() { + t := "oci" + h := "ghcr.io" + + Context("[+][::][://][:]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, ush := range []string{"", "http", "https"} { + for _, uh := range []string{h, h + ":3030", "localhost", "localhost:3030"} { + ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + ut, uf, ush, uh := ut, uf, ush, uh + + // tests parsing of all permutations of + // [+][::][://][:] + It("parses ref "+ref, func() { + // if you are coming from the ocm test and + // wondering why the corresponding tests if + // has an additional condition that the + // type has to be empty - this is because + // the corresponding parse method calls + // an intermediate handler based on the + // type that resolves the localhost in the + // info. + // For oci repositories, such this + // handling is done in the + // MapUniformRepositorySpec logic. + if strings.HasPrefix(uh, "localhost") { + CheckRepo(ref, &oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: Scheme(ush) + uh, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }) + } else { + CheckRepo(ref, &oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: ush, + Host: uh, + Info: "", + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }) + } + }) + } + } + } + } + } + }) + It("repository creation from parsed repo with localhost", func() { + ctx := oci.New() + repo := Must(oci.ParseRepo("http://localhost")) + spec := Must(ctx.MapUniformRepositorySpec(&repo)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("http://localhost"))) + }) + It("repository creation from parsed repo with localhost", func() { + ctx := oci.New() + + aliasreg := ocireg.NewRepositorySpec("http://ghcr.io") + ctx.SetAlias("myalias", aliasreg) + repo := Must(oci.ParseRepo("myalias")) + spec := Must(ctx.MapUniformRepositorySpec(&repo)) + Expect(spec).To(Equal(aliasreg)) + }) + }) + + Context("parse json repo spec refs", func() { + t := "oci" + h := "ghcr.io" + + repospec := ocireg.NewRepositorySpec(h) + jsonrepospec := string(Must(runtime.DefaultJSONEncoding.Marshal(repospec))) + + // Notice that the file formats (directory, tar, tgz) CAN BE PARSED in this notation, BUT for non file based + // implementations like oci, this information is not used. + Context("[+][::]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + ref := cm + Type(FileFormat(ut, uf)) + jsonrepospec + ut, uf := ut, uf + + // tests parsing of all permutations of + // [::] + It("parses ref "+ref, func() { + CheckRepo(ref, &oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: jsonrepospec, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }) + }) + } + } + } + }) + }) + It("succeeds for repository", func() { CheckRef("::ghcr.io/", &oci.RefSpec{UniformRepositorySpec: ghcr}) }) @@ -67,10 +586,11 @@ var _ = Describe("ref parsing", func() { }) CheckRef("type::https://ghcr.io/repo/repo:v1@"+digest.String(), &oci.RefSpec{ UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "type", - Scheme: "https", - Host: "ghcr.io", - Info: "", + Type: "type", + Scheme: "https", + Host: "ghcr.io", + Info: "", + TypeHint: "type", }, ArtSpec: oci.ArtSpec{ Repository: "repo/repo", @@ -93,10 +613,11 @@ var _ = Describe("ref parsing", func() { }) CheckRef("directory::a/b", &oci.RefSpec{ UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "directory", - Scheme: "", - Host: "", - Info: "a/b", + Type: "directory", + Scheme: "", + Host: "", + Info: "a/b", + TypeHint: "directory", }, ArtSpec: oci.ArtSpec{ Repository: "", @@ -104,10 +625,11 @@ var _ = Describe("ref parsing", func() { }) CheckRef("ctf+directory::a/b", &oci.RefSpec{ UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "ctf+directory", - Scheme: "", - Host: "", - Info: "a/b", + Type: "ctf", + Scheme: "", + Host: "", + Info: "a/b", + TypeHint: "ctf+directory", }, ArtSpec: oci.ArtSpec{ Repository: "", @@ -115,11 +637,12 @@ var _ = Describe("ref parsing", func() { }) CheckRef("+ctf+directory::a/b", &oci.RefSpec{ UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "ctf+directory", + Type: "ctf", Scheme: "", Host: "", Info: "a/b", CreateIfMissing: true, + TypeHint: "ctf+directory", }, ArtSpec: oci.ArtSpec{ Repository: "", @@ -140,10 +663,11 @@ var _ = Describe("ref parsing", func() { CheckRef("directory::a/b//c/d", &oci.RefSpec{ UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "directory", - Scheme: "", - Host: "", - Info: "a/b", + Type: "directory", + Scheme: "", + Host: "", + Info: "a/b", + TypeHint: "directory", }, ArtSpec: oci.ArtSpec{ Repository: "c/d", @@ -152,10 +676,11 @@ var _ = Describe("ref parsing", func() { CheckRef("oci::ghcr.io", &oci.RefSpec{ UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "oci", - Scheme: "", - Host: "ghcr.io", - Info: "", + Type: "oci", + Scheme: "", + Host: "ghcr.io", + Info: "", + TypeHint: "oci", }, ArtSpec: oci.ArtSpec{ Repository: "", @@ -184,6 +709,52 @@ var _ = Describe("ref parsing", func() { }) + It("json spec", func() { + ctx := oci.New() + + tag := "1.0.0" + CheckRef("OCIRegistry::{\"baseUrl\": \"test.com\"}//repo:1.0.0", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "OCIRegistry", + Scheme: "", + Host: "", + Info: "{\"baseUrl\": \"test.com\"}", + TypeHint: "OCIRegistry", + }, + ArtSpec: oci.ArtSpec{ + Repository: "repo", + Tag: &tag, + }, + }) + ref := Must(oci.ParseRef("OCIRegistry::{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}//repo:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + repo := Must(spec.Repository(ctx, nil)) + _ = repo + }) + + It("fail for json spec with type mismatch", func() { + ctx := oci.New() + + tag := "1.0.0" + CheckRef("oci::{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}//repo:1.0.0", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "oci", + Scheme: "", + Host: "", + Info: "{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}", + TypeHint: "oci", + }, + ArtSpec: oci.ArtSpec{ + Repository: "repo", + Tag: &tag, + }, + }) + ref := Must(oci.ParseRef("oci::{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}//repo:1.0.0")) + spec, err := ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec) + Expect(spec).To(BeNil()) + Expect(err).ToNot(BeNil()) + }) + It("fails", func() { CheckRef("https://ubuntu", nil) CheckRef("ubuntu@4711", nil) @@ -212,5 +783,49 @@ var _ = Describe("ref parsing", func() { Info: "a/b.tar", }) }) + It("localhost", func() { + ctx := oci.New() + // port is necessary here, otherwise it is ambiguous with dockerhub reference (localhost/test:1.0.0 could be + // an artifact stored on duckerhub) + ref := Must(oci.ParseRef("localhost:80/test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("localhost:80"))) + }) + It("localhost with unambiguous separator and without port", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("localhost//test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("localhost"))) + }) + It("localhost with unambiguous separator", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("localhost:80//test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("localhost:80"))) + }) + It("scheme://localhost:port//repository:version", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("http://localhost:80//test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("http://localhost:80"))) + }) + It("scheme://localhost:port/repository:version", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("http://localhost:80/test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("http://localhost:80"))) + }) + It("ctf with create", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("+ctf+directory::./file/path//github.com/mandelsoft/ocm")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(Must(ctf.NewRepositorySpec(accessobj.ACC_CREATE, "./file/path", accessio.FormatDirectory)))) + }) + It("ctf without create", func() { + ctx := oci.New() + ref := Must(oci.ParseRepo("ctf+directory::./file/path")) + spec := Must(ctx.MapUniformRepositorySpec(&ref)) + Expect(spec).To(Equal(Must(ctf.NewRepositorySpec(accessobj.ACC_WRITABLE, "./file/path")))) + }) }) diff --git a/pkg/contexts/oci/repositories/ctf/uniform.go b/pkg/contexts/oci/repositories/ctf/uniform.go index 2ecc3e462c..2b668addb9 100644 --- a/pkg/contexts/oci/repositories/ctf/uniform.go +++ b/pkg/contexts/oci/repositories/ctf/uniform.go @@ -17,6 +17,7 @@ func init() { h := &repospechandler{} cpi.RegisterRepositorySpecHandler(h, "") cpi.RegisterRepositorySpecHandler(h, Type) + cpi.RegisterRepositorySpecHandler(h, AltType) for _, f := range SupportedFormats() { cpi.RegisterRepositorySpecHandler(h, string(f)) } diff --git a/pkg/contexts/oci/repositories/ocireg/repository.go b/pkg/contexts/oci/repositories/ocireg/repository.go index 376e7851f2..ed474c4ffa 100644 --- a/pkg/contexts/oci/repositories/ocireg/repository.go +++ b/pkg/contexts/oci/repositories/ocireg/repository.go @@ -62,6 +62,9 @@ var ( func NewRepository(ctx cpi.Context, spec *RepositorySpec, info *RepositoryInfo) (cpi.Repository, error) { urs := spec.UniformRepositorySpec() + if urs.Scheme == "http" { + ocmlog.Logger(REALM).Warn("using insecure http for oci registry {{host}}", "host", urs.Host) + } i := &RepositoryImpl{ RepositoryImplBase: cpi.NewRepositoryImplBase(ctx), logger: logging.DynamicLogger(ctx, REALM, logging.NewAttribute(ocmlog.ATTR_HOST, urs.Host)), @@ -145,23 +148,28 @@ func (r *RepositoryImpl) getResolver(comp string) (resolve.Resolver, error) { }, DefaultScheme: r.info.Scheme, //nolint:gosec // used like the default, there are OCI servers (quay.io) not working with min version. - DefaultTLS: &tls.Config{ - // MinVersion: tls.VersionTLS13, - RootCAs: func() *x509.CertPool { - var rootCAs *x509.CertPool - if creds != nil { - c := creds.GetProperty(credentials.ATTR_CERTIFICATE_AUTHORITY) - if c != "" { - rootCAs = x509.NewCertPool() - rootCAs.AppendCertsFromPEM([]byte(c)) + DefaultTLS: func() *tls.Config { + if r.info.Scheme == "http" { + return nil + } + return &tls.Config{ + // MinVersion: tls.VersionTLS13, + RootCAs: func() *x509.CertPool { + var rootCAs *x509.CertPool + if creds != nil { + c := creds.GetProperty(credentials.ATTR_CERTIFICATE_AUTHORITY) + if c != "" { + rootCAs = x509.NewCertPool() + rootCAs.AppendCertsFromPEM([]byte(c)) + } } - } - if rootCAs == nil { - rootCAs = rootcertsattr.Get(r.GetContext()).GetRootCertPool(true) - } - return rootCAs - }(), - }, + if rootCAs == nil { + rootCAs = rootcertsattr.Get(r.GetContext()).GetRootCertPool(true) + } + return rootCAs + }(), + } + }(), })), } diff --git a/pkg/contexts/oci/repositories/ocireg/uniform.go b/pkg/contexts/oci/repositories/ocireg/uniform.go index 4cf9233581..761a772a60 100644 --- a/pkg/contexts/oci/repositories/ocireg/uniform.go +++ b/pkg/contexts/oci/repositories/ocireg/uniform.go @@ -6,6 +6,8 @@ package ocireg import ( "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" + "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" + "github.com/open-component-model/ocm/pkg/regex" ) func init() { @@ -15,9 +17,24 @@ func init() { type repospechandler struct{} func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { - if u.Info != "" || u.Host == "" { + scheme := u.Scheme + host := u.Host + if u.Host == "" && u.Scheme == "" && u.Info != "" { + host = u.Info + match := grammar.AnchoredSchemedRegexp.FindStringSubmatch(host) + if match != nil { + scheme = match[1] + host = match[2] + } + if !(regex.Anchored(grammar.HostPortRegexp).MatchString(host) || regex.Anchored(grammar.DomainPortRegexp).MatchString(host)) { + return nil, nil + } + } else if u.Info != "" || u.Host == "" { return nil, nil } - return NewRepositorySpec(u.Host), nil + if scheme != "" { + host = scheme + "://" + host + } + return NewRepositorySpec(host), nil } diff --git a/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/blobhandler.go index 14bf533573..5ce47f72e7 100644 --- a/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/blobhandler.go +++ b/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/blobhandler.go @@ -17,6 +17,7 @@ import ( "github.com/open-component-model/ocm/pkg/common/accessobj" "github.com/open-component-model/ocm/pkg/contexts/oci" "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" + "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" "github.com/open-component-model/ocm/pkg/contexts/oci/transfer" @@ -329,8 +330,16 @@ func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, g if err != nil { return nil, wrap(err, errhint, "transfer artifact") } - - ref := path.Join(base, namespace.GetNamespace()) + version + match := grammar.AnchoredSchemedRegexp.FindStringSubmatch(base) + scheme := "" + if match != nil { + scheme = match[1] + base = match[2] + } + if scheme != "" { + scheme += "://" + } + ref := scheme + path.Join(base, namespace.GetNamespace()) + version return ociartifact.New(ref), nil } diff --git a/pkg/contexts/ocm/grammar/grammar.go b/pkg/contexts/ocm/grammar/grammar.go index 96aa3ef385..d84ac83cf8 100644 --- a/pkg/contexts/ocm/grammar/grammar.go +++ b/pkg/contexts/ocm/grammar/grammar.go @@ -30,7 +30,19 @@ var ( // AnchoredRepositoryRegexp parses a uniform repository spec. AnchoredRepositoryRegexp = Anchored( Optional(Capture(TypeRegexp), Literal("::")), - Capture(grammar.DomainPortRegexp), Optional(grammar.RepositorySeparatorRegexp, Capture(grammar.RepositoryRegexp)), + grammar.SchemeDomainPortRegexp, Optional(grammar.RepositorySeparatorRegexp, Capture(grammar.RepositoryRegexp)), + ) + + // AnchoredSchemedHostPortRepositoryRegexp parses a uniform repository spec. + AnchoredSchemedHostPortRepositoryRegexp = Anchored( + Optional(Capture(TypeRegexp), Literal("::")), + grammar.SchemedHostPortRegexp, Optional(grammar.RepositorySeparatorRegexp, Capture(grammar.RepositoryRegexp)), + ) + + // AnchoredHostWithPortRepositoryRegexp parses a uniform repository spec. + AnchoredHostWithPortRepositoryRegexp = Anchored( + Optional(Capture(TypeRegexp), Literal("::")), + grammar.SchemeHostPortRegexp, Optional(grammar.RepositorySeparatorRegexp, Capture(grammar.RepositoryRegexp)), ) // AnchoredGenericRepositoryRegexp describes a CTF reference. @@ -54,7 +66,29 @@ var ( // It provides 5 captures: type, repository host port, sub path, component and version. AnchoredReferenceRegexp = Anchored( Optional(Capture(TypeRegexp), Literal("::")), - Capture(grammar.DomainPortRegexp), Optional(grammar.RepositorySeparatorRegexp, Capture(grammar.RepositoryRegexp)), + grammar.SchemeDomainPortRegexp, Optional(grammar.RepositorySeparatorRegexp, Capture(grammar.RepositoryRegexp)), + Literal("//"), Capture(ComponentRegexp), + Optional(Literal(VersionSeparator), Capture(VersionRegexp)), + ) + + // AnchoredSchemedHostPortReferenceRegexp parses a complete string representation for default component references + // including the repository part. Since the type is optional, the scheme is required to allow for a distinction + // from filepaths. + // It provides 6 captures: type, scheme, repository host port, sub path, component and version. + AnchoredSchemedHostPortReferenceRegexp = Anchored( + Optional(Capture(TypeRegexp), Literal("::")), + grammar.SchemedHostPortRegexp, Optional(grammar.RepositorySeparatorRegexp, Capture(grammar.RepositoryRegexp)), + Literal("//"), Capture(ComponentRegexp), + Optional(Literal(VersionSeparator), Capture(VersionRegexp)), + ) + + // AnchoredHostWithPortReferenceRegexp parses a complete string representation for default component references + // including the repository part. Since the type is optional, the scheme is required to allow for a distinction + // from filepaths. + // It provides 6 captures: type, scheme, repository host port, sub path, component and version. + AnchoredHostWithPortReferenceRegexp = Anchored( + Optional(Capture(TypeRegexp), Literal("::")), + grammar.SchemeHostPortRegexp, Optional(grammar.RepositorySeparatorRegexp, Capture(grammar.RepositoryRegexp)), Literal("//"), Capture(ComponentRegexp), Optional(Literal(VersionSeparator), Capture(VersionRegexp)), ) diff --git a/pkg/contexts/ocm/grammar/grammar_test.go b/pkg/contexts/ocm/grammar/grammar_test.go index 507b38ed04..1878e60256 100644 --- a/pkg/contexts/ocm/grammar/grammar_test.go +++ b/pkg/contexts/ocm/grammar/grammar_test.go @@ -50,6 +50,12 @@ func Type(t string) string { } return t + "::" } +func Scheme(sc string) string { + if sc == "" { + return sc + } + return sc + "://" +} func Sub(t string) string { if t == "" { return t @@ -90,6 +96,7 @@ var _ = Describe("ref matching", func() { Context("complete refs", func() { t := "OCIRepository" + sc := "http" s := "mandelsoft/cnudie" v := "v1" @@ -98,10 +105,12 @@ var _ = Describe("ref matching", func() { It("succeeds", func() { for _, ut := range []string{"", t} { - for _, us := range []string{"", s} { - for _, uv := range []string{"", v} { - ref := Type(ut) + h + Sub(us) + "//" + c + Vers(uv) - CheckRef(ref, ut, h, us, c, uv) + for _, usc := range []string{"", sc} { + for _, us := range []string{"", s} { + for _, uv := range []string{"", v} { + ref := Type(ut) + Scheme(usc) + h + Sub(us) + "//" + c + Vers(uv) + CheckRef(ref, ut, usc, h, us, c, uv) + } } } } @@ -129,10 +138,10 @@ var _ = Describe("ref matching", func() { Context("repo", func() { It("succeeds", func() { - Check("directory::ghcr.io/sub/path", AnchoredRepositoryRegexp, "directory", "ghcr.io", "sub/path") - Check("ghcr.io/sub/path", AnchoredRepositoryRegexp, "", "ghcr.io", "sub/path") - Check("ghcr.io", AnchoredRepositoryRegexp, "", "ghcr.io", "") - Check("ghcr.io/sub/path", AnchoredRepositoryRegexp, "", "ghcr.io", "sub/path") + Check("directory::ghcr.io/sub/path", AnchoredRepositoryRegexp, "directory", "", "ghcr.io", "sub/path") + Check("ghcr.io/sub/path", AnchoredRepositoryRegexp, "", "", "ghcr.io", "sub/path") + Check("ghcr.io", AnchoredRepositoryRegexp, "", "", "ghcr.io", "") + Check("ghcr.io/sub/path", AnchoredRepositoryRegexp, "", "", "ghcr.io", "sub/path") }) It("fails", func() { Check("/ghcr.io/sub/path", AnchoredRepositoryRegexp) diff --git a/pkg/contexts/ocm/internal/uniform.go b/pkg/contexts/ocm/internal/uniform.go index 6460d11284..91bcbbd3b2 100644 --- a/pkg/contexts/ocm/internal/uniform.go +++ b/pkg/contexts/ocm/internal/uniform.go @@ -27,6 +27,8 @@ const ( type UniformRepositorySpec struct { // Type Type string `json:"type,omitempty"` + // Scheme + Scheme string `json:"scheme,omitempty"` // Host is the hostname of an ocm ref. Host string `json:"host,omitempty"` // SubPath is the sub path spec used to host component versions @@ -36,7 +38,7 @@ type UniformRepositorySpec struct { // CreateIfMissing indicates whether a file based or dynamic repo should be created if it does not exist CreateIfMissing bool `json:"createIfMissing,omitempty"` - // TypeHintshould be set if CreateIfMissing is true to help to decide what kind of repo to create + // TypeHint should be set if CreateIfMissing is true to help to decide what kind of repo to create TypeHint string `json:"typeHint,omitempty"` } @@ -151,6 +153,14 @@ func (s *specHandlers) MapUniformRepositorySpec(ctx Context, u *UniformRepositor s.lock.RLock() defer s.lock.RUnlock() + if u.Info != "" && string(u.Info[0]) == "{" && u.Host == "" && u.Scheme == "" && u.SubPath == "" { + data, err := runtime.CompleteSpecWithType(u.Type, []byte(u.Info)) + if err != nil { + return nil, err + } + return ctx.RepositorySpecForConfig(data, runtime.DefaultJSONEncoding) + } + deferr := errors.ErrNotSupported("uniform repository ref", u.String()) if u.Type == "" { if u.Info != "" { diff --git a/pkg/contexts/ocm/ref.go b/pkg/contexts/ocm/ref.go index 366027188f..c630a621a0 100644 --- a/pkg/contexts/ocm/ref.go +++ b/pkg/contexts/ocm/ref.go @@ -33,27 +33,57 @@ func ParseRepo(ref string) (UniformRepositorySpec, error) { }) } match := grammar.AnchoredRepositoryRegexp.FindSubmatch([]byte(ref)) - if match == nil { - match = grammar.AnchoredGenericRepositoryRegexp.FindSubmatch([]byte(ref)) - if match == nil { - return UniformRepositorySpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) - } + if match != nil { + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return cpi.HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: string(match[2]), + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }) + } + + match = grammar.AnchoredSchemedHostPortRepositoryRegexp.FindSubmatch([]byte(ref)) + if match != nil { + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return cpi.HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: string(match[2]), + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }) + } + + match = grammar.AnchoredHostWithPortRepositoryRegexp.FindSubmatch([]byte(ref)) + if match != nil { h := string(match[1]) t, _ := grammar.SplitTypeSpec(h) return cpi.HandleRef(UniformRepositorySpec{ Type: t, TypeHint: h, - Info: string(match[2]), + Scheme: string(match[2]), + Host: string(match[3]), + SubPath: string(match[4]), CreateIfMissing: create, }) } + + match = grammar.AnchoredGenericRepositoryRegexp.FindSubmatch([]byte(ref)) + if match == nil { + return UniformRepositorySpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) + } h := string(match[1]) t, _ := grammar.SplitTypeSpec(h) return cpi.HandleRef(UniformRepositorySpec{ Type: t, TypeHint: h, - Host: string(match[2]), - SubPath: string(match[3]), + Info: string(match[2]), CreateIfMissing: create, }) } @@ -90,49 +120,104 @@ func ParseRef(ref string) (RefSpec, error) { var spec RefSpec v := "" match := grammar.AnchoredReferenceRegexp.FindSubmatch([]byte(ref)) - if match == nil { - match = grammar.AnchoredGenericReferenceRegexp.FindSubmatch([]byte(ref)) - if match == nil { - return RefSpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) - } - v = string(match[4]) + if match != nil { + v = string(match[6]) + s := string(match[2]) h := string(match[1]) t, _ := grammar.SplitTypeSpec(h) spec = RefSpec{ UniformRepositorySpec{ Type: t, TypeHint: h, - Info: string(match[2]), + Scheme: s, + Host: string(match[3]), + SubPath: string(match[4]), CreateIfMissing: create, }, CompSpec{ - Component: string(match[3]), + Component: string(match[5]), Version: nil, }, } - } else { - v = string(match[5]) + } + + if match == nil { + match = grammar.AnchoredSchemedHostPortReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + v = string(match[6]) + s := string(match[2]) + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + spec = RefSpec{ + UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: s, + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }, + CompSpec{ + Component: string(match[5]), + Version: nil, + }, + } + } + } + + if match == nil { + match = grammar.AnchoredHostWithPortReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + v = string(match[6]) + s := string(match[2]) + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + spec = RefSpec{ + UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: s, + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }, + CompSpec{ + Component: string(match[5]), + Version: nil, + }, + } + } + } + + if match == nil { + match = grammar.AnchoredGenericReferenceRegexp.FindSubmatch([]byte(ref)) + if match == nil { + return RefSpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) + } + v = string(match[4]) h := string(match[1]) t, _ := grammar.SplitTypeSpec(h) spec = RefSpec{ UniformRepositorySpec{ Type: t, TypeHint: h, - Host: string(match[2]), - SubPath: string(match[3]), + Info: string(match[2]), CreateIfMissing: create, }, CompSpec{ - Component: string(match[4]), + Component: string(match[3]), Version: nil, }, } } + if v != "" { spec.Version = &v } var err error - spec.UniformRepositorySpec, err = cpi.HandleRef(spec.UniformRepositorySpec) + if spec.Info == "" || !(string(spec.Info[0]) == "{") { + spec.UniformRepositorySpec, err = cpi.HandleRef(spec.UniformRepositorySpec) + } return spec, err } diff --git a/pkg/contexts/ocm/ref_test.go b/pkg/contexts/ocm/ref_test.go index 4fad348779..3501bc4905 100644 --- a/pkg/contexts/ocm/ref_test.go +++ b/pkg/contexts/ocm/ref_test.go @@ -5,8 +5,6 @@ package ocm_test import ( - "strings" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/open-component-model/ocm/pkg/testutils" @@ -22,6 +20,28 @@ func Type(t string) string { } return t + "::" } +func FileFormat(t, f string) string { + if t == "" { + return f + } + if f == "" { + return t + } + return t + "+" + f +} +func FileType(t, f string) string { + if t != "" { + return t + } else { + return f + } +} +func Scheme(s string) string { + if s == "" { + return s + } + return s + "://" +} func Sub(t string) string { if t == "" { return t @@ -35,7 +55,7 @@ func Vers(t string) string { return ":" + t } -func CheckRef(ref, ut, h, us, c, uv, i string, th ...string) { +func CheckRef(ref, ut, scheme, h, us, c, uv, i string, th ...string) { var v *string if uv != "" { v = &uv @@ -48,6 +68,7 @@ func CheckRef(ref, ut, h, us, c, uv, i string, th ...string) { Expect(spec).WithOffset(1).To(Equal(ocm.RefSpec{ UniformRepositorySpec: ocm.UniformRepositorySpec{ Type: ut, + Scheme: scheme, Host: h, SubPath: us, Info: i, @@ -62,7 +83,80 @@ func CheckRef(ref, ut, h, us, c, uv, i string, th ...string) { } var _ = Describe("ref parsing", func() { - Context("complete refs", func() { + Context("file path refs", func() { + t := "ctf" + p := "file/path" + c := "github.com/mandelsoft/ocm" + v := "v1" + + Context("[+][::][./][//[:]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, up := range []string{p, "./" + p} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + "+65", v + ".1.2-rc.1", v + ".1.2+65"} { + ref := cm + Type(FileFormat(ut, uf)) + up + "//" + c + Vers(uv) + ut, uf, uv, up := ut, uf, uv, up + + // tests parsing of all permutations of + // [+][::][./]//[:] + It("parses ref "+ref, func() { + if ut != "" || uf != "" { + CheckRef(ref, FileType(ut, uf), "", "", "", c, uv, up, FileFormat(ut, uf)) + } else { + CheckRef(ref, FileType(ut, uf), "", "", "", c, uv, up) + } + }) + } + } + } + } + } + }) + }) + + Context("json repo spec refs", func() { + t := ocireg.Type + s := "mandelsoft/cnudie" + v := "v1" + + h := "ghcr.io" + c := "github.com/mandelsoft/ocm" + + repospec := ocireg.NewRepositorySpec(h, &ocireg.ComponentRepositoryMeta{ + ComponentNameMapping: "", + SubPath: s, + }) + jsonrepospec := string(Must(repospec.MarshalJSON())) + + Context("[::][//][:]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{t, ""} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + "+65", v + ".1.2-rc.1", v + ".1.2+65"} { + ref := cm + Type(ut) + jsonrepospec + "//" + c + Vers(uv) + ut, uv := ut, uv + + // tests parsing of all permutations of + // [::][//][:] + It("parses ref "+ref, func() { + CheckRef(ref, ut, "", "", "", c, uv, jsonrepospec) + }) + } + } + } + }) + + It("fail if mismatch between type in ref (here, ctf) and type in json repo spec (here, OCIRegistry)", func() { + ctx := ocm.New() + + ref := Must(ocm.ParseRef("ctf::{\"baseUrl\":\"ghcr.io\",\"subPath\":\"mandelsoft/cnudie\",\"type\":\"OCIRegistry\"}//github.com/mandelsoft/ocm:v1")) + spec, err := ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec) + Expect(spec).To(BeNil()) + Expect(err).ToNot(BeNil()) + }) + }) + + Context("domain refs", func() { t := ocireg.Type s := "mandelsoft/cnudie" v := "v1" @@ -70,48 +164,110 @@ var _ = Describe("ref parsing", func() { h := "ghcr.io" c := "github.com/mandelsoft/ocm" - Context("without info", func() { - for _, ut := range []string{t, ""} { - for _, uh := range []string{h, h + ":3030", "localhost", "localhost:3030"} { - for _, us := range []string{"", s} { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + "+65", v + ".1.2-rc.1", v + ".1.2+65"} { - ref := Type(ut) + uh + Sub(us) + "//" + c + Vers(uv) - ut, uh, us, uv := ut, uh, us, uv - - It("parses ref "+ref, func() { - if ut == "" && strings.HasPrefix(uh, "localhost") { - CheckRef(ref, ut, "", "", c, uv, uh+Sub(us)) - } else { - CheckRef(ref, ut, uh, us, c, uv, "") + Context("[+][::][://][:][/]//[:::][scheme://][:][/]//[:::]://[:][/]//[:::]scheme://[:][/]//[:::][://]:[/]//[:::][scheme://]:[/]//[: 0 { - host = u.Info[:idx] - subp = u.Info[idx+1:] + host = info[:idx] + subp = info[idx+1:] } else { - host = u.Info + host = info } if grammar.HostPortRegexp.MatchString(host) || grammar.DomainPortRegexp.MatchString(host) { + u.Scheme = scheme u.Host = host u.SubPath = subp u.Info = "" diff --git a/pkg/runtime/utils.go b/pkg/runtime/utils.go index 2f8ec1a6d5..cbd1360537 100644 --- a/pkg/runtime/utils.go +++ b/pkg/runtime/utils.go @@ -122,3 +122,21 @@ func CheckSpecification(data []byte) error { } // --- end check --- + +func CompleteSpecWithType(typ string, data []byte) ([]byte, error) { + var m map[string]interface{} + err := DefaultJSONEncoding.Unmarshal(data, &m) + if err != nil { + return nil, err + } + if typ != "" { + if m["type"] != nil && m["type"] != typ { + return nil, fmt.Errorf("type mismatch between type in reference \"%s\" and type in json spec \"%s\"", typ, m["type"]) + } + m["type"] = typ + return DefaultJSONEncoding.Marshal(m) + } else if m["type"] == nil { + return nil, fmt.Errorf("type missing") + } + return data, nil +}