|
| 1 | +--- |
| 2 | +name: pattern-author |
| 3 | +description: > |
| 4 | + Author and modify Validated Patterns — GitOps-based deployment configurations |
| 5 | + for OpenShift. Use when creating new Patterns, adding applications/subscriptions/namespaces |
| 6 | + to existing Patterns, configuring secrets, setting up hub/spoke clusters, or working with |
| 7 | + clustergroup values files. |
| 8 | +when_to_use: > |
| 9 | + When the user asks to create a new Validated Pattern, add a helm chart or application to a |
| 10 | + Pattern, configure secrets or vault, set up spoke clusters, modify clustergroup values, |
| 11 | + or work with values-global.yaml, values-*.yaml, or values-secret.yaml.template files. |
| 12 | + Also when the user mentions "Validated Patterns", "clustergroup", "pattern init", or |
| 13 | + "patternizer". |
| 14 | +allowed-tools: Read Bash(pattern *) Bash(helm *) Bash(find *) Bash(ls *) |
| 15 | +--- |
| 16 | + |
| 17 | +# Validated Patterns Author |
| 18 | + |
| 19 | +You are helping author Validated Patterns — GitOps-based deployment configurations for OpenShift built on the [clustergroup helm chart](https://github.com/validatedpatterns/clustergroup-chart/). A Pattern is a Git repository containing values files that define what namespaces, operators, and applications to deploy on one or more OpenShift clusters via ArgoCD. |
| 20 | + |
| 21 | +For complete framework documentation, read [reference.md](reference.md) in this skill directory. Read it before your first Pattern authoring task in a session, or when you need details on a specific framework feature. |
| 22 | + |
| 23 | +## Authoring Workflow |
| 24 | + |
| 25 | +When creating a new Pattern: |
| 26 | + |
| 27 | +1. **Initialize** — Determine the Pattern name and whether secrets are needed. Run `pattern init` or `pattern init --with-secrets` in the Pattern directory. |
| 28 | +2. **Identify requirements** — What operators, applications, and custom helm charts does this Pattern need? |
| 29 | +3. **Define namespaces** — Add all required namespaces to the clustergroup values file (`values-<clusterGroupName>.yaml`). Include OperatorGroup configuration for operator namespaces. |
| 30 | +4. **Define subscriptions** — Add operator subscriptions with at minimum the operator `name`. Set `namespace`, `channel`, and `source` as needed. |
| 31 | +5. **Define applications** — Wire in helm charts as applications: |
| 32 | + - Local charts: set `path` to the chart location in the repository |
| 33 | + - VP-published charts: set `chart` and `chartVersion` |
| 34 | + - External Git charts: set `repoURL`, `path`, and `chartVersion` (Git ref) |
| 35 | +6. **Configure secrets** (if applicable) — Define secrets in `values-secret.yaml.template` and create corresponding ExternalSecret CRDs in chart templates. |
| 36 | +7. **Set up hub/spoke** (if multi-cluster) — Add ACM subscription and `managedClusterGroups` to the hub. Create spoke values files. |
| 37 | +8. **Add imperative jobs** (if needed) — Configure Ansible playbooks in the imperative framework for tasks that don't fit the declarative model. |
| 38 | + |
| 39 | +When modifying an existing Pattern, read the current `values-global.yaml` and clustergroup values files first to understand the existing structure before making changes. |
| 40 | + |
| 41 | +## Rules |
| 42 | + |
| 43 | +These rules must always be followed: |
| 44 | + |
| 45 | +- **Map form for namespaces** — Always define namespaces as a map, never a list. Maps merge across values files; lists override entirely. |
| 46 | +- **No secrets in Git** — Never put real secrets or credentials in the Pattern repository. Secrets belong in `~/values-secret-<pattern-name>.yaml` on the user's machine. |
| 47 | +- **`singleArgoCD: true`** — Always set this for new Patterns. |
| 48 | +- **`multiSourceConfig.enabled: true`** — Always set this for new Patterns. |
| 49 | +- **Vault only on hub** — The Vault application and namespace belong only on the hub/main cluster. Spoke clusters need ESO only (no Vault). The VP `openshift-external-secrets` chart auto-configures spokes to use the hub's Vault. |
| 50 | +- **Chart values stubs** — A chart's `values.yaml` must include default stubs for any `.Values.global.*` or `.Values.clusterGroup.*` values referenced in its templates, so `helm template` works standalone during development. |
| 51 | +- **ESO backtick escaping** — In ExternalSecret templates, escape ESO template expressions with backticks to prevent Helm from interpreting them: |
| 52 | + |
| 53 | + ```text |
| 54 | + "{{ `{{ .field_name }}` }}" |
| 55 | + ``` |
| 56 | + |
| 57 | +- **Idempotent imperative jobs** — All imperative jobs run on a schedule (every 10 minutes by default) and must be idempotent. |
| 58 | +- **Re-run `pattern init`** — After adding new local helm charts, re-run `pattern init` to wire them into the clustergroup values file. It is idempotent. |
| 59 | + |
| 60 | +## Common Tasks |
| 61 | + |
| 62 | +### Adding an Operator |
| 63 | + |
| 64 | +Add three things to the clustergroup values file: a namespace, a subscription, and (if the operator needs its own chart for configuration) an application. |
| 65 | + |
| 66 | +```yaml |
| 67 | +clusterGroup: |
| 68 | + namespaces: |
| 69 | + my-operator: |
| 70 | + operatorGroup: true |
| 71 | + targetNamespaces: [] |
| 72 | + |
| 73 | + subscriptions: |
| 74 | + my-operator: |
| 75 | + name: my-operator |
| 76 | + namespace: my-operator |
| 77 | + channel: stable |
| 78 | + |
| 79 | + applications: |
| 80 | + my-operator-config: |
| 81 | + name: my-operator-config |
| 82 | + namespace: my-operator |
| 83 | + path: charts/my-operator-config |
| 84 | +``` |
| 85 | +
|
| 86 | +Not every operator needs a local chart. If the operator requires no additional configuration beyond installation, the namespace and subscription are sufficient. |
| 87 | +
|
| 88 | +### Adding a Local Helm Chart |
| 89 | +
|
| 90 | +Place the chart anywhere in the repository (convention: `charts/`). Run `pattern init` to auto-discover it, or manually add it to the clustergroup values: |
| 91 | + |
| 92 | +```yaml |
| 93 | +clusterGroup: |
| 94 | + namespaces: |
| 95 | + my-app: |
| 96 | +
|
| 97 | + applications: |
| 98 | + my-app: |
| 99 | + name: my-app |
| 100 | + namespace: my-app |
| 101 | + path: charts/my-app |
| 102 | +``` |
| 103 | + |
| 104 | +### Adding a VP-Published Chart |
| 105 | + |
| 106 | +```yaml |
| 107 | +clusterGroup: |
| 108 | + applications: |
| 109 | + vault: |
| 110 | + name: vault |
| 111 | + namespace: vault |
| 112 | + chart: hashicorp-vault |
| 113 | + chartVersion: 0.1.* |
| 114 | +``` |
| 115 | + |
| 116 | +### Adding a Chart from an External Git Repository |
| 117 | + |
| 118 | +```yaml |
| 119 | +clusterGroup: |
| 120 | + applications: |
| 121 | + external-app: |
| 122 | + name: external-app |
| 123 | + namespace: external-app |
| 124 | + repoURL: https://github.com/org/repo.git |
| 125 | + chartVersion: main |
| 126 | + path: charts/the-chart |
| 127 | +``` |
| 128 | + |
| 129 | +### Adding a Secret |
| 130 | + |
| 131 | +1. Define the secret in `values-secret.yaml.template`: |
| 132 | + |
| 133 | + ```yaml |
| 134 | + version: "2.0" |
| 135 | +
|
| 136 | + secrets: |
| 137 | + - name: my-secret |
| 138 | + vaultPrefixes: |
| 139 | + - global |
| 140 | + fields: |
| 141 | + - name: api-key |
| 142 | + onMissingValue: prompt |
| 143 | + - name: password |
| 144 | + onMissingValue: generate |
| 145 | + vaultPolicy: validatedPatternDefaultPolicy |
| 146 | + ``` |
| 147 | + |
| 148 | +2. Add `secretStore` defaults to the chart's `values.yaml`: |
| 149 | + |
| 150 | + ```yaml |
| 151 | + secretStore: |
| 152 | + name: vault-backend |
| 153 | + kind: ClusterSecretStore |
| 154 | +
|
| 155 | + mysecret: |
| 156 | + key: secret/data/global/my-secret |
| 157 | + refreshInterval: 2m0s |
| 158 | + ``` |
| 159 | + |
| 160 | +3. Create an ExternalSecret template in the chart: |
| 161 | + |
| 162 | + ```yaml |
| 163 | + apiVersion: external-secrets.io/v1 |
| 164 | + kind: ExternalSecret |
| 165 | + metadata: |
| 166 | + name: my-secret |
| 167 | + spec: |
| 168 | + refreshInterval: {{ .Values.mysecret.refreshInterval }} |
| 169 | + secretStoreRef: |
| 170 | + name: {{ .Values.secretStore.name }} |
| 171 | + kind: {{ .Values.secretStore.kind }} |
| 172 | + target: |
| 173 | + name: my-secret |
| 174 | + template: |
| 175 | + type: Opaque |
| 176 | + data: |
| 177 | + api-key: "{{ `{{ .api_key }}` }}" |
| 178 | + password: "{{ `{{ .password }}` }}" |
| 179 | + data: |
| 180 | + - secretKey: api_key |
| 181 | + remoteRef: |
| 182 | + key: {{ .Values.mysecret.key }} |
| 183 | + property: api-key |
| 184 | + - secretKey: password |
| 185 | + remoteRef: |
| 186 | + key: {{ .Values.mysecret.key }} |
| 187 | + property: password |
| 188 | + ``` |
| 189 | +
|
| 190 | +The Vault path is `secret/data/<vaultPrefix>/<secret-name>`. The `secretKey` values become the template variables in `target.template.data`. |
| 191 | + |
| 192 | +### Adding a Spoke Cluster |
| 193 | + |
| 194 | +1. Add ACM and `managedClusterGroups` to the hub clustergroup values: |
| 195 | + |
| 196 | + ```yaml |
| 197 | + clusterGroup: |
| 198 | + name: hub |
| 199 | +
|
| 200 | + namespaces: |
| 201 | + open-cluster-management: |
| 202 | +
|
| 203 | + subscriptions: |
| 204 | + acm: |
| 205 | + name: advanced-cluster-management |
| 206 | + namespace: open-cluster-management |
| 207 | + channel: release-2.16 |
| 208 | +
|
| 209 | + applications: |
| 210 | + acm: |
| 211 | + name: acm |
| 212 | + namespace: open-cluster-management |
| 213 | + chart: acm |
| 214 | + chartVersion: 0.2.* |
| 215 | +
|
| 216 | + managedClusterGroups: |
| 217 | + region-one: |
| 218 | + name: group-one |
| 219 | + acmlabels: |
| 220 | + - name: clusterGroup |
| 221 | + value: group-one |
| 222 | + ``` |
| 223 | + |
| 224 | +2. Create `values-group-one.yaml` with the spoke's namespaces, subscriptions, and applications. |
| 225 | + |
| 226 | +3. If using secrets on the spoke, include ESO components (without Vault) in the spoke values. |
| 227 | + |
| 228 | +### Adding Conditional Overrides |
| 229 | + |
| 230 | +Define a custom global variable and use `sharedValueFiles`: |
| 231 | + |
| 232 | +```yaml |
| 233 | +# values-global.yaml |
| 234 | +global: |
| 235 | + device: gpu |
| 236 | +
|
| 237 | +# values-<clustergroup>.yaml |
| 238 | +clusterGroup: |
| 239 | + sharedValueFiles: |
| 240 | + - /overrides/values-{{ $.Values.global.device }}.yaml |
| 241 | +``` |
| 242 | + |
| 243 | +Create the override file (e.g., `/overrides/values-gpu.yaml`) with the conditional namespaces, subscriptions, and applications. |
0 commit comments