feat: explicit transfer module#2047
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughA new transfer package for OCM Go bindings was added, providing graph-based transfer graph construction and execution: public API, option types, a default builder, discovery/resolver logic, resource-specific transformation builders (local blob, OCI, Helm), helpers, and integration/unit tests. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API as transfer.BuildGraphDefinition
participant Discoverer as DAG Discoverer
participant Resolver as Repo Resolver
participant Graph as Graph Builder
participant Transformer as Resource Processors
participant Target as Target Repo
Client->>API: BuildGraphDefinition(fromSpec,toSpec,resolver,opts)
API->>API: apply options (recursive, copyMode, uploadType)
API->>Discoverer: create discoverer(resolver, recursive)
Discoverer->>Resolver: resolve component:version -> descriptor + repoSpec
Discoverer-->>API: discovered vertices (DAG)
API->>Graph: seed env with target spec ("to")
loop each vertex
API->>Graph: compute transformationID, store descriptor in env
loop each resource
Graph->>Transformer: select processor by access type
Transformer->>Resolver: (may) resolve source repo
Transformer->>Graph: append Get/Add/Convert transformations
end
Graph->>Graph: append component upload transformation
end
API-->>Client: return TransformationGraphDefinition
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (6)
bindings/go/transfer/internal/helpers.go (2)
99-101: Improve error message clarity.Line 100 uses
imageRef(the parsed struct) in the error message. Depending on itsString()implementation, this may not produce a user-friendly message. Consider using the originalimageReferencestring instead.♻️ Proposed fix
if imageRef.Repository == "" { - return "", fmt.Errorf("invalid image reference %q: repository is required", imageRef) + return "", fmt.Errorf("invalid image reference %q: repository is required", imageReference) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/helpers.go` around lines 99 - 101, The error message uses the parsed imageRef struct which may not render nicely; update the validation in helpers.go so the fmt.Errorf uses the original imageReference input string (not imageRef) when returning "invalid image reference ...: repository is required" — locate the check that references imageRef.Repository and change the error formatting to include imageReference for clearer user output (preserve the same error text and formatting otherwise).
15-25: Consider returning an error instead of panicking.The
panic()calls inasUnstructuredwill crash the program if conversion fails. While this may be acceptable for internal use where inputs are guaranteed valid, returning an error would make the function safer and more reusable.♻️ Proposed refactor to return error
-func asUnstructured(typed runtime.Typed) *runtime.Unstructured { +func asUnstructured(typed runtime.Typed) (*runtime.Unstructured, error) { var raw runtime.Raw if err := runtime.NewScheme(runtime.WithAllowUnknown()).Convert(typed, &raw); err != nil { - panic(fmt.Sprintf("cannot convert to raw: %v", err)) + return nil, fmt.Errorf("cannot convert to raw: %w", err) } var unstructured runtime.Unstructured if err := runtime.NewScheme(runtime.WithAllowUnknown()).Convert(&raw, &unstructured); err != nil { - panic(fmt.Sprintf("cannot convert to unstructured: %v", err)) + return nil, fmt.Errorf("cannot convert to unstructured: %w", err) } - return &unstructured + return &unstructured, nil }This would require updating all call sites to handle the returned error.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/helpers.go` around lines 15 - 25, Replace the panics in asUnstructured with proper error propagation: change the signature of asUnstructured(typed runtime.Typed) to return (*runtime.Unstructured, error), replace both panic(fmt.Sprintf(...)) calls with returning nil and the wrapped error, and update all callers of asUnstructured to handle the returned error (propagate or handle appropriately). Ensure you import/return errors as needed and keep the conversion logic using runtime.NewScheme(runtime.WithAllowUnknown()) unchanged.bindings/go/transfer/internal/localblob.go (1)
15-15: Consider removing or using the unused parameter.The second parameter
_ *descriptorv2.LocalBlobis unused. If it's reserved for future use, consider adding a brief comment explaining why, or remove it to keep the signature clean.♻️ Proposed fix
-func processLocalBlob(resource descriptorv2.Resource, _ *descriptorv2.LocalBlob, id string, val *discoveryValue, tgd *transformv1alpha1.TransformationGraphDefinition, toSpec runtime.Typed, resourceTransformIDs map[int]string, i int, uploadAsOCIArtifact bool) error { +func processLocalBlob(resource descriptorv2.Resource, id string, val *discoveryValue, tgd *transformv1alpha1.TransformationGraphDefinition, toSpec runtime.Typed, resourceTransformIDs map[int]string, i int, uploadAsOCIArtifact bool) error {Note: This would require updating the call site in
graph.goas well.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/localblob.go` at line 15, The function processLocalBlob currently has an unused parameter `_ *descriptorv2.LocalBlob`; either remove this parameter from the signature or use it if intended; update all callers (notably the call site in graph.go) to match the new signature; if you keep the parameter for future use, add a brief comment above the parameter (e.g., "// reserved for future use") to explain why it's intentionally unused and prevent linters from flagging it.bindings/go/transfer/internal/graph.go (1)
85-89: Consider adding a type assertion check for safety.The type assertion on line 87 could panic if the attribute is missing or has an unexpected type. While this is internal code with controlled inputs, a checked assertion would be more defensive.
🛡️ Proposed defensive assertion
for _, v := range d.Vertices { - val := v.Attributes[dagsync.AttributeValue].(*discoveryValue) + valAttr, ok := v.Attributes[dagsync.AttributeValue].(*discoveryValue) + if !ok { + return fmt.Errorf("unexpected attribute type for vertex %s", v.ID) + } + val := valAttr component := val.Descriptor.Component.Name🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/graph.go` around lines 85 - 89, The loop in fillGraphDefinitionWithPrefetchedComponents uses an unchecked type assertion v.Attributes[dagsync.AttributeValue].(*discoveryValue) which can panic if the attribute is missing or the type differs; change this to a checked assertion (e.g., raw, ok := v.Attributes[dagsync.AttributeValue]; dv, ok2 := raw.(*discoveryValue)) and if either the attribute is absent or ok2 is false, return a descriptive error (or skip/continue as appropriate) referencing dagsync.AttributeValue and discoveryValue so callers can diagnose missing/invalid attributes.bindings/go/transfer/internal/discovery_test.go (1)
59-64: Minor: Non-deterministic map iteration in mock.The fallback logic iterating over
m.reposmap (lines 60-63) has non-deterministic ordering. While current tests either setsharedRepoor have single-entry maps, this could cause flaky tests if future tests configure multiple repos without settingsharedRepo.♻️ Suggested improvement
Consider documenting the single-repo assumption or making the mock more explicit:
// Return the first repo (for simple tests) + // WARNING: Map iteration is non-deterministic; ensure only one repo is configured + // or use sharedRepo for multi-entry scenarios. for _, r := range m.repos { return r, nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/discovery_test.go` around lines 59 - 64, The mock currently returns the "first" repo by iterating m.repos which is non-deterministic; update the repo-returning logic in the mock (the code that iterates m.repos and returns r, nil) to be deterministic: if m.sharedRepo (or a similar explicit shared repo field) is set return that; else if len(m.repos)==1 return that single entry; otherwise either return a clear error indicating multiple repos are configured or select a deterministic entry by sorting the map keys and returning the first key’s repo. Ensure you modify the function that currently loops "for _, r := range m.repos { return r, nil }" to use one of these deterministic strategies.bindings/go/transfer/integration/integration_test.go (1)
232-260: Add an explicit timeout context for graph execution and target verification.Using
t.Context()alone can leave this integration test hanging longer than necessary on network/container issues. A local deadline makes failures deterministic.Suggested refactor
- ctx := t.Context() + ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute) + defer cancel() credResolver := &staticCredResolver{address: registryAddr, username: user, password: password}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/integration/integration_test.go` around lines 232 - 260, The test currently uses t.Context() (ctx) which can hang; wrap t.Context() with a local deadline via context.WithTimeout and defer cancel, then pass that timed ctx into graph.Process(ctx) and targetRepo.GetComponentVersion(ctx) (and any other network calls in this block) so the test fails fast on network/container issues; update the variable used by those calls (ctx) to the timed context and ensure cancel() is deferred.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@bindings/go/transfer/doc.go`:
- Around line 17-26: The example ignores and overwrites errors from
transfer.BuildGraphDefinition and b.BuildAndCheck; update the example to check
and handle the returned err after each call (after BuildGraphDefinition -> check
err before using tgd, after b.BuildAndCheck -> check err before using graph) so
that tgd and graph are only used when err is nil; reference the symbols
transfer.BuildGraphDefinition, transfer.NewDefaultBuilder, b.BuildAndCheck,
graph.Process and ensure each call’s error is checked and handled (return, log,
or fail) before proceeding to the next step.
In `@bindings/go/transfer/integration/integration_test.go`:
- Around line 241-246: The test registers an events channel with
WithEvents(make(chan graphRuntime.ProgressEvent, 16)) but never reads from it,
which can block graph.Process when the buffer fills; fix by creating the events
channel (events := make(chan graphRuntime.ProgressEvent, 16)), start a
short-lived goroutine that drains it (looping reads or select on ctx.Done/chan
to discard events) before calling graph.BuildAndCheck/graph.Process, pass that
channel into WithEvents, and ensure the goroutine exits when the test context
(ctx) is done or after Process returns so the drain stops cleanly.
In `@bindings/go/transfer/internal/discovery.go`:
- Around line 107-109: The len(words) == 0 check is unreachable because words is
initialized as []string{"transform"}; either remove the dead conditional or
initialize words as an empty slice when you intended to allow zero-length
(change the initialization of the variable named words) — update the code in
discovery.go around the words declaration and the conditional so the branch is
either removed or the slice is correctly initialized to []string{} depending on
intended behavior; ensure any subsequent logic that depends on words being
non-empty is adjusted accordingly.
In `@bindings/go/transfer/transfer.go`:
- Around line 23-24: The loop applying variadic Option functions can panic if a
nil Option is passed; update the loop in transfer.go where opts are applied (the
code iterating "for _, opt := range opts") to skip nil entries before invoking
them (e.g., check opt != nil and continue if nil) so Option functions like
opt(&o) are only called when non-nil. Ensure this change is applied in the
function that constructs or configures the transfer options to safely handle nil
variadic arguments.
---
Nitpick comments:
In `@bindings/go/transfer/integration/integration_test.go`:
- Around line 232-260: The test currently uses t.Context() (ctx) which can hang;
wrap t.Context() with a local deadline via context.WithTimeout and defer cancel,
then pass that timed ctx into graph.Process(ctx) and
targetRepo.GetComponentVersion(ctx) (and any other network calls in this block)
so the test fails fast on network/container issues; update the variable used by
those calls (ctx) to the timed context and ensure cancel() is deferred.
In `@bindings/go/transfer/internal/discovery_test.go`:
- Around line 59-64: The mock currently returns the "first" repo by iterating
m.repos which is non-deterministic; update the repo-returning logic in the mock
(the code that iterates m.repos and returns r, nil) to be deterministic: if
m.sharedRepo (or a similar explicit shared repo field) is set return that; else
if len(m.repos)==1 return that single entry; otherwise either return a clear
error indicating multiple repos are configured or select a deterministic entry
by sorting the map keys and returning the first key’s repo. Ensure you modify
the function that currently loops "for _, r := range m.repos { return r, nil }"
to use one of these deterministic strategies.
In `@bindings/go/transfer/internal/graph.go`:
- Around line 85-89: The loop in fillGraphDefinitionWithPrefetchedComponents
uses an unchecked type assertion
v.Attributes[dagsync.AttributeValue].(*discoveryValue) which can panic if the
attribute is missing or the type differs; change this to a checked assertion
(e.g., raw, ok := v.Attributes[dagsync.AttributeValue]; dv, ok2 :=
raw.(*discoveryValue)) and if either the attribute is absent or ok2 is false,
return a descriptive error (or skip/continue as appropriate) referencing
dagsync.AttributeValue and discoveryValue so callers can diagnose
missing/invalid attributes.
In `@bindings/go/transfer/internal/helpers.go`:
- Around line 99-101: The error message uses the parsed imageRef struct which
may not render nicely; update the validation in helpers.go so the fmt.Errorf
uses the original imageReference input string (not imageRef) when returning
"invalid image reference ...: repository is required" — locate the check that
references imageRef.Repository and change the error formatting to include
imageReference for clearer user output (preserve the same error text and
formatting otherwise).
- Around line 15-25: Replace the panics in asUnstructured with proper error
propagation: change the signature of asUnstructured(typed runtime.Typed) to
return (*runtime.Unstructured, error), replace both panic(fmt.Sprintf(...))
calls with returning nil and the wrapped error, and update all callers of
asUnstructured to handle the returned error (propagate or handle appropriately).
Ensure you import/return errors as needed and keep the conversion logic using
runtime.NewScheme(runtime.WithAllowUnknown()) unchanged.
In `@bindings/go/transfer/internal/localblob.go`:
- Line 15: The function processLocalBlob currently has an unused parameter `_
*descriptorv2.LocalBlob`; either remove this parameter from the signature or use
it if intended; update all callers (notably the call site in graph.go) to match
the new signature; if you keep the parameter for future use, add a brief comment
above the parameter (e.g., "// reserved for future use") to explain why it's
intentionally unused and prevent linters from flagging it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f7f0779f-ec90-420d-87e4-f72a67eadcdb
⛔ Files ignored due to path filters (2)
bindings/go/transfer/go.sumis excluded by!**/*.sumbindings/go/transfer/integration/go.sumis excluded by!**/*.sum
📒 Files selected for processing (25)
Taskfile.ymlbindings/go/transfer/Taskfile.ymlbindings/go/transfer/builder.gobindings/go/transfer/doc.gobindings/go/transfer/go.modbindings/go/transfer/integration/Taskfile.ymlbindings/go/transfer/integration/go.modbindings/go/transfer/integration/integration_test.gobindings/go/transfer/internal/builder.gobindings/go/transfer/internal/builder_test.gobindings/go/transfer/internal/discovery.gobindings/go/transfer/internal/discovery_test.gobindings/go/transfer/internal/graph.gobindings/go/transfer/internal/graph_test.gobindings/go/transfer/internal/helm.gobindings/go/transfer/internal/helpers.gobindings/go/transfer/internal/helpers_test.gobindings/go/transfer/internal/localblob.gobindings/go/transfer/internal/oci.gobindings/go/transfer/internal/oci_test.gobindings/go/transfer/internal/options.gobindings/go/transfer/internal/scheme.gobindings/go/transfer/options.gobindings/go/transfer/options_test.gobindings/go/transfer/transfer.go
61b3578 to
2c6e8fe
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (5)
bindings/go/transfer/internal/discovery_test.go (3)
165-225: Consider adding tests for additional error paths.The resolver tests cover key format errors and
GetRepositorySpecificationForComponentfailures, but the implementation (per context snippet 1) has additional failure points:
GetComponentVersionRepositoryForSpecificationreturning an errorrepo.GetComponentVersionreturning an error- Digest verification failure (when
expectedDigestreturns a non-nil value)Adding these would improve coverage of the resolver's error handling paths.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/discovery_test.go` around lines 165 - 225, Tests currently miss covering three resolver error paths: GetComponentVersionRepositoryForSpecification failures, repo.GetComponentVersion failures, and digest verification failures in resolver.Resolve. Add separate unit tests that instantiate resolver with a mockCVRepoResolver that returns an error from GetComponentVersionRepositoryForSpecification to assert the error is propagated; another test where mockCVRepo (the repo value returned) has GetComponentVersion return an error and assert resolver.Resolve returns that error; and a test where expectedDigest returns a non-nil descriptor differing from the actual descriptor digest to assert a digest verification failure is raised. Use the existing mock types (mockCVRepoResolver, mockCVRepo) and the resolver.Resolve call to exercise these code paths and assert error messages contain the expected cause.
52-64: Non-deterministic map iteration may cause flaky behavior.When
sharedRepois nil and multiple repos are configured, iterating overm.reposwithfor _, r := range m.reposreturns an arbitrary entry due to Go's randomized map iteration order. Current tests only configure one repo, so this works, but future tests with multiple repos could become flaky.Consider using
sharedRepoconsistently in tests or keying the lookup by the specification:♻️ Suggested improvement
func (m *mockCVRepoResolver) GetComponentVersionRepositoryForSpecification(_ context.Context, _ runtime.Typed) (repository.ComponentVersionRepository, error) { if m.err != nil { return nil, m.err } if m.sharedRepo != nil { return m.sharedRepo, nil } - // Return the first repo (for simple tests) - for _, r := range m.repos { - return r, nil - } - return nil, fmt.Errorf("no repos configured") + return nil, fmt.Errorf("no sharedRepo configured for GetComponentVersionRepositoryForSpecification") }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/discovery_test.go` around lines 52 - 64, The GetComponentVersionRepositoryForSpecification method on mockCVRepoResolver can return a non-deterministic repo because iterating m.repos (a map) yields randomized order; change the logic to be deterministic by either (a) preferring and returning m.sharedRepo when present in all tests, or (b) selecting by a stable key derived from the incoming specification (use the runtime.Typed argument) or (c) deterministically picking the first map entry by sorting the map keys before selecting. Update GetComponentVersionRepositoryForSpecification to implement one of these deterministic choices (reference: mockCVRepoResolver, GetComponentVersionRepositoryForSpecification, sharedRepo, repos) so future tests won’t be flaky.
181-195: Consider usingsharedRepofor clarity.This test relies on the map iteration in
GetComponentVersionRepositoryForSpecificationreturning the single configured repo. UsingsharedRepowould make the test intent clearer and avoid coupling to the mock's fallback behavior:♻️ Suggested improvement
r := &resolver{ repoResolver: &mockCVRepoResolver{ specs: map[string]runtime.Typed{ "ocm.software/test:1.0.0": repoSpec, }, - repos: map[string]repository.ComponentVersionRepository{ - "ocm.software/test:1.0.0": &mockCVRepo{ - descriptors: map[string]*descriptor.Descriptor{ - "ocm.software/test:1.0.0": desc, - }, - }, + sharedRepo: &mockCVRepo{ + descriptors: map[string]*descriptor.Descriptor{ + "ocm.software/test:1.0.0": desc, + }, }, }, expectedDigest: func(_ runtime.Identity) *descriptor.Digest { return nil }, }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/discovery_test.go` around lines 181 - 195, The test should create a single shared repo instance and use it in the mock resolver instead of relying on map iteration/fallback: create sharedRepo := &mockCVRepo{descriptors: map[string]*descriptor.Descriptor{"ocm.software/test:1.0.0": desc}} and then set the mockCVRepoResolver.repos entry for the spec key to that sharedRepo (keep repoSpec in the specs map as-is); update the resolver initialization to reference sharedRepo so resolver, mockCVRepoResolver, mockCVRepo, desc and repoSpec are clearly linked and the test no longer depends on GetComponentVersionRepositoryForSpecification iteration order.bindings/go/transfer/internal/helpers.go (1)
15-25: Consider reusing the scheme instance instead of creating new ones on each call.
asUnstructuredcreates two newruntime.Schemeinstances per invocation. Since this function may be called frequently during graph building, reusing the package-levelschemevariable (or a dedicated unstructured-conversion scheme) would reduce allocation overhead.Also, the
paniccalls assume conversion can never fail for valid inputs. If this assumption is violated, it will crash the process. Consider returning an error instead, or add a doc comment explaining when panics are acceptable.♻️ Suggested improvement
+var unstructuredScheme = runtime.NewScheme(runtime.WithAllowUnknown()) + -func asUnstructured(typed runtime.Typed) *runtime.Unstructured { +func asUnstructured(typed runtime.Typed) (*runtime.Unstructured, error) { var raw runtime.Raw - if err := runtime.NewScheme(runtime.WithAllowUnknown()).Convert(typed, &raw); err != nil { - panic(fmt.Sprintf("cannot convert to raw: %v", err)) + if err := unstructuredScheme.Convert(typed, &raw); err != nil { + return nil, fmt.Errorf("cannot convert to raw: %w", err) } var unstructured runtime.Unstructured - if err := runtime.NewScheme(runtime.WithAllowUnknown()).Convert(&raw, &unstructured); err != nil { - panic(fmt.Sprintf("cannot convert to unstructured: %v", err)) + if err := unstructuredScheme.Convert(&raw, &unstructured); err != nil { + return nil, fmt.Errorf("cannot convert to unstructured: %w", err) } - return &unstructured + return &unstructured, nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/helpers.go` around lines 15 - 25, The asUnstructured function currently allocates two runtime.Scheme instances per call; change it to reuse a package-level scheme (e.g., a pre-initialized variable named scheme or a dedicated unstructuredScheme) for Convert operations instead of calling runtime.NewScheme every time, and handle conversion failures without unguarded panics by returning an error from asUnstructured (or, if you must keep panics, add a clear doc comment stating the function panics on conversion failure). Update callers to handle the new error return if you choose the error approach and reference asUnstructured, runtime.Scheme, and the package-level scheme/unstructuredScheme when making the change.bindings/go/transfer/internal/oci.go (1)
102-104: Minor: Inconsistent spacing in CEL expressions.The CEL expressions have double spaces before the colon (e.g.,
"labels :"). While this likely doesn't affect functionality, it's inconsistent with typical formatting.♻️ Formatting fix
- "digest": fmt.Sprintf("${%s.output.resource.digest}", getResourceID), - "labels": fmt.Sprintf("${has(%s.output.resource.labels) ? %s.output.resource.labels : []}", getResourceID, getResourceID), - "extraIdentity": fmt.Sprintf("${has(%s.output.resource.extraIdentity) ? %s.output.resource.extraIdentity : {}}", getResourceID, getResourceID), - "srcRefs": fmt.Sprintf("${has(%s.output.resource.srcRefs) ? %s.output.resource.srcRefs : []}", getResourceID, getResourceID), + "digest": fmt.Sprintf("${%s.output.resource.digest}", getResourceID), + "labels": fmt.Sprintf("${has(%s.output.resource.labels) ? %s.output.resource.labels : []}", getResourceID, getResourceID), + "extraIdentity": fmt.Sprintf("${has(%s.output.resource.extraIdentity) ? %s.output.resource.extraIdentity : {}}", getResourceID, getResourceID), + "srcRefs": fmt.Sprintf("${has(%s.output.resource.srcRefs) ? %s.output.resource.srcRefs : []}", getResourceID, getResourceID),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/oci.go` around lines 102 - 104, Fix inconsistent spacing in the CEL expressions used in the map entries built with fmt.Sprintf (look for the "labels", "extraIdentity", and "srcRefs" entries that use getResourceID) by removing the extra space before the colon so the ternary-like CEL snippets read "... ? %s.output.resource.labels : []" (and similarly for extraIdentity and srcRefs); adjust the string literals passed to fmt.Sprintf accordingly to ensure consistent single-space formatting around the colon.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@bindings/go/transfer/internal/discovery_test.go`:
- Around line 165-225: Tests currently miss covering three resolver error paths:
GetComponentVersionRepositoryForSpecification failures, repo.GetComponentVersion
failures, and digest verification failures in resolver.Resolve. Add separate
unit tests that instantiate resolver with a mockCVRepoResolver that returns an
error from GetComponentVersionRepositoryForSpecification to assert the error is
propagated; another test where mockCVRepo (the repo value returned) has
GetComponentVersion return an error and assert resolver.Resolve returns that
error; and a test where expectedDigest returns a non-nil descriptor differing
from the actual descriptor digest to assert a digest verification failure is
raised. Use the existing mock types (mockCVRepoResolver, mockCVRepo) and the
resolver.Resolve call to exercise these code paths and assert error messages
contain the expected cause.
- Around line 52-64: The GetComponentVersionRepositoryForSpecification method on
mockCVRepoResolver can return a non-deterministic repo because iterating m.repos
(a map) yields randomized order; change the logic to be deterministic by either
(a) preferring and returning m.sharedRepo when present in all tests, or (b)
selecting by a stable key derived from the incoming specification (use the
runtime.Typed argument) or (c) deterministically picking the first map entry by
sorting the map keys before selecting. Update
GetComponentVersionRepositoryForSpecification to implement one of these
deterministic choices (reference: mockCVRepoResolver,
GetComponentVersionRepositoryForSpecification, sharedRepo, repos) so future
tests won’t be flaky.
- Around line 181-195: The test should create a single shared repo instance and
use it in the mock resolver instead of relying on map iteration/fallback: create
sharedRepo := &mockCVRepo{descriptors:
map[string]*descriptor.Descriptor{"ocm.software/test:1.0.0": desc}} and then set
the mockCVRepoResolver.repos entry for the spec key to that sharedRepo (keep
repoSpec in the specs map as-is); update the resolver initialization to
reference sharedRepo so resolver, mockCVRepoResolver, mockCVRepo, desc and
repoSpec are clearly linked and the test no longer depends on
GetComponentVersionRepositoryForSpecification iteration order.
In `@bindings/go/transfer/internal/helpers.go`:
- Around line 15-25: The asUnstructured function currently allocates two
runtime.Scheme instances per call; change it to reuse a package-level scheme
(e.g., a pre-initialized variable named scheme or a dedicated
unstructuredScheme) for Convert operations instead of calling runtime.NewScheme
every time, and handle conversion failures without unguarded panics by returning
an error from asUnstructured (or, if you must keep panics, add a clear doc
comment stating the function panics on conversion failure). Update callers to
handle the new error return if you choose the error approach and reference
asUnstructured, runtime.Scheme, and the package-level scheme/unstructuredScheme
when making the change.
In `@bindings/go/transfer/internal/oci.go`:
- Around line 102-104: Fix inconsistent spacing in the CEL expressions used in
the map entries built with fmt.Sprintf (look for the "labels", "extraIdentity",
and "srcRefs" entries that use getResourceID) by removing the extra space before
the colon so the ternary-like CEL snippets read "... ? %s.output.resource.labels
: []" (and similarly for extraIdentity and srcRefs); adjust the string literals
passed to fmt.Sprintf accordingly to ensure consistent single-space formatting
around the colon.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5b48c48a-f655-4f74-8492-7296e40b0e7d
⛔ Files ignored due to path filters (2)
bindings/go/transfer/go.sumis excluded by!**/*.sumbindings/go/transfer/integration/go.sumis excluded by!**/*.sum
📒 Files selected for processing (25)
Taskfile.ymlbindings/go/transfer/Taskfile.ymlbindings/go/transfer/builder.gobindings/go/transfer/doc.gobindings/go/transfer/go.modbindings/go/transfer/integration/Taskfile.ymlbindings/go/transfer/integration/go.modbindings/go/transfer/integration/integration_test.gobindings/go/transfer/internal/builder.gobindings/go/transfer/internal/builder_test.gobindings/go/transfer/internal/discovery.gobindings/go/transfer/internal/discovery_test.gobindings/go/transfer/internal/graph.gobindings/go/transfer/internal/graph_test.gobindings/go/transfer/internal/helm.gobindings/go/transfer/internal/helpers.gobindings/go/transfer/internal/helpers_test.gobindings/go/transfer/internal/localblob.gobindings/go/transfer/internal/oci.gobindings/go/transfer/internal/oci_test.gobindings/go/transfer/internal/options.gobindings/go/transfer/internal/scheme.gobindings/go/transfer/options.gobindings/go/transfer/options_test.gobindings/go/transfer/transfer.go
✅ Files skipped from review due to trivial changes (14)
- bindings/go/transfer/Taskfile.yml
- bindings/go/transfer/internal/builder_test.go
- bindings/go/transfer/internal/scheme.go
- bindings/go/transfer/builder.go
- Taskfile.yml
- bindings/go/transfer/options_test.go
- bindings/go/transfer/go.mod
- bindings/go/transfer/internal/options.go
- bindings/go/transfer/integration/Taskfile.yml
- bindings/go/transfer/doc.go
- bindings/go/transfer/integration/go.mod
- bindings/go/transfer/internal/oci_test.go
- bindings/go/transfer/options.go
- bindings/go/transfer/internal/graph_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
- bindings/go/transfer/transfer.go
- bindings/go/transfer/internal/helm.go
- bindings/go/transfer/internal/helpers_test.go
- bindings/go/transfer/internal/discovery.go
- bindings/go/transfer/internal/localblob.go
- bindings/go/transfer/internal/builder.go
- bindings/go/transfer/internal/graph.go
797356a to
e43f8e7
Compare
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
e43f8e7 to
6dc3612
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
cli/cmd/transfer/component-version/cmd.go (2)
229-231:⚠️ Potential issue | 🟡 MinorKeep the underlying graph execution error.
tracker.Summary(err)is only side output; returningfmt.Errorf("graph execution failed")drops the actual failure reason for callers and tests. Wraperrhere so auth, repository, and validation failures are still visible.🔎 Preserve the original error
if err := graph.Process(ctx); err != nil { tracker.Summary(err) - return fmt.Errorf("graph execution failed") + return fmt.Errorf("graph execution failed: %w", err) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/cmd/transfer/component-version/cmd.go` around lines 229 - 231, The current error path calls tracker.Summary(err) but then returns a generic error, losing the original failure; change the return to wrap the original error from graph.Process so callers see the root cause (i.e., after calling tracker.Summary(err) return fmt.Errorf("graph execution failed: %w", err) or otherwise wrap err). Ensure this change is made in the block that handles graph.Process and references graph.Process and tracker.Summary so the original error is preserved for upstream callers and tests.
196-205:⚠️ Potential issue | 🟠 MajorDon't panic while formatting a
BuildAndCheckfailure.If
renderTGDreturns an error,readercan be nil here and the deferredreader.Close()will panic over the original validation error. Guard the close/read path and only append rendered output when a reader was actually returned.🐛 Guard the optional debug-rendering path
reader, rerr := renderTGD(tgd, output) - defer func() { - _ = reader.Close() - }() - raw, _ := io.ReadAll(reader) - return errors.Join( - err, - rerr, - fmt.Errorf("%s", raw), - ) + errs := []error{err} + if rerr != nil { + errs = append(errs, fmt.Errorf("rendering transformation graph failed: %w", rerr)) + } + if reader != nil { + defer func() { + _ = reader.Close() + }() + if raw, readErr := io.ReadAll(reader); readErr == nil && len(raw) > 0 { + errs = append(errs, errors.New(string(raw))) + } + } + return errors.Join(errs...) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/cmd/transfer/component-version/cmd.go` around lines 196 - 205, The code calls renderTGD(tgd, output) and defers reader.Close() unconditionally, which can panic if renderTGD returned a nil reader when rerr is non-nil; change the logic around the render/debug path so you only defer Close and read the contents when reader != nil (guard the reader variable), and only include the rendered output (fmt.Errorf("%s", raw)) in errors.Join when you successfully read it; reference renderTGD, reader, rerr and the surrounding return that currently uses errors.Join to locate and update the logic.
🧹 Nitpick comments (1)
bindings/go/transfer/internal/graph_test.go (1)
157-252: Add a regression that covers descriptor metadata plus a transformed resource.These cases exercise the resource wiring well, but they still don't cover a descriptor that has component-level metadata (for example labels) and then goes through the transformed-resource branch. A focused case there would lock in the fix for
buildDescriptorSpecand prevent silent descriptor truncation from regressing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/graph_test.go` around lines 157 - 252, Add a regression test that constructs a descriptor with component-level metadata (e.g., labels) using testDescriptor and a resource that triggers the transformed-resource path (e.g., helmResource), then call BuildGraphDefinition (same flags as existing helm tests) and assert that the resulting graph/component spec preserves the original metadata; this will exercise the transformed-resource branch and lock in the fix for buildDescriptorSpec by ensuring component metadata is not dropped during transformation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@bindings/go/transfer/internal/graph.go`:
- Around line 239-273: The current buildDescriptorSpec function rebuilds
component fields when resourceTransformIDs is non-empty, discarding any other
component metadata; instead, marshal v2desc into a generic map (e.g., decode
v2desc to map[string]any), locate the nested "component" map, replace only its
"resources" entry with the constructed resourcesArray, and leave all other
fields intact; keep using setOptionalField for conditional fields if needed but
operate on the marshalled descriptor map so buildDescriptorSpec returns the
original descriptor shape with only component.resources patched.
---
Outside diff comments:
In `@cli/cmd/transfer/component-version/cmd.go`:
- Around line 229-231: The current error path calls tracker.Summary(err) but
then returns a generic error, losing the original failure; change the return to
wrap the original error from graph.Process so callers see the root cause (i.e.,
after calling tracker.Summary(err) return fmt.Errorf("graph execution failed:
%w", err) or otherwise wrap err). Ensure this change is made in the block that
handles graph.Process and references graph.Process and tracker.Summary so the
original error is preserved for upstream callers and tests.
- Around line 196-205: The code calls renderTGD(tgd, output) and defers
reader.Close() unconditionally, which can panic if renderTGD returned a nil
reader when rerr is non-nil; change the logic around the render/debug path so
you only defer Close and read the contents when reader != nil (guard the reader
variable), and only include the rendered output (fmt.Errorf("%s", raw)) in
errors.Join when you successfully read it; reference renderTGD, reader, rerr and
the surrounding return that currently uses errors.Join to locate and update the
logic.
---
Nitpick comments:
In `@bindings/go/transfer/internal/graph_test.go`:
- Around line 157-252: Add a regression test that constructs a descriptor with
component-level metadata (e.g., labels) using testDescriptor and a resource that
triggers the transformed-resource path (e.g., helmResource), then call
BuildGraphDefinition (same flags as existing helm tests) and assert that the
resulting graph/component spec preserves the original metadata; this will
exercise the transformed-resource branch and lock in the fix for
buildDescriptorSpec by ensuring component metadata is not dropped during
transformation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c7c2ca55-b2c2-45ac-ae4b-68d17c89088e
⛔ Files ignored due to path filters (2)
bindings/go/transfer/go.sumis excluded by!**/*.sumbindings/go/transfer/integration/go.sumis excluded by!**/*.sum
📒 Files selected for processing (30)
Taskfile.ymlbindings/go/transfer/Taskfile.ymlbindings/go/transfer/builder.gobindings/go/transfer/doc.gobindings/go/transfer/go.modbindings/go/transfer/integration/Taskfile.ymlbindings/go/transfer/integration/go.modbindings/go/transfer/integration/integration_test.gobindings/go/transfer/internal/builder.gobindings/go/transfer/internal/builder_test.gobindings/go/transfer/internal/discovery.gobindings/go/transfer/internal/discovery_test.gobindings/go/transfer/internal/graph.gobindings/go/transfer/internal/graph_test.gobindings/go/transfer/internal/helm.gobindings/go/transfer/internal/helpers.gobindings/go/transfer/internal/helpers_test.gobindings/go/transfer/internal/localblob.gobindings/go/transfer/internal/oci.gobindings/go/transfer/internal/oci_test.gobindings/go/transfer/internal/options.gobindings/go/transfer/internal/scheme.gobindings/go/transfer/options.gobindings/go/transfer/options_test.gobindings/go/transfer/transfer.gocli/cmd/transfer/component-version/cmd.gocli/cmd/transfer/component-version/internal/graph.gocli/cmd/transfer/component-version/internal/helpers_test.gocli/cmd/transfer/component-version/internal/options.gocli/go.mod
💤 Files with no reviewable changes (3)
- cli/cmd/transfer/component-version/internal/helpers_test.go
- cli/cmd/transfer/component-version/internal/options.go
- cli/cmd/transfer/component-version/internal/graph.go
✅ Files skipped from review due to trivial changes (12)
- bindings/go/transfer/internal/builder_test.go
- bindings/go/transfer/Taskfile.yml
- bindings/go/transfer/builder.go
- bindings/go/transfer/internal/options.go
- bindings/go/transfer/integration/Taskfile.yml
- bindings/go/transfer/doc.go
- bindings/go/transfer/options_test.go
- Taskfile.yml
- bindings/go/transfer/go.mod
- bindings/go/transfer/internal/oci_test.go
- bindings/go/transfer/integration/go.mod
- bindings/go/transfer/options.go
🚧 Files skipped from review as they are similar to previous changes (6)
- bindings/go/transfer/internal/scheme.go
- bindings/go/transfer/transfer.go
- bindings/go/transfer/internal/helpers_test.go
- bindings/go/transfer/internal/oci.go
- bindings/go/transfer/internal/discovery.go
- bindings/go/transfer/internal/localblob.go
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
bindings/go/transfer/internal/localblob.go (1)
15-15: Consider using or removing the unused*descriptorv2.LocalBlobparameter.The second parameter
_ *descriptorv2.LocalBlobis passed from the call site ingraph.go(where it's the typed access) but never used. If it provides useful metadata for future enhancements, consider keeping it with a comment; otherwise, removing it simplifies the interface.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bindings/go/transfer/internal/localblob.go` at line 15, The parameter list for processLocalBlob includes an unused second parameter (_ *descriptorv2.LocalBlob); either remove this parameter from processLocalBlob and its callers (including the call in graph.go that currently passes the typed access) to simplify the signature, or keep it and add a brief comment inside the function signature explaining it's reserved for future metadata use so linters/readers know it's intentionally unused; update all call sites (e.g., the graph.go invocation) to match the new signature if you remove it, or leave them as-is if you keep it with the comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@bindings/go/transfer/internal/helpers.go`:
- Around line 15-25: The asUnstructured function is creating fresh schemes with
runtime.NewScheme(runtime.WithAllowUnknown()) which omit the package-level
registered types; change asUnstructured to use the shared package-level scheme
(the same scheme used by convertToConcreteRepo and localblob.go) so
type-specific conversion logic is preserved: replace the local NewScheme usages
with the package-level scheme when converting typed -> runtime.Raw and Raw ->
runtime.Unstructured, and ensure imports/visibility allow referencing the
package-level variable named scheme.
---
Nitpick comments:
In `@bindings/go/transfer/internal/localblob.go`:
- Line 15: The parameter list for processLocalBlob includes an unused second
parameter (_ *descriptorv2.LocalBlob); either remove this parameter from
processLocalBlob and its callers (including the call in graph.go that currently
passes the typed access) to simplify the signature, or keep it and add a brief
comment inside the function signature explaining it's reserved for future
metadata use so linters/readers know it's intentionally unused; update all call
sites (e.g., the graph.go invocation) to match the new signature if you remove
it, or leave them as-is if you keep it with the comment.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 441082e8-3006-400a-8dce-766e196b0ece
⛔ Files ignored due to path filters (2)
bindings/go/transfer/go.sumis excluded by!**/*.sumbindings/go/transfer/integration/go.sumis excluded by!**/*.sum
📒 Files selected for processing (25)
Taskfile.ymlbindings/go/transfer/Taskfile.ymlbindings/go/transfer/builder.gobindings/go/transfer/doc.gobindings/go/transfer/go.modbindings/go/transfer/integration/Taskfile.ymlbindings/go/transfer/integration/go.modbindings/go/transfer/integration/integration_test.gobindings/go/transfer/internal/builder.gobindings/go/transfer/internal/builder_test.gobindings/go/transfer/internal/discovery.gobindings/go/transfer/internal/discovery_test.gobindings/go/transfer/internal/graph.gobindings/go/transfer/internal/graph_test.gobindings/go/transfer/internal/helm.gobindings/go/transfer/internal/helpers.gobindings/go/transfer/internal/helpers_test.gobindings/go/transfer/internal/localblob.gobindings/go/transfer/internal/oci.gobindings/go/transfer/internal/oci_test.gobindings/go/transfer/internal/options.gobindings/go/transfer/internal/scheme.gobindings/go/transfer/options.gobindings/go/transfer/options_test.gobindings/go/transfer/transfer.go
✅ Files skipped from review due to trivial changes (14)
- bindings/go/transfer/Taskfile.yml
- bindings/go/transfer/internal/builder_test.go
- Taskfile.yml
- bindings/go/transfer/builder.go
- bindings/go/transfer/doc.go
- bindings/go/transfer/integration/Taskfile.yml
- bindings/go/transfer/go.mod
- bindings/go/transfer/internal/options.go
- bindings/go/transfer/options_test.go
- bindings/go/transfer/integration/go.mod
- bindings/go/transfer/internal/discovery.go
- bindings/go/transfer/internal/oci_test.go
- bindings/go/transfer/options.go
- bindings/go/transfer/internal/graph.go
🚧 Files skipped from review as they are similar to previous changes (3)
- bindings/go/transfer/transfer.go
- bindings/go/transfer/internal/builder.go
- bindings/go/transfer/internal/helm.go
fabianburth
left a comment
There was a problem hiding this comment.
Rather superficial review, since most of the code is just copied. What I paid attention to is that we do not offer too much of the internals as public API, but seems like this is taken care of!
LGTM
|
Maybe it is a good idea to try integrating CLI with this transfer module from this branch before merging it? |
|
I ran this in go work before and have a pr coming up that will build on this |
What this PR does / why we need it
makes sure that transfer can be used as a library when required. code is mostly moved out from CLI, but i have added unit and integration tests to reach sufficient coverage (80+ throughout)
Which issue(s) this PR fixes
part of open-component-model/ocm-project#933
Testing
How to test the changes
See the integration test for testing of the library.
Summary by CodeRabbit
New Features
Tests