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