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 }