Writing an element¶
An element is a Helm chart under stack/ that renders ACM policies through the
policy-library
chart. Every option of every policy type is in that README.
Naming¶
- Lowercase kebab-case. Directory name and chart
namemust match: the Application and Helm release take the directory name (Applications), the values key takes the chart name, andelement:dependencies and the validator assume both are equal. - Short: the common abbreviation (
acm,odf) or a plain noun (logging). Noopenshift-prefix, no-operatorsuffix (Naming limits). - Must not start with another element's name plus
-.gitopson clusterdev-candgitops-devon clustercwould both be releasegitops-dev-c(POLICY091). lifecycleis reserved.
Warning
Element and policy names are part of every object name. Renaming one recreates its Policies on every cluster.
Creating an element¶
./tools/create-element.sh cert-manager "Certificate management"
# Created stack/cert-manager, values key stack.certManager. ...
It prompts for missing arguments, refuses a name that is not kebab-case or already exists, and
copies tools/sample-element/
to stack/<name>/, replacing my-element with the name and myElement with its camelCase form.
The result passes the validator as is. Then:
- Set the operator's
namespace,operatorGroupandsubscriptioninvalues.yaml. - Replace
converters/<name>-configmap.yamlwith one file pertemplateNamesentry. A missing converter fails (POLICY020), an unused one warns (POLICY021). - Describe each setting with
# @desc:and generate the README. - Run the validator.
- Turn it on for one nonprod cluster in
values/clusters/<cluster>.yaml.
Layout¶
The files of tools/sample-element/. It has one of each piece: an operator under an install
policy, a config policy that depends on it, a toggle, and one converter.
apiVersion: v2
name: my-element # (1)!
description: Example element. tools/create-element.sh copies it to stack/<name>.
type: application
version: 0.1.0
dependencies:
- name: policy-library
alias: stack # (2)!
version: "3.4.0"
repository: "https://policystack.github.io/PolicyStack-chart"
- Same as the directory name.
- The alias puts values under
stack:and lets the library schema check them.
stack:
# @description: Example element. tools/create-element.sh copies it to stack/<name>.
myElement: # (1)!
# @desc: Master control to enable/disable all policies in this element
enabled: false # (2)!
# @description: Optional pieces, switchable per cluster
toggles:
# @desc: Apply the ConfigMap configured below
config: true # (3)!
# @description: Settings the converters read. Maps merge across the values cascade.
config:
# @desc: ConfigMap data, e.g. {logLevel: info}
data: {} # (4)!
# @description: Default policy metadata applied unless overridden per-policy
default:
categories:
- CM Configuration Management
controls:
- CM-2 Baseline Configuration
standards:
- NIST SP 800-53
policies:
- name: install
enabled: true
description: "Install the operator"
remediationAction: inform # (5)!
severity: medium
disabled: false
- name: config
enabled: true
description: "Configure the operator"
remediationAction: inform
severity: medium
disabled: false
# @desc: ACM holds this policy Pending until install is compliant
dependencies:
- name: install
operatorPolicies:
my-element:
enabled: true
description: "Example operator"
displayName: "Example Operator"
policyRef: install # (6)!
severity: medium
remediationAction: inform
complianceType: musthave
namespace: my-element
operatorGroup:
name: my-element
subscription:
channel: stable
name: my-element-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
upgradeApproval: Automatic
# @desc: Allowed CSV names; empty allows whatever the channel offers
versions: [] # (7)!
configPolicies:
- name: my-element-config # (8)!
enabled: true
description: "Example ConfigMap"
policyRef: config
complianceType: musthave
remediationAction: inform
severity: medium
# @desc: Wait for the operator's CSV, and stay compliant while waiting
waitForOperator: my-element
ignorePending: true
templateNames:
- name: my-element-configmap # (9)!
- camelCase of the chart name (Element values).
- Off by default. A layer in
values/turns it on. - Overrides
enabledof the entry namedconfig. - Site data. Converters read it, layers set it.
- New policies start in
inform. - The policy this operator renders into.
- Pin versions in layers, not here (Operator upgrades).
- Prefixed with the element name: template names must be unique across all Policies on a cluster.
- Renders
converters/my-element-configmap.yaml.
A converter is the manifest for one configPolicies[].templateNames entry, rendered with Helm's
tpl. It reads .Values, and .Parameters (that config policy's templateParameters) when the
config policy sets enableTemplateParameters: true. README.md is
generated.
Conventions¶
From stack/metallb:
| Convention | metallb |
|---|---|
| Off by default | enabled: false; a values layer turns it on |
| Toggles for optional pieces | toggles.quota and toggles.addressing ship false; a cluster flips one in one line instead of restating the policies list |
A config map for site data |
Converters read config.ipAddressPools, l2Advertisements, bgpPeers, quota. Maps merge (Merge rules), so a cluster sets only its keys |
| Install, then configure | config depends on install, addressing on config. ACM holds each Pending until the previous one is compliant (dependencies) |
| Wait for the CSV | metallb-instance sets waitForOperator: metallb and ignorePending: true, so it waits for the operator without going noncompliant (waiting for an operator) |
rawTemplate when the object count varies |
Pool, advertisement and peer converters emit one object per map key. An empty map renders an empty list (raw object templates) |
| Short names | Element, policy, config and operator policy names end up in length-limited object names (Naming limits) |
A cluster turning it on, values/clusters/nonprod-west-1.yaml:
stack:
metallb:
enabled: true
toggles:
addressing: true
config:
ipAddressPools:
services: # (1)!
addresses:
- 10.20.30.100-10.20.30.120
autoAssign: true
l2Advertisements:
services:
ipAddressPools:
- services
- Map key is the object name, value is the
IPAddressPoolspec.
The rawTemplate converter that reads it, converters/metallb-ipaddresspools.yaml:
{{- range $name, $spec := .Values.stack.metallb.config.ipAddressPools }}
- complianceType: mustonlyhave
objectDefinition:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: {{ $name }}
namespace: {{ $.Parameters.namespace }} # (1)!
spec:{{ toYaml $spec | nindent 6 }}
{{- end }}
- From the
poolsconfig policy'stemplateParameters.
Good practices¶
- One product per element. Clusters turn an element on as a unit. An operator and its configuration belong together, unrelated settings do not.
- Prefix config and operator policy names with the element. Two elements that both put a
ns-monitoringconfig policy under aninstallpolicy render the same template nameinstall-ns-monitoringand collide on the cluster (POLICY003). metallb usesmetallb-instance. - Start in
inform. Roll out to a nonprod revision, read compliance, then switch toenforce. - Pin operator versions in layers. Ship
versions: [], set it per environment or cluster (Operator upgrades). - Try changes on one cluster first. Push a branch, then pin a nonprod cluster's fleet file to it with a pull request to the default branch (Promoting and rolling back).
Policy-level remediationAction overrides inform gates
ACM applies it to every template in the Policy, so enforce there enforces readiness checks
and mustnothave signals. On a policy with inform gates, leave it unset and set the action on
each config and operator policy, as upgrade, acs and cert-manager do.
global.remediationAction: inform still overrides all of them.
pruneObjectBehavior decides whether removal deletes objects
Set it only on templates without dependencies (POLICY033), in its own commit, so it reaches the cluster before the removal (What happens on the managed cluster).
Keep the branch until the pin moves back
A fleet file pinned to a deleted branch freezes the Applications of every cluster (Revision).
Secrets¶
Values live in Git. Read runtime secrets on the hub with an ACM hub template, escaped so Helm
passes it through. From
stack/acs/converters/acs-sync-collector.yaml:
data:
ca.pem: '{{ "{{" }}hub fromSecret "{{ .Parameters.hubNamespace }}" "collector-tls" "ca.pem" hub{{ "}}" }}'
Hub lookups are limited to the policy namespace. To read another, point the policy at a ServiceAccount in the policy namespace that can read it, as acs does:
- Trimmed; see
stack/acs/values.yaml.
The validator evaluates escaped templates as POLICY081 (ACM templates).
Element reference¶
stack/<element>/README.md is generated from values.yaml and its @description / @desc
comments. Do not edit it by hand.
pip install -r tools/requirements.txt
python tools/doc-generator.py # regenerate all READMEs
python tools/doc-generator.py --check # report stale ones, write nothing
Commit the README with the values.yaml change. CI runs the check on pull requests, and the
update-docs label regenerates READMEs on the PR branch
(Documentation Check). Flags and comment syntax:
tools/README.md.