|
| 1 | +# Patching Installer Manifests |
| 2 | + |
| 3 | +- [Overview](#overview) |
| 4 | +- [Groundwork](#groundwork) |
| 5 | + - [Existing Manifest Patching](#existing-manifest-patching) |
| 6 | + - [`ClusterDeploymentCustomization`](#clusterdeploymentcustomization) |
| 7 | +- [API](#api) |
| 8 | + - [`ClusterDeploymentCustomization.Spec.InstallerManifestPatches`](#clusterdeploymentcustomizationspecinstallermanifestpatches) |
| 9 | + - [`ClusterDeployment.Spec.Provisioning.CustomizationRef`](#clusterdeploymentspecprovisioningcustomizationref) |
| 10 | + - [`ClusterDeployment.Spec.ClusterPoolRef.CustomizationRef`](#clusterdeploymentspecclusterpoolrefcustomizationref) |
| 11 | + - [`ClusterPool.Spec.CustomizationRef`](#clusterpoolspeccustomizationref) |
| 12 | + - [`ClusterDeployment.Status`](#clusterdeploymentstatus) |
| 13 | + - [`ClusterPool.Status`](#clusterpoolstatus) |
| 14 | +- [What About Ignition Configs?](#what-about-ignition-configs) |
| 15 | +- [Failure Modes](#failure-modes) |
| 16 | + |
| 17 | +## Overview |
| 18 | +Installer accepts spoke configuration via the |
| 19 | +[install-config.yaml](https://github.com/openshift/hive/blob/c392ca38fb489267cd7bfbdb3c5a76cc36163686/vendor/github.com/openshift/installer/pkg/types/installconfig.go#L93) |
| 20 | +file. |
| 21 | +However, some options are only available by editing the OpenShift object manifests generated by |
| 22 | +`openshift-install create manifests` prior to feeding them to `openshift-install create cluster`. |
| 23 | +Moving forward, installer frequently prefers new features to be enabled via this latter mechanism. |
| 24 | + |
| 25 | +Up to this point, hive has incorporated some piecemeal patching of these manifests at the behest of |
| 26 | +specific values passed through via hive APIs. |
| 27 | +Example: Extra worker security groups for AWS. |
| 28 | +However, this model is awkward, brittle, and not extensible. |
| 29 | +What is needed is a mechanism for the hive *user* to specify arbitrary modifications to OpenShift |
| 30 | +manifests generated by the installer. |
| 31 | + |
| 32 | +## Groundwork |
| 33 | + |
| 34 | +### Existing Manifest Patching |
| 35 | +The code that invokes `openshift-install` lives in installmanager.go. |
| 36 | +[This block](https://github.com/openshift/hive/blob/9615c77cd786dd82079a9dacd60b64c882f56327/pkg/installmanager/installmanager.go#L882-L921) |
| 37 | +contains logic to: |
| 38 | +- Walk the `openshift/` directory (one of two created by `openshift-install create manifests`, the |
| 39 | + other being `manifests/`). |
| 40 | +- Load yaml files. |
| 41 | +- Parse the files as JSON. |
| 42 | +- Patch the JSON. |
| 43 | +- Convert the JSON back to YAML. |
| 44 | +- Write the files back to storage. |
| 45 | + |
| 46 | +### `ClusterDeploymentCustomization` |
| 47 | +An earlier feature, [ClusterPool Inventory](clusterpool-inventory.md), invented the concept of |
| 48 | +[`ClusterDeploymentCustomization`](https://github.com/openshift/hive/blob/8c79fedae7011e434e8a7ff0fef40994ef92deb2/vendor/github.com/openshift/hive/apis/hive/v1/clusterdeploymentcustomization_types.go), |
| 49 | +a hive API used by ClusterPools when specific discrete values (such as reserved IP addresses) must |
| 50 | +be injected into the install-config.yaml for each cluster in the pool. |
| 51 | +As such, ClusterDeploymentCustomization originally contained only one member, |
| 52 | +[`InstallConfigPatches`](https://github.com/openshift/hive/blob/4eaf5fd7858e8def3b2ba3229801090610246243/vendor/github.com/openshift/hive/apis/hive/v1/clusterdeploymentcustomization_types.go#L45), |
| 53 | +a list of patches to apply to the install-config.yaml generated by the ClusterPool controller based |
| 54 | +on the user-provided template. |
| 55 | +Each entry in `InstallConfigPatches` is a |
| 56 | +[`PatchEntity`](https://github.com/openshift/hive/blob/4eaf5fd7858e8def3b2ba3229801090610246243/vendor/github.com/openshift/hive/apis/hive/v1/clusterdeploymentcustomization_types.go#L49), |
| 57 | +a hive-owned CRD corresponding to an |
| 58 | +[RFC 6902 JSON patch](https://datatracker.ietf.org/doc/html/rfc6902). |
| 59 | + |
| 60 | +## API |
| 61 | + |
| 62 | +### `ClusterDeploymentCustomization.Spec.InstallerManifestPatches` |
| 63 | + |
| 64 | +**Example:** |
| 65 | +```yaml |
| 66 | +apiVersion: v1 |
| 67 | +kind: ClusterDeploymentCustomization |
| 68 | +metadata: |
| 69 | + name: foo-cluster-deployment-customization |
| 70 | + namespace: my-project |
| 71 | +spec: |
| 72 | + installerManifestPatches: |
| 73 | + # Add custom labels to all master machine manifests |
| 74 | + - manifestSelector: |
| 75 | + glob: openshift/99_openshift-cluster-api_master-machines-*.yaml |
| 76 | + patches: |
| 77 | + - op: add |
| 78 | + path: /metadata/labels/a-custom-label |
| 79 | + value: foo |
| 80 | + - op: add |
| 81 | + path: /metadata/labels/b-custom-label |
| 82 | + value: bar |
| 83 | + - ... |
| 84 | + ... |
| 85 | +``` |
| 86 | + |
| 87 | +- Build on the existing [groundwork](#groundwork), extending `ClusterDeploymentCustomization` to |
| 88 | + expose a new field, `InstallerManifestPatches`. |
| 89 | +- Because installer generates multiple manifests, and RFC 6902 syntax can only reference a single |
| 90 | + document at a time, we can't directly use `PatchEntity`; we must use an intervening type to |
| 91 | + identify which file(s) a patch is to be applied to. |
| 92 | + We'll call this `InstallerManifestPatch`. |
| 93 | +- Each `InstallerManifestPatch`: |
| 94 | + - Identifies one or more files via subfield `ManifestSelector`. |
| 95 | + - Lists `PatchEntity`s to apply to files thus identified. |
| 96 | + This is the same `PatchEntity` used for `InstallConfigPatches`. |
| 97 | +- `ManifestSelector` supports one subfield, `Glob`, which accepts a path matching string as |
| 98 | + supported by golang's [filepath.Glob](https://pkg.go.dev/path/filepath#Glob). |
| 99 | + - We will execute the glob relative to the working directory in which manifests were generated. |
| 100 | + - For security, we will attempt to detect and raise an error if any paths attempt to point outside of the working directory. |
| 101 | +- `ManifestSelector` is extensible. |
| 102 | + E.g. in the future we may wish to match manifests based on the GVK of the object therein. |
| 103 | + |
| 104 | +### `ClusterDeployment.Spec.Provisioning.CustomizationRef` |
| 105 | + |
| 106 | +**Example:** |
| 107 | +```yaml |
| 108 | +apiVersion: v1 |
| 109 | +kind: ClusterDeployment |
| 110 | +metadata: |
| 111 | + name: foo-cluster-deployment |
| 112 | + namespace: my-project |
| 113 | +spec: |
| 114 | + provisioning: |
| 115 | + installConfigSecretRef: ic-secret |
| 116 | + customizationRef: |
| 117 | + name: foo-cluster-deployment-customization |
| 118 | + ... |
| 119 | + ... |
| 120 | +``` |
| 121 | + |
| 122 | +- Extend `ClusterDeployment`, adding a `CustomizationRef` field at the same level as |
| 123 | + `InstallConfigSecretRef`, i.e. under `Spec.Provisioning`. |
| 124 | +- `CustomizationRef` is a `LocalObjectReference` to the name of a `ClusterDeploymentCustomization` |
| 125 | + in the same namespace as the `ClusterDeployment`. |
| 126 | + |
| 127 | +### `ClusterDeployment.Spec.ClusterPoolRef.CustomizationRef` |
| 128 | + |
| 129 | +This field already existed. |
| 130 | +It was used by the ClusterPool Inventory feature to help track which CDC from the pool's inventory |
| 131 | +was assigned to this CD. |
| 132 | + |
| 133 | +**We will add support for inventory CDCs to contain `InstallerManifestPatch`.** |
| 134 | + |
| 135 | +That is, in addition to supporting per-pool-CD patching of the install-config, we will now also |
| 136 | +support per-pool-CD patching of generated manifests. |
| 137 | + |
| 138 | +**NOTE:** This support will require copying the referenced CDC into the pool CD's (generated) |
| 139 | +namespace so it can be applied by the provisioner pod. |
| 140 | +(This was not previously necessary, as the install-config patches were used to customize the |
| 141 | +install-config Secret in memory before it was created in the target namespace.) |
| 142 | + |
| 143 | +### `ClusterPool.Spec.CustomizationRef` |
| 144 | + |
| 145 | +**Example:** |
| 146 | +```yaml |
| 147 | +apiVersion: v1 |
| 148 | +kind: ClusterPool |
| 149 | +metadata: |
| 150 | + name: foo-cluster-pool |
| 151 | + namespace: my-project |
| 152 | +spec: |
| 153 | + customizationRef: |
| 154 | + name: foo-cluster-deployment-customization |
| 155 | + ... |
| 156 | + ... |
| 157 | +``` |
| 158 | + |
| 159 | +- Extend `ClusterPool`, adding a `CustomizationRef` field at the top `Spec` level. |
| 160 | +- `CustomizationRef` is a `LocalObjectReference` to the name of a `ClusterDeploymentCustomization` |
| 161 | + in the same namespace as the `ClusterPool`. |
| 162 | +- In contrast to the inventory CDC, this can (should) be used when the same manifest patches are to |
| 163 | + be applied to *all* CDs in the pool. |
| 164 | +- This can be used with or without Inventory. |
| 165 | +- As with `ClusterDeployment.Spec.ClusterPoolRef.CustomizationRef`, the CDC referenced by this field will be copied into |
| 166 | + the pool CD's namespace so it can be applied by the provisioner. |
| 167 | +- If both references exist, we will apply `Spec.CustomizationRef` first so that the CD-specific |
| 168 | + patches in `ClusterDeployment.Spec.ClusterPoolRef.CustomizationRef` "win" in case of conflicts. |
| 169 | +- Note that, unlike inventory CDCs, these pool-wide CDCs will not be reserved/assigned to a single |
| 170 | + CD, or even a single ClusterPool (though they will be limited to ClusterPools in the same |
| 171 | + namespace). |
| 172 | + |
| 173 | +### `ClusterDeployment.Status` |
| 174 | + |
| 175 | +When a referenced CDC does not exist, we will update the `RequirementsMet` condition accordingly. |
| 176 | + |
| 177 | +For eventual consistency, if the CDC is subsequently created, we will "immediately" clear the |
| 178 | +condition (i.e. we need a `Watch()` on CDC that enqueues any ClusterDeployments that reference it). |
| 179 | + |
| 180 | +### `ClusterPool.Status` |
| 181 | + |
| 182 | +When the CDC referenced by `ClusterPool.Spec.CustomizationRef` does not exist, we'll set the |
| 183 | +`MissingDependencies` condition. |
| 184 | + |
| 185 | +For eventual consistency, if the CDC is subsequently created, we will "immediately" clear the |
| 186 | +condition (i.e. we need a `Watch()` on CDC that enqueues any ClusterPools that reference it). |
| 187 | + |
| 188 | +## What About Ignition Configs? |
| 189 | +Between `create manifests` and `create cluster` we `create ignition-configs`. |
| 190 | +- This feature will *not* support patching ignition configs. |
| 191 | + If we decide to add that support at some point in the future, it will be via a new CDC subfield, e.g. `IgnitionConfigPatches`. |
| 192 | +- Because `create ignition-configs` consumes and deletes generated manifests, we must apply manifest patches _between_ these two phases. |
| 193 | + |
| 194 | +## Failure Modes |
| 195 | +Other than the status conditions mentioned above, all new failure modes will occur within the |
| 196 | +provision pod, causing that pod to log an error message and fail. |
| 197 | + |
| 198 | +Error conditions: |
| 199 | +- A `ManifestSelector` matches zero manifests. |
| 200 | +- A `ManifestSelector.Glob` resolves to a path outside of the installer's working directory. |
| 201 | +- A patch has invalid syntax. |
| 202 | +- Applying a patch fails. |
| 203 | +- The usual things like files unexpectedly not being readable/writable/parseable (not expected). |
0 commit comments