| /* | |
| Copyright The containerd Authors. | |
| 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 plugin | |
| import ( | |
| "context" | |
| "errors" | |
| "fmt" | |
| ) | |
| var ( | |
| // ErrNoType is returned when no type is specified | |
| ErrNoType = errors.New("plugin: no type") | |
| // ErrNoPluginID is returned when no id is specified | |
| ErrNoPluginID = errors.New("plugin: no id") | |
| // ErrIDRegistered is returned when a duplicate id is already registered | |
| ErrIDRegistered = errors.New("plugin: id already registered") | |
| // ErrSkipPlugin is used when a plugin is not initialized and should not be loaded, | |
| // this allows the plugin loader differentiate between a plugin which is configured | |
| // not to load and one that fails to load. | |
| ErrSkipPlugin = errors.New("skip plugin") | |
| // ErrPluginInitialized is used when a plugin is already initialized | |
| ErrPluginInitialized = errors.New("plugin: already initialized") | |
| // ErrPluginNotFound is used when a plugin is looked up but not found | |
| ErrPluginNotFound = errors.New("plugin: not found") | |
| // ErrPluginMultipleInstances is used when a plugin is expected a single instance but has multiple | |
| ErrPluginMultipleInstances = errors.New("plugin: multiple instances") | |
| // ErrPluginCircularDependency is used when the graph detect a circular plugin dependency | |
| ErrPluginCircularDependency = errors.New("plugin: dependency loop detected") | |
| // ErrInvalidRequires will be thrown if the requirements for a plugin are | |
| // defined in an invalid manner. | |
| ErrInvalidRequires = errors.New("invalid requires") | |
| ) | |
| // IsSkipPlugin returns true if the error is skipping the plugin | |
| func IsSkipPlugin(err error) bool { | |
| return errors.Is(err, ErrSkipPlugin) | |
| } | |
| // Type is the type of the plugin | |
| type Type string | |
| func (t Type) String() string { return string(t) } | |
| // Registration contains information for registering a plugin | |
| type Registration struct { | |
| // Type of the plugin | |
| Type Type | |
| // ID of the plugin | |
| ID string | |
| // Config specific to the plugin | |
| Config interface{} | |
| // Requires is a list of plugins that the registered plugin requires to be available | |
| Requires []Type | |
| // InitFn is called when initializing a plugin. The registration and | |
| // context are passed in. The init function may modify the registration to | |
| // add exports, capabilities and platform support declarations. | |
| InitFn func(*InitContext) (interface{}, error) | |
| // ConfigMigration allows a plugin to migrate configurations from an older | |
| // version to handle plugin renames or moving of features from one plugin | |
| // to another in a later version. | |
| // The configuration map is keyed off the plugin name and the value | |
| // is the configuration for that objects, with the structure defined | |
| // for the plugin. No validation is done on the value before performing | |
| // the migration. | |
| ConfigMigration func(context.Context, int, map[string]interface{}) error | |
| } | |
| // Init the registered plugin | |
| func (r Registration) Init(ic *InitContext) *Plugin { | |
| p, err := r.InitFn(ic) | |
| return &Plugin{ | |
| Registration: r, | |
| Config: ic.Config, | |
| Meta: *ic.Meta, | |
| instance: p, | |
| err: err, | |
| } | |
| } | |
| // URI returns the full plugin URI | |
| func (r *Registration) URI() string { | |
| return r.Type.String() + "." + r.ID | |
| } | |
| // DisableFilter filters out disabled plugins | |
| type DisableFilter func(r *Registration) bool | |
| // Registry is list of registrations which can be registered to and | |
| // produce a filtered and ordered output. | |
| // The Registry itself is immutable and the list will be copied | |
| // and appeneded to a new registry when new items are registered. | |
| type Registry []*Registration | |
| // Graph computes the ordered list of registrations based on their dependencies, | |
| // filtering out any plugins which match the provided filter. | |
| func (registry Registry) Graph(filter DisableFilter) []Registration { | |
| handled := make(map[*Registration]struct{}, len(registry)) | |
| if filter != nil { | |
| for _, r := range registry { | |
| if filter(r) { | |
| handled[r] = struct{}{} | |
| } | |
| } | |
| } | |
| ordered := make([]Registration, 0, len(registry)-len(handled)) | |
| stack := make([]*Registration, 0, cap(ordered)) | |
| for _, r := range registry { | |
| if _, ok := handled[r]; ok { | |
| continue | |
| } | |
| children(append(stack, r), registry, handled, &ordered) | |
| handled[r] = struct{}{} | |
| ordered = append(ordered, *r) | |
| } | |
| return ordered | |
| } | |
| func children(stack []*Registration, registry []*Registration, handled map[*Registration]struct{}, ordered *[]Registration) { | |
| reg := stack[len(stack)-1] | |
| for _, t := range reg.Requires { | |
| for _, r := range registry { | |
| if (t == "*" || r.Type == t) && r != reg { | |
| if _, ok := handled[r]; !ok { | |
| // Ensure not in current stack | |
| for _, p := range stack[:len(stack)-1] { | |
| if p == r { | |
| panic(fmt.Errorf("circular plugin dependency at %s: %w", r.URI(), ErrPluginCircularDependency)) | |
| } | |
| } | |
| children(append(stack, r), registry, handled, ordered) | |
| handled[r] = struct{}{} | |
| *ordered = append(*ordered, *r) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Register adds the registration to a Registry and returns the | |
| // updated Registry, panicking if registration could not succeed. | |
| func (registry Registry) Register(r *Registration) Registry { | |
| if r.Type == "" { | |
| panic(ErrNoType) | |
| } | |
| if r.ID == "" { | |
| panic(ErrNoPluginID) | |
| } | |
| if err := checkUnique(registry, r); err != nil { | |
| panic(err) | |
| } | |
| for _, requires := range r.Requires { | |
| if (requires == "*" && len(r.Requires) != 1) || requires == r.Type { | |
| panic(ErrInvalidRequires) | |
| } | |
| } | |
| return append(registry, r) | |
| } | |
| func checkUnique(registry Registry, r *Registration) error { | |
| for _, registered := range registry { | |
| if r.Type == registered.Type && r.ID == registered.ID { | |
| return fmt.Errorf("%s: %w", r.URI(), ErrIDRegistered) | |
| } | |
| } | |
| return nil | |
| } |