forked from velero-io/velero
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathincludes_excludes.go
More file actions
782 lines (667 loc) · 28.4 KB
/
includes_excludes.go
File metadata and controls
782 lines (667 loc) · 28.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
/*
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package collections
import (
"strings"
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
"github.com/gobwas/glob"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/discovery"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/wildcard"
)
type globStringSet struct {
sets.String
}
func newGlobStringSet() globStringSet {
return globStringSet{sets.NewString()}
}
func (gss globStringSet) match(match string) bool {
for _, item := range gss.List() {
g, err := glob.Compile(item)
if err != nil {
return false
}
if g.Match(match) {
return true
}
}
return false
}
// NamespaceIncludesExcludes adds some features to IncludesExcludes
// to handle namespace-specific functionality. In particular, it
// provides a way to list all namespaces included in order to determine
// overlap between backups, and it will be expanded in the future to
// handle namespace wildcard values
type NamespaceIncludesExcludes struct {
activeNamespaces []string
includesExcludes *IncludesExcludes
wildcardExpanded bool
wildcardResult []string
}
func NewNamespaceIncludesExcludes() *NamespaceIncludesExcludes {
return &NamespaceIncludesExcludes{
activeNamespaces: []string{},
includesExcludes: NewIncludesExcludes(),
}
}
func (nie *NamespaceIncludesExcludes) ActiveNamespaces(activeNamespaces []string) *NamespaceIncludesExcludes {
nie.activeNamespaces = activeNamespaces
return nie
}
func (nie *NamespaceIncludesExcludes) IsWildcardExpanded() bool {
return nie.wildcardExpanded
}
// Includes adds items to the includes list. '*' is a wildcard
// value meaning "include everything".
func (nie *NamespaceIncludesExcludes) Includes(includes ...string) *NamespaceIncludesExcludes {
nie.includesExcludes.Includes(includes...)
return nie
}
// GetIncludes returns the items in the includes list
func (nie *NamespaceIncludesExcludes) GetIncludes() []string {
return nie.includesExcludes.GetIncludes()
}
func (nie *NamespaceIncludesExcludes) GetExcludes() []string {
return nie.includesExcludes.GetExcludes()
}
// SetIncludes sets the includes list to the given list
func (nie *NamespaceIncludesExcludes) SetIncludes(includes []string) *NamespaceIncludesExcludes {
nie.includesExcludes.includes = newGlobStringSet()
nie.includesExcludes.includes.Insert(includes...)
return nie
}
// SetExcludes sets the excludes list to the given list
func (nie *NamespaceIncludesExcludes) SetExcludes(excludes []string) *NamespaceIncludesExcludes {
nie.includesExcludes.excludes = newGlobStringSet()
nie.includesExcludes.excludes.Insert(excludes...)
return nie
}
// IncludesString returns a string containing all of the includes, separated by commas, or * if the
// list is empty.
func (nie *NamespaceIncludesExcludes) IncludesString() string {
return nie.includesExcludes.IncludesString()
}
// Excludes adds items to the includes list. '*' is a wildcard
// value meaning "include everything".
func (nie *NamespaceIncludesExcludes) Excludes(excludes ...string) *NamespaceIncludesExcludes {
nie.includesExcludes.Excludes(excludes...)
return nie
}
// IncludesString returns a string containing all of the excludes, separated by commas, or * if the
// list is empty.
func (nie *NamespaceIncludesExcludes) ExcludesString() string {
return nie.includesExcludes.ExcludesString()
}
// ShouldInclude returns whether the specified item should be
// included or not. Everything in the includes list except those
// items in the excludes list should be included.
func (nie *NamespaceIncludesExcludes) ShouldInclude(s string) bool {
// Special case: if wildcard expansion occurred and resulted in an empty includes list,
// it means the wildcard pattern matched nothing, so we should include nothing.
// This differs from the default behavior where an empty includes list means "include everything".
if nie.wildcardExpanded && nie.includesExcludes.includes.Len() == 0 {
return false
}
return nie.includesExcludes.ShouldInclude(s)
}
// IncludeEverything returns true if the includes list is empty or '*'
// and the excludes list is empty, or false otherwise.
func (nie *NamespaceIncludesExcludes) IncludeEverything() bool {
return nie.includesExcludes.IncludeEverything()
}
// Attempts to expand wildcard patterns, if any, in the includes and excludes lists.
func (nie *NamespaceIncludesExcludes) ExpandIncludesExcludes() error {
includes := nie.GetIncludes()
excludes := nie.GetExcludes()
if wildcard.ShouldExpandWildcards(includes, excludes) {
expandedIncludes, expandedExcludes, err := wildcard.ExpandWildcards(
nie.activeNamespaces, includes, excludes)
if err != nil {
return err
}
// Empty includes means "include all", so normalize to "*" to
// prevent the wildcardExpanded+empty guard from excluding
// everything, and to match the CLI path's behavior.
// Note: Namespace CRs for excluded namespaces will still be
// backed up — this is intentional Velero behavior (see
// item_backupper.go itemInclusionChecks).
if len(includes) == 0 {
expandedIncludes = []string{"*"}
}
nie.SetIncludes(expandedIncludes)
nie.SetExcludes(expandedExcludes)
nie.wildcardExpanded = true
}
return nil
}
// ResolveNamespaceList returns a list of all namespaces which will be backed up.
// The second return value indicates whether wildcard expansion was performed.
func (nie *NamespaceIncludesExcludes) ResolveNamespaceList() ([]string, error) {
// Check if this is being called by non-backup processing e.g. backup queue controller
if !nie.wildcardExpanded {
err := nie.ExpandIncludesExcludes()
if err != nil {
return nil, err
}
}
outNamespaces := []string{}
for _, ns := range nie.activeNamespaces {
if nie.ShouldInclude(ns) {
outNamespaces = append(outNamespaces, ns)
}
}
nie.wildcardResult = outNamespaces
return nie.wildcardResult, nil
}
// IncludesExcludes is a type that manages lists of included
// and excluded items. The logic implemented is that everything
// in the included list except those items in the excluded list
// should be included. '*' in the includes list means "include
// everything", but it is not valid in the exclude list.
type IncludesExcludes struct {
includes globStringSet
excludes globStringSet
}
func NewIncludesExcludes() *IncludesExcludes {
return &IncludesExcludes{
includes: newGlobStringSet(),
excludes: newGlobStringSet(),
}
}
// Includes adds items to the includes list. '*' is a wildcard
// value meaning "include everything".
func (ie *IncludesExcludes) Includes(includes ...string) *IncludesExcludes {
ie.includes.Insert(includes...)
return ie
}
// GetIncludes returns the items in the includes list
func (ie *IncludesExcludes) GetIncludes() []string {
return ie.includes.List()
}
// Excludes adds items to the excludes list
func (ie *IncludesExcludes) Excludes(excludes ...string) *IncludesExcludes {
ie.excludes.Insert(excludes...)
return ie
}
// GetExcludes returns the items in the excludes list
func (ie *IncludesExcludes) GetExcludes() []string {
return ie.excludes.List()
}
// ShouldInclude returns whether the specified item should be
// included or not. Everything in the includes list except those
// items in the excludes list should be included.
func (ie *IncludesExcludes) ShouldInclude(s string) bool {
if ie.excludes.match(s) {
return false
}
// len=0 means include everything
return ie.includes.Len() == 0 || ie.includes.Has("*") || ie.includes.match(s)
}
// IncludesString returns a string containing all of the includes, separated by commas, or * if the
// list is empty.
func (ie *IncludesExcludes) IncludesString() string {
return asString(ie.GetIncludes(), "*")
}
// ExcludesString returns a string containing all of the excludes, separated by commas, or <none> if the
// list is empty.
func (ie *IncludesExcludes) ExcludesString() string {
return asString(ie.GetExcludes(), "<none>")
}
// IncludeEverything returns true if the includes list is empty or '*'
// and the excludes list is empty, or false otherwise.
func (ie *IncludesExcludes) IncludeEverything() bool {
return ie.excludes.Len() == 0 && (ie.includes.Len() == 0 || (ie.includes.Len() == 1 && ie.includes.Has("*")))
}
// GetResourceIncludesExcludes takes the lists of resources to include and exclude, uses the
// discovery helper to resolve them to fully-qualified group-resource names, and returns an
// IncludesExcludes list.
func GetResourceIncludesExcludes(helper discovery.Helper, includes, excludes []string) *IncludesExcludes {
resources := generateIncludesExcludes(
includes,
excludes,
func(item string) string {
gvr, _, err := helper.ResourceFor(schema.ParseGroupResource(item).WithVersion(""))
if err != nil {
// If we can't resolve it, return it as-is. This prevents the generated
// includes-excludes list from including *everything*, if none of the includes
// can be resolved. ref. https://github.com/vmware-tanzu/velero/issues/2461
return item
}
gr := gvr.GroupResource()
return gr.String()
},
)
return resources
}
func asString(in []string, empty string) string {
if len(in) == 0 {
return empty
}
return strings.Join(in, ", ")
}
// IncludesExcludesInterface is used as polymorphic IncludesExcludes for Global and scope
// resources Include/Exclude.
type IncludesExcludesInterface interface {
// ShouldInclude checks whether the type name passed in by parameter should be included.
// typeName should be k8s.io/apimachinery/pkg/runtime/schema GroupResource's String() result.
ShouldInclude(typeName string) bool
// ShouldExclude checks whether the type name passed in by parameter should be excluded.
// typeName should be k8s.io/apimachinery/pkg/runtime/schema GroupResource's String() result.
ShouldExclude(typeName string) bool
}
type GlobalIncludesExcludes struct {
resourceFilter IncludesExcludes
includeClusterResources *bool
namespaceFilter NamespaceIncludesExcludes
helper discovery.Helper
logger logrus.FieldLogger
}
// ShouldInclude returns whether the specified item should be
// included or not. Everything in the includes list except those
// items in the excludes list should be included.
// It has some exceptional cases. When IncludeClusterResources is set to false,
// no need to check the filter, all cluster resources are excluded.
func (ie *GlobalIncludesExcludes) ShouldInclude(typeName string) bool {
_, resource, err := ie.helper.ResourceFor(schema.ParseGroupResource(typeName).WithVersion(""))
if err != nil {
ie.logger.Errorf("fail to get resource %s. %s", typeName, err.Error())
return false
}
if !resource.Namespaced && boolptr.IsSetToFalse(ie.includeClusterResources) {
ie.logger.Info("Skipping resource %s, because it's cluster-scoped, and IncludeClusterResources is set to false.", typeName)
return false
}
// when IncludeClusterResources == nil (auto), only directly
// back up cluster-scoped resources if we're doing a full-cluster
// (all namespaces and all namespace scope types) backup. Note that in the case of a subset of
// namespaces being backed up, some related cluster-scoped resources
// may still be backed up if triggered by a custom action (e.g. PVC->PV).
// If we're processing namespaces themselves, we will not skip here, they may be
// filtered out later.
if typeName != kuberesource.Namespaces.String() && !resource.Namespaced &&
ie.includeClusterResources == nil && !ie.namespaceFilter.IncludeEverything() {
ie.logger.Infof("Skipping resource %s, because it's cluster-scoped and only specific namespaces or namespace scope types are included in the backup.", typeName)
return false
}
return ie.resourceFilter.ShouldInclude(typeName)
}
// ShouldExclude returns whether the resource type should be excluded or not.
func (ie *GlobalIncludesExcludes) ShouldExclude(typeName string) bool {
// if the type name is specified in excluded list, it's excluded.
if ie.resourceFilter.excludes.match(typeName) {
return true
}
_, resource, err := ie.helper.ResourceFor(schema.ParseGroupResource(typeName).WithVersion(""))
if err != nil {
ie.logger.Errorf("fail to get resource %s. %s", typeName, err.Error())
return true
}
// the resource type is cluster scope
if !resource.Namespaced {
// if includeClusterResources is set to false, cluster resource should be excluded.
if boolptr.IsSetToFalse(ie.includeClusterResources) {
return true
}
// if includeClusterResources is set to nil, check whether it's included by resource
// filter.
if ie.includeClusterResources == nil && !ie.resourceFilter.ShouldInclude(typeName) {
return true
}
}
return false
}
func GetGlobalResourceIncludesExcludes(helper discovery.Helper, logger logrus.FieldLogger, includes, excludes []string, includeClusterResources *bool, nsIncludesExcludes NamespaceIncludesExcludes) *GlobalIncludesExcludes {
ret := &GlobalIncludesExcludes{
resourceFilter: *GetResourceIncludesExcludes(helper, includes, excludes),
includeClusterResources: includeClusterResources,
namespaceFilter: nsIncludesExcludes,
helper: helper,
logger: logger,
}
logger.Infof("Including resources: %s", ret.resourceFilter.IncludesString())
logger.Infof("Excluding resources: %s", ret.resourceFilter.ExcludesString())
return ret
}
type ScopeIncludesExcludes struct {
namespaceScopedResourceFilter IncludesExcludes // namespace-scoped resource filter
clusterScopedResourceFilter IncludesExcludes // cluster-scoped resource filter
namespaceFilter NamespaceIncludesExcludes // namespace filter
helper discovery.Helper
logger logrus.FieldLogger
}
// ShouldInclude returns whether the specified resource should be included or not.
// The function will check whether the resource is namespace-scoped resource first.
// For namespace-scoped resource, except resources listed in excludes, other things should be included.
// For cluster-scoped resource, except resources listed in excludes, only include the resource specified by the included.
// It also has some exceptional checks. For namespace, as long as it's not excluded, it is involved.
// If all namespace-scoped resources are included, all cluster-scoped resource are returned to get a full backup.
func (ie *ScopeIncludesExcludes) ShouldInclude(typeName string) bool {
_, resource, err := ie.helper.ResourceFor(schema.ParseGroupResource(typeName).WithVersion(""))
if err != nil {
ie.logger.Errorf("fail to get resource %s. %s", typeName, err.Error())
return false
}
if resource.Namespaced {
if ie.namespaceScopedResourceFilter.excludes.Has("*") || ie.namespaceScopedResourceFilter.excludes.match(typeName) {
return false
}
// len=0 means include everything
return ie.namespaceScopedResourceFilter.includes.Len() == 0 || ie.namespaceScopedResourceFilter.includes.Has("*") || ie.namespaceScopedResourceFilter.includes.match(typeName)
}
if ie.clusterScopedResourceFilter.excludes.Has("*") || ie.clusterScopedResourceFilter.excludes.match(typeName) {
return false
}
// when IncludedClusterScopedResources and ExcludedClusterScopedResources are not specified,
// only directly back up cluster-scoped resources if we're doing a full-cluster
// (all namespaces and all namespace-scoped types) backup.
if len(ie.clusterScopedResourceFilter.includes.List()) == 0 &&
len(ie.clusterScopedResourceFilter.excludes.List()) == 0 &&
ie.namespaceFilter.IncludeEverything() &&
ie.namespaceScopedResourceFilter.IncludeEverything() {
return true
}
// Also include namespace resource by default.
return ie.clusterScopedResourceFilter.includes.Has("*") || ie.clusterScopedResourceFilter.includes.match(typeName) || typeName == kuberesource.Namespaces.String()
}
// ShouldExclude returns whether the resource type should be excluded or not.
// For ScopeIncludesExcludes, if the resource type is specified in the exclude
// list, it should be excluded.
func (ie *ScopeIncludesExcludes) ShouldExclude(typeName string) bool {
_, resource, err := ie.helper.ResourceFor(schema.ParseGroupResource(typeName).WithVersion(""))
if err != nil {
ie.logger.Errorf("fail to get resource %s. %s", typeName, err.Error())
return true
}
if resource.Namespaced {
if ie.namespaceScopedResourceFilter.excludes.match(typeName) {
return true
}
} else {
if ie.clusterScopedResourceFilter.excludes.match(typeName) {
return true
}
}
return false
}
func (ie *ScopeIncludesExcludes) CombineWithPolicy(policy *resourcepolicies.IncludeExcludePolicy) {
if policy == nil {
return
}
mapFunc := scopeResourceMapFunc(ie.helper)
for _, item := range policy.ExcludedNamespaceScopedResources {
resolvedItem := mapFunc(item, true)
if resolvedItem == "" {
continue
}
// The existing includeExcludes in the struct has higher priority, therefore, we should only add the item to the filter
// when the struct does not include this item and this item is not yet in the excludes filter.
if !ie.namespaceScopedResourceFilter.includes.match(resolvedItem) &&
!ie.namespaceScopedResourceFilter.excludes.match(resolvedItem) {
ie.namespaceScopedResourceFilter.Excludes(resolvedItem)
}
}
for _, item := range policy.IncludedNamespaceScopedResources {
resolvedItem := mapFunc(item, true)
if resolvedItem == "" {
continue
}
// The existing includeExcludes in the struct has higher priority, therefore, we should only add the item to the filter
// when the struct does not exclude this item and this item is not yet in the includes filter.
if !ie.namespaceScopedResourceFilter.includes.match(resolvedItem) &&
!ie.namespaceScopedResourceFilter.excludes.match(resolvedItem) {
ie.namespaceScopedResourceFilter.Includes(resolvedItem)
}
}
for _, item := range policy.ExcludedClusterScopedResources {
resolvedItem := mapFunc(item, false)
if resolvedItem == "" {
continue
}
if !ie.clusterScopedResourceFilter.includes.match(resolvedItem) &&
!ie.clusterScopedResourceFilter.excludes.match(resolvedItem) {
// The existing includeExcludes in the struct has higher priority, therefore, we should only add the item to the filter
// when the struct does not exclude this item and this item is not yet in the includes filter.
ie.clusterScopedResourceFilter.Excludes(resolvedItem)
}
}
for _, item := range policy.IncludedClusterScopedResources {
resolvedItem := mapFunc(item, false)
if resolvedItem == "" {
continue
}
if !ie.clusterScopedResourceFilter.includes.match(resolvedItem) &&
!ie.clusterScopedResourceFilter.excludes.match(resolvedItem) {
// The existing includeExcludes in the struct has higher priority, therefore, we should only add the item to the filter
// when the struct does not exclude this item and this item is not yet in the includes filter.
ie.clusterScopedResourceFilter.Includes(resolvedItem)
}
}
ie.logger.Infof("Scoped resource includes/excludes after combining with resource policy")
ie.logger.Infof("Including namespace-scoped resources: %s", ie.namespaceScopedResourceFilter.IncludesString())
ie.logger.Infof("Excluding namespace-scoped resources: %s", ie.namespaceScopedResourceFilter.ExcludesString())
ie.logger.Infof("Including cluster-scoped resources: %s", ie.clusterScopedResourceFilter.GetIncludes())
ie.logger.Infof("Excluding cluster-scoped resources: %s", ie.clusterScopedResourceFilter.ExcludesString())
}
func newScopeIncludesExcludes(nsIncludesExcludes NamespaceIncludesExcludes, helper discovery.Helper, logger logrus.FieldLogger) *ScopeIncludesExcludes {
ret := &ScopeIncludesExcludes{
namespaceScopedResourceFilter: IncludesExcludes{
includes: newGlobStringSet(),
excludes: newGlobStringSet(),
},
clusterScopedResourceFilter: IncludesExcludes{
includes: newGlobStringSet(),
excludes: newGlobStringSet(),
},
namespaceFilter: nsIncludesExcludes,
helper: helper,
logger: logger,
}
return ret
}
// GetScopeResourceIncludesExcludes function is similar with GetResourceIncludesExcludes,
// but it's used for scoped Includes/Excludes, and can handle both cluster-scoped and namespace-scoped resources.
func GetScopeResourceIncludesExcludes(helper discovery.Helper, logger logrus.FieldLogger, namespaceIncludes, namespaceExcludes, clusterIncludes, clusterExcludes []string, nsIncludesExcludes NamespaceIncludesExcludes) *ScopeIncludesExcludes {
ret := generateScopedIncludesExcludes(
namespaceIncludes,
namespaceExcludes,
clusterIncludes,
clusterExcludes,
scopeResourceMapFunc(helper),
nsIncludesExcludes,
helper,
logger,
)
logger.Infof("Scoped resource includes/excludes after initialization")
logger.Infof("Including namespace-scoped resources: %s", ret.namespaceScopedResourceFilter.IncludesString())
logger.Infof("Excluding namespace-scoped resources: %s", ret.namespaceScopedResourceFilter.ExcludesString())
logger.Infof("Including cluster-scoped resources: %s", ret.clusterScopedResourceFilter.GetIncludes())
logger.Infof("Excluding cluster-scoped resources: %s", ret.clusterScopedResourceFilter.ExcludesString())
return ret
}
func scopeResourceMapFunc(helper discovery.Helper) func(string, bool) string {
return func(item string, namespaced bool) string {
gvr, resource, err := helper.ResourceFor(schema.ParseGroupResource(item).WithVersion(""))
if err != nil {
return item
}
if resource.Namespaced != namespaced {
return ""
}
gr := gvr.GroupResource()
return gr.String()
}
}
// ValidateIncludesExcludes checks provided lists of included and excluded
// items to ensure they are a valid set of IncludesExcludes data.
func ValidateIncludesExcludes(includesList, excludesList []string) []error {
// TODO we should not allow an IncludesExcludes object to be created that
// does not meet these criteria. Do a more significant refactoring to embed
// this logic in object creation/modification.
var errs []error
includes := sets.NewString(includesList...)
excludes := sets.NewString(excludesList...)
if includes.Len() > 1 && includes.Has("*") {
errs = append(errs, errors.New("includes list must either contain '*' only, or a non-empty list of items"))
}
if excludes.Has("*") {
errs = append(errs, errors.New("excludes list cannot contain '*'"))
}
for _, itm := range excludes.List() {
if includes.Has(itm) {
errs = append(errs, errors.Errorf("excludes list cannot contain an item in the includes list: %v", itm))
}
}
return errs
}
// ValidateNamespaceIncludesExcludes checks provided lists of included and
// excluded namespaces to ensure they are a valid set of IncludesExcludes data.
func ValidateNamespaceIncludesExcludes(includesList, excludesList []string) []error {
errs := ValidateIncludesExcludes(includesList, excludesList)
includes := sets.NewString(includesList...)
excludes := sets.NewString(excludesList...)
for _, itm := range includes.List() {
if nsErrs := validateNamespaceName(itm); nsErrs != nil {
errs = append(errs, nsErrs...)
}
}
for _, itm := range excludes.List() {
if nsErrs := validateNamespaceName(itm); nsErrs != nil {
errs = append(errs, nsErrs...)
}
}
return errs
}
// ValidateScopedIncludesExcludes checks provided lists of namespace-scoped or cluster-scoped
// included and excluded items to ensure they are a valid set of IncludesExcludes data.
func ValidateScopedIncludesExcludes(includesList, excludesList []string) []error {
var errs []error
includes := sets.NewString(includesList...)
excludes := sets.NewString(excludesList...)
if includes.Len() > 1 && includes.Has("*") {
errs = append(errs, errors.New("includes list must either contain '*' only, or a non-empty list of items"))
}
if excludes.Len() > 1 && excludes.Has("*") {
errs = append(errs, errors.New("excludes list must either contain '*' only, or a non-empty list of items"))
}
if includes.Len() > 0 && excludes.Has("*") {
errs = append(errs, errors.New("when exclude is '*', include cannot have value"))
}
for _, itm := range excludes.List() {
if includes.Has(itm) {
errs = append(errs, errors.Errorf("excludes list cannot contain an item in the includes list: %v", itm))
}
}
return errs
}
func validateNamespaceName(ns string) []error {
var errs []error
// Velero interprets empty string as "no namespace", so allow it even though
// it is not a valid Kubernetes name.
if ns == "" {
return nil
}
// Validate the namespace name to ensure it is a valid wildcard pattern
if err := wildcard.ValidateNamespaceName(ns); err != nil {
return []error{err}
}
// Kubernetes does not allow wildcard characters in namespaces but Velero uses them
// for glob patterns. Replace wildcard characters with valid characters to pass
// Kubernetes validation.
tmpNamespace := ns
// Replace glob wildcard characters with valid alphanumeric characters
// Note: Validation of wildcard patterns is handled by the wildcard package.
tmpNamespace = strings.ReplaceAll(tmpNamespace, "*", "x") // matches any sequence
tmpNamespace = strings.ReplaceAll(tmpNamespace, "?", "x") // matches single character
tmpNamespace = strings.ReplaceAll(tmpNamespace, "[", "x") // character class start
tmpNamespace = strings.ReplaceAll(tmpNamespace, "]", "x") // character class end
if errMsgs := validation.ValidateNamespaceName(tmpNamespace, false); errMsgs != nil {
for _, msg := range errMsgs {
errs = append(errs, errors.Errorf("invalid namespace %q: %s", ns, msg))
}
}
return errs
}
// generateIncludesExcludes constructs an IncludesExcludes struct by taking the provided
// include/exclude slices, applying the specified mapping function to each item in them,
// and adding the output of the function to the new struct. If the mapping function returns
// an empty string for an item, it is omitted from the result.
func generateIncludesExcludes(includes, excludes []string, mapFunc func(string) string) *IncludesExcludes {
res := NewIncludesExcludes()
for _, item := range includes {
if item == "*" {
res.Includes(item)
continue
}
key := mapFunc(item)
if key == "" {
continue
}
res.Includes(key)
}
for _, item := range excludes {
// wildcards are invalid for excludes,
// so ignore them.
if item == "*" {
continue
}
key := mapFunc(item)
if key == "" {
continue
}
res.Excludes(key)
}
return res
}
// generateScopedIncludesExcludes function is similar with generateIncludesExcludes,
// but it's used for scoped Includes/Excludes.
func generateScopedIncludesExcludes(namespacedIncludes, namespacedExcludes, clusterIncludes, clusterExcludes []string, mapFunc func(string, bool) string, nsIncludesExcludes NamespaceIncludesExcludes, helper discovery.Helper, logger logrus.FieldLogger) *ScopeIncludesExcludes {
res := newScopeIncludesExcludes(nsIncludesExcludes, helper, logger)
generateFilter(res.namespaceScopedResourceFilter.includes, namespacedIncludes, mapFunc, true)
generateFilter(res.namespaceScopedResourceFilter.excludes, namespacedExcludes, mapFunc, true)
generateFilter(res.clusterScopedResourceFilter.includes, clusterIncludes, mapFunc, false)
generateFilter(res.clusterScopedResourceFilter.excludes, clusterExcludes, mapFunc, false)
return res
}
func generateFilter(filter globStringSet, resources []string, mapFunc func(string, bool) string, namespaced bool) {
for _, item := range resources {
if item == "*" {
filter.Insert(item)
continue
}
key := mapFunc(item, namespaced)
if key == "" {
continue
}
filter.Insert(key)
}
}
// UseOldResourceFilters checks whether to use old resource filters (IncludeClusterResources,
// IncludedResources and ExcludedResources), depending the backup's filters setting.
// New filters are IncludedClusterScopedResources, ExcludedClusterScopedResources,
// IncludedNamespaceScopedResources and ExcludedNamespaceScopedResources.
// If all resource filters are none, it is treated as using new parameter filters.
func UseOldResourceFilters(backupSpec velerov1api.BackupSpec) bool {
if backupSpec.IncludeClusterResources != nil ||
len(backupSpec.IncludedResources) > 0 ||
len(backupSpec.ExcludedResources) > 0 {
return true
}
return false
}