mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-22 02:50:42 +00:00
ipn/ipn{ext,local}: allow extension lookup by name or type
In this PR, we add two methods to facilitate extension lookup by both extensions, and non-extensions (e.g., PeerAPI or LocalAPI handlers): - FindExtensionByName returns an extension with the specified name. It can then be type asserted to a given type. - FindMatchingExtension is like errors.As, but for extensions. It returns the first extension that matches the target type (either a specific extension or an interface). Updates tailscale/corp#27645 Updates tailscale/corp#27502 Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -233,6 +234,60 @@ func (h *ExtensionHost) init() {
|
||||
|
||||
}
|
||||
|
||||
// Extensions implements [ipnext.Host].
|
||||
func (h *ExtensionHost) Extensions() ipnext.ExtensionServices {
|
||||
// Currently, [ExtensionHost] implements [ExtensionServices] directly.
|
||||
// We might want to extract it to a separate type in the future.
|
||||
return h
|
||||
}
|
||||
|
||||
// FindExtensionByName implements [ipnext.ExtensionServices]
|
||||
// and is also used by the [LocalBackend].
|
||||
// It returns nil if the extension is not found.
|
||||
func (h *ExtensionHost) FindExtensionByName(name string) any {
|
||||
if h == nil {
|
||||
return nil
|
||||
}
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
return h.extensionsByName[name]
|
||||
}
|
||||
|
||||
// extensionIfaceType is the runtime type of the [ipnext.Extension] interface.
|
||||
var extensionIfaceType = reflect.TypeFor[ipnext.Extension]()
|
||||
|
||||
// FindMatchingExtension implements [ipnext.ExtensionServices]
|
||||
// and is also used by the [LocalBackend].
|
||||
func (h *ExtensionHost) FindMatchingExtension(target any) bool {
|
||||
if h == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
panic("ipnext: target cannot be nil")
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(target)
|
||||
typ := val.Type()
|
||||
if typ.Kind() != reflect.Ptr || val.IsNil() {
|
||||
panic("ipnext: target must be a non-nil pointer")
|
||||
}
|
||||
targetType := typ.Elem()
|
||||
if targetType.Kind() != reflect.Interface && !targetType.Implements(extensionIfaceType) {
|
||||
panic("ipnext: *target must be interface or implement ipnext.Extension")
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
for _, ext := range h.activeExtensions {
|
||||
if reflect.TypeOf(ext).AssignableTo(targetType) {
|
||||
val.Elem().Set(reflect.ValueOf(ext))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Profiles implements [ipnext.Host].
|
||||
func (h *ExtensionHost) Profiles() ipnext.ProfileServices {
|
||||
// Currently, [ExtensionHost] implements [ipnext.ProfileServices] directly.
|
||||
|
Reference in New Issue
Block a user