Skip to content

Commit 6f2e3a7

Browse files
authored
feat(repository): add GetRepositorySpecForComponent method (open-component-model#1887)
<!-- markdownlint-disable MD041 --> #### What this PR does / why we need it Introduces a new method to retrieve the repository specification for a given component version, enhancing the transfer operations. #### Which issue(s) this PR fixes <!-- Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. --> Contributes to open-component-model/ocm-project#905 #### Testing ##### How to test the changes <!-- Required files to test the changes: .ocmconfig ```yaml type: generic.config.ocm.software/v1 configurations: - type: credentials.config.ocm.software repositories: - repository: type: DockerConfig/v1 dockerConfigFile: "~/.docker/config.json" ``` Commands that test the change: ```bash ocm get cv xxx ocm transfer xxx ``` --> ##### Verification - [ ] I have tested the changes locally by running `ocm` Manual tests will be performed as part of the follow up TGD adjustment PR: open-component-model#1888 --------- Signed-off-by: Fabian Burth <fabian.burth@sap.com>
1 parent 55e1698 commit 6f2e3a7

File tree

6 files changed

+432
-13
lines changed

6 files changed

+432
-13
lines changed

bindings/go/repository/component/fallback/v1/repository.go

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ const Realm = "repository/component/fallback"
3737
// Deprecated: FallbackRepository is an implementation for the deprecated config
3838
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
3939
// and only added for backwards compatibility.
40-
// New concepts will likely be introduced in the future (contributions welcome!).
40+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
41+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
4142
type FallbackRepository struct {
4243
// GoRoutineLimit limits the number of active goroutines for concurrent
4344
// operations.
@@ -78,7 +79,8 @@ type FallbackRepositoryOptions struct {
7879
// Deprecated: FallbackRepository is an implementation for the deprecated config
7980
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
8081
// and only added for backwards compatibility.
81-
// New concepts will likely be introduced in the future (contributions welcome!).
82+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
83+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
8284
func NewFallbackRepository(_ context.Context, repositoryProvider repository.ComponentVersionRepositoryProvider, credentialsResolver credentials.Resolver, res []*resolverruntime.Resolver, opts ...FallbackRepositoryOption) (*FallbackRepository, error) {
8385
options := &FallbackRepositoryOptions{}
8486
for _, opt := range opts {
@@ -111,7 +113,8 @@ func NewFallbackRepository(_ context.Context, repositoryProvider repository.Comp
111113
// Deprecated: FallbackRepository is an implementation for the deprecated config
112114
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
113115
// and only added for backwards compatibility.
114-
// New concepts will likely be introduced in the future (contributions welcome!).
116+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
117+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
115118
func (f *FallbackRepository) AddComponentVersion(ctx context.Context, descriptor *descriptor.Descriptor) error {
116119
repos := f.RepositoriesForComponentIterator(ctx, descriptor.Component.Name)
117120
for repo, err := range repos {
@@ -130,7 +133,8 @@ func (f *FallbackRepository) AddComponentVersion(ctx context.Context, descriptor
130133
// Deprecated: FallbackRepository is an implementation for the deprecated config
131134
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
132135
// and only added for backwards compatibility.
133-
// New concepts will likely be introduced in the future (contributions welcome!).
136+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
137+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
134138
func (f *FallbackRepository) GetComponentVersion(ctx context.Context, component, version string) (*descriptor.Descriptor, error) {
135139
repos := f.RepositoriesForComponentIterator(ctx, component)
136140
for repo, err := range repos {
@@ -157,7 +161,8 @@ func (f *FallbackRepository) GetComponentVersion(ctx context.Context, component,
157161
// Deprecated: FallbackRepository is an implementation for the deprecated config
158162
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
159163
// and only added for backwards compatibility.
160-
// New concepts will likely be introduced in the future (contributions welcome!).
164+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
165+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
161166
func (f *FallbackRepository) ListComponentVersions(ctx context.Context, component string) ([]string, error) {
162167
repos := f.RepositoriesForComponentIterator(ctx, component)
163168

@@ -207,7 +212,8 @@ func (f *FallbackRepository) ListComponentVersions(ctx context.Context, componen
207212
// Deprecated: FallbackRepository is an implementation for the deprecated config
208213
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
209214
// and only added for backwards compatibility.
210-
// New concepts will likely be introduced in the future (contributions welcome!).
215+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
216+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
211217
func (f *FallbackRepository) AddLocalResource(ctx context.Context, component, version string, res *descriptor.Resource, content blob.ReadOnlyBlob) (*descriptor.Resource, error) {
212218
repos := f.RepositoriesForComponentIterator(ctx, component)
213219
for repo, err := range repos {
@@ -226,7 +232,8 @@ func (f *FallbackRepository) AddLocalResource(ctx context.Context, component, ve
226232
// Deprecated: FallbackRepository is an implementation for the deprecated config
227233
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
228234
// and only added for backwards compatibility.
229-
// New concepts will likely be introduced in the future (contributions welcome!).
235+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
236+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
230237
func (f *FallbackRepository) GetLocalResource(ctx context.Context, component, version string, identity runtime.Identity) (blob.ReadOnlyBlob, *descriptor.Resource, error) {
231238
repos := f.RepositoriesForComponentIterator(ctx, component)
232239
for repo, err := range repos {
@@ -252,7 +259,8 @@ func (f *FallbackRepository) GetLocalResource(ctx context.Context, component, ve
252259
// Deprecated: FallbackRepository is an implementation for the deprecated config
253260
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
254261
// and only added for backwards compatibility.
255-
// New concepts will likely be introduced in the future (contributions welcome!).
262+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
263+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
256264
func (f *FallbackRepository) AddLocalSource(ctx context.Context, component, version string, source *descriptor.Source, content blob.ReadOnlyBlob) (*descriptor.Source, error) {
257265
repos := f.RepositoriesForComponentIterator(ctx, component)
258266
for repo, err := range repos {
@@ -271,7 +279,8 @@ func (f *FallbackRepository) AddLocalSource(ctx context.Context, component, vers
271279
// Deprecated: FallbackRepository is an implementation for the deprecated config
272280
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
273281
// and only added for backwards compatibility.
274-
// New concepts will likely be introduced in the future (contributions welcome!).
282+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
283+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
275284
func (f *FallbackRepository) GetLocalSource(ctx context.Context, component, version string, identity runtime.Identity) (blob.ReadOnlyBlob, *descriptor.Source, error) {
276285
repos := f.RepositoriesForComponentIterator(ctx, component)
277286
for repo, err := range repos {
@@ -299,7 +308,8 @@ func (f *FallbackRepository) GetLocalSource(ctx context.Context, component, vers
299308
// Deprecated: FallbackRepository is an implementation for the deprecated config
300309
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
301310
// and only added for backwards compatibility.
302-
// New concepts will likely be introduced in the future (contributions welcome!).
311+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
312+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
303313
func (f *FallbackRepository) RepositoriesForComponentIterator(ctx context.Context, component string) iter.Seq2[repository.ComponentVersionRepository, error] {
304314
return func(yield func(repository.ComponentVersionRepository, error) bool) {
305315
for _, resolver := range f.resolvers {
@@ -324,7 +334,8 @@ func (f *FallbackRepository) RepositoriesForComponentIterator(ctx context.Contex
324334
// Deprecated: FallbackRepository is an implementation for the deprecated config
325335
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
326336
// and only added for backwards compatibility.
327-
// New concepts will likely be introduced in the future (contributions welcome!).
337+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
338+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
328339
func (f *FallbackRepository) GetResolvers() []*resolverruntime.Resolver {
329340
// Return a copy of the resolvers to ensure immutability
330341
return deepCopyResolvers(f.resolvers)
@@ -358,10 +369,52 @@ func (f *FallbackRepository) getRepositoryForSpecification(ctx context.Context,
358369
return repo, nil
359370
}
360371

372+
// GetComponentVersionRepositoryForSpecification returns a repository for the given specification.
373+
//
374+
// Deprecated: FallbackRepository is an implementation for the deprecated config
375+
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
376+
// and only added for backwards compatibility.
377+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
378+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
361379
func (f *FallbackRepository) GetComponentVersionRepositoryForSpecification(ctx context.Context, specification runtime.Typed) (repository.ComponentVersionRepository, error) {
362380
return f.getRepositoryFromCache(ctx, specification)
363381
}
364382

383+
// GetRepositorySpecificationForComponent probes repositories in priority order and returns
384+
// the spec of the first repository that contains the component version.
385+
// Note: This does NOT cache results to maintain consistency with the existing
386+
// non-deterministic fallback behavior.
387+
//
388+
// Deprecated: FallbackRepository is an implementation for the deprecated config
389+
// type "ocm.config.ocm.software/v1". This concept of fallback resolvers is deprecated
390+
// and only added for backwards compatibility.
391+
// Use the path matcher resolver (ocm.software/open-component-model/bindings/go/repository/component/pathmatcher)
392+
// with "resolvers.ocm.software/v1alpha1" configuration instead.
393+
func (f *FallbackRepository) GetRepositorySpecificationForComponent(ctx context.Context, component, version string) (runtime.Typed, error) {
394+
for _, resolver := range f.resolvers {
395+
if resolver.Prefix != "" && resolver.Prefix != component &&
396+
!strings.HasPrefix(component, strings.TrimSuffix(resolver.Prefix, "/")+"/") {
397+
continue
398+
}
399+
repo, err := f.getRepositoryFromCache(ctx, resolver.Repository)
400+
if err != nil {
401+
return nil, fmt.Errorf("getting repository for resolver %v failed: %w", resolver, err)
402+
}
403+
_, err = repo.GetComponentVersion(ctx, component, version)
404+
if errors.Is(err, repository.ErrNotFound) {
405+
slog.DebugContext(ctx, "component version not found in repository during spec resolution",
406+
"realm", Realm, "repository", resolver.Repository, "component", component, "version", version)
407+
continue
408+
}
409+
if err != nil {
410+
return nil, fmt.Errorf("probing component version %s/%s in repository %v failed: %w",
411+
component, version, resolver.Repository, err)
412+
}
413+
return resolver.Repository, nil
414+
}
415+
return nil, fmt.Errorf("component version %s/%s not found in any repository", component, version)
416+
}
417+
365418
func (f *FallbackRepository) getRepositoryFromCache(ctx context.Context, specification runtime.Typed) (repository.ComponentVersionRepository, error) {
366419
specdata, err := json.Marshal(specification)
367420
if err != nil {

bindings/go/repository/component/fallback/v1/repository_test.go

Lines changed: 156 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,23 @@ func (m MockRepository) AddComponentVersion(ctx context.Context, descriptor *des
440440
}
441441

442442
func (m MockRepository) GetComponentVersion(ctx context.Context, component, version string) (*descriptor.Descriptor, error) {
443-
//TODO implement me
444-
panic("implement me")
443+
if versions, ok := m.Components[component]; ok {
444+
for _, v := range versions {
445+
if v == version {
446+
return &descriptor.Descriptor{
447+
Component: descriptor.Component{
448+
ComponentMeta: descriptor.ComponentMeta{
449+
ObjectMeta: descriptor.ObjectMeta{
450+
Name: component,
451+
Version: version,
452+
},
453+
},
454+
},
455+
}, nil
456+
}
457+
}
458+
}
459+
return nil, repository.ErrNotFound
445460
}
446461

447462
func (m MockRepository) ListComponentVersions(ctx context.Context, component string) ([]string, error) {
@@ -468,3 +483,142 @@ func (m MockRepository) GetLocalSource(ctx context.Context, component, version s
468483
//TODO implement me
469484
panic("implement me")
470485
}
486+
487+
func Test_GetRepositorySpecForComponent(t *testing.T) {
488+
ctx := context.Background()
489+
490+
cases := []struct {
491+
name string
492+
component string
493+
version string
494+
resolvers []*resolverruntime.Resolver
495+
expectedRepo string
496+
assertErr assert.ErrorAssertionFunc
497+
}{
498+
{
499+
name: "component found in first repository",
500+
component: "ocm.software/test",
501+
version: "v1.0.0",
502+
resolvers: []*resolverruntime.Resolver{
503+
{
504+
Repository: NewRepositorySpec("repo-a", map[string][]string{
505+
"ocm.software/test": {"v1.0.0"},
506+
}),
507+
Prefix: "",
508+
Priority: 2,
509+
},
510+
{
511+
Repository: NewRepositorySpec("repo-b", map[string][]string{
512+
"ocm.software/other": {"v1.0.0"},
513+
}),
514+
Prefix: "",
515+
Priority: 1,
516+
},
517+
},
518+
expectedRepo: "repo-a",
519+
assertErr: assert.NoError,
520+
},
521+
{
522+
name: "component found in second repository (not first)",
523+
component: "ocm.software/other",
524+
version: "v1.0.0",
525+
resolvers: []*resolverruntime.Resolver{
526+
{
527+
Repository: NewRepositorySpec("repo-a", map[string][]string{
528+
"ocm.software/test": {"v1.0.0"},
529+
}),
530+
Prefix: "",
531+
Priority: 2,
532+
},
533+
{
534+
Repository: NewRepositorySpec("repo-b", map[string][]string{
535+
"ocm.software/other": {"v1.0.0"},
536+
}),
537+
Prefix: "",
538+
Priority: 1,
539+
},
540+
},
541+
expectedRepo: "repo-b",
542+
assertErr: assert.NoError,
543+
},
544+
{
545+
name: "component with prefix match",
546+
component: "prefixA/component",
547+
version: "v1.0.0",
548+
resolvers: []*resolverruntime.Resolver{
549+
{
550+
Repository: NewRepositorySpec("repo-a", map[string][]string{
551+
"prefixA/component": {"v1.0.0"},
552+
}),
553+
Prefix: "prefixA",
554+
Priority: 1,
555+
},
556+
{
557+
Repository: NewRepositorySpec("repo-b", map[string][]string{
558+
"prefixB/component": {"v1.0.0"},
559+
}),
560+
Prefix: "prefixB",
561+
Priority: 1,
562+
},
563+
},
564+
expectedRepo: "repo-a",
565+
assertErr: assert.NoError,
566+
},
567+
{
568+
name: "component not found in any repository",
569+
component: "ocm.software/missing",
570+
version: "v1.0.0",
571+
resolvers: []*resolverruntime.Resolver{
572+
{
573+
Repository: NewRepositorySpec("repo-a", map[string][]string{
574+
"ocm.software/test": {"v1.0.0"},
575+
}),
576+
Prefix: "",
577+
Priority: 1,
578+
},
579+
},
580+
expectedRepo: "",
581+
assertErr: assert.Error,
582+
},
583+
{
584+
name: "version not found in repository",
585+
component: "ocm.software/test",
586+
version: "v2.0.0",
587+
resolvers: []*resolverruntime.Resolver{
588+
{
589+
Repository: NewRepositorySpec("repo-a", map[string][]string{
590+
"ocm.software/test": {"v1.0.0"},
591+
}),
592+
Prefix: "",
593+
Priority: 1,
594+
},
595+
},
596+
expectedRepo: "",
597+
assertErr: assert.Error,
598+
},
599+
}
600+
601+
for _, tc := range cases {
602+
t.Run(tc.name, func(t *testing.T) {
603+
r := require.New(t)
604+
605+
fb, err := fallback.NewFallbackRepository(ctx, MockProvider{}, nil, tc.resolvers)
606+
r.NoError(err)
607+
608+
repoSpec, err := fb.GetRepositorySpecificationForComponent(ctx, tc.component, tc.version)
609+
if !tc.assertErr(t, err) {
610+
return
611+
}
612+
613+
if tc.expectedRepo == "" {
614+
assert.Nil(t, repoSpec)
615+
return
616+
}
617+
618+
require.NotNil(t, repoSpec)
619+
spec, ok := repoSpec.(*RepositorySpec)
620+
require.True(t, ok, "expected *RepositorySpec, got %T", repoSpec)
621+
assert.Equal(t, tc.expectedRepo, spec.Name)
622+
})
623+
}
624+
}

bindings/go/repository/component/resolvers/fallback.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,15 @@ type fallbackResolver struct {
1919
var _ ComponentVersionRepositoryResolver = (*fallbackResolver)(nil)
2020

2121
func (f *fallbackResolver) GetComponentVersionRepositoryForSpecification(ctx context.Context, specification runtime.Typed) (repository.ComponentVersionRepository, error) {
22+
//nolint:staticcheck // compatibility mode for deprecated resolvers
2223
return f.repo.GetComponentVersionRepositoryForSpecification(ctx, specification)
2324
}
2425

2526
func (f *fallbackResolver) GetComponentVersionRepositoryForComponent(ctx context.Context, _, _ string) (repository.ComponentVersionRepository, error) {
2627
return f.repo, nil
2728
}
29+
30+
func (f *fallbackResolver) GetRepositorySpecificationForComponent(ctx context.Context, component, version string) (runtime.Typed, error) {
31+
//nolint:staticcheck // compatibility mode for deprecated resolvers
32+
return f.repo.GetRepositorySpecificationForComponent(ctx, component, version)
33+
}

bindings/go/repository/component/resolvers/interface.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@ type ComponentVersionRepositoryResolver interface {
1818
// GetComponentVersionRepositoryForSpecification returns a [repository.ComponentVersionRepository]
1919
// based on a given repository specification.
2020
GetComponentVersionRepositoryForSpecification(ctx context.Context, specification runtime.Typed) (repository.ComponentVersionRepository, error)
21+
22+
// GetRepositorySpecificationForComponent returns the repository specification that should be used
23+
// to access the given component version. This is useful for operations that need to know
24+
// the actual source repository of a component (e.g., transfer operations).
25+
GetRepositorySpecificationForComponent(ctx context.Context, component, version string) (runtime.Typed, error)
2126
}

bindings/go/repository/component/resolvers/pathmatcher.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,10 @@ func (p *pathMatcherResolver) GetComponentVersionRepositoryForComponent(ctx cont
9494
func (p *pathMatcherResolver) GetComponentVersionRepositoryForSpecification(ctx context.Context, specification runtime.Typed) (repository.ComponentVersionRepository, error) {
9595
return p.getRepository(ctx, specification)
9696
}
97+
98+
func (p *pathMatcherResolver) GetRepositorySpecificationForComponent(ctx context.Context, component, version string) (runtime.Typed, error) {
99+
return p.specProvider.GetRepositorySpec(ctx, runtime.Identity{
100+
descruntime.IdentityAttributeName: component,
101+
descruntime.IdentityAttributeVersion: version,
102+
})
103+
}

0 commit comments

Comments
 (0)