@@ -39,25 +39,132 @@ metadata:
3939 - `"all"` : General resources needed by all deployments
4040- `openstack.org/restore-order` : Numeric order for restore sequence (e.g., `"1"`, `"2"`, `"3"`)
4141
42- # ## Mutating Webhook
43-
44- A mutating webhook in the openstack-operator watches resource creation and automatically adds labels based on CRD annotations.
45-
46- **Webhook Logic:**
47- 1. Resource CREATE request arrives
48- 2. Webhook looks up CRD for resource type
49- 3. If CRD has `openstack.org/backup : " true" ` :
50- - For Secrets/ConfigMaps: Only label if no ` ownerReferences` (user-provided resources)
51- - For all other resources : Add labels
52- 4. Labels added to resource :
53- - `openstack.org/backup : " true" `
54- - ` openstack.org/backup-category: "<category>"`
55- - `openstack.org/restore-order : " <order>" `
56-
57- **Special Handling for Secrets and ConfigMaps:**
58- - Only label if ` metadata.ownerReferences` is empty (user-provided)
59- - Skip operator-managed resources (created by controllers)
60- - This prevents backing up temporary/generated resources
42+ # ## Mutating Webhooks
43+
44+ Reuse the existing webhook pattern where openstack-operator already calls `ValidateCreate` and `ValidateUpdate` functions from service operators.
45+
46+ **Architecture:**
47+
48+ 1. **OpenStack Operator Webhooks** (existing pattern) :
49+ - Existing webhooks in `api/core/v1beta1/openstackcontrolplane_webhook.go`
50+ - Already calls `ValidateCreate` from service operators (e.g., `keystone.ValidateCreate`)
51+ - Add backup labeling logic to these existing validation functions
52+ - Works on both Create and Update (handles existing environments)
53+
54+ 2. **Infrastructure Operator Webhook** (independent) :
55+ - Runs its own separate mutating webhook
56+ - Handles infrastructure resources (NetConfig, IPSet, RabbitMQUser, etc.)
57+ - Independent from openstack-operator webhook
58+
59+ **Example: Adding Backup Labeling to Existing Webhooks**
60+
61+ In `keystone-operator/api/v1beta1/keystoneapi_webhook.go` :
62+
63+ ` ` ` go
64+ func (r *KeystoneAPI) ValidateCreate() error {
65+ // Existing validation logic...
66+
67+ // NEW: Add backup labels to user-provided resources
68+ if err := r.labelUserProvidedResources(); err != nil {
69+ return err
70+ }
71+
72+ return nil
73+ }
74+
75+ func (r *KeystoneAPI) labelUserProvidedResources() error {
76+ ctx := context.Background()
77+
78+ // Label Secret if user-provided (no ownerReferences)
79+ if r.Spec.Secret != "" {
80+ if err := labelResourceIfUserProvided(ctx, r.Namespace, "Secret", r.Spec.Secret); err != nil {
81+ return err
82+ }
83+ }
84+
85+ // Label CustomConfigSecret if user-provided
86+ if r.Spec.HttpdCustomization != nil && r.Spec.HttpdCustomization.CustomConfigSecret != "" {
87+ if err := labelResourceIfUserProvided(ctx, r.Namespace, "Secret",
88+ r.Spec.HttpdCustomization.CustomConfigSecret); err != nil {
89+ return err
90+ }
91+ }
92+
93+ // Label referenced ConfigMaps in ExtraMounts if user-provided
94+ for _, mount := range r.Spec.ExtraMounts {
95+ for _, vol := range mount.Propagation {
96+ if vol.ConfigMap != nil {
97+ if err := labelResourceIfUserProvided(ctx, r.Namespace, "ConfigMap",
98+ vol.ConfigMap.Name); err != nil {
99+ return err
100+ }
101+ }
102+ }
103+ }
104+
105+ return nil
106+ }
107+ ` ` `
108+
109+ **Generic Helper Function (in lib-common):**
110+
111+ ` ` ` go
112+ // labelResourceIfUserProvided adds backup label to resource if it has no ownerReferences
113+ func labelResourceIfUserProvided(ctx context.Context, namespace, kind, name string) error {
114+ // Get the resource
115+ var obj client.Object
116+ switch kind {
117+ case "Secret":
118+ obj = &corev1.Secret{}
119+ case "ConfigMap":
120+ obj = &corev1.ConfigMap{}
121+ default:
122+ return fmt.Errorf("unsupported resource kind: %s", kind)
123+ }
124+
125+ key := client.ObjectKey{Namespace: namespace, Name: name}
126+ if err := k8sClient.Get(ctx, key, obj); err != nil {
127+ if errors.IsNotFound(err) {
128+ // Resource doesn't exist yet, skip labeling
129+ return nil
130+ }
131+ return err
132+ }
133+
134+ // Check if resource has ownerReferences
135+ if len(obj.GetOwnerReferences()) > 0 {
136+ // Resource is managed by controller, skip labeling
137+ return nil
138+ }
139+
140+ // Add backup label (user-provided resource)
141+ labels := obj.GetLabels()
142+ if labels == nil {
143+ labels = make(map[string]string)
144+ }
145+
146+ // Only add if not already labeled
147+ if labels["openstack.org/backup"] == "true" {
148+ return nil
149+ }
150+
151+ labels["openstack.org/backup"] = "true"
152+ labels["openstack.org/backup-category"] = "all"
153+ labels["openstack.org/restore-order"] = "1" // Secrets/ConfigMaps always order 1
154+ obj.SetLabels(labels)
155+
156+ // Update the resource
157+ return k8sClient.Update(ctx, obj)
158+ }
159+ ` ` `
160+
161+ **Key Points:**
162+
163+ 1. **Reuse Existing Pattern** : No new webhook infrastructure needed
164+ 2. **Service Operator Knowledge** : Each service operator knows what resources it references
165+ 3. **Generic Helper** : Common logic to check ownerReferences and add labels
166+ 4. **Works on Create and Update** : Handles both new and existing deployments
167+ 5. **User-Provided Detection** : Only labels resources without ownerReferences
61168
62169# ## OADP Integration
63170
0 commit comments