
These utilities resulted in the platforms package to have the containerd
API as dependency. As this package is used in many parts of the code, as
well as external consumers, we should try to keep it light on dependencies,
with the potential to make it a standalone module.
These utilities were added in f3b7436b61
,
which has not yet been included in a release, so skipping deprecation
and aliases for these.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
234 lines
5.8 KiB
Go
234 lines
5.8 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 introspection
|
|
|
|
import (
|
|
context "context"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync"
|
|
|
|
api "github.com/containerd/containerd/api/services/introspection/v1"
|
|
"github.com/containerd/containerd/api/types"
|
|
"github.com/containerd/containerd/errdefs"
|
|
"github.com/containerd/containerd/filters"
|
|
"github.com/containerd/containerd/plugin"
|
|
ptypes "github.com/containerd/containerd/protobuf/types"
|
|
"github.com/containerd/containerd/services"
|
|
"github.com/google/uuid"
|
|
"google.golang.org/genproto/googleapis/rpc/code"
|
|
rpc "google.golang.org/genproto/googleapis/rpc/status"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
func init() {
|
|
plugin.Register(&plugin.Registration{
|
|
Type: plugin.ServicePlugin,
|
|
ID: services.IntrospectionService,
|
|
Requires: []plugin.Type{},
|
|
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
|
// this service fetches all plugins through the plugin set of the plugin context
|
|
return &Local{
|
|
plugins: ic.Plugins(),
|
|
root: ic.Root,
|
|
}, nil
|
|
},
|
|
})
|
|
}
|
|
|
|
// Local is a local implementation of the introspection service
|
|
type Local struct {
|
|
mu sync.Mutex
|
|
root string
|
|
plugins *plugin.Set
|
|
pluginCache []*api.Plugin
|
|
}
|
|
|
|
var _ = (api.IntrospectionClient)(&Local{})
|
|
|
|
// UpdateLocal updates the local introspection service
|
|
func (l *Local) UpdateLocal(root string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.root = root
|
|
}
|
|
|
|
// Plugins returns the locally defined plugins
|
|
func (l *Local) Plugins(ctx context.Context, req *api.PluginsRequest, _ ...grpc.CallOption) (*api.PluginsResponse, error) {
|
|
filter, err := filters.ParseAll(req.Filters...)
|
|
if err != nil {
|
|
return nil, errdefs.ToGRPCf(errdefs.ErrInvalidArgument, err.Error())
|
|
}
|
|
|
|
var plugins []*api.Plugin
|
|
allPlugins := l.getPlugins()
|
|
for _, p := range allPlugins {
|
|
p := p
|
|
if filter.Match(adaptPlugin(p)) {
|
|
plugins = append(plugins, p)
|
|
}
|
|
}
|
|
|
|
return &api.PluginsResponse{
|
|
Plugins: plugins,
|
|
}, nil
|
|
}
|
|
|
|
func (l *Local) getPlugins() []*api.Plugin {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
plugins := l.plugins.GetAll()
|
|
if l.pluginCache == nil || len(plugins) != len(l.pluginCache) {
|
|
l.pluginCache = pluginsToPB(plugins)
|
|
}
|
|
return l.pluginCache
|
|
}
|
|
|
|
// Server returns the local server information
|
|
func (l *Local) Server(ctx context.Context, _ *ptypes.Empty, _ ...grpc.CallOption) (*api.ServerResponse, error) {
|
|
u, err := l.getUUID()
|
|
if err != nil {
|
|
return nil, errdefs.ToGRPC(err)
|
|
}
|
|
pid := os.Getpid()
|
|
var pidns uint64
|
|
if runtime.GOOS == "linux" {
|
|
pidns, err = statPIDNS(pid)
|
|
if err != nil {
|
|
return nil, errdefs.ToGRPC(err)
|
|
}
|
|
}
|
|
return &api.ServerResponse{
|
|
UUID: u,
|
|
Pid: uint64(pid),
|
|
Pidns: pidns,
|
|
}, nil
|
|
}
|
|
|
|
func (l *Local) getUUID() (string, error) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
data, err := os.ReadFile(l.uuidPath())
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return l.generateUUID()
|
|
}
|
|
return "", err
|
|
}
|
|
u := string(data)
|
|
if _, err := uuid.Parse(u); err != nil {
|
|
return "", err
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
func (l *Local) generateUUID() (string, error) {
|
|
u, err := uuid.NewRandom()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
path := l.uuidPath()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
|
return "", err
|
|
}
|
|
uu := u.String()
|
|
if err := os.WriteFile(path, []byte(uu), 0666); err != nil {
|
|
return "", err
|
|
}
|
|
return uu, nil
|
|
}
|
|
|
|
func (l *Local) uuidPath() string {
|
|
return filepath.Join(l.root, "uuid")
|
|
}
|
|
|
|
func adaptPlugin(o interface{}) filters.Adaptor {
|
|
obj := o.(*api.Plugin)
|
|
return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
|
|
if len(fieldpath) == 0 {
|
|
return "", false
|
|
}
|
|
|
|
switch fieldpath[0] {
|
|
case "type":
|
|
return obj.Type, len(obj.Type) > 0
|
|
case "id":
|
|
return obj.ID, len(obj.ID) > 0
|
|
case "platforms":
|
|
// TODO(stevvooe): Another case here where have multiple values.
|
|
// May need to refactor the filter system to allow filtering by
|
|
// platform, if this is required.
|
|
case "capabilities":
|
|
// TODO(stevvooe): Need a better way to match against
|
|
// collections. We can only return "the value" but really it
|
|
// would be best if we could return a set of values for the
|
|
// path, any of which could match.
|
|
}
|
|
|
|
return "", false
|
|
})
|
|
}
|
|
|
|
func pluginsToPB(plugins []*plugin.Plugin) []*api.Plugin {
|
|
var pluginsPB []*api.Plugin
|
|
for _, p := range plugins {
|
|
var requires []string
|
|
for _, r := range p.Registration.Requires {
|
|
requires = append(requires, r.String())
|
|
}
|
|
|
|
var initErr *rpc.Status
|
|
if err := p.Err(); err != nil {
|
|
st, ok := status.FromError(errdefs.ToGRPC(err))
|
|
if ok {
|
|
var details []*ptypes.Any
|
|
for _, d := range st.Proto().Details {
|
|
details = append(details, &ptypes.Any{
|
|
TypeUrl: d.TypeUrl,
|
|
Value: d.Value,
|
|
})
|
|
}
|
|
initErr = &rpc.Status{
|
|
Code: int32(st.Code()),
|
|
Message: st.Message(),
|
|
Details: details,
|
|
}
|
|
} else {
|
|
initErr = &rpc.Status{
|
|
Code: int32(code.Code_UNKNOWN),
|
|
Message: err.Error(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pluginsPB = append(pluginsPB, &api.Plugin{
|
|
Type: p.Registration.Type.String(),
|
|
ID: p.Registration.ID,
|
|
Requires: requires,
|
|
Platforms: types.OCIPlatformToProto(p.Meta.Platforms),
|
|
Capabilities: p.Meta.Capabilities,
|
|
Exports: p.Meta.Exports,
|
|
InitErr: initErr,
|
|
})
|
|
}
|
|
|
|
return pluginsPB
|
|
}
|