feature.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package feature tracks which features are linked into the binary.
  4. package feature
  5. import (
  6. "errors"
  7. "reflect"
  8. "tailscale.com/util/testenv"
  9. )
  10. var ErrUnavailable = errors.New("feature not included in this build")
  11. var in = map[string]bool{}
  12. // Registered reports the set of registered features.
  13. //
  14. // The returned map should not be modified by the caller,
  15. // not accessed concurrently with calls to Register.
  16. func Registered() map[string]bool { return in }
  17. // Register notes that the named feature is linked into the binary.
  18. func Register(name string) {
  19. if _, ok := in[name]; ok {
  20. panic("duplicate feature registration for " + name)
  21. }
  22. in[name] = true
  23. }
  24. // Hook is a func that can only be set once.
  25. //
  26. // It is not safe for concurrent use.
  27. type Hook[Func any] struct {
  28. f Func
  29. ok bool
  30. }
  31. // IsSet reports whether the hook has been set.
  32. func (h *Hook[Func]) IsSet() bool {
  33. return h.ok
  34. }
  35. // Set sets the hook function, panicking if it's already been set
  36. // or f is the zero value.
  37. //
  38. // It's meant to be called in init.
  39. func (h *Hook[Func]) Set(f Func) {
  40. if h.ok {
  41. panic("Set on already-set feature hook")
  42. }
  43. if reflect.ValueOf(f).IsZero() {
  44. panic("Set with zero value")
  45. }
  46. h.f = f
  47. h.ok = true
  48. }
  49. // SetForTest sets the hook function for tests, blowing
  50. // away any previous value. It will panic if called from
  51. // non-test code.
  52. //
  53. // It returns a restore function that resets the hook
  54. // to its previous value.
  55. func (h *Hook[Func]) SetForTest(f Func) (restore func()) {
  56. testenv.AssertInTest()
  57. old := *h
  58. h.f, h.ok = f, true
  59. return func() { *h = old }
  60. }
  61. // Get returns the hook function, or panics if it hasn't been set.
  62. // Use IsSet to check if it's been set, or use GetOrNil if you're
  63. // okay with a nil return value.
  64. func (h *Hook[Func]) Get() Func {
  65. if !h.ok {
  66. panic("Get on unset feature hook, without IsSet")
  67. }
  68. return h.f
  69. }
  70. // GetOk returns the hook function and true if it has been set,
  71. // otherwise its zero value and false.
  72. func (h *Hook[Func]) GetOk() (f Func, ok bool) {
  73. return h.f, h.ok
  74. }
  75. // GetOrNil returns the hook function or nil if it hasn't been set.
  76. func (h *Hook[Func]) GetOrNil() Func {
  77. return h.f
  78. }
  79. // Hooks is a slice of funcs.
  80. //
  81. // As opposed to a single Hook, this is meant to be used when
  82. // multiple parties are able to install the same hook.
  83. type Hooks[Func any] []Func
  84. // Add adds a hook to the list of hooks.
  85. //
  86. // Add should only be called during early program
  87. // startup before Tailscale has started.
  88. // It is not safe for concurrent use.
  89. func (h *Hooks[Func]) Add(f Func) {
  90. if reflect.ValueOf(f).IsZero() {
  91. panic("Add with zero value")
  92. }
  93. *h = append(*h, f)
  94. }