Configure Custom Uploads During Transfer
On this page
By default, ocm transfer either leaves external resources where they are or, with
a local blob uploader configuration, downloads and re-embeds them into the target component version.
An uploader configuration gives you a third option: route a matching resource
through a custom transformer that streams it to an upload target of your choice and
rewrites the resource to point at the new location.
This tutorial configures the built-in HTTP streaming uploader to stream a
Wget/v1 resource to an HTTP PUT endpoint during transfer.
What You’ll Learn
- How an uploader configuration matches a resource by access type and routes it to a custom target
- How the HTTP streaming uploader streams content to a
PUTendpoint without buffering it - How the transferred resource is rewritten to a
Wget/v1access at the upload target - How to template the target URL from the source resource
How It Works
flowchart LR
Source["Source repository<br/>(resource: Wget/v1 access)"]
Transfer["ocm transfer cv<br/>+ uploader config"]
Target["Target HTTP endpoint<br/>(PUT)"]
Descriptor["Target component version<br/>(resource: Wget/v1 @ target URL)"]
Source -- "stream bytes" --> Transfer -- "PUT" --> Target
Transfer -- "rewrite access" --> Descriptor
When a resource’s access type matches the match of the uploader, transfer streams the
resource’s bytes straight from the source into an HTTP request to your target URL,
computes (or verifies) its digest as the bytes pass through, and records a new
Wget/v1 access on the transferred resource pointing at the upload target.
Estimated time: ~10 minutes
Prerequisites
- OCM CLI installed
- A component version containing a resource with
Wget/v1access (see Working with HTTP Resources) - An HTTP endpoint that accepts
PUTuploads and any credentials it requires - A target OCM repository (an OCI registry or a CTF archive) for the component descriptor
Scenario
- Component:
ocm.software/demo:1.0.0with a resourcedocsusingWget/v1access - Source URL:
https://source.example.com/artifacts/docs.tar - Upload target:
https://mytarget.example.com/uploads/artifacts/docs.tar - Config file:
.ocmconfigin the working directory
Tutorial Steps
Write the uploader configuration
Create
.ocmconfigwith an uploader config that matchesWget/v1resources and streams them to your target, followed by a catch-all that copies every other resource as a local blob:type: generic.config.ocm.software/v1 configurations: - type: http.uploader.transfer.config.ocm.software/v1alpha1 match: resource.access.isType("Wget/v1") targetURL: '${"https://mytarget.example.com/uploads" + url(resource.access.url).path}' method: PUT - type: localblob.uploader.transfer.config.ocm.software/v1alpha1Note: The CLI merges
.ocmconfigfrom the current directory with your other OCM configuration (such as$HOME/.ocmconfig), so credentials and resolvers stay in effect.The
targetURLis a CEL expression wrapped in${…}, evaluated against the source resource, exposed asresource. Hereurl(resource.access.url).pathparses the source resource’s URL with the inbuilturl()function and takes its path (/artifacts/docs.tar), so the expression resolves tohttps://mytarget.example.com/uploads/artifacts/docs.tar. You can also useresource.name,resource.version, otherurl()parts such asurl(resource.access.url).host,resource.extraIdentity.<key>, and CEL conditionals. The uploader is not limited to wget sources: every field of the source access is exposed underresource.access.<field>(e.g.resource.access.imageReferencefor an OCI source), so you can route any access type to an HTTP target — see the Transfer Configuration reference.Uploaders are evaluated in declaration order and the first one that selects a resource handles it, so the HTTP uploader takes the
Wget/v1resource and thelocalblob.uploadercatch-all copies everything else as a local blob (the same as having a local blob uploader entry in your OCM configuration). Without the catch-all, local blobs are still copied and all other resources stay by reference.Forward a checksum header (optional)
If the target should receive the resource’s digest on the
PUT, template a header fromresource.digest. Header values use the same${…}CEL expressions astargetURL; a value without${…}is sent as a literal.resource.digestis only present when the source resource carries a digest (for example when it is pinned from the source via the checksum-http configuration), so keep the rule scoped so every matched resource has one.- type: http.uploader.transfer.config.ocm.software/v1alpha1 match: resource.access.isType("Wget/v1") targetURL: '${"https://mytarget.example.com/uploads" + url(resource.access.url).path}' method: PUT header: # RFC 9530 Content-Digest: sha-256=:<base64>: Content-Digest: ['${contentDigestAlgorithm(resource.digest.hashAlgorithm) + "=:" + base64.encode(hex.decode(resource.digest.value)) + ":"}'] # A simple non-standard checksum header carrying the raw hex value. X-Checksum-Sha256: ['${resource.digest.value}'] # A static literal header is sent verbatim. X-Uploaded-By: ['ocm-transfer']resource.digest.valueis the hex digest andresource.digest.hashAlgorithmis the OCM algorithm name (e.g.SHA-256).contentDigestAlgorithm()maps that name to the RFC 9530 field key (sha-256), andbase64.encode(hex.decode(...))converts the hex digest to the base64 value RFC 9530 expects — see the Templating Headers reference.Configure target credentials
If your upload endpoint requires authentication, add credentials for the target host. The uploader resolves credentials for the target URL using the
Wgetconsumer identity, independently of the source resource:- type: credentials.config.ocm.software consumers: - identity: type: Wget hostname: mytarget.example.com credentials: - type: Credentials/v1 properties: username: uploader password: <token>Add this entry to the
configurationslist in.ocmconfig. SeeWgetCredentials/v1and Credential Consumer Identities for details.Run the transfer
Transfer the component version. The CLI picks up the uploader configuration from
.ocmconfigin the current directory:ocm transfer cv \ ghcr.io/source-org/ocm//ocm.software/demo:1.0.0 \ ghcr.io/target-org/ocmDuring transfer you will see the streaming step in the progress output, labelled with the resource and target host:
Expected progress output
✓ Transferring component versions... ✓ demo@1.0.0 [Stream docs to mytarget.example.com] ✓ demo@1.0.0 [Upload to OCI] ✓ Cleanup temp filesThe
[Stream docs to ...]line is the HTTP streaming uploader performing thePUT.Verify the rewritten access
Inspect the transferred component version. The
docsresource now has aWget/v1access pointing at your upload target:ocm get cv ghcr.io/target-org/ocm//ocm.software/demo:1.0.0 -o yamlExpected resource access
resources: - name: docs type: blob access: type: Wget/v1 url: https://mytarget.example.com/uploads/artifacts/docs.tar digest: hashAlgorithm: SHA-256 normalisationAlgorithm: genericBlobDigest/v1 value: <sha256-of-the-streamed-bytes>The digest was computed while the bytes were streamed to the target. If the source resource already carried a digest, transfer instead verified it as the bytes passed through and failed the transfer on any mismatch.
Note the published access carries only
url(andmediaType) — not thePUTmethod or any upload headers. Those apply to the upload request only, so a laterocm downloadreads the object with a plain GET instead of re-issuing the write.
What you’ve learned
- An uploader configuration matches a resource by access type and streams it to a dedicated target during transfer.
- The
http.uploader.transfer.config.ocm.software/v1alpha1uploader streams the resource straight to an HTTPPUTtarget without buffering it, and computes or verifies its digest inline. - The transferred resource is rewritten to a
Wget/v1read access at the upload target (url+mediaType); the upload request fields (method,header,body,noRedirect) drive thePUTonly and are not recorded on the published access, so a later download reads the object with a plain GET. targetURLandheadervalues are${…}CEL expressions over the source resource, so you can template the upload URL and forward headers such as a checksum fromresource.digest.
Troubleshooting
Problem: targetURL fails to evaluate
Cause: The CEL expression references a field that is not available on the resource, or is not valid CEL.
Fix: Reference only documented fields — resource.name, resource.version,
resource.access.<field> (e.g. resource.access.url, and url(...) parts such
as url(resource.access.url).path), resource.extraIdentity.<key>,
resource.digest.value — and quote string literals ("https://..."). A missing
field fails the transfer deliberately rather than producing a partial URL.
Problem: The upload returns 401/403
Cause: No credentials matched the target host.
Fix: Add a credentials.config.ocm.software consumer with type: Wget and the
target hostname, as in Step 2.
Problem: The transfer succeeds but nothing is uploaded
Symptom: The resource is stored as LocalBlob/v1 in the target and the log
warns uploader selected no resource.
Cause: The match expression tests the access type the resource would get in
the target (such as LocalBlob) instead of its access in the source component
version.
Fix: Check the source with ocm get cv <source> -o yaml and write
match against the resource’s access.type there, for example
resource.access.isType("OCIImage") for an ociArtifact access.
Upload to JFrog Artifactory or Sonatype Nexus
To upload into Artifactory or Nexus repositories, use the vendor uploaders instead of the HTTP uploader. They detect the repository type (Helm, Maven, npm, generic or raw) through the server API and publish an access consumers can use with their own tools. See Using Vendor-Specific APIs.
Next steps
Related documentation
- Reference: Transfer Configuration — full field reference for transfer and uploader configuration
- Concept: Transfer and Transport — how OCM moves component versions between repositories
- Tutorial: Working with HTTP Resources — the
Wget/v1type produced by the uploader