@@ -21,7 +21,9 @@ import (
2121 "emperror.dev/errors"
2222 v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2323 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
24+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
2425 "k8s.io/apimachinery/pkg/api/meta"
26+ apimeta "k8s.io/apimachinery/pkg/api/meta"
2527 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2628 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2729 "k8s.io/apimachinery/pkg/runtime"
@@ -32,21 +34,28 @@ import (
3234 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
3335 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3436 "sigs.k8s.io/controller-runtime/pkg/reconcile"
35- )
3637
37- const BanzaiCloudManagedComponent = "banzaicloud.io/managed-component"
38+ "github.com/banzaicloud/operator-tools/pkg/types"
39+ "github.com/banzaicloud/operator-tools/pkg/utils"
40+ )
3841
3942type ResourceOwner interface {
4043 // to be aware of metadata
4144 metav1.Object
4245 // to be aware of the owner's type
4346 runtime.Object
47+ }
48+
49+ type ResourceOwnerWithControlNamespace interface {
50+ ResourceOwner
4451 // control namespace dictates where namespaced objects should belong to
4552 GetControlNamespace () string
4653}
4754
4855type ResourceBuilders func (parent ResourceOwner , object interface {}) []ResourceBuilder
4956type ResourceBuilder func () (runtime.Object , DesiredState , error )
57+ type ResourceTranslate func (runtime.Object ) (parent ResourceOwner , config interface {})
58+ type PurgeTypesFunc func () []schema.GroupVersionKind
5059
5160type NativeReconciledComponent interface {
5261 ResourceBuilders (parent ResourceOwner , object interface {}) []ResourceBuilder
@@ -93,11 +102,13 @@ func (d *DefaultReconciledComponent) PurgeTypes() []schema.GroupVersionKind {
93102type NativeReconciler struct {
94103 * GenericResourceReconciler
95104 client.Client
96- scheme * runtime.Scheme
97- reconciledComponent NativeReconciledComponent
98- configTranslate func (runtime.Object ) (parent ResourceOwner , config interface {})
99- componentName string
100- setControllerRef bool
105+ scheme * runtime.Scheme
106+ restMapper meta.RESTMapper
107+ reconciledComponent NativeReconciledComponent
108+ configTranslate ResourceTranslate
109+ componentName string
110+ setControllerRef bool
111+ reconciledObjectStates map [reconciledObjectState ][]runtime.Object
101112}
102113
103114type NativeReconcilerOpt func (* NativeReconciler )
@@ -114,6 +125,12 @@ func NativeReconcilerSetControllerRef() NativeReconcilerOpt {
114125 }
115126}
116127
128+ func NativeReconcilerSetRESTMapper (mapper meta.RESTMapper ) NativeReconcilerOpt {
129+ return func (r * NativeReconciler ) {
130+ r .restMapper = mapper
131+ }
132+ }
133+
117134func NewNativeReconciler (
118135 componentName string ,
119136 rec * GenericResourceReconciler ,
@@ -129,6 +146,8 @@ func NewNativeReconciler(
129146 componentName : componentName ,
130147 }
131148
149+ reconciler .initReconciledObjectStates ()
150+
132151 for _ , opt := range opts {
133152 opt (reconciler )
134153 }
@@ -159,11 +178,12 @@ func (rec *NativeReconciler) Reconcile(owner runtime.Object) (*reconcile.Result,
159178 if err != nil {
160179 combinedResult .CombineErr (err )
161180 } else {
162- objectMeta , err := rec .addAnnotation (o , componentID )
181+ objectMeta , err := rec .addComponentIDAnnotation (o , componentID )
163182 if err != nil {
164183 combinedResult .CombineErr (err )
165184 continue
166185 }
186+ rec .addRelatedToAnnotation (objectMeta , ownerMeta )
167187 if rec .setControllerRef {
168188 isCrd := false
169189 switch o .(type ) {
@@ -173,9 +193,12 @@ func (rec *NativeReconciler) Reconcile(owner runtime.Object) (*reconcile.Result,
173193 isCrd = true
174194 }
175195 if ! isCrd {
176- if err := controllerutil .SetControllerReference (ownerMeta , objectMeta , rec .scheme ); err != nil {
177- combinedResult .CombineErr (err )
178- continue
196+ // namespaced resource can only own resources in the same namespace
197+ if ownerMeta .GetNamespace () == "" || ownerMeta .GetNamespace () == objectMeta .GetNamespace () {
198+ if err := controllerutil .SetControllerReference (ownerMeta , objectMeta , rec .scheme ); err != nil {
199+ combinedResult .CombineErr (err )
200+ continue
201+ }
179202 }
180203 }
181204 }
@@ -187,6 +210,12 @@ func (rec *NativeReconciler) Reconcile(owner runtime.Object) (*reconcile.Result,
187210 continue
188211 }
189212 excludeFromPurge [resourceID ] = true
213+
214+ s := ReconciledObjectStatePresent
215+ if state == StateAbsent {
216+ s = ReconciledObjectStateAbsent
217+ }
218+ rec .addReconciledObjectState (s , o .DeepCopyObject ())
190219 }
191220 combinedResult .Combine (result , err )
192221 }
@@ -260,12 +289,42 @@ func (rec *NativeReconciler) generateResourceID(resource runtime.Object) (string
260289 return strings .Join (identifiers , "-" ), nil
261290}
262291
292+ func (rec * NativeReconciler ) gvkExists (gvk schema.GroupVersionKind ) bool {
293+ if rec .restMapper == nil {
294+ return true
295+ }
296+
297+ mappings , err := rec .restMapper .RESTMappings (gvk .GroupKind (), gvk .Version )
298+ if apimeta .IsNoMatchError (err ) {
299+ return false
300+ }
301+ if err != nil {
302+ return true
303+ }
304+
305+ for _ , m := range mappings {
306+ if gvk == m .GroupVersionKind {
307+ return true
308+ }
309+ }
310+
311+ return false
312+ }
313+
263314func (rec * NativeReconciler ) purge (excluded map [string ]bool , componentId string ) error {
264315 var allErr error
265316 for _ , gvk := range rec .reconciledComponent .PurgeTypes () {
317+ rec .Log .V (2 ).Info ("purging GVK" , "gvk" , gvk )
318+ if ! rec .gvkExists (gvk ) {
319+ continue
320+ }
266321 objects := & unstructured.UnstructuredList {}
267322 objects .SetGroupVersionKind (gvk )
268323 err := rec .List (context .TODO (), objects )
324+ if apimeta .IsNoMatchError (err ) {
325+ // skip unknown GVKs
326+ continue
327+ }
269328 if err != nil {
270329 rec .Log .Error (err , "failed list objects to prune" ,
271330 "groupversion" , gvk .GroupVersion ().String (),
@@ -286,23 +345,54 @@ func (rec *NativeReconciler) purge(excluded map[string]bool, componentId string)
286345 if excluded [resourceID ] {
287346 continue
288347 }
289- if objectMeta .GetAnnotations () != nil && objectMeta . GetAnnotations ()[ BanzaiCloudManagedComponent ] == componentId {
348+ if objectMeta .GetAnnotations ()[ types . BanzaiCloudManagedComponent ] == componentId {
290349 rec .Log .Info ("pruning unmmanaged resource" ,
291350 "name" , objectMeta .GetName (),
292351 "namespace" , objectMeta .GetNamespace (),
293352 "group" , gvk .Group ,
294353 "version" , gvk .Version ,
295354 "listKind" , gvk .Kind )
296- if err := rec .Client .Delete (context .TODO (), & o ); err != nil {
355+ if err := rec .Client .Delete (context .TODO (), & o ); err != nil && ! k8serrors . IsNotFound ( err ) {
297356 allErr = errors .Combine (allErr , err )
357+ } else {
358+ rec .addReconciledObjectState (ReconciledObjectStatePurged , o .DeepCopy ())
298359 }
299360 }
300361 }
301362 }
302363 return allErr
303364}
304365
305- func (rec * NativeReconciler ) addAnnotation (o runtime.Object , componentId string ) (metav1.Object , error ) {
366+ type reconciledObjectState string
367+
368+ const (
369+ ReconciledObjectStateAbsent reconciledObjectState = "Absent"
370+ ReconciledObjectStatePresent reconciledObjectState = "Present"
371+ ReconciledObjectStatePurged reconciledObjectState = "Purged"
372+ )
373+
374+ func (rec * NativeReconciler ) initReconciledObjectStates () {
375+ rec .reconciledObjectStates = make (map [reconciledObjectState ][]runtime.Object )
376+ }
377+
378+ func (rec * NativeReconciler ) addReconciledObjectState (state reconciledObjectState , o runtime.Object ) {
379+ rec .reconciledObjectStates [state ] = append (rec .reconciledObjectStates [state ], o )
380+ }
381+
382+ func (rec * NativeReconciler ) GetReconciledObjectWithState (state reconciledObjectState ) []runtime.Object {
383+ return rec .reconciledObjectStates [state ]
384+ }
385+
386+ func (rec * NativeReconciler ) addRelatedToAnnotation (objectMeta , ownerMeta metav1.Object ) {
387+ annotations := objectMeta .GetAnnotations ()
388+ if annotations == nil {
389+ annotations = make (map [string ]string )
390+ }
391+ annotations [types .BanzaiCloudRelatedTo ] = utils .ObjectKeyFromObjectMeta (ownerMeta ).String ()
392+ objectMeta .SetAnnotations (annotations )
393+ }
394+
395+ func (rec * NativeReconciler ) addComponentIDAnnotation (o runtime.Object , componentId string ) (metav1.Object , error ) {
306396 objectMeta , err := meta .Accessor (o )
307397 if err != nil {
308398 return nil , errors .Wrapf (err , "failed to access object metadata" )
@@ -311,14 +401,14 @@ func (rec *NativeReconciler) addAnnotation(o runtime.Object, componentId string)
311401 if annotations == nil {
312402 annotations = make (map [string ]string )
313403 }
314- if currentComponentId , ok := annotations [BanzaiCloudManagedComponent ]; ok {
404+ if currentComponentId , ok := annotations [types . BanzaiCloudManagedComponent ]; ok {
315405 if currentComponentId != componentId {
316406 return nil , errors .Errorf (
317407 "object actual component id `%s` is different from the one defined by the component `%s`" ,
318408 currentComponentId , componentId )
319409 }
320410 } else {
321- annotations [BanzaiCloudManagedComponent ] = componentId
411+ annotations [types . BanzaiCloudManagedComponent ] = componentId
322412 objectMeta .SetAnnotations (annotations )
323413 }
324414 return objectMeta , nil
0 commit comments