matcher.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. package docker // import "github.com/open-telemetry/opentelemetry-collector-contrib/internal/docker"
  4. import (
  5. "fmt"
  6. "regexp"
  7. "strings"
  8. "github.com/gobwas/glob"
  9. )
  10. type stringMatcher struct {
  11. standardItems map[string]bool
  12. anyNegatedStandards bool
  13. regexItems []regexItem
  14. globItems []globbedItem
  15. }
  16. // This utility defines a regex as
  17. // any string between two '/' characters
  18. // with the option of a leading '!' to
  19. // signify negation.
  20. type regexItem struct {
  21. re *regexp.Regexp
  22. isNegated bool
  23. }
  24. func isRegex(s string) bool {
  25. return len(s) > 2 && s[0] == '/' && s[len(s)-1] == '/'
  26. }
  27. type globbedItem struct {
  28. glob glob.Glob
  29. isNegated bool
  30. }
  31. func isGlobbed(s string) bool {
  32. return strings.ContainsAny(s, "*?[]{}!")
  33. }
  34. func newStringMatcher(items []string) (*stringMatcher, error) {
  35. standards := make(map[string]bool)
  36. var regexes []regexItem
  37. var globs []globbedItem
  38. anyNegatedStandards := false
  39. for _, i := range items {
  40. item, isNegated := isNegatedItem(i)
  41. switch {
  42. case isRegex(item):
  43. var re *regexp.Regexp
  44. var err error
  45. // by definition this must lead and end with '/' chars
  46. reText := item[1 : len(item)-1]
  47. re, err = regexp.Compile(reText)
  48. if err != nil {
  49. return nil, fmt.Errorf("invalid regex item: %w", err)
  50. }
  51. regexes = append(regexes, regexItem{re: re, isNegated: isNegated})
  52. case isGlobbed(item):
  53. g, err := glob.Compile(item)
  54. if err != nil {
  55. return nil, fmt.Errorf("invalid glob item: %w", err)
  56. }
  57. globs = append(globs, globbedItem{glob: g, isNegated: isNegated})
  58. default:
  59. standards[item] = isNegated
  60. if isNegated {
  61. anyNegatedStandards = true
  62. }
  63. }
  64. }
  65. return &stringMatcher{
  66. standardItems: standards,
  67. regexItems: regexes,
  68. globItems: globs,
  69. anyNegatedStandards: anyNegatedStandards,
  70. }, nil
  71. }
  72. // isNegatedItem strips a leading '!' and returns
  73. // the remaining substring and true. If no leading
  74. // '!' is found, it returns the input string and false.
  75. func isNegatedItem(value string) (string, bool) {
  76. if strings.HasPrefix(value, "!") {
  77. return value[1:], true
  78. }
  79. return value, false
  80. }
  81. func (f *stringMatcher) matches(s string) bool {
  82. negated, matched := f.standardItems[s]
  83. if matched {
  84. return !negated
  85. }
  86. // If negated standard item "!something" is provided
  87. // and "anything else" is evaluated we will always match.
  88. if f.anyNegatedStandards {
  89. return true
  90. }
  91. for _, reMatch := range f.regexItems {
  92. if reMatch.re.MatchString(s) != reMatch.isNegated {
  93. return true
  94. }
  95. }
  96. for _, globMatch := range f.globItems {
  97. if globMatch.glob.Match(s) != globMatch.isNegated {
  98. return true
  99. }
  100. }
  101. return false
  102. }