Commodore Component Developer Guidelines

This page defines the various conventions and best practices for writing a Commodore component for OpenShift 4.

See the Writing a Commodore Component guide for a more general introduction and technical details of Commodore components.

Conventions Used in These Guidelines

The requirement level keywords must, must not, required, shall, shall not, should, shouldn’t, recommended, may, and optional used in this document are to be interpreted as described in RFC 2119.

Component Naming

The name of a component which configures something OpenShift 4 specific must be prefixed with openshift4-.

When using the commodore component new command to bootstrap a new component, the component- prefix will be added automatically and must not be specified:

commodore component new openshift4-registry --name "OpenShift 4 Registry" --owner appuio

Operator Lifecycle Manager (OLM)

We recommend to avoid using the OLM to install operators on a cluster. This is mainly due to the added complexity the OLM introduces and us not using any of it’s features. This might change in the future.

Exceptions to this rule include cases where the Red Hat documentation explicitly requires the OLM.

Any operators we do install with the OLM should be restricted to single namespaces and must not be installed using the AllNamespaces InstallMode.

We don’t provide any support for operators installed by customers via the OLM.

CustomResource (CR) Specs

Components which configure one or multiple CRs should do so by exposing its .spec field as a parameter. This enables the configuration of all possible fields via the inventory and provides maximum flexibility. Default values may either be set in the defaults.yml file of the component or in Jsonnet code. It should always be possible to override default values in the inventory.

Lists in the inventory get extended by reclass when being merged together. This means that the items of each list are added together in a new list. It can lead to undesired behavior if not accounted for.

When creating multiple CRs, a dict should be used where its keys map to the names used for the CRs. Iterating over the keys of this dict in Jsonnet enables the enrichment of possible default values. This allows to omit default values within the inventory.

Due to the way reclass handels null overrides, the component must handle null values in dicts and lists gracefully.

main.jsonnet
local ingressControllers =
  if params.ingressControllers != null
  then std.objectFields(params.ingressControllers)
  else [];

{
  [name]:
    [kube._Object('operator.openshift.io/v1', 'IngressController', name) {
      metadata+: {
        namespace: params.namespace,
      },
      spec: {
        defaultCertificate: {
          name: acmeCertName,
        },
      } + params.ingressControllers[name],
    }]
  for name in ingressControllers
} + {
  [if std.length(ingressControllers) == 0 then '.gitkeep']: {},
}

Inventory Parameters

A component may define various parameters in the inventory (defaults.yml). These must be namespaced in a dict with the component’s name (hyphens replaced with underscores). A group of components may agree upon a common prefix to share parameters between each other.

defaults.yml
parameters:
  openshift4_ingress:
    namespace: openshift-ingress
    cloud:
      provider: ${cloud:provider}
    ingressControllers:
      default:
        replicas: 2

If the component requires additional information like the cloud provider, cluster version or infrastructure ID, these may also be accessed via parameters of the inventory (usually in the form of facts). In the future this information will be automatically collected on the cluster and exposed in the inventory as facts. For information which isn’t yet collected automatically, a manual entry in the cluster’s class is required.

c-misty-sun-2392.yml
parameters:
  openshift:
    infraID: c-mist-x7bvn
    clusterID: ce7e78b4-dead-beef-9367-7c2223d2506b
    appsDomain: apps.example.com

To avoid a high coupling of components, parameters of other components must not be referenced directly. There must always be a "glue-layer" to provide such parameters. In other words: a component must only reference parameters in its own inventory namespace (dict).

The following example shows how the hostname field of the openshift4-registry component is set in the inventory using the openshift:appsDomain fact, instead of using the fact in the component directly:

openshift4.yml
parameters:
  openshift4_registry:
    config:
      routes:
        - name: registry-route
          hostname: registry.${openshift:appsDomain}