selectors cleanup (#3997)
some code movement and removal in selectors and scopes before adding scope-to-reason. Removed code was only being used in testing. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🧹 Tech Debt/Cleanup #### Issue(s) * #3993 #### Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
1fdaa29b3f
commit
67e38faf5e
@ -163,12 +163,11 @@ func (suite *SharePointUnitSuite) TestSharePointBackupCreateSelectors() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
site []string
|
site []string
|
||||||
weburl []string
|
weburl []string
|
||||||
data []string
|
data []string
|
||||||
expect []string
|
expect []string
|
||||||
expectScopesLen int
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no sites or urls",
|
name: "no sites or urls",
|
||||||
@ -181,63 +180,54 @@ func (suite *SharePointUnitSuite) TestSharePointBackupCreateSelectors() {
|
|||||||
expect: selectors.None(),
|
expect: selectors.None(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "site wildcard",
|
name: "site wildcard",
|
||||||
site: []string{flags.Wildcard},
|
site: []string{flags.Wildcard},
|
||||||
expect: bothIDs,
|
expect: bothIDs,
|
||||||
expectScopesLen: 2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "url wildcard",
|
name: "url wildcard",
|
||||||
weburl: []string{flags.Wildcard},
|
weburl: []string{flags.Wildcard},
|
||||||
expect: bothIDs,
|
expect: bothIDs,
|
||||||
expectScopesLen: 2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sites",
|
name: "sites",
|
||||||
site: []string{id1, id2},
|
site: []string{id1, id2},
|
||||||
expect: []string{id1, id2},
|
expect: []string{id1, id2},
|
||||||
expectScopesLen: 2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "urls",
|
name: "urls",
|
||||||
weburl: []string{url1, url2},
|
weburl: []string{url1, url2},
|
||||||
expect: []string{url1, url2},
|
expect: []string{url1, url2},
|
||||||
expectScopesLen: 2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mix sites and urls",
|
name: "mix sites and urls",
|
||||||
site: []string{id1},
|
site: []string{id1},
|
||||||
weburl: []string{url2},
|
weburl: []string{url2},
|
||||||
expect: []string{id1, url2},
|
expect: []string{id1, url2},
|
||||||
expectScopesLen: 2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "duplicate sites and urls",
|
name: "duplicate sites and urls",
|
||||||
site: []string{id1, id2},
|
site: []string{id1, id2},
|
||||||
weburl: []string{url1, url2},
|
weburl: []string{url1, url2},
|
||||||
expect: []string{id1, id2, url1, url2},
|
expect: []string{id1, id2, url1, url2},
|
||||||
expectScopesLen: 2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unnecessary site wildcard",
|
name: "unnecessary site wildcard",
|
||||||
site: []string{id1, flags.Wildcard},
|
site: []string{id1, flags.Wildcard},
|
||||||
weburl: []string{url1, url2},
|
weburl: []string{url1, url2},
|
||||||
expect: bothIDs,
|
expect: bothIDs,
|
||||||
expectScopesLen: 2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unnecessary url wildcard",
|
name: "unnecessary url wildcard",
|
||||||
site: []string{id1},
|
site: []string{id1},
|
||||||
weburl: []string{url1, flags.Wildcard},
|
weburl: []string{url1, flags.Wildcard},
|
||||||
expect: bothIDs,
|
expect: bothIDs,
|
||||||
expectScopesLen: 2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Pages",
|
name: "Pages",
|
||||||
site: bothIDs,
|
site: bothIDs,
|
||||||
data: []string{dataPages},
|
data: []string{dataPages},
|
||||||
expect: bothIDs,
|
expect: bothIDs,
|
||||||
expectScopesLen: 1,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
@ -249,7 +239,7 @@ func (suite *SharePointUnitSuite) TestSharePointBackupCreateSelectors() {
|
|||||||
|
|
||||||
sel, err := sharePointBackupCreateSelectors(ctx, ins, test.site, test.weburl, test.data)
|
sel, err := sharePointBackupCreateSelectors(ctx, ins, test.site, test.weburl, test.data)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
assert.ElementsMatch(t, test.expect, sel.DiscreteResourceOwners())
|
assert.ElementsMatch(t, test.expect, sel.ResourceOwners.Targets)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,7 +69,7 @@ func (s Selector) ToExchangeBackup() (*ExchangeBackup, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s ExchangeBackup) SplitByResourceOwner(users []string) []ExchangeBackup {
|
func (s ExchangeBackup) SplitByResourceOwner(users []string) []ExchangeBackup {
|
||||||
sels := splitByResourceOwner[ExchangeScope](s.Selector, users, ExchangeUser)
|
sels := splitByProtectedResource[ExchangeScope](s.Selector, users, ExchangeUser)
|
||||||
|
|
||||||
ss := make([]ExchangeBackup, 0, len(sels))
|
ss := make([]ExchangeBackup, 0, len(sels))
|
||||||
for _, sel := range sels {
|
for _, sel := range sels {
|
||||||
@ -103,7 +103,7 @@ func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sr ExchangeRestore) SplitByResourceOwner(users []string) []ExchangeRestore {
|
func (sr ExchangeRestore) SplitByResourceOwner(users []string) []ExchangeRestore {
|
||||||
sels := splitByResourceOwner[ExchangeScope](sr.Selector, users, ExchangeUser)
|
sels := splitByProtectedResource[ExchangeScope](sr.Selector, users, ExchangeUser)
|
||||||
|
|
||||||
ss := make([]ExchangeRestore, 0, len(sels))
|
ss := make([]ExchangeRestore, 0, len(sels))
|
||||||
for _, sel := range sels {
|
for _, sel := range sels {
|
||||||
|
|||||||
@ -66,7 +66,7 @@ func (s Selector) ToGroupsBackup() (*GroupsBackup, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s GroupsBackup) SplitByResourceOwner(resources []string) []GroupsBackup {
|
func (s GroupsBackup) SplitByResourceOwner(resources []string) []GroupsBackup {
|
||||||
sels := splitByResourceOwner[GroupsScope](s.Selector, resources, GroupsGroup)
|
sels := splitByProtectedResource[GroupsScope](s.Selector, resources, GroupsGroup)
|
||||||
|
|
||||||
ss := make([]GroupsBackup, 0, len(sels))
|
ss := make([]GroupsBackup, 0, len(sels))
|
||||||
for _, sel := range sels {
|
for _, sel := range sels {
|
||||||
@ -100,7 +100,7 @@ func (s Selector) ToGroupsRestore() (*GroupsRestore, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s GroupsRestore) SplitByResourceOwner(resources []string) []GroupsRestore {
|
func (s GroupsRestore) SplitByResourceOwner(resources []string) []GroupsRestore {
|
||||||
sels := splitByResourceOwner[GroupsScope](s.Selector, resources, GroupsGroup)
|
sels := splitByProtectedResource[GroupsScope](s.Selector, resources, GroupsGroup)
|
||||||
|
|
||||||
ss := make([]GroupsRestore, 0, len(sels))
|
ss := make([]GroupsRestore, 0, len(sels))
|
||||||
for _, sel := range sels {
|
for _, sel := range sels {
|
||||||
|
|||||||
@ -68,7 +68,7 @@ func (s Selector) ToOneDriveBackup() (*OneDriveBackup, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s OneDriveBackup) SplitByResourceOwner(users []string) []OneDriveBackup {
|
func (s OneDriveBackup) SplitByResourceOwner(users []string) []OneDriveBackup {
|
||||||
sels := splitByResourceOwner[OneDriveScope](s.Selector, users, OneDriveUser)
|
sels := splitByProtectedResource[OneDriveScope](s.Selector, users, OneDriveUser)
|
||||||
|
|
||||||
ss := make([]OneDriveBackup, 0, len(sels))
|
ss := make([]OneDriveBackup, 0, len(sels))
|
||||||
for _, sel := range sels {
|
for _, sel := range sels {
|
||||||
@ -102,7 +102,7 @@ func (s Selector) ToOneDriveRestore() (*OneDriveRestore, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s OneDriveRestore) SplitByResourceOwner(users []string) []OneDriveRestore {
|
func (s OneDriveRestore) SplitByResourceOwner(users []string) []OneDriveRestore {
|
||||||
sels := splitByResourceOwner[OneDriveScope](s.Selector, users, OneDriveUser)
|
sels := splitByProtectedResource[OneDriveScope](s.Selector, users, OneDriveUser)
|
||||||
|
|
||||||
ss := make([]OneDriveRestore, 0, len(sels))
|
ss := make([]OneDriveRestore, 0, len(sels))
|
||||||
for _, sel := range sels {
|
for _, sel := range sels {
|
||||||
|
|||||||
@ -43,16 +43,12 @@ func (suite *OneDriveSelectorSuite) TestToOneDriveBackup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveSelectorSuite) TestOneDriveSelector_AllData() {
|
func (suite *OneDriveSelectorSuite) TestOneDriveSelector_AllData() {
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
users = []string{"u1", "u2"}
|
users = []string{"u1", "u2"}
|
||||||
sel = NewOneDriveBackup(users)
|
sel = NewOneDriveBackup(users)
|
||||||
allScopes = sel.AllData()
|
allScopes = sel.AllData()
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.ElementsMatch(t, users, sel.DiscreteResourceOwners())
|
|
||||||
|
|
||||||
// Initialize the selector Include, Exclude, Filter
|
// Initialize the selector Include, Exclude, Filter
|
||||||
sel.Exclude(allScopes)
|
sel.Exclude(allScopes)
|
||||||
sel.Include(allScopes)
|
sel.Include(allScopes)
|
||||||
|
|||||||
@ -161,6 +161,267 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// appendScopes iterates through each scope in the list of scope slices,
|
||||||
|
// calling setDefaults() to ensure it is completely populated, and appends
|
||||||
|
// those scopes to the `to` slice.
|
||||||
|
func appendScopes[T scopeT](to []scope, scopes ...[]T) []scope {
|
||||||
|
if len(to) == 0 {
|
||||||
|
to = []scope{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scopeSl := range scopes {
|
||||||
|
for _, s := range scopeSl {
|
||||||
|
s.setDefaults()
|
||||||
|
to = append(to, scope(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return to
|
||||||
|
}
|
||||||
|
|
||||||
|
// scopes retrieves the list of scopes in the selector.
|
||||||
|
func scopes[T scopeT](s Selector) []T {
|
||||||
|
scopes := []T{}
|
||||||
|
|
||||||
|
for _, v := range s.Includes {
|
||||||
|
scopes = append(scopes, T(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// scope config & constructors
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// constructs the default item-scope comparator options according
|
||||||
|
// to the selector configuration.
|
||||||
|
// - if cfg.OnlyMatchItemNames == false, then comparison assumes item IDs,
|
||||||
|
// which are case sensitive, resulting in StrictEqualsMatch
|
||||||
|
func defaultItemOptions(cfg Config) []option {
|
||||||
|
opts := []option{}
|
||||||
|
|
||||||
|
if !cfg.OnlyMatchItemNames {
|
||||||
|
opts = append(opts, StrictEqualMatch())
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
type scopeConfig struct {
|
||||||
|
usePathFilter bool
|
||||||
|
usePrefixFilter bool
|
||||||
|
useSuffixFilter bool
|
||||||
|
useEqualsFilter bool
|
||||||
|
useStrictEqualsFilter bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type option func(*scopeConfig)
|
||||||
|
|
||||||
|
func (sc *scopeConfig) populate(opts ...option) {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(sc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrefixMatch ensures the selector uses a Prefix comparator, instead
|
||||||
|
// of contains or equals. Will not override a default Any() or None()
|
||||||
|
// comparator.
|
||||||
|
func PrefixMatch() option {
|
||||||
|
return func(sc *scopeConfig) {
|
||||||
|
sc.usePrefixFilter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuffixMatch ensures the selector uses a Suffix comparator, instead
|
||||||
|
// of contains or equals. Will not override a default Any() or None()
|
||||||
|
// comparator.
|
||||||
|
func SuffixMatch() option {
|
||||||
|
return func(sc *scopeConfig) {
|
||||||
|
sc.useSuffixFilter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictEqualsMatch ensures the selector uses a StrictEquals comparator, instead
|
||||||
|
// of contains. Will not override a default Any() or None() comparator.
|
||||||
|
func StrictEqualMatch() option {
|
||||||
|
return func(sc *scopeConfig) {
|
||||||
|
sc.useStrictEqualsFilter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExactMatch ensures the selector uses an Equals comparator, instead
|
||||||
|
// of contains. Will not override a default Any() or None() comparator.
|
||||||
|
func ExactMatch() option {
|
||||||
|
return func(sc *scopeConfig) {
|
||||||
|
sc.useEqualsFilter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathComparator is an internal-facing option. It is assumed that scope
|
||||||
|
// constructors will provide the pathComparator option whenever a folder-
|
||||||
|
// level scope (ie, a scope that compares path hierarchies) is created.
|
||||||
|
func pathComparator() option {
|
||||||
|
return func(sc *scopeConfig) {
|
||||||
|
sc.usePathFilter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func badCastErr(cast, is service) error {
|
||||||
|
return clues.Stack(ErrorBadSelectorCast, clues.New(fmt.Sprintf("%s is not %s", cast, is)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the provided slice contains Any, returns [Any]
|
||||||
|
// if the slice contains None, returns [None]
|
||||||
|
// if the slice contains Any and None, returns the first
|
||||||
|
// if the slice is empty, returns [None]
|
||||||
|
// otherwise returns the input
|
||||||
|
func clean(s []string) []string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return None()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range s {
|
||||||
|
if e == AnyTgt {
|
||||||
|
return Any()
|
||||||
|
}
|
||||||
|
|
||||||
|
if e == NoneTgt {
|
||||||
|
return None()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type filterFunc func([]string) filters.Filter
|
||||||
|
|
||||||
|
// filterize turns the slice into a filter.
|
||||||
|
// if the input is Any(), returns a passAny filter.
|
||||||
|
// if the input is None(), returns a failAny filter.
|
||||||
|
// if the scopeConfig specifies a filter, use that filter.
|
||||||
|
// if the input is len(1), returns an Equals filter.
|
||||||
|
// otherwise returns a Contains filter.
|
||||||
|
func filterFor(sc scopeConfig, targets ...string) filters.Filter {
|
||||||
|
return filterize(sc, nil, targets...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterize turns the slice into a filter.
|
||||||
|
// if the input is Any(), returns a passAny filter.
|
||||||
|
// if the input is None(), returns a failAny filter.
|
||||||
|
// if the scopeConfig specifies a filter, use that filter.
|
||||||
|
// if defaultFilter is non-nil, returns that filter.
|
||||||
|
// if the input is len(1), returns an Equals filter.
|
||||||
|
// otherwise returns a Contains filter.
|
||||||
|
func filterize(
|
||||||
|
sc scopeConfig,
|
||||||
|
defaultFilter filterFunc,
|
||||||
|
targets ...string,
|
||||||
|
) filters.Filter {
|
||||||
|
targets = clean(targets)
|
||||||
|
|
||||||
|
if len(targets) == 0 || targets[0] == NoneTgt {
|
||||||
|
return failAny
|
||||||
|
}
|
||||||
|
|
||||||
|
if targets[0] == AnyTgt {
|
||||||
|
return passAny
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc.usePathFilter {
|
||||||
|
if sc.useEqualsFilter {
|
||||||
|
return filters.PathEquals(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc.usePrefixFilter {
|
||||||
|
return filters.PathPrefix(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc.useSuffixFilter {
|
||||||
|
return filters.PathSuffix(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters.PathContains(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc.usePrefixFilter {
|
||||||
|
return filters.Prefix(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc.useSuffixFilter {
|
||||||
|
return filters.Suffix(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc.useStrictEqualsFilter {
|
||||||
|
return filters.StrictEqual(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultFilter != nil {
|
||||||
|
return defaultFilter(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters.Equal(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathFilterFactory returns the appropriate path filter
|
||||||
|
// (contains, prefix, or suffix) for the provided options.
|
||||||
|
// If multiple options are flagged, Prefix takes priority.
|
||||||
|
// If no options are provided, returns PathContains.
|
||||||
|
func pathFilterFactory(opts ...option) filterFunc {
|
||||||
|
sc := &scopeConfig{}
|
||||||
|
sc.populate(opts...)
|
||||||
|
|
||||||
|
var ff filterFunc
|
||||||
|
|
||||||
|
switch true {
|
||||||
|
case sc.usePrefixFilter:
|
||||||
|
ff = filters.PathPrefix
|
||||||
|
case sc.useSuffixFilter:
|
||||||
|
ff = filters.PathSuffix
|
||||||
|
case sc.useEqualsFilter:
|
||||||
|
ff = filters.PathEquals
|
||||||
|
default:
|
||||||
|
ff = filters.PathContains
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapSliceFilter(ff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapSliceFilter(ff filterFunc) filterFunc {
|
||||||
|
return func(s []string) filters.Filter {
|
||||||
|
s = clean(s)
|
||||||
|
|
||||||
|
if f, ok := isAnyOrNone(s); ok {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns (<filter>, true) if s is len==1 and s[0] is
|
||||||
|
// anyTgt or noneTgt, implying that the caller should use
|
||||||
|
// the returned filter. On (<filter>, false), the caller
|
||||||
|
// can ignore the returned filter.
|
||||||
|
// a special case exists for len(s)==0, interpreted as
|
||||||
|
// "noneTgt"
|
||||||
|
func isAnyOrNone(s []string) (filters.Filter, bool) {
|
||||||
|
switch len(s) {
|
||||||
|
case 0:
|
||||||
|
return failAny, true
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
switch s[0] {
|
||||||
|
case AnyTgt:
|
||||||
|
return passAny, true
|
||||||
|
case NoneTgt:
|
||||||
|
return failAny, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return failAny, false
|
||||||
|
}
|
||||||
|
|
||||||
// makeScope produces a well formatted, typed scope that ensures all base values are populated.
|
// makeScope produces a well formatted, typed scope that ensures all base values are populated.
|
||||||
func makeScope[T scopeT](
|
func makeScope[T scopeT](
|
||||||
cat categorizer,
|
cat categorizer,
|
||||||
@ -239,95 +500,9 @@ func marshalScope(mss map[string]string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// scope funcs
|
// reducer & filtering
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// matches returns true if the category is included in the scope's
|
|
||||||
// data type, and the input string passes the scope's filter for
|
|
||||||
// that category.
|
|
||||||
func matches[T scopeT, C categoryT](s T, cat C, inpt string) bool {
|
|
||||||
if !typeAndCategoryMatches(cat, s.categorizer()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(inpt) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return s[cat.String()].Compare(inpt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchesAny returns true if the category is included in the scope's
|
|
||||||
// data type, and any one of the input strings passes the scope's filter.
|
|
||||||
func matchesAny[T scopeT, C categoryT](s T, cat C, inpts []string) bool {
|
|
||||||
if !typeAndCategoryMatches(cat, s.categorizer()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(inpts) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return s[cat.String()].CompareAny(inpts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCategory returns the scope's category value.
|
|
||||||
// if s is an info-type scope, returns the info category.
|
|
||||||
func getCategory[T scopeT](s T) string {
|
|
||||||
return s[scopeKeyCategory].Identity
|
|
||||||
}
|
|
||||||
|
|
||||||
// getInfoCategory returns the scope's infoFilter category value.
|
|
||||||
func getInfoCategory[T scopeT](s T) string {
|
|
||||||
return s[scopeKeyInfoCategory].Identity
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCatValue takes the value of s[cat] and returns the slice.
|
|
||||||
// If s[cat] is nil, returns None().
|
|
||||||
func getCatValue[T scopeT](s T, cat categorizer) []string {
|
|
||||||
filt, ok := s[cat.String()]
|
|
||||||
if !ok {
|
|
||||||
return None()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filt.Targets) > 0 {
|
|
||||||
return filt.Targets
|
|
||||||
}
|
|
||||||
|
|
||||||
return filt.Targets
|
|
||||||
}
|
|
||||||
|
|
||||||
// set sets a value by category to the scope. Only intended for internal
|
|
||||||
// use, not for exporting to callers.
|
|
||||||
func set[T scopeT](s T, cat categorizer, v []string, opts ...option) T {
|
|
||||||
sc := &scopeConfig{}
|
|
||||||
sc.populate(opts...)
|
|
||||||
|
|
||||||
s[cat.String()] = filterFor(*sc, v...)
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns true if the category is included in the scope's category type,
|
|
||||||
// and the value is set to None().
|
|
||||||
func isNoneTarget[T scopeT, C categoryT](s T, cat C) bool {
|
|
||||||
if !typeAndCategoryMatches(cat, s.categorizer()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return s[cat.String()].Comparator == filters.Fails
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns true if the category is included in the scope's category type,
|
|
||||||
// and the value is set to Any().
|
|
||||||
func isAnyTarget[T scopeT, C categoryT](s T, cat C) bool {
|
|
||||||
if !typeAndCategoryMatches(cat, s.categorizer()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return s[cat.String()].Comparator == filters.Passes
|
|
||||||
}
|
|
||||||
|
|
||||||
// reduce filters the entries in the details to only those that match the
|
// reduce filters the entries in the details to only those that match the
|
||||||
// inclusions, filters, and exclusions in the selector.
|
// inclusions, filters, and exclusions in the selector.
|
||||||
func reduce[T scopeT, C categoryT](
|
func reduce[T scopeT, C categoryT](
|
||||||
@ -542,6 +717,92 @@ func matchesPathValues[T scopeT, C categoryT](
|
|||||||
// helper funcs
|
// helper funcs
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// matches returns true if the category is included in the scope's
|
||||||
|
// data type, and the input string passes the scope's filter for
|
||||||
|
// that category.
|
||||||
|
func matches[T scopeT, C categoryT](s T, cat C, inpt string) bool {
|
||||||
|
if !typeAndCategoryMatches(cat, s.categorizer()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(inpt) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s[cat.String()].Compare(inpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchesAny returns true if the category is included in the scope's
|
||||||
|
// data type, and any one of the input strings passes the scope's filter.
|
||||||
|
func matchesAny[T scopeT, C categoryT](s T, cat C, inpts []string) bool {
|
||||||
|
if !typeAndCategoryMatches(cat, s.categorizer()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(inpts) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s[cat.String()].CompareAny(inpts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCategory returns the scope's category value.
|
||||||
|
// if s is an info-type scope, returns the info category.
|
||||||
|
func getCategory[T scopeT](s T) string {
|
||||||
|
return s[scopeKeyCategory].Identity
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInfoCategory returns the scope's infoFilter category value.
|
||||||
|
func getInfoCategory[T scopeT](s T) string {
|
||||||
|
return s[scopeKeyInfoCategory].Identity
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCatValue takes the value of s[cat] and returns the slice.
|
||||||
|
// If s[cat] is nil, returns None().
|
||||||
|
func getCatValue[T scopeT](s T, cat categorizer) []string {
|
||||||
|
filt, ok := s[cat.String()]
|
||||||
|
if !ok {
|
||||||
|
return None()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filt.Targets) > 0 {
|
||||||
|
return filt.Targets
|
||||||
|
}
|
||||||
|
|
||||||
|
return filt.Targets
|
||||||
|
}
|
||||||
|
|
||||||
|
// set sets a value by category to the scope. Only intended for internal
|
||||||
|
// use, not for exporting to callers.
|
||||||
|
func set[T scopeT](s T, cat categorizer, v []string, opts ...option) T {
|
||||||
|
sc := &scopeConfig{}
|
||||||
|
sc.populate(opts...)
|
||||||
|
|
||||||
|
s[cat.String()] = filterFor(*sc, v...)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if the category is included in the scope's category type,
|
||||||
|
// and the value is set to None().
|
||||||
|
func isNoneTarget[T scopeT, C categoryT](s T, cat C) bool {
|
||||||
|
if !typeAndCategoryMatches(cat, s.categorizer()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s[cat.String()].Comparator == filters.Fails
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if the category is included in the scope's category type,
|
||||||
|
// and the value is set to Any().
|
||||||
|
func isAnyTarget[T scopeT, C categoryT](s T, cat C) bool {
|
||||||
|
if !typeAndCategoryMatches(cat, s.categorizer()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s[cat.String()].Comparator == filters.Passes
|
||||||
|
}
|
||||||
|
|
||||||
// categoryMatches returns true if:
|
// categoryMatches returns true if:
|
||||||
// - neither type is 'unknown'
|
// - neither type is 'unknown'
|
||||||
// - either type is the root type
|
// - either type is the root type
|
||||||
|
|||||||
@ -159,11 +159,25 @@ func (s *Selector) Configure(cfg Config) {
|
|||||||
s.Cfg = cfg
|
s.Cfg = cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscreteResourceOwners returns the list of individual resourceOwners used
|
// ---------------------------------------------------------------------------
|
||||||
// in the selector.
|
// protected resources & idname provider compliance
|
||||||
// TODO(rkeepers): remove in favor of split and s.DiscreteOwner
|
// ---------------------------------------------------------------------------
|
||||||
func (s Selector) DiscreteResourceOwners() []string {
|
|
||||||
return s.ResourceOwners.Targets
|
var _ idname.Provider = &Selector{}
|
||||||
|
|
||||||
|
// ID returns s.discreteOwner, which is assumed to be a stable ID.
|
||||||
|
func (s Selector) ID() string {
|
||||||
|
return s.DiscreteOwner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns s.discreteOwnerName. If that value is empty, it returns
|
||||||
|
// s.DiscreteOwner instead.
|
||||||
|
func (s Selector) Name() string {
|
||||||
|
if len(s.DiscreteOwnerName) == 0 {
|
||||||
|
return s.DiscreteOwner
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.DiscreteOwnerName
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDiscreteOwnerIDName ensures the selector has the correct discrete owner
|
// SetDiscreteOwnerIDName ensures the selector has the correct discrete owner
|
||||||
@ -193,32 +207,17 @@ func (s Selector) SetDiscreteOwnerIDName(id, name string) Selector {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID returns s.discreteOwner, which is assumed to be a stable ID.
|
// isAnyProtectedResource returns true if the selector includes all resource owners.
|
||||||
func (s Selector) ID() string {
|
func isAnyProtectedResource(s Selector) bool {
|
||||||
return s.DiscreteOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns s.discreteOwnerName. If that value is empty, it returns
|
|
||||||
// s.DiscreteOwner instead.
|
|
||||||
func (s Selector) Name() string {
|
|
||||||
if len(s.DiscreteOwnerName) == 0 {
|
|
||||||
return s.DiscreteOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.DiscreteOwnerName
|
|
||||||
}
|
|
||||||
|
|
||||||
// isAnyResourceOwner returns true if the selector includes all resource owners.
|
|
||||||
func isAnyResourceOwner(s Selector) bool {
|
|
||||||
return s.ResourceOwners.Comparator == filters.Passes
|
return s.ResourceOwners.Comparator == filters.Passes
|
||||||
}
|
}
|
||||||
|
|
||||||
// isNoneResourceOwner returns true if the selector includes no resource owners.
|
// isNoneProtectedResource returns true if the selector includes no resource owners.
|
||||||
func isNoneResourceOwner(s Selector) bool {
|
func isNoneProtectedResource(s Selector) bool {
|
||||||
return s.ResourceOwners.Comparator == filters.Fails
|
return s.ResourceOwners.Comparator == filters.Fails
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitByResourceOwner makes one shallow clone for each resourceOwner in the
|
// splitByProtectedResource makes one shallow clone for each resourceOwner in the
|
||||||
// selector, specifying a new DiscreteOwner for each one.
|
// selector, specifying a new DiscreteOwner for each one.
|
||||||
// If the original selector already specified a discrete slice of resource owners,
|
// If the original selector already specified a discrete slice of resource owners,
|
||||||
// only those owners are used in the result.
|
// only those owners are used in the result.
|
||||||
@ -230,14 +229,14 @@ func isNoneResourceOwner(s Selector) bool {
|
|||||||
//
|
//
|
||||||
// temporarily, clones all scopes in each selector and replaces the owners with
|
// temporarily, clones all scopes in each selector and replaces the owners with
|
||||||
// the discrete owner.
|
// the discrete owner.
|
||||||
func splitByResourceOwner[T scopeT, C categoryT](s Selector, allOwners []string, rootCat C) []Selector {
|
func splitByProtectedResource[T scopeT, C categoryT](s Selector, allOwners []string, rootCat C) []Selector {
|
||||||
if isNoneResourceOwner(s) {
|
if isNoneProtectedResource(s) {
|
||||||
return []Selector{}
|
return []Selector{}
|
||||||
}
|
}
|
||||||
|
|
||||||
targets := allOwners
|
targets := allOwners
|
||||||
|
|
||||||
if !isAnyResourceOwner(s) {
|
if !isAnyProtectedResource(s) {
|
||||||
targets = s.ResourceOwners.Targets
|
targets = s.ResourceOwners.Targets
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,35 +251,6 @@ func splitByResourceOwner[T scopeT, C categoryT](s Selector, allOwners []string,
|
|||||||
return ss
|
return ss
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendScopes iterates through each scope in the list of scope slices,
|
|
||||||
// calling setDefaults() to ensure it is completely populated, and appends
|
|
||||||
// those scopes to the `to` slice.
|
|
||||||
func appendScopes[T scopeT](to []scope, scopes ...[]T) []scope {
|
|
||||||
if len(to) == 0 {
|
|
||||||
to = []scope{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, scopeSl := range scopes {
|
|
||||||
for _, s := range scopeSl {
|
|
||||||
s.setDefaults()
|
|
||||||
to = append(to, scope(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return to
|
|
||||||
}
|
|
||||||
|
|
||||||
// scopes retrieves the list of scopes in the selector.
|
|
||||||
func scopes[T scopeT](s Selector) []T {
|
|
||||||
scopes := []T{}
|
|
||||||
|
|
||||||
for _, v := range s.Includes {
|
|
||||||
scopes = append(scopes, T(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
return scopes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the path.ServiceType matching the selector service.
|
// Returns the path.ServiceType matching the selector service.
|
||||||
func (s Selector) PathService() path.ServiceType {
|
func (s Selector) PathService() path.ServiceType {
|
||||||
return serviceToPathType[s.Service]
|
return serviceToPathType[s.Service]
|
||||||
@ -331,6 +301,9 @@ func selectorAsIface[T any](s Selector) (T, error) {
|
|||||||
case ServiceSharePoint:
|
case ServiceSharePoint:
|
||||||
a, err = func() (any, error) { return s.ToSharePointRestore() }()
|
a, err = func() (any, error) { return s.ToSharePointRestore() }()
|
||||||
t = a.(T)
|
t = a.(T)
|
||||||
|
case ServiceGroups:
|
||||||
|
a, err = func() (any, error) { return s.ToGroupsRestore() }()
|
||||||
|
t = a.(T)
|
||||||
default:
|
default:
|
||||||
err = clues.Stack(ErrorUnrecognizedService, clues.New(s.Service.String()))
|
err = clues.Stack(ErrorUnrecognizedService, clues.New(s.Service.String()))
|
||||||
}
|
}
|
||||||
@ -420,28 +393,6 @@ func (ls loggableSelector) marshal() string {
|
|||||||
// helpers
|
// helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// produces the discrete set of resource owners in the slice of scopes.
|
|
||||||
// Any and None values are discarded.
|
|
||||||
func resourceOwnersIn(s []scope, rootCat string) []string {
|
|
||||||
rm := map[string]struct{}{}
|
|
||||||
|
|
||||||
for _, sc := range s {
|
|
||||||
for _, v := range sc[rootCat].Targets {
|
|
||||||
rm[v] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rs := []string{}
|
|
||||||
|
|
||||||
for k := range rm {
|
|
||||||
if k != AnyTgt && k != NoneTgt {
|
|
||||||
rs = append(rs, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs
|
|
||||||
}
|
|
||||||
|
|
||||||
// produces the discrete set of path categories in the slice of scopes.
|
// produces the discrete set of path categories in the slice of scopes.
|
||||||
func pathCategoriesIn[T scopeT, C categoryT](ss []scope) []path.CategoryType {
|
func pathCategoriesIn[T scopeT, C categoryT](ss []scope) []path.CategoryType {
|
||||||
m := map[path.CategoryType]struct{}{}
|
m := map[path.CategoryType]struct{}{}
|
||||||
@ -459,235 +410,3 @@ func pathCategoriesIn[T scopeT, C categoryT](ss []scope) []path.CategoryType {
|
|||||||
|
|
||||||
return maps.Keys(m)
|
return maps.Keys(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// scope constructors
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// constructs the default item-scope comparator options according
|
|
||||||
// to the selector configuration.
|
|
||||||
// - if cfg.OnlyMatchItemNames == false, then comparison assumes item IDs,
|
|
||||||
// which are case sensitive, resulting in StrictEqualsMatch
|
|
||||||
func defaultItemOptions(cfg Config) []option {
|
|
||||||
opts := []option{}
|
|
||||||
|
|
||||||
if !cfg.OnlyMatchItemNames {
|
|
||||||
opts = append(opts, StrictEqualMatch())
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts
|
|
||||||
}
|
|
||||||
|
|
||||||
type scopeConfig struct {
|
|
||||||
usePathFilter bool
|
|
||||||
usePrefixFilter bool
|
|
||||||
useSuffixFilter bool
|
|
||||||
useEqualsFilter bool
|
|
||||||
useStrictEqualsFilter bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type option func(*scopeConfig)
|
|
||||||
|
|
||||||
func (sc *scopeConfig) populate(opts ...option) {
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(sc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefixMatch ensures the selector uses a Prefix comparator, instead
|
|
||||||
// of contains or equals. Will not override a default Any() or None()
|
|
||||||
// comparator.
|
|
||||||
func PrefixMatch() option {
|
|
||||||
return func(sc *scopeConfig) {
|
|
||||||
sc.usePrefixFilter = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuffixMatch ensures the selector uses a Suffix comparator, instead
|
|
||||||
// of contains or equals. Will not override a default Any() or None()
|
|
||||||
// comparator.
|
|
||||||
func SuffixMatch() option {
|
|
||||||
return func(sc *scopeConfig) {
|
|
||||||
sc.useSuffixFilter = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictEqualsMatch ensures the selector uses a StrictEquals comparator, instead
|
|
||||||
// of contains. Will not override a default Any() or None() comparator.
|
|
||||||
func StrictEqualMatch() option {
|
|
||||||
return func(sc *scopeConfig) {
|
|
||||||
sc.useStrictEqualsFilter = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExactMatch ensures the selector uses an Equals comparator, instead
|
|
||||||
// of contains. Will not override a default Any() or None() comparator.
|
|
||||||
func ExactMatch() option {
|
|
||||||
return func(sc *scopeConfig) {
|
|
||||||
sc.useEqualsFilter = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pathComparator is an internal-facing option. It is assumed that scope
|
|
||||||
// constructors will provide the pathComparator option whenever a folder-
|
|
||||||
// level scope (ie, a scope that compares path hierarchies) is created.
|
|
||||||
func pathComparator() option {
|
|
||||||
return func(sc *scopeConfig) {
|
|
||||||
sc.usePathFilter = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func badCastErr(cast, is service) error {
|
|
||||||
return clues.Stack(ErrorBadSelectorCast, clues.New(fmt.Sprintf("%s is not %s", cast, is)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the provided slice contains Any, returns [Any]
|
|
||||||
// if the slice contains None, returns [None]
|
|
||||||
// if the slice contains Any and None, returns the first
|
|
||||||
// if the slice is empty, returns [None]
|
|
||||||
// otherwise returns the input
|
|
||||||
func clean(s []string) []string {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return None()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range s {
|
|
||||||
if e == AnyTgt {
|
|
||||||
return Any()
|
|
||||||
}
|
|
||||||
|
|
||||||
if e == NoneTgt {
|
|
||||||
return None()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
type filterFunc func([]string) filters.Filter
|
|
||||||
|
|
||||||
// filterize turns the slice into a filter.
|
|
||||||
// if the input is Any(), returns a passAny filter.
|
|
||||||
// if the input is None(), returns a failAny filter.
|
|
||||||
// if the scopeConfig specifies a filter, use that filter.
|
|
||||||
// if the input is len(1), returns an Equals filter.
|
|
||||||
// otherwise returns a Contains filter.
|
|
||||||
func filterFor(sc scopeConfig, targets ...string) filters.Filter {
|
|
||||||
return filterize(sc, nil, targets...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterize turns the slice into a filter.
|
|
||||||
// if the input is Any(), returns a passAny filter.
|
|
||||||
// if the input is None(), returns a failAny filter.
|
|
||||||
// if the scopeConfig specifies a filter, use that filter.
|
|
||||||
// if defaultFilter is non-nil, returns that filter.
|
|
||||||
// if the input is len(1), returns an Equals filter.
|
|
||||||
// otherwise returns a Contains filter.
|
|
||||||
func filterize(
|
|
||||||
sc scopeConfig,
|
|
||||||
defaultFilter filterFunc,
|
|
||||||
targets ...string,
|
|
||||||
) filters.Filter {
|
|
||||||
targets = clean(targets)
|
|
||||||
|
|
||||||
if len(targets) == 0 || targets[0] == NoneTgt {
|
|
||||||
return failAny
|
|
||||||
}
|
|
||||||
|
|
||||||
if targets[0] == AnyTgt {
|
|
||||||
return passAny
|
|
||||||
}
|
|
||||||
|
|
||||||
if sc.usePathFilter {
|
|
||||||
if sc.useEqualsFilter {
|
|
||||||
return filters.PathEquals(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sc.usePrefixFilter {
|
|
||||||
return filters.PathPrefix(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sc.useSuffixFilter {
|
|
||||||
return filters.PathSuffix(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters.PathContains(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sc.usePrefixFilter {
|
|
||||||
return filters.Prefix(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sc.useSuffixFilter {
|
|
||||||
return filters.Suffix(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sc.useStrictEqualsFilter {
|
|
||||||
return filters.StrictEqual(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
if defaultFilter != nil {
|
|
||||||
return defaultFilter(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters.Equal(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pathFilterFactory returns the appropriate path filter
|
|
||||||
// (contains, prefix, or suffix) for the provided options.
|
|
||||||
// If multiple options are flagged, Prefix takes priority.
|
|
||||||
// If no options are provided, returns PathContains.
|
|
||||||
func pathFilterFactory(opts ...option) filterFunc {
|
|
||||||
sc := &scopeConfig{}
|
|
||||||
sc.populate(opts...)
|
|
||||||
|
|
||||||
var ff filterFunc
|
|
||||||
|
|
||||||
switch true {
|
|
||||||
case sc.usePrefixFilter:
|
|
||||||
ff = filters.PathPrefix
|
|
||||||
case sc.useSuffixFilter:
|
|
||||||
ff = filters.PathSuffix
|
|
||||||
case sc.useEqualsFilter:
|
|
||||||
ff = filters.PathEquals
|
|
||||||
default:
|
|
||||||
ff = filters.PathContains
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapSliceFilter(ff)
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapSliceFilter(ff filterFunc) filterFunc {
|
|
||||||
return func(s []string) filters.Filter {
|
|
||||||
s = clean(s)
|
|
||||||
|
|
||||||
if f, ok := isAnyOrNone(s); ok {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
return ff(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns (<filter>, true) if s is len==1 and s[0] is
|
|
||||||
// anyTgt or noneTgt, implying that the caller should use
|
|
||||||
// the returned filter. On (<filter>, false), the caller
|
|
||||||
// can ignore the returned filter.
|
|
||||||
// a special case exists for len(s)==0, interpreted as
|
|
||||||
// "noneTgt"
|
|
||||||
func isAnyOrNone(s []string) (filters.Filter, bool) {
|
|
||||||
switch len(s) {
|
|
||||||
case 0:
|
|
||||||
return failAny, true
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
switch s[0] {
|
|
||||||
case AnyTgt:
|
|
||||||
return passAny, true
|
|
||||||
case NoneTgt:
|
|
||||||
return failAny, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return failAny, false
|
|
||||||
}
|
|
||||||
|
|||||||
@ -44,56 +44,6 @@ func (suite *SelectorSuite) TestBadCastErr() {
|
|||||||
assert.Error(suite.T(), err, clues.ToCore(err))
|
assert.Error(suite.T(), err, clues.ToCore(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SelectorSuite) TestResourceOwnersIn() {
|
|
||||||
rootCat := rootCatStub.String()
|
|
||||||
|
|
||||||
table := []struct {
|
|
||||||
name string
|
|
||||||
input []scope
|
|
||||||
expect []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nil",
|
|
||||||
input: nil,
|
|
||||||
expect: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
input: []scope{},
|
|
||||||
expect: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "single",
|
|
||||||
input: []scope{{rootCat: filters.Identity("foo")}},
|
|
||||||
expect: []string{"foo"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple scopes",
|
|
||||||
input: []scope{
|
|
||||||
{rootCat: filters.Identity("foo,bar")},
|
|
||||||
{rootCat: filters.Identity("baz")},
|
|
||||||
},
|
|
||||||
expect: []string{"foo,bar", "baz"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple scopes with duplicates",
|
|
||||||
input: []scope{
|
|
||||||
{rootCat: filters.Identity("foo")},
|
|
||||||
{rootCat: filters.Identity("foo")},
|
|
||||||
},
|
|
||||||
expect: []string{"foo"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range table {
|
|
||||||
suite.Run(test.name, func() {
|
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
result := resourceOwnersIn(test.input, rootCat)
|
|
||||||
assert.ElementsMatch(t, test.expect, result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *SelectorSuite) TestPathCategoriesIn() {
|
func (suite *SelectorSuite) TestPathCategoriesIn() {
|
||||||
leafCat := leafCatStub.String()
|
leafCat := leafCatStub.String()
|
||||||
f := filters.Identity(leafCat)
|
f := filters.Identity(leafCat)
|
||||||
@ -144,20 +94,20 @@ func (suite *SelectorSuite) TestContains() {
|
|||||||
|
|
||||||
func (suite *SelectorSuite) TestIsAnyResourceOwner() {
|
func (suite *SelectorSuite) TestIsAnyResourceOwner() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
assert.False(t, isAnyResourceOwner(newSelector(ServiceUnknown, []string{"foo"})))
|
assert.False(t, isAnyProtectedResource(newSelector(ServiceUnknown, []string{"foo"})))
|
||||||
assert.False(t, isAnyResourceOwner(newSelector(ServiceUnknown, []string{})))
|
assert.False(t, isAnyProtectedResource(newSelector(ServiceUnknown, []string{})))
|
||||||
assert.False(t, isAnyResourceOwner(newSelector(ServiceUnknown, nil)))
|
assert.False(t, isAnyProtectedResource(newSelector(ServiceUnknown, nil)))
|
||||||
assert.True(t, isAnyResourceOwner(newSelector(ServiceUnknown, []string{AnyTgt})))
|
assert.True(t, isAnyProtectedResource(newSelector(ServiceUnknown, []string{AnyTgt})))
|
||||||
assert.True(t, isAnyResourceOwner(newSelector(ServiceUnknown, Any())))
|
assert.True(t, isAnyProtectedResource(newSelector(ServiceUnknown, Any())))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SelectorSuite) TestIsNoneResourceOwner() {
|
func (suite *SelectorSuite) TestIsNoneResourceOwner() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
assert.False(t, isNoneResourceOwner(newSelector(ServiceUnknown, []string{"foo"})))
|
assert.False(t, isNoneProtectedResource(newSelector(ServiceUnknown, []string{"foo"})))
|
||||||
assert.True(t, isNoneResourceOwner(newSelector(ServiceUnknown, []string{})))
|
assert.True(t, isNoneProtectedResource(newSelector(ServiceUnknown, []string{})))
|
||||||
assert.True(t, isNoneResourceOwner(newSelector(ServiceUnknown, nil)))
|
assert.True(t, isNoneProtectedResource(newSelector(ServiceUnknown, nil)))
|
||||||
assert.True(t, isNoneResourceOwner(newSelector(ServiceUnknown, []string{NoneTgt})))
|
assert.True(t, isNoneProtectedResource(newSelector(ServiceUnknown, []string{NoneTgt})))
|
||||||
assert.True(t, isNoneResourceOwner(newSelector(ServiceUnknown, None())))
|
assert.True(t, isNoneProtectedResource(newSelector(ServiceUnknown, None())))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SelectorSuite) TestSplitByResourceOnwer() {
|
func (suite *SelectorSuite) TestSplitByResourceOnwer() {
|
||||||
@ -224,7 +174,7 @@ func (suite *SelectorSuite) TestSplitByResourceOnwer() {
|
|||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
s := newSelector(ServiceUnknown, test.input)
|
s := newSelector(ServiceUnknown, test.input)
|
||||||
result := splitByResourceOwner[mockScope](s, allOwners, rootCatStub)
|
result := splitByProtectedResource[mockScope](s, allOwners, rootCatStub)
|
||||||
|
|
||||||
assert.Len(t, result, test.expectLen)
|
assert.Len(t, result, test.expectLen)
|
||||||
|
|
||||||
|
|||||||
@ -68,7 +68,7 @@ func (s Selector) ToSharePointBackup() (*SharePointBackup, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s SharePointBackup) SplitByResourceOwner(sites []string) []SharePointBackup {
|
func (s SharePointBackup) SplitByResourceOwner(sites []string) []SharePointBackup {
|
||||||
sels := splitByResourceOwner[SharePointScope](s.Selector, sites, SharePointSite)
|
sels := splitByProtectedResource[SharePointScope](s.Selector, sites, SharePointSite)
|
||||||
|
|
||||||
ss := make([]SharePointBackup, 0, len(sels))
|
ss := make([]SharePointBackup, 0, len(sels))
|
||||||
for _, sel := range sels {
|
for _, sel := range sels {
|
||||||
@ -102,7 +102,7 @@ func (s Selector) ToSharePointRestore() (*SharePointRestore, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s SharePointRestore) SplitByResourceOwner(sites []string) []SharePointRestore {
|
func (s SharePointRestore) SplitByResourceOwner(sites []string) []SharePointRestore {
|
||||||
sels := splitByResourceOwner[SharePointScope](s.Selector, sites, SharePointSite)
|
sels := splitByProtectedResource[SharePointScope](s.Selector, sites, SharePointSite)
|
||||||
|
|
||||||
ss := make([]SharePointRestore, 0, len(sels))
|
ss := make([]SharePointRestore, 0, len(sels))
|
||||||
for _, sel := range sels {
|
for _, sel := range sels {
|
||||||
|
|||||||
@ -44,66 +44,6 @@ func (suite *SharePointSelectorSuite) TestToSharePointBackup() {
|
|||||||
assert.NotZero(t, ob.Scopes())
|
assert.NotZero(t, ob.Scopes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointSelectorSuite) TestSharePointSelector_AllData() {
|
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
sites := []string{"s1", "s2"}
|
|
||||||
|
|
||||||
sel := NewSharePointBackup(sites)
|
|
||||||
siteScopes := sel.AllData()
|
|
||||||
|
|
||||||
assert.ElementsMatch(t, sites, sel.DiscreteResourceOwners())
|
|
||||||
|
|
||||||
// Initialize the selector Include, Exclude, Filter
|
|
||||||
sel.Exclude(siteScopes)
|
|
||||||
sel.Include(siteScopes)
|
|
||||||
sel.Filter(siteScopes)
|
|
||||||
|
|
||||||
table := []struct {
|
|
||||||
name string
|
|
||||||
scopesToCheck []scope
|
|
||||||
}{
|
|
||||||
{"Include Scopes", sel.Includes},
|
|
||||||
{"Exclude Scopes", sel.Excludes},
|
|
||||||
{"info scopes", sel.Filters},
|
|
||||||
}
|
|
||||||
for _, test := range table {
|
|
||||||
require.Len(t, test.scopesToCheck, 3)
|
|
||||||
|
|
||||||
for _, scope := range test.scopesToCheck {
|
|
||||||
var (
|
|
||||||
spsc = SharePointScope(scope)
|
|
||||||
cat = spsc.Category()
|
|
||||||
)
|
|
||||||
|
|
||||||
suite.Run(test.name+"-"+cat.String(), func() {
|
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
switch cat {
|
|
||||||
case SharePointLibraryItem:
|
|
||||||
scopeMustHave(
|
|
||||||
t,
|
|
||||||
spsc,
|
|
||||||
map[categorizer][]string{
|
|
||||||
SharePointLibraryItem: Any(),
|
|
||||||
SharePointLibraryFolder: Any(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
case SharePointListItem:
|
|
||||||
scopeMustHave(
|
|
||||||
t,
|
|
||||||
spsc,
|
|
||||||
map[categorizer][]string{
|
|
||||||
SharePointListItem: Any(),
|
|
||||||
SharePointList: Any(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs() {
|
func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user