github.com/demonoid81/containerd@v1.3.4/services/introspection/service.go (about)

     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  
    17  package introspection
    18  
    19  import (
    20  	context "context"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"sync"
    25  
    26  	api "github.com/containerd/containerd/api/services/introspection/v1"
    27  	"github.com/containerd/containerd/api/types"
    28  	"github.com/containerd/containerd/errdefs"
    29  	"github.com/containerd/containerd/filters"
    30  	"github.com/containerd/containerd/plugin"
    31  	"github.com/gogo/googleapis/google/rpc"
    32  	ptypes "github.com/gogo/protobuf/types"
    33  	"github.com/google/uuid"
    34  	"google.golang.org/grpc"
    35  	"google.golang.org/grpc/status"
    36  )
    37  
    38  func init() {
    39  	plugin.Register(&plugin.Registration{
    40  		Type:     plugin.GRPCPlugin,
    41  		ID:       "introspection",
    42  		Requires: []plugin.Type{"*"},
    43  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
    44  			// this service works by using the plugin context up till the point
    45  			// this service is initialized. Since we require this service last,
    46  			// it should provide the full set of plugins.
    47  			pluginsPB := pluginsToPB(ic.GetAll())
    48  			return NewService(pluginsPB, ic.Root), nil
    49  		},
    50  	})
    51  }
    52  
    53  type service struct {
    54  	mu      sync.Mutex
    55  	plugins []api.Plugin
    56  	root    string
    57  }
    58  
    59  // NewService returns the GRPC introspection server
    60  func NewService(plugins []api.Plugin, root string) api.IntrospectionServer {
    61  	return &service{
    62  		plugins: plugins,
    63  		root:    root,
    64  	}
    65  }
    66  
    67  func (s *service) Register(server *grpc.Server) error {
    68  	api.RegisterIntrospectionServer(server, s)
    69  	return nil
    70  }
    71  
    72  func (s *service) Plugins(ctx context.Context, req *api.PluginsRequest) (*api.PluginsResponse, error) {
    73  	filter, err := filters.ParseAll(req.Filters...)
    74  	if err != nil {
    75  		return nil, errdefs.ToGRPCf(errdefs.ErrInvalidArgument, err.Error())
    76  	}
    77  
    78  	var plugins []api.Plugin
    79  	for _, p := range s.plugins {
    80  		if !filter.Match(adaptPlugin(p)) {
    81  			continue
    82  		}
    83  
    84  		plugins = append(plugins, p)
    85  	}
    86  
    87  	return &api.PluginsResponse{
    88  		Plugins: plugins,
    89  	}, nil
    90  }
    91  
    92  func (s *service) Server(ctx context.Context, _ *ptypes.Empty) (*api.ServerResponse, error) {
    93  	u, err := s.getUUID()
    94  	if err != nil {
    95  		return nil, errdefs.ToGRPC(err)
    96  	}
    97  	return &api.ServerResponse{
    98  		UUID: u,
    99  	}, nil
   100  }
   101  
   102  func (s *service) getUUID() (string, error) {
   103  	s.mu.Lock()
   104  	defer s.mu.Unlock()
   105  
   106  	data, err := ioutil.ReadFile(s.uuidPath())
   107  	if err != nil {
   108  		if os.IsNotExist(err) {
   109  			return s.generateUUID()
   110  		}
   111  		return "", err
   112  	}
   113  	u := string(data)
   114  	if _, err := uuid.Parse(u); err != nil {
   115  		return "", err
   116  	}
   117  	return u, nil
   118  }
   119  
   120  func (s *service) generateUUID() (string, error) {
   121  	u, err := uuid.NewRandom()
   122  	if err != nil {
   123  		return "", err
   124  	}
   125  	path := s.uuidPath()
   126  	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
   127  		return "", err
   128  	}
   129  	uu := u.String()
   130  	if err := ioutil.WriteFile(path, []byte(uu), 0666); err != nil {
   131  		return "", err
   132  	}
   133  	return uu, nil
   134  }
   135  
   136  func (s *service) uuidPath() string {
   137  	return filepath.Join(s.root, "uuid")
   138  }
   139  
   140  func adaptPlugin(o interface{}) filters.Adaptor {
   141  	obj := o.(api.Plugin)
   142  	return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
   143  		if len(fieldpath) == 0 {
   144  			return "", false
   145  		}
   146  
   147  		switch fieldpath[0] {
   148  		case "type":
   149  			return obj.Type, len(obj.Type) > 0
   150  		case "id":
   151  			return obj.ID, len(obj.ID) > 0
   152  		case "platforms":
   153  			// TODO(stevvooe): Another case here where have multiple values.
   154  			// May need to refactor the filter system to allow filtering by
   155  			// platform, if this is required.
   156  		case "capabilities":
   157  			// TODO(stevvooe): Need a better way to match against
   158  			// collections. We can only return "the value" but really it
   159  			// would be best if we could return a set of values for the
   160  			// path, any of which could match.
   161  		}
   162  
   163  		return "", false
   164  	})
   165  }
   166  
   167  func pluginsToPB(plugins []*plugin.Plugin) []api.Plugin {
   168  	var pluginsPB []api.Plugin
   169  	for _, p := range plugins {
   170  		var platforms []types.Platform
   171  		for _, p := range p.Meta.Platforms {
   172  			platforms = append(platforms, types.Platform{
   173  				OS:           p.OS,
   174  				Architecture: p.Architecture,
   175  				Variant:      p.Variant,
   176  			})
   177  		}
   178  
   179  		var requires []string
   180  		for _, r := range p.Registration.Requires {
   181  			requires = append(requires, r.String())
   182  		}
   183  
   184  		var initErr *rpc.Status
   185  		if err := p.Err(); err != nil {
   186  			st, ok := status.FromError(errdefs.ToGRPC(err))
   187  			if ok {
   188  				var details []*ptypes.Any
   189  				for _, d := range st.Proto().Details {
   190  					details = append(details, &ptypes.Any{
   191  						TypeUrl: d.TypeUrl,
   192  						Value:   d.Value,
   193  					})
   194  				}
   195  				initErr = &rpc.Status{
   196  					Code:    int32(st.Code()),
   197  					Message: st.Message(),
   198  					Details: details,
   199  				}
   200  			} else {
   201  				initErr = &rpc.Status{
   202  					Code:    int32(rpc.UNKNOWN),
   203  					Message: err.Error(),
   204  				}
   205  			}
   206  		}
   207  
   208  		pluginsPB = append(pluginsPB, api.Plugin{
   209  			Type:         p.Registration.Type.String(),
   210  			ID:           p.Registration.ID,
   211  			Requires:     requires,
   212  			Platforms:    platforms,
   213  			Capabilities: p.Meta.Capabilities,
   214  			Exports:      p.Meta.Exports,
   215  			InitErr:      initErr,
   216  		})
   217  	}
   218  
   219  	return pluginsPB
   220  }