<!--
SPDX-FileCopyrightText: Red Hat

SPDX-License-Identifier: Apache-2.0
-->

# Template Overview

- [Template Overview](#template-overview)
  - [ClusterTemplate CR](#clustertemplate-cr)
    - [Template name](#template-name)
    - [Template schema](#template-schema)
    - [Hardware resources](#hardware-resources)
      - [HardwareProfile](#hardwareprofile)
      - [hwMgmtDefaults](#hwmgmtdefaults)
        - [Label selectors](#label-selectors)
        - [Hardware data selectors](#hardware-data-selectors)
        - [Complete example](#complete-example)
    - [ClusterInstance defaults ConfigMap](#clusterinstance-defaults-configmap)
    - [PolicyTemplate defaults ConfigMap](#policytemplate-defaults-configmap)
  - [Validation](#validation)
  - [Viewing ClusterTemplates](#viewing-clustertemplates)
    - [Using the CLI](#using-the-cli)
    - [Using the REST API](#using-the-rest-api)

**Prerequisites:** Familiarity with the [GitOps repository layout](./gitops-layout-and-setup.md)
and [server onboarding](./server-onboarding.md) process. Templates reference
hardware resources and configuration defaults that are managed in the GitOps repository.

## ClusterTemplate CR

A namespace-scoped CR that defines the resources and input parameters required for cluster provisioning. It is delivered via GitOps and reconciled by the O‑Cloud Manager.

The [CRD](../../config/crd/bases/clcm.openshift.io_clustertemplates.yaml)'s `spec` fields include:

- name: Specifies the base name of the ClusterTemplate.
- version: Defines the version of the ClusterTemplate.
- release: OCP release version that will be installed with this template. The Git layout uses a matching directory name `version_4.Y.Z/`; keep the release here and the directory version aligned.
- description: A description of the ClusterTemplate.
- templateDefaults: Contains default values for templates.
  - hwMgmtDefaults: Inline hardware management defaults including nodeGroupData and hardwareProvisioningTimeout. Either nodeGroupData must be provided here or the templateParameterSchema must expose hwMgmtParameters so the ProvisioningRequest can supply it.
  - clusterInstanceDefaults: References the ConfigMap containing default values for ClusterInstance.
  - policyTemplateDefaults: References the ConfigMap containing default values for ACM policy templates.
- templateParameterSchema: Specifies the OpenAPI v3 schema that defines which parameters the SMO must provide and which values are accepted in the ProvisioningRequest.
- templateId: A unique identifier (UUID) for the ClusterTemplate, automatically generated by the O-Cloud Manager if not provided.
- characteristics(FFS): A list of key-value pairs describing characteristics associated with the template.

The ClusterTemplate references defaults stored in Git (for example, installation and policy defaults) and, through its `templateParameterSchema`, specifies the inputs that the SMO can supply in the ProvisioningRequest.
At provisioning time, the O‑Cloud Manager validates the SMO‑supplied values against the schema and merges them with the template’s defaults to render the concrete artifacts used to install and configure the cluster.
When both the ProvisioningRequest and the ClusterTemplate defaults provide the same field, the ProvisioningRequest value takes precedence, with one exception: `extraLabels` and `extraAnnotations` provided in the ProvisioningRequest's `clusterInstanceParameters` cannot override those already
set in the `clusterInstanceDefaults` ConfigMap. Only new `extraLabels` and `extraAnnotations` that do not exist in the defaults can be appended through the ProvisioningRequest.

Complete examples of ClusterTemplate are available under the GitOps sample content:
[SNO](../samples/git-setup/clustertemplates/version_4.Y.Z/sno-ran-du/sno-ran-du-v4-Y-Z-1.yaml),
[3node](../samples/git-setup/clustertemplates/version_4.Y.Z/3node-ran-du/3node-ran-du-v4-Y-Z-1.yaml) and
[Standard](../samples/git-setup/clustertemplates/version_4.Y.Z/std-ran-du/std-ran-du-v4-Y-Z-1.yaml).

### Template name

The `metadata.name` of the ClusterTemplate CR must follow the format `<name.version>` and **must be unique across all namespaces**. The combination of name and version uniquely identifies a template.

### Template schema

The schema defined in the `templateParameterSchema` must include the following **mandatory** parameters for the cluster provisioning process:

- nodeClusterName: Specifies the name of the node cluster.
- oCloudSiteId: Specifies the oCloud site identifier.
- policyTemplateParameters: A subschema that defines the parameters for cluster configuration.
- clusterInstanceParameters: A subschema for [ClusterInstance](https://github.com/stolostron/siteconfig/blob/main/config/crd/bases/siteconfig.open-cluster-management.io_clusterinstances.yaml), defining the parameters that are allowed in the ProvisioningRequest for cluster installation.

The schema may also include the **optional** `hwMgmtParameters` property, which
allows the ProvisioningRequest to override settings from the ClusterTemplate's
`hwMgmtDefaults`:

- **`hwMgmtParameters.hardwareProvisioningTimeout`**: Override the hardware
  provisioning timeout (e.g., `"120m"`).
- **`hwMgmtParameters.nodeGroupData[].hwProfile`**: Override the HardwareProfile
  for a specific node group (matched by `name`).
- **`hwMgmtParameters.nodeGroupData[].resourceSelector`**: Add or override
  resource selector criteria for a specific node group. These are merged with the
  selectors from `hwMgmtDefaults` — existing criteria can be overridden by
  specifying the same key, but cannot be removed.

Using `hwMgmtParameters` overrides allows a single ClusterTemplate to be shared
across multiple deployments with different hardware configurations. Instead of
creating separate templates for each server type, color, or firmware profile, you
can define a base template with common settings and push the per-deployment
specifics (such as server selection criteria and firmware profiles) into each
ProvisioningRequest.

For example, given a ClusterTemplate with `hwMgmtDefaults` that selects XR8620t servers:

```yaml
# ClusterTemplate hwMgmtDefaults (shared across deployments)
spec:
  templateDefaults:
    hwMgmtDefaults:
      hardwareProvisioningTimeout: "90m"
      nodeGroupData:
        - name: master
          role: master
          resourceSelector:
            "resourceselector.clcm.openshift.io/server-type": "XR8620t"
```

Each ProvisioningRequest can then override settings as needed:

```yaml
# ProvisioningRequest: target a specific server, with a specific firmware profile
spec:
  templateParameters:
    hwMgmtParameters:
      hardwareProvisioningTimeout: "120m"
      nodeGroupData:
        - name: master
          hwProfile: rh-profile-xr8620t-idrac-7.20.30.50-bios-2.6.3
          resourceSelector:
            "resourceselector.clcm.openshift.io/server-id": "xr8620txdg16"
```

In this example, the ProvisioningRequest overrides the timeout, specifies the firmware
profile, and narrows the BMH selection to a specific server — all without requiring a
dedicated ClusterTemplate.

> [!NOTE]
> The `hwProfile` is optional. When specified, it must reference an existing
> `HardwareProfile` CR. The `hwProfile` can be set in `hwMgmtDefaults.nodeGroupData[]`
> or overridden via `hwMgmtParameters` in the ProvisioningRequest.

See the [hwMgmtParameters sample](../samples/hwMgmtParameters.yaml) for the schema
definition of `hwMgmtParameters`.

### Hardware resources

The template references hardware artifacts that the O‑Cloud hardware manager uses to allocate and prepare bare‑metal nodes.

> [!NOTE]
> Hardware provisioning is always required. The ClusterTemplate must provide
> `nodeGroupData` via `hwMgmtDefaults` or expose `hwMgmtParameters` in the
> `templateParameterSchema` so the ProvisioningRequest can supply it.

#### HardwareProfile

HardwareProfile describes the desired hardware state for a class of servers, such as BIOS settings
and target firmware levels. It is applied by the hardware manager during Day‑0 (initial provision)
and can also be used for Day‑2 updates. HardwareProfile is optional — nodes can be provisioned without one.
Example profiles are provided under [hardwareprofiles](../samples/git-setup/clustertemplates/hardwareprofiles/).

#### hwMgmtDefaults

The `hwMgmtDefaults` section of the ClusterTemplate defines the node groups that the cluster needs and how matching hosts are selected from the inventory. For each group, specify attributes such as the role and group name, and optionally provide a `resourceSelector` with matching criteria.

A single-node cluster requires one node group, while multi-node clusters define
multiple groups — for example, a `master` group and a `worker` group, each with its own
hardware profile and resource selectors. The node group **name** should match the
corresponding MachineConfigPool (MCP) name on the spoke cluster (e.g. `master`,
`worker`).

The hardware manager uses a two-stage filtering process to find suitable BareMetalHosts:

1. **Label-based filtering** — selects BMHs by Kubernetes label.
2. **Hardware data filtering** — further filters BMHs based on their HardwareData CR (hardware inventory details such as CPU, memory, storage, and NICs).

All criteria in the `resourceSelector` must match for a BMH to be selected (AND logic). Only BMHs in the `Available` provisioning state are evaluated.

##### Label selectors

You can specify either fully-qualified labels such as `resourceselector.clcm.openshift.io/server-type: Dell-R740` or short keys like `server-type: Dell-R740`; the controller will automatically prepend the `resourceselector.clcm.openshift.io/` prefix when it is omitted.

##### Hardware data selectors

Keys prefixed with `hardwaredata/` match against fields in the BMH's HardwareData CR. The general format is:

```text
hardwaredata/<field>[;qualifier1][;qualifier2]
```

The value is compared against the corresponding field from the HardwareData. For complex resource criteria (NICs and storage), the value must be set to `"present"`.

**String fields** — `productname`, `manufacturer`, `cpu_arch`, `cpu_model`

These fields support the following qualifiers:

| Qualifier | Description |
|---|---|
| *(none)* | Exact match (case-sensitive) |
| `icase` | Case-insensitive match |
| `substring` | Substring match |
| `substring;icase` | Case-insensitive substring match |

Examples:

```yaml
resourceSelector:
  "hardwaredata/productname": "ProLiant DL110 Gen11"              # exact
  "hardwaredata/productname;substring;icase": "dl110"             # substring, case-insensitive
  "hardwaredata/manufacturer;icase": "hpe"                        # case-insensitive
  "hardwaredata/cpu_arch": "x86_64"                               # exact
  "hardwaredata/cpu_model;substring": "Xeon(R)"                   # substring
```

**Integer fields** — `num_threads`, `ramMebibytes`

These fields support comparison qualifiers:

| Qualifier | Alternate | Description |
|---|---|---|
| *(none)* or `eq` | `==` | Equal |
| `neq` | `!=` | Not equal |
| `gt` | `>` | Greater than |
| `gte` | `>=` | Greater than or equal |
| `lt` | `<` | Less than |
| `lte` | `<=` | Less than or equal |

Examples:

```yaml
resourceSelector:
  "hardwaredata/num_threads;>=": "64"                             # at least 64 threads
  "hardwaredata/ramMebibytes;gt": "100000"                        # more than ~100 GiB RAM
```

**NIC criteria** — `nics`

NIC selectors use a semicolon-delimited set of sub-criteria within the key. The value must be `"present"`. Available sub-criteria:

| Sub-criterion | Operators | Description |
|---|---|---|
| `model` | `==`, `!=`, `~` (contains), `!~` | Match NIC model string |
| `speedGbps` | `==`, `!=`, `>`, `>=`, `<`, `<=` | Match NIC speed in Gbps |
| `count` | `==`, `!=`, `>`, `>=`, `<`, `<=` | Number of NICs matching the other sub-criteria |

When `model` and/or `speedGbps` are specified, they filter the set of NICs first, and then `count` is evaluated against the matching subset.

Examples:

```yaml
resourceSelector:
  "hardwaredata/nics;count>4": "present"                          # more than 4 NICs total
  "hardwaredata/nics;model~0x8086;count=8": "present"             # 8 Intel NICs
  "hardwaredata/nics;model~0x8086;count=8;speedGbps>=25": "present"  # 8 Intel NICs at 25+ Gbps
```

**Storage criteria** — `storage`

Storage selectors use a similar semicolon-delimited format. The value must be `"present"`. Available sub-criteria:

| Sub-criterion | Operators | Description |
|---|---|---|
| `type` | `==`, `!=`, `~`, `!~` | Storage type: `NVME`, `SSD`, or `HDD` |
| `model` | `==`, `!=`, `~`, `!~` | Storage device model |
| `vendor` | `==`, `!=`, `~`, `!~` | Storage device vendor |
| `name` | `==`, `!=`, `~`, `!~` | Device name or alternate name |
| `sizeBytes` | `==`, `!=`, `>`, `>=`, `<`, `<=` | Storage device size in bytes |
| `count` | `==`, `!=`, `>`, `>=`, `<`, `<=` | Number of devices matching the other sub-criteria |

When filtering sub-criteria (type, model, vendor, name, sizeBytes) are specified, they filter the set of devices first, and then `count` is evaluated against the matching subset. The `icase` qualifier can be appended for case-insensitive type matching.

Examples:

```yaml
resourceSelector:
  "hardwaredata/storage;type==NVME;count>=2": "present"           # at least 2 NVMe disks
  "hardwaredata/storage;type==NVME;count>=2;sizeBytes>100000": "present"  # 2+ NVMe > 100KB
  "hardwaredata/storage;type==SSD;count=1": "present"             # exactly 1 SSD
  "hardwaredata/storage;model~VR000480": "present"                # model contains "VR000480"
  "hardwaredata/storage;type==nvme;count>=2;icase": "present"     # case-insensitive type
```

##### Complete example

The following example demonstrates the `master` node group with label and hardware data selectors to match Dell XR8620t hosts with specific CPU, NIC, and storage requirements:

```yaml
# ClusterTemplate spec.templateDefaults.hwMgmtDefaults
hwMgmtDefaults:
  hardwareProvisioningTimeout: "90m"
  nodeGroupData:
    - name: master
      role: master
      hwProfile: rh-profile-xr8620t-bios-settings
      resourceSelector:
        # Label-based criteria
        "server-type": "XR8620t"
        "resourceselector.clcm.openshift.io/server-colour": "blue"
        # Hardware data criteria
        "hardwaredata/cpu_arch": "x86_64"
        "hardwaredata/productname;substring;icase": "xr8620t"
        "hardwaredata/num_threads;>=": "64"
        "hardwaredata/storage;type==NVME;count>=2": "present"
        "hardwaredata/nics;model~0x8086;count=8;speedGbps>=25": "present"
```

The HardwareProfile to apply to matched hosts can be specified either in `hwMgmtDefaults.nodeGroupData[].hwProfile`
or via `templateParameters.hwMgmtParameters.nodeGroupData[].hwProfile` in the
ProvisioningRequest. When both are set, the ProvisioningRequest value takes precedence.

Example ClusterTemplates with hwMgmtDefaults:
[SNO](../samples/git-setup/clustertemplates/version_4.Y.Z/sno-ran-du/sno-ran-du-v4-Y-Z-1.yaml),
[3node](../samples/git-setup/clustertemplates/version_4.Y.Z/3node-ran-du/3node-ran-du-v4-Y-Z-1.yaml) and
[Standard](../samples/git-setup/clustertemplates/version_4.Y.Z/std-ran-du/std-ran-du-v4-Y-Z-1.yaml).

For details about server onboarding, refer to [Server Onboarding](./server-onboarding.md).

### ClusterInstance defaults ConfigMap

Cluster installation defaults that are common across clusters are provided in a ConfigMap and referenced by the ClusterTemplate. These defaults can include node configuration, networking, and other baseline settings. All fields must comply with the SiteConfig `ClusterInstance` schema.

Each node’s boot interface in the `interfaces` list must include a `boot-interface` label.  This allows the O-Cloud manager to resolve the boot NIC’s MAC address from the NIC-to-MAC mappings retrieved from the hardware manager.

For example,

```yaml
nodes:
  - role: master
    nodeNetwork:
      interfaces:
        - name: ens3f0
          label: boot-interface
        - name: ens3f1
```

Complete examples of ClusterInstance defaults:
[SNO](../samples/git-setup/clustertemplates/version_4.Y.Z/sno-ran-du/clusterinstance-defaults-v1.yaml),
[3node](../samples/git-setup/clustertemplates/version_4.Y.Z/3node-ran-du/clusterinstance-defaults-v1.yaml) and
[Standard](../samples/git-setup/clustertemplates/version_4.Y.Z/std-ran-du/clusterinstance-defaults-v1.yaml).

### PolicyTemplate defaults ConfigMap

Configuration is driven by ACM policies that are rendered from policy templates (See [PolicyTemplate example](../samples/git-setup/policytemplates/version_4.Y.Z/sno-ran-du/sno-ran-du-pg-v4-Y-Z-v1.yaml)). The ClusterTemplate references a ConfigMap with default input values for these templates.
All parameters used in the policy templates must be declared in the schema `templateParameterSchema.policyTemplateParameters` and provided either through the PolicyTemplate default ConfigMap or via the ProvisioningRequest (`spec.templateParameters.policyTemplateParameters`).

Complete examples of PolicyTemplate defaults:
[SNO](../samples/git-setup/clustertemplates/version_4.Y.Z/sno-ran-du/policytemplates-defaults-v1.yaml),
[3node](../samples/git-setup/clustertemplates/version_4.Y.Z/3node-ran-du/policytemplates-defaults-v1.yaml) and
[Standard](../samples/git-setup/clustertemplates/version_4.Y.Z/std-ran-du/policytemplates-defaults-v1.yaml).

## Validation

When a ClusterTemplate is created, O-Cloud Manager validates the following to ensure:

- The name is unique across all namespaces.
- All referenced ConfigMaps and resources exist.
- The required parameters are present in the schema.

Once validation succeeds, the status condition `ClusterTemplateValidated` is set to True. After successful validation, the `ClusterTemplate` becomes **immutable** — any changes to its spec are disallowed and will be rejected. The referenced ConfigMaps are also **immutable** after validation.

```console
status:
  conditions:
  - lastTransitionTime: "2024-10-19T00:00:06Z"
    message: The cluster template validation succeeded
    reason: Completed
    status: "True"
    type: ClusterTemplateValidated
```

## Viewing ClusterTemplates

You can view ClusterTemplates either from the oc CLI or via the O‑Cloud Manager REST APIs.

### Using the CLI

```console
oc get clustertemplates.clcm.openshift.io -A
oc get clustertemplates.clcm.openshift.io -n <namespace> <name> -o yaml
```

### Using the REST API

First, acquire an authorization token as described in [Testing API endpoints on a cluster](./environment-setup.md#testing-api-endpoints-on-a-cluster).
Then, get API endpoint URLs.

```console
export API_URI=$(oc get route -n oran-o2ims -o jsonpath='{.items[?(@.spec.path=="/o2ims-infrastructureArtifacts")].spec.host}')
export BASE_URL="https://${API_URI}/o2ims-infrastructureArtifacts/v1"
```

#### List all templates

```console
curl -ks -H "Authorization: Bearer ${MY_TOKEN}" \
  "${BASE_URL}/managedInfrastructureTemplates" | jq
```

#### Filter template by name and version

```console
curl -ks -H "Authorization: Bearer ${MY_TOKEN}" \
  "${BASE_URL}/managedInfrastructureTemplates?filter=(eq,version,v4-18-5-v1)%3b(eq,name,sno-ran-du)" | jq
```

#### Get a specific template by `managedInfrastructureTemplateId`

```console
curl -ks -H "Authorization: Bearer ${MY_TOKEN}" \
  "${BASE_URL}/managedInfrastructureTemplates/sno-ran-du.v4-18-5-v1" | jq
```

#### Get a specific template defaults by `managedInfrastructureTemplateId`

```console
curl -ks -H "Authorization: Bearer ${MY_TOKEN}" \
  "${BASE_URL}/managedInfrastructureTemplates/sno-ran-du.v4-18-5-v1/defaults" | jq
```
