177 lines
5.3 KiB
Go
177 lines
5.3 KiB
Go
/*
|
|
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")
|
|
|
|
// 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 {
|
|
disabled := map[*Registration]bool{}
|
|
for _, r := range registry {
|
|
if filter(r) {
|
|
disabled[r] = true
|
|
}
|
|
}
|
|
|
|
ordered := make([]Registration, 0, len(registry)-len(disabled))
|
|
added := map[*Registration]bool{}
|
|
for _, r := range registry {
|
|
if disabled[r] {
|
|
continue
|
|
}
|
|
children(r, registry, added, disabled, &ordered)
|
|
if !added[r] {
|
|
ordered = append(ordered, *r)
|
|
added[r] = true
|
|
}
|
|
}
|
|
return ordered
|
|
}
|
|
|
|
func children(reg *Registration, registry []*Registration, added, disabled map[*Registration]bool, ordered *[]Registration) {
|
|
for _, t := range reg.Requires {
|
|
for _, r := range registry {
|
|
if !disabled[r] && r.URI() != reg.URI() && (t == "*" || r.Type == t) {
|
|
children(r, registry, added, disabled, ordered)
|
|
if !added[r] {
|
|
*ordered = append(*ordered, *r)
|
|
added[r] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
panic(ErrInvalidRequires)
|
|
}
|
|
}
|
|
|
|
return append(registry, r)
|
|
}
|
|
|
|
func checkUnique(registry Registry, r *Registration) error {
|
|
for _, registered := range registry {
|
|
if r.URI() == registered.URI() {
|
|
return fmt.Errorf("%s: %w", r.URI(), ErrIDRegistered)
|
|
}
|
|
}
|
|
return nil
|
|
}
|