@@ -32,6 +32,7 @@ import (
32
32
"k8s.io/apimachinery/pkg/labels"
33
33
"k8s.io/apimachinery/pkg/runtime"
34
34
"k8s.io/apimachinery/pkg/runtime/schema"
35
+ "k8s.io/apimachinery/pkg/util/sets"
35
36
"k8s.io/client-go/tools/pager"
36
37
37
38
"github.com/vmware-tanzu/velero/pkg/client"
@@ -44,14 +45,16 @@ import (
44
45
// itemCollector collects items from the Kubernetes API according to
45
46
// the backup spec and writes them to files inside dir.
46
47
type itemCollector struct {
47
- log logrus.FieldLogger
48
- backupRequest * Request
49
- discoveryHelper discovery.Helper
50
- dynamicFactory client.DynamicFactory
51
- cohabitatingResources map [string ]* cohabitatingResource
52
- dir string
53
- pageSize int
54
- nsTracker nsTracker
48
+ log logrus.FieldLogger
49
+ backupRequest * Request
50
+ // Namespaces that are included by the backup's labelSelector will backup all resources in that namespace even if resources are not labeled.
51
+ namespacesIncludedByLabelSelector sets.Set [string ]
52
+ discoveryHelper discovery.Helper
53
+ dynamicFactory client.DynamicFactory
54
+ cohabitatingResources map [string ]* cohabitatingResource
55
+ dir string
56
+ pageSize int
57
+ nsTracker nsTracker
55
58
}
56
59
57
60
// nsTracker is used to integrate several namespace filters together.
@@ -72,42 +75,47 @@ type nsTracker struct {
72
75
orLabelSelector []labels.Selector
73
76
namespaceFilter * collections.IncludesExcludes
74
77
logger logrus.FieldLogger
75
-
76
- namespaceMap map [string ]bool
78
+ namespaceMap sets. Set [ string ] // namespaceMap is a set of namespaces tracked to include in backup
79
+ labeledNamespaces sets. Set [string ] // labeledNamespaces is a subset of namespaceMap added via label selectors.
77
80
}
78
81
79
82
// track add the namespace into the namespaceMap.
80
83
func (nt * nsTracker ) track (ns string ) {
81
84
if nt .namespaceMap == nil {
82
- nt .namespaceMap = make (map [string ]bool )
85
+ nt .namespaceMap = make (sets. Set [string ])
83
86
}
87
+ nt .namespaceMap .Insert (ns )
88
+ }
84
89
85
- if _ , ok := nt .namespaceMap [ns ]; ! ok {
86
- nt .namespaceMap [ns ] = true
90
+ // trackViaLabel add the namespace into the labeledNamespaces and namespacesMap.
91
+ func (nt * nsTracker ) trackViaLabel (ns string ) {
92
+ if nt .labeledNamespaces == nil {
93
+ nt .labeledNamespaces = make (sets.Set [string ])
87
94
}
95
+ nt .labeledNamespaces .Insert (ns )
96
+ nt .track (ns )
88
97
}
89
98
90
99
// isTracked check whether the namespace's name exists in
91
100
// namespaceMap.
92
101
func (nt * nsTracker ) isTracked (ns string ) bool {
93
- if nt .namespaceMap != nil {
94
- return nt .namespaceMap [ns ]
95
- }
96
- return false
102
+ return nt .namespaceMap .Has (ns )
103
+ }
104
+
105
+ func (nt * nsTracker ) isTrackedViaLabel (ns string ) bool {
106
+ return nt .labeledNamespaces .Has (ns )
97
107
}
98
108
99
- // init initialize the namespaceMap, and add elements according to
109
+ // init initialize the namespaceMap, and add (track) elements according to
100
110
// namespace include/exclude filters and the backup label selectors.
111
+ // and return only namespaces that are added to tracker.
101
112
func (nt * nsTracker ) init (
102
113
unstructuredNSs []unstructured.Unstructured ,
103
114
singleLabelSelector labels.Selector ,
104
115
orLabelSelector []labels.Selector ,
105
116
namespaceFilter * collections.IncludesExcludes ,
106
117
logger logrus.FieldLogger ,
107
118
) {
108
- if nt .namespaceMap == nil {
109
- nt .namespaceMap = make (map [string ]bool )
110
- }
111
119
nt .singleLabelSelector = singleLabelSelector
112
120
nt .orLabelSelector = orLabelSelector
113
121
nt .namespaceFilter = namespaceFilter
@@ -120,7 +128,7 @@ func (nt *nsTracker) init(
120
128
namespace .GetName (),
121
129
)
122
130
123
- nt .track (namespace .GetName ())
131
+ nt .trackViaLabel (namespace .GetName ())
124
132
continue
125
133
}
126
134
@@ -130,7 +138,7 @@ func (nt *nsTracker) init(
130
138
nt .logger .Debugf ("Track namespace %s, because its labels match the backup OrLabelSelector." ,
131
139
namespace .GetName (),
132
140
)
133
- nt .track (namespace .GetName ())
141
+ nt .trackViaLabel (namespace .GetName ())
134
142
continue
135
143
}
136
144
}
@@ -194,6 +202,7 @@ func (r *itemCollector) getItemsFromResourceIdentifiers(
194
202
195
203
// getAllItems gets all backup-relevant items from all API groups.
196
204
func (r * itemCollector ) getAllItems () []* kubernetesResource {
205
+ // getItems should return all namespaces and will be filtered by nsTracker.filterNamespaces
197
206
resources := r .getItems (nil )
198
207
199
208
return r .nsTracker .filterNamespaces (resources )
@@ -436,13 +445,33 @@ func (r *itemCollector) getResourceItems(
436
445
// Namespace are filtered by namespace include/exclude filters,
437
446
// backup LabelSelectors and OrLabelSelectors are checked too.
438
447
if gr == kuberesource .Namespaces {
439
- return r .collectNamespaces (
448
+ // collectNamespaces initializes the nsTracker which should only contain namespaces to backup but returns all namespaces.
449
+ namespaces , err := r .collectNamespaces (
440
450
resource ,
441
451
gv ,
442
452
gr ,
443
453
preferredGVR ,
444
454
log ,
445
455
)
456
+ if err != nil {
457
+ return nil , err
458
+ }
459
+ // The namespaces collected contains namespaces selected by namespace filters like include/exclude and label selector.
460
+ // Per https://github.com/vmware-tanzu/velero/issues/7492#issuecomment-1986146411 we want labelSelector included namespace to act as IncludedNamespaces so add to NamespaceIncludesExcludes string set.
461
+ r .namespacesIncludedByLabelSelector = make (sets.Set [string ])
462
+ for i := range namespaces {
463
+ if namespaces [i ] != nil {
464
+ if r .nsTracker .isTrackedViaLabel (namespaces [i ].name ) {
465
+ // this namespace is included by the backup's label selector, not the namespace include/exclude filter
466
+ // this is a special case where we want to include this namespace in the backup and all resources in it regardless of the resource's label selector
467
+ // in other cases, if label selector is set, we only want to include resources that match the label selector
468
+ r .namespacesIncludedByLabelSelector .Insert (namespaces [i ].name )
469
+ r .backupRequest .NamespaceIncludesExcludes .Includes (namespaces [i ].name )
470
+ r .log .Infof ("including namespace %s by labelselector\n " , namespaces [i ].name )
471
+ }
472
+ }
473
+ }
474
+ return namespaces , err
446
475
}
447
476
448
477
clusterScoped := ! resource .Namespaced
@@ -509,9 +538,13 @@ func (r *itemCollector) listResourceByLabelsPerNamespace(
509
538
logger .WithError (err ).Error ("Error getting dynamic client" )
510
539
return nil , err
511
540
}
512
-
541
+ labeledNsSoBackupUnlabled := false
542
+ if r .namespacesIncludedByLabelSelector .Has (namespace ) {
543
+ logger .Infof ("Listing all items including unlabled in namespace %s because ns was selected by the backup's label selector and acts as IncludedNamespaces" , namespace )
544
+ labeledNsSoBackupUnlabled = true
545
+ }
513
546
var orLabelSelectors []string
514
- if r .backupRequest .Spec .OrLabelSelectors != nil {
547
+ if r .backupRequest .Spec .OrLabelSelectors != nil && ! labeledNsSoBackupUnlabled {
515
548
for _ , s := range r .backupRequest .Spec .OrLabelSelectors {
516
549
orLabelSelectors = append (orLabelSelectors , metav1 .FormatLabelSelector (s ))
517
550
}
@@ -537,7 +570,7 @@ func (r *itemCollector) listResourceByLabelsPerNamespace(
537
570
}
538
571
539
572
var labelSelector string
540
- if selector := r .backupRequest .Spec .LabelSelector ; selector != nil {
573
+ if selector := r .backupRequest .Spec .LabelSelector ; selector != nil && ! labeledNsSoBackupUnlabled {
541
574
labelSelector = metav1 .FormatLabelSelector (selector )
542
575
}
543
576
@@ -594,7 +627,8 @@ func sortCoreGroup(group *metav1.APIResourceList) {
594
627
// pod is backed up, we can perform a pre hook, then process pvcs and pvs (including taking a
595
628
// snapshot), then perform a post hook on the pod.
596
629
const (
597
- pod = iota
630
+ namespaces = iota
631
+ pod
598
632
pvc
599
633
pv
600
634
other
@@ -604,6 +638,8 @@ const (
604
638
// pods, pvcs, pvs, everything else.
605
639
func coreGroupResourcePriority (resource string ) int {
606
640
switch strings .ToLower (resource ) {
641
+ case "namespaces" :
642
+ return namespaces
607
643
case "pods" :
608
644
return pod
609
645
case "persistentvolumeclaims" :
@@ -722,6 +758,7 @@ func (r *itemCollector) listItemsForLabel(
722
758
}
723
759
724
760
// collectNamespaces process namespace resource according to namespace filters.
761
+ // returns a list of ALL namespaces. Filtering will happen outside of this function.
725
762
func (r * itemCollector ) collectNamespaces (
726
763
resource metav1.APIResource ,
727
764
gv schema.GroupVersion ,
@@ -780,7 +817,8 @@ func (r *itemCollector) collectNamespaces(
780
817
orSelectors = append (orSelectors , orSelector )
781
818
}
782
819
}
783
-
820
+ // init initialize the namespaceMap, and add elements according to
821
+ // namespace include/exclude filters and the backup label selectors.
784
822
r .nsTracker .init (
785
823
unstructuredList .Items ,
786
824
singleSelector ,
@@ -806,6 +844,5 @@ func (r *itemCollector) collectNamespaces(
806
844
path : path ,
807
845
})
808
846
}
809
-
810
847
return items , nil
811
848
}
0 commit comments