1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package plugin
18
19import (
20	"fmt"
21	"sync"
22
23	"github.com/containerd/ttrpc"
24	"github.com/pkg/errors"
25	"google.golang.org/grpc"
26)
27
28var (
29	// ErrNoType is returned when no type is specified
30	ErrNoType = errors.New("plugin: no type")
31	// ErrNoPluginID is returned when no id is specified
32	ErrNoPluginID = errors.New("plugin: no id")
33	// ErrIDRegistered is returned when a duplicate id is already registered
34	ErrIDRegistered = errors.New("plugin: id already registered")
35	// ErrSkipPlugin is used when a plugin is not initialized and should not be loaded,
36	// this allows the plugin loader differentiate between a plugin which is configured
37	// not to load and one that fails to load.
38	ErrSkipPlugin = errors.New("skip plugin")
39
40	// ErrInvalidRequires will be thrown if the requirements for a plugin are
41	// defined in an invalid manner.
42	ErrInvalidRequires = errors.New("invalid requires")
43)
44
45// IsSkipPlugin returns true if the error is skipping the plugin
46func IsSkipPlugin(err error) bool {
47	return errors.Cause(err) == ErrSkipPlugin
48}
49
50// Type is the type of the plugin
51type Type string
52
53func (t Type) String() string { return string(t) }
54
55const (
56	// InternalPlugin implements an internal plugin to containerd
57	InternalPlugin Type = "io.containerd.internal.v1"
58	// RuntimePlugin implements a runtime
59	RuntimePlugin Type = "io.containerd.runtime.v1"
60	// RuntimePluginV2 implements a runtime v2
61	RuntimePluginV2 Type = "io.containerd.runtime.v2"
62	// ServicePlugin implements a internal service
63	ServicePlugin Type = "io.containerd.service.v1"
64	// GRPCPlugin implements a grpc service
65	GRPCPlugin Type = "io.containerd.grpc.v1"
66	// SnapshotPlugin implements a snapshotter
67	SnapshotPlugin Type = "io.containerd.snapshotter.v1"
68	// TaskMonitorPlugin implements a task monitor
69	TaskMonitorPlugin Type = "io.containerd.monitor.v1"
70	// DiffPlugin implements a differ
71	DiffPlugin Type = "io.containerd.differ.v1"
72	// MetadataPlugin implements a metadata store
73	MetadataPlugin Type = "io.containerd.metadata.v1"
74	// ContentPlugin implements a content store
75	ContentPlugin Type = "io.containerd.content.v1"
76	// GCPlugin implements garbage collection policy
77	GCPlugin Type = "io.containerd.gc.v1"
78)
79
80const (
81	// RuntimeLinuxV1 is the legacy linux runtime
82	RuntimeLinuxV1 = "io.containerd.runtime.v1.linux"
83	// RuntimeRuncV1 is the runc runtime that supports a single container
84	RuntimeRuncV1 = "io.containerd.runc.v1"
85	// RuntimeRuncV2 is the runc runtime that supports multiple containers per shim
86	RuntimeRuncV2 = "io.containerd.runc.v2"
87)
88
89// Registration contains information for registering a plugin
90type Registration struct {
91	// Type of the plugin
92	Type Type
93	// ID of the plugin
94	ID string
95	// Config specific to the plugin
96	Config interface{}
97	// Requires is a list of plugins that the registered plugin requires to be available
98	Requires []Type
99
100	// InitFn is called when initializing a plugin. The registration and
101	// context are passed in. The init function may modify the registration to
102	// add exports, capabilities and platform support declarations.
103	InitFn func(*InitContext) (interface{}, error)
104	// Disable the plugin from loading
105	Disable bool
106}
107
108// Init the registered plugin
109func (r *Registration) Init(ic *InitContext) *Plugin {
110	p, err := r.InitFn(ic)
111	return &Plugin{
112		Registration: r,
113		Config:       ic.Config,
114		Meta:         ic.Meta,
115		instance:     p,
116		err:          err,
117	}
118}
119
120// URI returns the full plugin URI
121func (r *Registration) URI() string {
122	return fmt.Sprintf("%s.%s", r.Type, r.ID)
123}
124
125// Service allows GRPC services to be registered with the underlying server
126type Service interface {
127	Register(*grpc.Server) error
128}
129
130// TTRPCService allows TTRPC services to be registered with the underlying server
131type TTRPCService interface {
132	RegisterTTRPC(*ttrpc.Server) error
133}
134
135// TCPService allows GRPC services to be registered with the underlying tcp server
136type TCPService interface {
137	RegisterTCP(*grpc.Server) error
138}
139
140var register = struct {
141	sync.RWMutex
142	r []*Registration
143}{}
144
145// Load loads all plugins at the provided path into containerd
146func Load(path string) (err error) {
147	defer func() {
148		if v := recover(); v != nil {
149			rerr, ok := v.(error)
150			if !ok {
151				rerr = fmt.Errorf("%s", v)
152			}
153			err = rerr
154		}
155	}()
156	return loadPlugins(path)
157}
158
159// Register allows plugins to register
160func Register(r *Registration) {
161	register.Lock()
162	defer register.Unlock()
163
164	if r.Type == "" {
165		panic(ErrNoType)
166	}
167	if r.ID == "" {
168		panic(ErrNoPluginID)
169	}
170	if err := checkUnique(r); err != nil {
171		panic(err)
172	}
173
174	var last bool
175	for _, requires := range r.Requires {
176		if requires == "*" {
177			last = true
178		}
179	}
180	if last && len(r.Requires) != 1 {
181		panic(ErrInvalidRequires)
182	}
183
184	register.r = append(register.r, r)
185}
186
187func checkUnique(r *Registration) error {
188	for _, registered := range register.r {
189		if r.URI() == registered.URI() {
190			return errors.Wrap(ErrIDRegistered, r.URI())
191		}
192	}
193	return nil
194}
195
196// DisableFilter filters out disabled plugins
197type DisableFilter func(r *Registration) bool
198
199// Graph returns an ordered list of registered plugins for initialization.
200// Plugins in disableList specified by id will be disabled.
201func Graph(filter DisableFilter) (ordered []*Registration) {
202	register.RLock()
203	defer register.RUnlock()
204
205	for _, r := range register.r {
206		if filter(r) {
207			r.Disable = true
208		}
209	}
210
211	added := map[*Registration]bool{}
212	for _, r := range register.r {
213		if r.Disable {
214			continue
215		}
216		children(r, added, &ordered)
217		if !added[r] {
218			ordered = append(ordered, r)
219			added[r] = true
220		}
221	}
222	return ordered
223}
224
225func children(reg *Registration, added map[*Registration]bool, ordered *[]*Registration) {
226	for _, t := range reg.Requires {
227		for _, r := range register.r {
228			if !r.Disable &&
229				r.URI() != reg.URI() &&
230				(t == "*" || r.Type == t) {
231				children(r, added, ordered)
232				if !added[r] {
233					*ordered = append(*ordered, r)
234					added[r] = true
235				}
236			}
237		}
238	}
239}
240