openstack-sync-operator
Deploys the OpenStack sync shell-operator in the OpenStack namespace.
This component owns the pieces that must version with hook code:
- the shell-operator Deployment
- the operator ServiceAccount
- the Role or ClusterRole used by enabled hooks
- the CRDs read by those hooks
It does not own plugin custom resources. Plugin CRs are plain YAML data applied
by the separate openstack-sync-plugins
Application.
Deployment Scope
- Cluster scope: site
- Values key:
site.openstack_sync_operator - Helm chart:
components/openstack-sync-operator/ - ArgoCD Application template:
charts/argocd-understack/templates/application-openstack-sync-operator.yaml - Default namespace:
site.openstack.namespace, normallyopenstack
How ArgoCD Builds It
- ArgoCD renders Helm chart
components/openstack-sync-operator. - The deploy repo contributes
values.yamlfor this component.
Related Plugin Application
The matching plugin data is deployed by
charts/argocd-understack/templates/application-openstack-sync-plugins.yaml.
That Application is Kustomize/raw YAML, not Helm. It reads:
This split is intentional:
- CRDs live with the operator because they are the API contract consumed by hook
code. - RBAC lives with the operator because it grants permissions to the operator
ServiceAccount. - CRs live in the plugin Application because they are data, not runtime code.
Applying plugin CRs alone does not sync OpenStack. The matching hook must also
be enabled in the operator values, and the operator image must contain the hook
executable.
Important behavior:
site.openstack_sync_plugins.enabledcontrols whether ArgoCD creates the
plugin Application.- The plugin Application applies every CR listed in
components/openstack-sync-plugins/kustomization.yamland
<deploy-repo>/<site>/openstack-sync-plugins/kustomization.yaml. plugins.<name>does not control CR creation. It only controls the operator
runtime for that hook: enablement env vars, hook RBAC, and theverify-hooks
initContainer.
Because of that split, plugin CRs can exist while their hook is disabled. In
that state ArgoCD can be Synced, but the operator will not reconcile those CRs
into OpenStack.
Enablement
Enable the operator Application in the site deploy values:
Pin the operator image in the deployment repo:
The chart leaves image.tag unset by default, so Helm falls back to
Chart.appVersion. Site deployments should pin the image tag or digest they
intend to run in <deploy-repo>/<site>/openstack-sync-operator/values.yaml.
Hook Enablement
Built-in hooks are declared in
components/openstack-sync-operator/values.yaml.
For each built-in CRD hook, the chart values use this shape:
plugins:
<name>: false
pluginData:
<name>:
hook:
path: /hooks/<hook>.py
crd: crds/<group>_<plural>.yaml
envPrefix: <ENV_PREFIX>
Enable the hook from the deployment repo after the site is pinned to an
operator image built from this code:
The image build in containers/openstack-sync-operator/Dockerfile copies the
enabled hook executables into /hooks/.
When plugins.<name>: false, that hook may still exist in the image but
publishes only a no-op startup binding. That keeps shell-operator startup valid
while preventing any watch, schedule, OpenStack sync, or hook-specific RBAC for
that resource.
When a hook is enabled, the chart:
- sets
<ENV_PREFIX>_ENABLED=true - derives CRD environment variables from the CRD file
- renders RBAC to
get,list, andwatchthat CRD's resource - renders status RBAC when the CRD has a
statussubresource - adds the
verify-hooksinitContainer to fail fast if the hook executable is
missing
verify-hooks is automatic. Plugin authors do not write this initContainer.
They declare the hook path in pluginData.<name>.hook.path, and the chart
generates one startup check for each enabled hook. The plugin author must still
copy the hook executable into the operator image at that path.
Rendered shape:
initContainers:
- name: verify-hooks
image: ghcr.io/rackerlabs/understack/openstack-sync-operator:...
command:
- /bin/sh
- -ec
- |
missing=0
if [ ! -x "/hooks/<hook>.py" ]; then
echo "enabled hook <name> missing or not executable: /hooks/<hook>.py" >&2
missing=1
fi
exit "${missing}"
Hook-specific OpenStack behavior belongs with the plugin's CR examples or schema
docs. This page documents only the operator deployment contract.
When no hook is enabled, the operator can still start. In that state the Role
has no custom-resource permissions and no OpenStack sync work is expected.
Health Probes
The operator Deployment uses TCP liveness and readiness probes against
shell-operator's base HTTP port, 9115.
This is chart behavior. Plugin authors do not need to implement a health
endpoint.
RBAC
RBAC is rendered by
components/openstack-sync-operator/templates/rbac.yaml.tpl.
The chart starts with rbac.rules from values, then adds hook-specific rules
for each enabled hook.
Keep plugin RBAC in the operator chart. The permission is tied to the operator
ServiceAccount and to the hook code that uses it. Keeping RBAC with hook
enablement prevents this skew:
- CRs exist but the operator cannot read them.
- RBAC exists for a hook that is disabled or missing from the image.
The plugin Application should continue to apply only CR manifests.
CRDs and Validation
The CRDs live under components/openstack-sync-operator/crds/.
Each plugin CRD defines:
- Scope: namespaced
- Status subresource: enabled
- Required
spec.cloudCredentialsRef.secretName - Required
spec.cloudCredentialsRef.cloudName
Status and readiness
Every plugin CR reports the same status:
conditions-- oneReadycondition, reasonReconciledor
ReconcileError.syncStatus--SyncedorFailed. This is the field theSyncStatus
printer column shows.message-- detail from the last reconcile.lastSyncTime-- when the status last changed. A reconcile that finds
nothing to change writes nothing, so this is not a heartbeat.observedGeneration-- themetadata.generationthis status was computed
from. A value behindmetadata.generationmeans the latest spec has not been
applied yet.
So a CR is waitable:
lastTransitionTime marks the last time the condition's status changed rather
than the last reconcile, so a message-only change leaves it alone.
A CR that converged but needs manual attention is still Ready, with the detail
appended to the message after needs manual action:. That is deliberate -- the
resource really is converged, and reporting a bare success while state diverges
from the spec is how a broken resource stays invisible until it is used.
A failed status write never fails the reconcile, because status is reporting and
not the work itself. It is logged at error level even so: anything waiting on
Ready stays stuck until someone reads that log.
The chart reads this CRD through
components/openstack-sync-operator/templates/_crd.tpl so
RBAC and hook environment variables are derived from the same schema Kubernetes
applies.
Plugin CR files can also reference editor schemas under:
schema/openstack-sync/
That schema focuses on plugin data under spec. Kubernetes validates the
full custom resource through the operator-owned CRD when ArgoCD applies it.
Plugin CR Data
Shared plugin CRs live under:
components/openstack-sync-plugins/
Site-specific additions live in the deploy repo:
Use the plugin Application for these CRs. Do not pass CR lists through the
operator Helm values; changing CR data should not roll the operator Pod.
Add a New Sync Hook
For each new sync hook, keep the same ownership split:
- Add the CRD to
components/openstack-sync-operator/crds/. - Add hook metadata under
pluginData.<name>.hookin the operator chart values. - Add a
plugins.<name>boolean defaulted tofalse. - Add the hook executable under
python/openstack-sync/openstack_sync/hooks/
and copy it into/hooks/fromcontainers/openstack-sync-operator/Dockerfile. - Add shared CR YAML under
components/openstack-sync-plugins/, or
site-specific CR YAML under<deploy-repo>/<site>/openstack-sync-plugins/. - Enable the hook in
<deploy-repo>/<site>/openstack-sync-operator/values.yaml
after the image contains the hook.
That keeps API schema, hook runtime, RBAC, and CR data in the places that own
them.