240 lines
6.4 KiB
Go
240 lines
6.4 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 (
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/containerd/ttrpc"
|
||
|
"github.com/pkg/errors"
|
||
|
"google.golang.org/grpc"
|
||
|
)
|
||
|
|
||
|
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")
|
||
|
|
||
|
// 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) }
|
||
|
|
||
|
const (
|
||
|
// InternalPlugin implements an internal plugin to containerd
|
||
|
InternalPlugin Type = "io.containerd.internal.v1"
|
||
|
// RuntimePlugin implements a runtime
|
||
|
RuntimePlugin Type = "io.containerd.runtime.v1"
|
||
|
// RuntimePluginV2 implements a runtime v2
|
||
|
RuntimePluginV2 Type = "io.containerd.runtime.v2"
|
||
|
// ServicePlugin implements a internal service
|
||
|
ServicePlugin Type = "io.containerd.service.v1"
|
||
|
// GRPCPlugin implements a grpc service
|
||
|
GRPCPlugin Type = "io.containerd.grpc.v1"
|
||
|
// SnapshotPlugin implements a snapshotter
|
||
|
SnapshotPlugin Type = "io.containerd.snapshotter.v1"
|
||
|
// TaskMonitorPlugin implements a task monitor
|
||
|
TaskMonitorPlugin Type = "io.containerd.monitor.v1"
|
||
|
// DiffPlugin implements a differ
|
||
|
DiffPlugin Type = "io.containerd.differ.v1"
|
||
|
// MetadataPlugin implements a metadata store
|
||
|
MetadataPlugin Type = "io.containerd.metadata.v1"
|
||
|
// ContentPlugin implements a content store
|
||
|
ContentPlugin Type = "io.containerd.content.v1"
|
||
|
// GCPlugin implements garbage collection policy
|
||
|
GCPlugin Type = "io.containerd.gc.v1"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// RuntimeLinuxV1 is the legacy linux runtime
|
||
|
RuntimeLinuxV1 = "io.containerd.runtime.v1.linux"
|
||
|
// RuntimeRuncV1 is the runc runtime that supports a single container
|
||
|
RuntimeRuncV1 = "io.containerd.runc.v1"
|
||
|
// RuntimeRuncV2 is the runc runtime that supports multiple containers per shim
|
||
|
RuntimeRuncV2 = "io.containerd.runc.v2"
|
||
|
)
|
||
|
|
||
|
// 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)
|
||
|
// Disable the plugin from loading
|
||
|
Disable bool
|
||
|
}
|
||
|
|
||
|
// 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 fmt.Sprintf("%s.%s", r.Type, r.ID)
|
||
|
}
|
||
|
|
||
|
// Service allows GRPC services to be registered with the underlying server
|
||
|
type Service interface {
|
||
|
Register(*grpc.Server) error
|
||
|
}
|
||
|
|
||
|
// TTRPCService allows TTRPC services to be registered with the underlying server
|
||
|
type TTRPCService interface {
|
||
|
RegisterTTRPC(*ttrpc.Server) error
|
||
|
}
|
||
|
|
||
|
// TCPService allows GRPC services to be registered with the underlying tcp server
|
||
|
type TCPService interface {
|
||
|
RegisterTCP(*grpc.Server) error
|
||
|
}
|
||
|
|
||
|
var register = struct {
|
||
|
sync.RWMutex
|
||
|
r []*Registration
|
||
|
}{}
|
||
|
|
||
|
// Load loads all plugins at the provided path into containerd
|
||
|
func Load(path string) (err error) {
|
||
|
defer func() {
|
||
|
if v := recover(); v != nil {
|
||
|
rerr, ok := v.(error)
|
||
|
if !ok {
|
||
|
rerr = fmt.Errorf("%s", v)
|
||
|
}
|
||
|
err = rerr
|
||
|
}
|
||
|
}()
|
||
|
return loadPlugins(path)
|
||
|
}
|
||
|
|
||
|
// Register allows plugins to register
|
||
|
func Register(r *Registration) {
|
||
|
register.Lock()
|
||
|
defer register.Unlock()
|
||
|
|
||
|
if r.Type == "" {
|
||
|
panic(ErrNoType)
|
||
|
}
|
||
|
if r.ID == "" {
|
||
|
panic(ErrNoPluginID)
|
||
|
}
|
||
|
if err := checkUnique(r); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
var last bool
|
||
|
for _, requires := range r.Requires {
|
||
|
if requires == "*" {
|
||
|
last = true
|
||
|
}
|
||
|
}
|
||
|
if last && len(r.Requires) != 1 {
|
||
|
panic(ErrInvalidRequires)
|
||
|
}
|
||
|
|
||
|
register.r = append(register.r, r)
|
||
|
}
|
||
|
|
||
|
func checkUnique(r *Registration) error {
|
||
|
for _, registered := range register.r {
|
||
|
if r.URI() == registered.URI() {
|
||
|
return errors.Wrap(ErrIDRegistered, r.URI())
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// DisableFilter filters out disabled plugins
|
||
|
type DisableFilter func(r *Registration) bool
|
||
|
|
||
|
// Graph returns an ordered list of registered plugins for initialization.
|
||
|
// Plugins in disableList specified by id will be disabled.
|
||
|
func Graph(filter DisableFilter) (ordered []*Registration) {
|
||
|
register.RLock()
|
||
|
defer register.RUnlock()
|
||
|
|
||
|
for _, r := range register.r {
|
||
|
if filter(r) {
|
||
|
r.Disable = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
added := map[*Registration]bool{}
|
||
|
for _, r := range register.r {
|
||
|
if r.Disable {
|
||
|
continue
|
||
|
}
|
||
|
children(r, added, &ordered)
|
||
|
if !added[r] {
|
||
|
ordered = append(ordered, r)
|
||
|
added[r] = true
|
||
|
}
|
||
|
}
|
||
|
return ordered
|
||
|
}
|
||
|
|
||
|
func children(reg *Registration, added map[*Registration]bool, ordered *[]*Registration) {
|
||
|
for _, t := range reg.Requires {
|
||
|
for _, r := range register.r {
|
||
|
if !r.Disable &&
|
||
|
r.URI() != reg.URI() &&
|
||
|
(t == "*" || r.Type == t) {
|
||
|
children(r, added, ordered)
|
||
|
if !added[r] {
|
||
|
*ordered = append(*ordered, r)
|
||
|
added[r] = true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|