Replicate Component Versions with the Controller

Goal

Use the OCM Kubernetes controller to transfer a component version, together with the full graph of components it references (if recursion is enabled), from one OCM repository to another. The transfer re-runs automatically whenever the source Component resolves to a new version.

Prerequisites

  • Controller environment set up
    • For this example, you don’t need Kro or Flux. Just a cluster and ocm-controller running.
  • The OCM CLI installed, to build and push the source component
  • A source and a target OCI registry you can push to, with credentials that have write access (for example a ghcr.io token with write:packages)

If you already have a component version in a source repository, skip the first step and point the Component at it.

How it works

A Replication references two objects:

  • spec.componentRef, a Component that resolves and verifies the source version. The version that gets transferred is the one recorded in that Component’s status, so it has already been successfully reconciled.
  • spec.targetRepositoryRef, the Repository the content is written to.

When the source Component’s resolved version changes, the controller walks the component’s reference graph and transfers the component version into the target. It records the transferred version and digest in its status and treats an unchanged digest as a no-op, so it never re-transfers content that is already present.

Transfer behaviour (recursion depth, copy mode, upload type) and the registry credentials are supplied as OCM configuration through ocmConfig. In the steps below they live in two Secrets: the Component carries the credentials and propagates them down, while the Replication declares the transfer settings itself and references the Component to merge in those propagated credentials.

The configuration influences the way the transfer happens: recursive controls following references, copyMode controls which resources are transferred, and uploadType controls how they are uploaded. These options mirror the flags on the ocm transfer component-version CLI command, mapped out in the configuration step below.

Steps

  1. Build a source component graph

    To show recursion in action, build a small graph: a parent component that references a child, each carrying a blob. Replicating the parent pulls the child along with it.

    component-constructor.yaml
    echo "parent payload" > parent.txt
    echo "child payload" > child.txt
    
    cat <<EOF > component-constructor.yaml
    components:
      - name: ocm.software/examples/replication/child
        version: 1.0.0
        provider:
          name: ocm.software
        resources:
          - name: data
            type: blob
            version: 1.0.0
            input:
              type: file
              path: ./child.txt
      - name: ocm.software/examples/replication/parent
        version: 1.0.0
        provider:
          name: ocm.software
        resources:
          - name: data
            type: blob
            version: 1.0.0
            input:
              type: file
              path: ./parent.txt
        componentReferences:
          - name: child
            componentName: ocm.software/examples/replication/child
            version: 1.0.0
    EOF

    Push both components to the source registry:

    ocm add cv --repository ghcr.io/<source-namespace> --constructor component-constructor.yaml
  2. Create the source and target Repository

    Both repositories are plain Repository objects pointing at OCI registries. The source holds the component you already published; the target is where the transfer writes.

    repositories.yaml
    cat <<EOF > repositories.yaml
    apiVersion: delivery.ocm.software/v1alpha1
    kind: Repository
    metadata:
      name: replication-source
    spec:
      repositorySpec:
        baseUrl: ghcr.io/<source-namespace>
        type: OCIRepository
      interval: 10m
    ---
    apiVersion: delivery.ocm.software/v1alpha1
    kind: Repository
    metadata:
      name: replication-target
    spec:
      repositorySpec:
        baseUrl: ghcr.io/<target-namespace>
        type: OCIRepository
      interval: 10m
    EOF
    kubectl apply -f repositories.yaml
  3. Create the transfer configuration and credentials

    Store the configuration in two Secrets under the .ocmconfig key. The ocm-credentials Secret holds the registry credentials and is propagated by the Component; the ocm-transfer-config Secret holds the transfer settings and is referenced directly by the Replication. The controller reads both as OCM configuration.

    config.yaml
    cat <<EOF > config.yaml
    apiVersion: v1
    kind: Secret
    metadata:
      name: ocm-credentials
    type: Opaque
    stringData:
      .ocmconfig: |
        type: generic.config.ocm.software/v1
        configurations:
          - type: credentials.config.ocm.software
            consumers:
              - identity:
                  type: OCIRegistry
                  hostname: ghcr.io
                credentials:
                  - type: Credentials
                    properties:
                      username: <username>
                      password: <token>
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: ocm-transfer-config
    type: Opaque
    stringData:
      .ocmconfig: |
        type: generic.config.ocm.software/v1
        configurations:
          - type: transfer.config.ocm.software/v1alpha1
            recursive: -1
            copyMode: localBlob
    EOF
    kubectl apply -f config.yaml

    The transfer.config.ocm.software entry controls the transfer itself. Each key maps directly to a flag on the ocm transfer component-version CLI command:

    Config keyCLI flagEffect
    recursive-r, --recursive-1 follows component references without limit; 0 disables recursion. The CLI flag is a boolean: set means -1, unset means 0.
    copyMode--copy-resourceslocalBlob (default) includes only local-blob resources; allResources also pulls in remote OCI artifacts and Helm charts. Set the CLI flag for allResources, leave it unset for localBlob.
    uploadType-u, --upload-asHow resources are stored in the target: default lets the transfer decide, localBlob embeds them in the descriptor, ociArtifact uploads them as separate OCI artifacts (OCI target only). Only relevant for resources that copyMode includes.

    The example sets recursive: -1 and copyMode: localBlob because the sample components carry blob resources, and omits uploadType to let the transfer choose the right storage for the OCI target. To use streaming for resources that are themselves OCI artifacts, set copyMode: allResources with uploadType: ociArtifact.

    Propagating a single config

    Splitting the configuration is optional. If you set policy: Propagate, you can keep everything (transfer settings and credentials) in one config on the Component and propagate the whole thing down to the Replication, instead of having the Replication declare its own transfer config.

  4. Create the source Component

    The Component resolves the version to replicate. Reference the ocm-credentials Secret with policy: Propagate so the credentials flow on to the Replication.

    component.yaml
    cat <<EOF > component.yaml
    apiVersion: delivery.ocm.software/v1alpha1
    kind: Component
    metadata:
      name: replication-component
    spec:
      component: ocm.software/examples/replication/parent
      repositoryRef:
        name: replication-source
      semver: 1.0.0
      interval: 10m
      ocmConfig:
        - kind: Secret
          name: ocm-credentials
          policy: Propagate
    EOF
    kubectl apply -f component.yaml

    Wait for it to become ready:

    kubectl get component replication-component -o wide
  5. Create the Replication

    The Replication ties the source Component to the target Repository. It declares the transfer settings through its own ocmConfig. Because declaring an ocmConfig opts out of automatic inheritance from the parent, it also references the Component explicitly to pull in the registry credentials the Component propagates.

    replication.yaml
    cat <<EOF > replication.yaml
    apiVersion: delivery.ocm.software/v1alpha1
    kind: Replication
    metadata:
      name: replication-example
    spec:
      componentRef:
        name: replication-component
      targetRepositoryRef:
        name: replication-target
      ocmConfig:
        - kind: Secret
          name: ocm-transfer-config
        - kind: Component
          apiVersion: delivery.ocm.software/v1alpha1
          name: replication-component
    EOF
    kubectl apply -f replication.yaml
    Effective configuration

    The effective configuration the Replication reconciles with is the combination of what it declares in its own spec.ocmConfig, what the Component propagates, and the target Repository’s configuration.

  6. Watch it run

    kubectl get replication replication-example -owide

    The transfer proceeds in two stages:

    • While the controller walks the component’s reference graph through the resolution service, the Ready condition stays False with reason ResolutionInProgress (one pass per graph level, event-driven).
    • While the transfer executes, the TransferInProgress condition is True.
    • On completion the Ready condition flips to True, status.lastTransferredVersion and status.lastTransferredDigest are set, and TransferInProgress returns to False.
    kubectl get replication replication-example -o yaml

    Re-applying with the same source digest is a no-op, the controller short-circuits on lastTransferredDigest.

  7. Verify the target

    Confirm the parent landed in the target repository:

    ocm get cv ghcr.io/<target-namespace>//ocm.software/examples/replication/parent:1.0.0

    Because the transfer is recursive, the referenced child is present too:

    ocm get cv ghcr.io/<target-namespace>//ocm.software/examples/replication/child:1.0.0

Troubleshooting

When a transfer fails, the Ready condition is set to False with the error message, and per-transformation failures are recorded in status.lastFailedTransferEvents:

kubectl get replication replication-example -o jsonpath='{.status.lastFailedTransferEvents}'

Symptom: Ready=False stuck on ResolutionInProgress

Cause: The controller is still discovering the component’s reference graph, or a referenced component version cannot be resolved from the source repository.

Fix: Confirm the source Component is ready and that every referenced component version actually exists in the source repository. Resolution is event-driven and advances one graph level per pass, so a large graph takes several reconciliations.

Symptom: authentication or “unauthorized” errors during transfer

Cause: The credentials in the transfer Secret do not cover one of the hosts involved, or lack push permission on the target.

Fix: Ensure a credentials.config.ocm.software consumer exists for every registry hostname (source and target), and that the target token has write access (for example write:packages on ghcr.io).

Symptom: nothing happens after applying the Replication

Cause: The source Component or the target Repository is not yet Ready. The controller waits for both before transferring.

Fix: Check both objects:

kubectl get component replication-component -o wide
kubectl get repository replication-target -o wide

Next Steps