Structuring Software Products with OCM

Introduction

In this tutorial software products are comprised of logical units called components. A component version consists of a set of technical artifacts (e.g., Docker images, Helm charts, binaries, configuration data, etc.). Such artifacts are called resources in this specification. Resources are usually built from something, e.g., code in a git repo. Those are named sources in this specification.

OCM introduces a Component Version for every component version that describes the resources, sources, and other component versions belonging to a particular component version and how to access them.

Usually, however, real-life applications are composed of multiple components. For example, an application might consist of a frontend, a backend, a database, and a web server. During the software development process new component versions are created and third-party components might be consumed from a public registry and updated from time to time.

Not all component version combinations of frontend, backend, database, etc. are compatible and form a valid product version. In order to define reasonable version combinations for the software product, we could use another feature of OCM’s Component Version, called a Component Reference (or reference in short), which allows the aggregation of component versions.

For each sub-component and each version in use, there is a Component Version. For the entire application, we introduce a new component that describes the overall software product referencing all components. This describes the entire application or product.

A particular version of this application is again described by a Component Version, which contains references to the Component Versions of its sub-components in their version in use. You are not restricted to this approach. It is, e.g., possible to create multi-level hierarchies or you could just maintain a list of component version combinations which build a valid product release.

In a nutshell, OCM provides a simple approach to specify what belongs to a product version. Starting with the Component Version for a product version and following the component references, you could collect all artifacts belonging to this product version.

Prerequisites

We assume that you have already read the guides in the Getting Started section, as this guide discusses a more complex scenario.

Constructing the Component

We are going to use podinfo in microservices mode. This describes a setup with multiple microservices forming a larger application.

podinfo has three services which we are going to model using individual component versions:

  • backend
  • frontend
  • cache (redis)

We will use the following example application to demonstrate a multi-component structure using podinfo: Podinfo Component.

This repository contains the following items:

Component File

The following component-constructor file describes four components: three components, each representing a podinfo microservice and one aggregated component that brings together the podinfo components using references. We refer to the aggregated component as the product component. A component-constructor file can contain one or multiple components and references to other components. The file is a YAML file and can be validated using the OCM schema.

# specify a schema to validate the configuration and get auto-completion in your editor
# yaml-language-server: $schema=https://ocm.software/schemas/configuration-schema.yaml
components:

# -- product component
- name: ocm.software/podinfo
  version: 1.0.2
  labels:
  - name: ocm.software/labels/podinfo/purpose
    value:
      - kind: test
        type: manual
  provider:
    name: open-component-model
  componentReferences:
  - name: backend
    componentName: ocm.software/podinfo/backend
    version: 1.0.0
  - name: frontend
    componentName: ocm.software/podinfo/frontend
    version: 1.0.0
  - name: redis
    componentName: ocm.software/redis
    version: 1.0.0
  sources:
  - access:
      commit: ac0afafcf4aa333546634cba631f0090a0a4cbe3
      ref: refs/heads/main
      repoUrl: https://github.com/open-component-model/podinfo
      type: github
    name: github_com_open_component_model_podinfo
    type: git
    version: 1.0.0

# -- backend component
- name: ocm.software/podinfo/backend
  version: 1.0.0
  provider:
    name: open-component-model
  labels:
  - name: ocm.software/labels/podinfo/service
    value: backend
  resources:
  - name: config
    type: configdata.ocm.software
    input:
      type: file
      mediaType: application/yaml
      path: backend/config.yaml
      compress: true
  - name: image
    relation: external
    type: ociImage
    version: 6.2.0
    access:
      type: ociArtifact
      imageReference: ghcr.io/stefanprodan/podinfo:6.2.0
  - name: manifests
    type: kustomize.ocm.fluxcd.io
    input:
      type: dir
      path: backend/manifests
      compress: true
  sources:
  - access:
      commit: 9d294e85d8d3fe7803d1eccbf009619078d30cb9
      ref: refs/heads/main
      repoUrl: https://github.com/open-component-model/podinfo
      type: github
    name: github_com_open_component_model_podinfo
    type: git
    version: 1.0.0

# -- frontend component
- name: ocm.software/podinfo/frontend
  version: 1.0.0
  provider:
    name: open-component-model
  labels:
  - name: ocm.software/labels/podinfo/service
    value: frontend
  resources:
  - name: config
    type: configdata.ocm.software
    input:
      type: file
      mediaType: application/yaml
      path: frontend/config.yaml
      compress: true
  - name: image
    relation: external
    type: ociImage
    version: 6.2.0
    access:
      type: ociArtifact
      imageReference: ghcr.io/stefanprodan/podinfo:6.2.0
  - name: manifests
    type: kustomize.ocm.fluxcd.io
    input:
      type: dir
      path: frontend/manifests
      compress: true
  sources:
  - access:
      commit: 9d294e85d8d3fe7803d1eccbf009619078d30cb9
      ref: refs/heads/main
      repoUrl: https://github.com/open-component-model/podinfo
      type: github
    name: github_com_open_component_model_podinfo
    type: git
    version: 1.0.0

# -- redis component
- name: ocm.software/redis
  version: 1.0.0
  provider:
    name: open-component-model
  labels:
  - name: ocm.software/labels/podinfo/service
    value: redis
  resources:
  - name: config
    type: configdata.ocm.software
    input:
      type: file
      mediaType: application/yaml
      path: redis/config.yaml
      compress: true
  - name: image
    relation: external
    type: ociImage
    version: 6.0.1
    access:
      type: ociArtifact
      imageReference: redis:6.0.1
  - name: manifests
    type: kustomize.ocm.fluxcd.io
    input:
      type: dir
      path: redis/manifests
      compress: true
  sources:
  - access:
      commit: 9d294e85d8d3fe7803d1eccbf009619078d30cb9
      ref: refs/heads/main
      repoUrl: https://github.com/open-component-model/podinfo
      type: github
    name: github_com_open_component_model_podinfo
    type: git
    version: 1.0.0

With the components modeled we can start to build a component archive using the ocm cli:

ocm add componentversions --create --file component-archive component-constructor.yaml
processing component-constructor.yaml...
  processing document 1...
    processing index 1
    processing index 2
    processing index 3
    processing index 4
found 4 components
adding component ocm.software/podinfo:1.0.2...
  adding reference ocm.software/podinfo/backend: "name"="backend","version"="1.0.0"...
  adding reference ocm.software/podinfo/frontend: "name"="frontend","version"="1.0.0"...
  adding reference ocm.software/redis: "name"="redis","version"="1.0.0"...
adding component ocm.software/podinfo/backend:1.0.0...
  adding resource configdata.ocm.software: "name"="config","version"="<componentversion>"...
  adding resource ociImage: "name"="image","version"="6.2.0"...
  adding resource kustomize.ocm.fluxcd.io: "name"="manifests","version"="<componentversion>"...
adding component ocm.software/podinfo/frontend:1.0.0...
  adding resource configdata.ocm.software: "name"="config","version"="<componentversion>"...
  adding resource ociImage: "name"="image","version"="6.2.0"...
  adding resource kustomize.ocm.fluxcd.io: "name"="manifests","version"="<componentversion>"...
adding component ocm.software/redis:1.0.0...
  adding resource configdata.ocm.software: "name"="config","version"="<componentversion>"...
  adding resource ociImage: "name"="image","version"="6.0.1"...
  adding resource kustomize.ocm.fluxcd.io: "name"="manifests","version"="<componentversion>"...

This will create a folder called component-archive. The structure of that should look something like this:

tree .
.
├── artifact-index.json
└── blobs
    ├── sha256.03ac3a7611e118d08fcf70e9b7be263c4a7082066f9763f71d8901d7fa2afc9d
    ├── sha256.118b6e8282ee1d335b1638a76a20022b6acc319177dbbce3089700da835afb6a
    ├── sha256.12073781e4fba95f19f046c51c90f0c4e1338d47afe4795bf6fcca163ae46eb8
    ├── sha256.1f239399104ec0cc7680956eb60960d212b3368609feb83dac2c95040d24b480
    ├── sha256.3c9c902ce013ca070a29634e4603c90063c96df632ef2c8e6b4447aaeb70b67e
    ├── sha256.3dc6209959eb782fa6f5f44892f66e9657276735bfb40407bd00ddca30d0a9d1
    ├── sha256.654debd65dbadbcee73e55b675980865ddf22acffcec166c59a5e48a213e4dd5
    ├── sha256.699ea8628e39256048cd1687c496fe64999a41f16f200ef5ce938ee9f19c37f0
    ├── sha256.70a47378c043721e3099801dec02c44b1dd9cdef0ebf79c55784eb4666bdbc29
    ├── sha256.773b28fb63f1195ff73e328744639ddc1c574d58c1e723d6e386fcd66b45bd9c
    ├── sha256.893be914eebd8230ef848ea82b3433c6201152f5d9925e7b5b8d68e0cec7133e
    ├── sha256.92991cf391167c928f3afe6891001f3dd325b64ce800cf34fad4c038141fc57f
    ├── sha256.98ca4d46130f5c09a704b3d8ee9af94de3c0ac73d7e990df53e64606c418fea8
    ├── sha256.a779270c2fea310835d3125de90e089e423c9730a98f1acdda328470d21fced0
    ├── sha256.a7dd532f80e8417ed33cf0c97328582847017895fc5146e499bdf4c94a9d17b5
    ├── sha256.cae4365f264251c616210707aa4765bd95f23fd22f98abc68bae9f58d6e4506d
    ├── sha256.ee79c92bbcce9e7a98f07c6577fd56dd45cf6f7c2d3115216ee249f42119030e
    └── sha256.f6a82a23220752c232e5f66ce46f0be28b27a5af19474072c77dac6d1feb0c16

2 directories, 19 files

These blobs contain the resources we described when modelling our podinfo application. If we cat a random blob we get something like this:

cat sha256.3c9c902ce013ca070a29634e4603c90063c96df632ef2c8e6b4447aaeb70b67e
{"componentDescriptorLayer":{"mediaType":"application/vnd.ocm.software.component-descriptor.v2+yaml+tar","digest":"sha256:699ea8628e39256048cd1687c496fe64999a41f16f200ef5ce938ee9f19c37f0","size":2560}}%

Next, we transfer this component to an OCI registry of your choice. Here <your-location> for me was ghcr.io/skarlso/demo-component.

ocm transfer component ./component-archive <your-location>
transferring version "ocm.software/podinfo:1.0.2"...
...adding component version...
transferring version "ocm.software/podinfo/backend:1.0.0"...
...resource 0...
...resource 2...
...adding component version...
transferring version "ocm.software/podinfo/frontend:1.0.0"...
...resource 0...
...resource 2...
...adding component version...
transferring version "ocm.software/redis:1.0.0"...
...resource 0...
...resource 2...
...adding component version...
4 versions transferred

With the transfer completed, we now have a product *Component Version* that describes a set of sub-components using *Component References*. It bundles all required artifacts for a successful deployment of the complete product.

Conclusion

We saw how to create a complex, multi-service architecture product component and store it in an OCI registry.