I have written an Ansible operator using the operator-sdk that assists with our cluster on boarding: from a single ProjectDefinition resource, the operator provisions a namespace, groups, rolebindings, resourcequotas, and limitranges.
The resourcequotas and limitranges are defined in ProjectQuota resource, and are referenced by name in the ProjectDefinition.
When the content of a ProjectQuota resource is modified, I want to ensure that those changes propagate to any managed namespaces that use the named quota. Right now, I'm doing it this way:
- calculating a content hash for the
ProjectQuota - Look up namespaces that reference the quota using a label selector
- Annotate the discovered namespaces with the content hash
The namespace update triggers a reconciliation of the associated ProjectDefinition because of the ownerReference on the namespace, and this in turn propagates the changes in the ProjectQuota.
In other words, the projectquota role does this:
---
# tasks file for ProjectQuota
- name: Calculate content_hash
set_fact:
content_hash: "{{ quotadef|to_json|hash('sha256') }}"
vars:
quotadef:
q: "{{ resource_quota|default({}) }}"
l: "{{ limit_range|default({}) }}"
- name: Lookup up affected namespaces
kubernetes.core.k8s_info:
api_version: v1
kind: namespace
label_selectors:
- "example.com/named-quota = {{ ansible_operator_meta.name }}"
register: namespaces
- name: Update namespaces
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ namespace.metadata.name }}"
annotations:
example.com/named-quota-hash: "{{ content_hash }}"
loop: "{{ namespaces.resources }}"
loop_control:
loop_var: namespace
label: "{{ namespace.metadata.name }}"
And the projectdefinition role does (among other things) this:
- name: "{{ ansible_operator_meta.name }} : handle named quota"
when: >-
quota.quota_name|default(false)
block:
- name: "{{ ansible_operator_meta.name }} : look up named quota"
kubernetes.core.k8s_info:
api_version: "{{ apiVersion }}"
kind: ProjectQuota
name: "{{ quota.quota_name }}"
register: named_quota
- name: "{{ ansible_operator_meta.name }} : label namespace"
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ ansible_operator_meta.name }}"
namespace: "{{ ansible_operator_meta.name }}"
labels:
example.com/named-quota: "{{ quota.quota_name }}"
- name: "{{ ansible_operator_meta.name }} : apply resourcequota"
when: >-
"resourceQuota" in named_quota.resources[0].spec
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: ResourceQuota
metadata:
name: "default"
namespace: "{{ ansible_operator_meta.name }}"
labels:
example.com/project: "{{ ansible_operator_meta.name }}"
spec: "{{ named_quota.resources[0].spec.resourceQuota }}"
- name: "{{ ansible_operator_meta.name }} : apply limitrange"
when: >-
"limitRange" in named_quota.resources[0].spec
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: LimitRange
metadata:
name: "default"
namespace: "{{ ansible_operator_meta.name }}"
labels:
example.com/project: "{{ ansible_operator_meta.name }}"
spec: "{{ named_quota.resources[0].spec.limitRange }}"
This all works, but this seems like the sort of thing for which there
is probably a canonical solution. Is this it? I initially tried using
the generation on the ProjectQuota instead of a content hash, but
this value isn't exposed to the role by the Ansible operator.