diff --git a/api/ocm/internal/repository.go b/api/ocm/internal/repository.go index 7ed53168f..a647dcf93 100644 --- a/api/ocm/internal/repository.go +++ b/api/ocm/internal/repository.go @@ -57,15 +57,22 @@ type ( MimeType = blobaccess.MimeType ) +// VersionLookup is the interface used to resolve component versions +// in any context (especially for a ComponentAccess). +type VersionLookup interface { + ListVersions() ([]string, error) + HasVersion(vers string) (bool, error) + LookupVersion(version string) (ComponentVersionAccess, error) +} + type ComponentAccess interface { resource.ResourceView[ComponentAccess] GetContext() Context GetName() string - ListVersions() ([]string, error) - LookupVersion(version string) (ComponentVersionAccess, error) - HasVersion(vers string) (bool, error) + VersionLookup + NewVersion(version string, overrides ...bool) (ComponentVersionAccess, error) AddVersion(cv ComponentVersionAccess, overrides ...bool) error AddVersionOpt(cv ComponentVersionAccess, opts ...AddVersionOption) error @@ -118,6 +125,8 @@ type ComponentVersionAccess interface { ReadOnlyFeature GetContext() Context + // Repository returns the backing repository object without an additional + // ref. It should not be closed. Repository() Repository GetDescriptor() *compdesc.ComponentDescriptor diff --git a/api/ocm/resolvers/forward.go b/api/ocm/resolvers/forward.go index f5cdacc16..8907f2242 100644 --- a/api/ocm/resolvers/forward.go +++ b/api/ocm/resolvers/forward.go @@ -1,12 +1,17 @@ package resolvers import ( + "github.com/mandelsoft/goutils/errors" + "golang.org/x/exp/maps" + "ocm.software/ocm/api/ocm/internal" + common "ocm.software/ocm/api/utils/misc" ) type ( ContextProvider = internal.ContextProvider RepositorySpec = internal.RepositorySpec + VersionLookup = internal.VersionLookup ComponentVersionAccess = internal.ComponentVersionAccess ComponentVersionResolver = internal.ComponentVersionResolver ComponentResolver = internal.ComponentResolver @@ -25,3 +30,62 @@ const ( func NewResolverRule(prefix string, spec RepositorySpec, prio ...int) ResolverRule { return internal.NewResolverRule(prefix, spec, prio...) } + +// VersionResolverForComponent provides a VersionLookup for a component resolver. +// It resolves all versions provided by a component known to a ComponentResolver. +// The version set may be composed by versions of the component found in +// multiple repositories according to the result of the ComponentResolver. +func VersionResolverForComponent(name string, resolver ComponentResolver) (VersionLookup, error) { + crs := resolver.LookupComponentProviders(name) + if len(crs) == 0 { + return nil, errors.ErrNotFound(KIND_COMPONENT, name) + } + + versions := map[string]ResolvedComponentProvider{} + for _, cr := range crs { + c, err := cr.LookupComponent(name) + if err != nil { + return nil, err + } + vers, err := c.ListVersions() + if err != nil { + return nil, err + } + for _, v := range vers { + if _, ok := versions[v]; !ok { + versions[v] = cr + } + } + } + return &versionResolver{name, versions}, nil +} + +type versionResolver struct { + comp string + versions map[string]ResolvedComponentProvider +} + +func (v *versionResolver) ListVersions() ([]string, error) { + return maps.Keys(v.versions), nil +} + +func (v *versionResolver) LookupVersion(version string) (ComponentVersionAccess, error) { + p := v.versions[version] + if p == nil { + return nil, errors.ErrNotFound(KIND_COMPONENTVERSION, common.NewNameVersion(v.comp, version).String()) + } + vp, err := p.LookupComponent(v.comp) + if err != nil { + return nil, err + } + return vp.LookupVersion(version) +} + +func (v *versionResolver) HasVersion(vers string) (bool, error) { + cv, err := v.LookupVersion(vers) + if err != nil { + return false, err + } + defer cv.Close() + return cv != nil, nil +} diff --git a/api/ocm/resolvers/resolver_test.go b/api/ocm/resolvers/resolver_test.go index 4440b7c1a..370edc386 100644 --- a/api/ocm/resolvers/resolver_test.go +++ b/api/ocm/resolvers/resolver_test.go @@ -42,25 +42,29 @@ var _ = Describe("resolver", func() { env.Cleanup() }) - It("lookup cv per standard resolver", func() { - // ocmlog.Context().AddRule(logging.NewConditionRule(logging.TraceLevel, accessio.ALLOC_REALM)) + Context("resolver", func() { + var ctx ocm.Context - ctx := ocm.New() + BeforeEach(func() { + ctx = ocm.New() + spec := Must(ctf.NewRepositorySpec(accessobj.ACC_READONLY, ARCH, env)) + ctx.AddResolverRule("ocm.software", spec, 10) + }) - spec := Must(ctf.NewRepositorySpec(accessobj.ACC_READONLY, ARCH, env)) - ctx.AddResolverRule("ocm.software", spec, 10) + It("lookup cv per standard resolver", func() { + cv := Must(ctx.GetResolver().LookupComponentVersion(COMPONENT, VERSION)) + Close(cv) + Expect(ctx.Finalize()).To(Succeed()) + }) - cv := Must(ctx.GetResolver().LookupComponentVersion(COMPONENT, VERSION)) + It("lookup cv per version resolver", func() { + vr := Must(resolvers.VersionResolverForComponent(COMPONENT, ctx.GetResolver().(resolvers.ComponentResolver))) + Expect(vr.ListVersions()).To(ContainElements(VERSION)) - /* - err := cv.Repository().Close() - if err != nil { - defer cv.Close() - Expect(err).To(Succeed()) - } - */ - Close(cv) - Expect(ctx.Finalize()).To(Succeed()) + cv := Must(vr.LookupVersion(VERSION)) + Close(cv) + Expect(ctx.Finalize()).To(Succeed()) + }) }) It("orders resolver rules", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/typehandler.go index 3112ba5c9..080a607a6 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/typehandler.go @@ -12,6 +12,7 @@ import ( "ocm.software/ocm/api/ocm" "ocm.software/ocm/api/ocm/compdesc" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/resolvers" common "ocm.software/ocm/api/utils/misc" "ocm.software/ocm/api/utils/semverutils" "ocm.software/ocm/cmds/ocm/common/output" @@ -148,7 +149,7 @@ func (h *TypeHandler) filterVersions(vers []string) ([]string, error) { } func (h *TypeHandler) get(repo ocm.Repository, elemspec utils.ElemSpec) ([]output.Object, error) { - var component ocm.ComponentAccess + var component resolvers.VersionLookup var result []output.Object var err error @@ -176,11 +177,25 @@ func (h *TypeHandler) get(repo ocm.Repository, elemspec utils.ElemSpec) ([]outpu evaluated.Repository = cv.Repository() h.session.Closer(cv) } + } else { + // if the resolver is a component resolver, we can use it to list all available version + // cross all repositories found for the given component. + if cr, ok := h.resolver.(resolvers.ComponentResolver); ok { + cvr, err := resolvers.VersionResolverForComponent(comp.Component, cr) + if err != nil { + return nil, err + } + component = cvr + evaluated = &ocm.EvaluationResult{} + evaluated.Ref.CompSpec = comp + } } } if evaluated == nil { return nil, errors.Wrapf(err, "%s: invalid component version reference", name) } + } else { + component = evaluated.Component } if evaluated.Version != nil { result = append(result, &Object{ @@ -192,7 +207,6 @@ func (h *TypeHandler) get(repo ocm.Repository, elemspec utils.ElemSpec) ([]outpu return result, nil } spec = evaluated.Ref - component = evaluated.Component repo = evaluated.Repository } else { comp := ocm.CompSpec{Component: ""} @@ -247,7 +261,7 @@ func (h *TypeHandler) get(repo ocm.Repository, elemspec utils.ElemSpec) ([]outpu s := spec s.Version = &t result = append(result, &Object{ - Repository: repo, + Repository: v.Repository(), Spec: s, // Component: component, ComponentVersion: v,