github.com/banmanh482/nomad@v0.11.8/helper/pluginutils/loader/loader.go (about) 1 package loader 2 3 import ( 4 "context" 5 "fmt" 6 "os/exec" 7 8 log "github.com/hashicorp/go-hclog" 9 plugin "github.com/hashicorp/go-plugin" 10 version "github.com/hashicorp/go-version" 11 "github.com/hashicorp/nomad/nomad/structs/config" 12 "github.com/hashicorp/nomad/plugins" 13 "github.com/hashicorp/nomad/plugins/base" 14 "github.com/hashicorp/nomad/plugins/device" 15 "github.com/hashicorp/nomad/plugins/drivers" 16 "github.com/hashicorp/nomad/plugins/shared/hclspec" 17 ) 18 19 // PluginCatalog is used to retrieve plugins, either external or internal 20 type PluginCatalog interface { 21 // Dispense returns the plugin given its name and type. This will also 22 // configure the plugin 23 Dispense(name, pluginType string, config *base.AgentConfig, logger log.Logger) (PluginInstance, error) 24 25 // Reattach is used to reattach to a previously launched external plugin. 26 Reattach(name, pluginType string, config *plugin.ReattachConfig) (PluginInstance, error) 27 28 // Catalog returns the catalog of all plugins keyed by plugin type 29 Catalog() map[string][]*base.PluginInfoResponse 30 } 31 32 // InternalPluginConfig is used to configure launching an internal plugin. 33 type InternalPluginConfig struct { 34 Config map[string]interface{} 35 Factory plugins.PluginCtxFactory 36 } 37 38 // PluginID is a tuple identifying a plugin 39 type PluginID struct { 40 // Name is the name of the plugin 41 Name string 42 43 // PluginType is the plugin's type 44 PluginType string 45 } 46 47 // String returns a friendly representation of the plugin. 48 func (id PluginID) String() string { 49 return fmt.Sprintf("%q (%v)", id.Name, id.PluginType) 50 } 51 52 func PluginInfoID(resp *base.PluginInfoResponse) PluginID { 53 return PluginID{ 54 Name: resp.Name, 55 PluginType: resp.Type, 56 } 57 } 58 59 // PluginLoaderConfig configures a plugin loader. 60 type PluginLoaderConfig struct { 61 // Logger is the logger used by the plugin loader 62 Logger log.Logger 63 64 // PluginDir is the directory scanned for loading plugins 65 PluginDir string 66 67 // Configs is an optional set of configs for plugins 68 Configs []*config.PluginConfig 69 70 // InternalPlugins allows registering internal plugins. 71 InternalPlugins map[PluginID]*InternalPluginConfig 72 73 // SupportedVersions is a mapping of plugin type to the supported versions 74 SupportedVersions map[string][]string 75 } 76 77 // PluginLoader is used to retrieve plugins either externally or from internal 78 // factories. 79 type PluginLoader struct { 80 // logger is the plugin loaders logger 81 logger log.Logger 82 83 // supportedVersions is a mapping of plugin type to the supported versions 84 supportedVersions map[string][]*version.Version 85 86 // pluginDir is the directory containing plugin binaries 87 pluginDir string 88 89 // plugins maps a plugin to information required to launch it 90 plugins map[PluginID]*pluginInfo 91 } 92 93 // pluginInfo captures the necessary information to launch and configure a 94 // plugin. 95 type pluginInfo struct { 96 factory plugins.PluginCtxFactory 97 98 exePath string 99 args []string 100 101 baseInfo *base.PluginInfoResponse 102 version *version.Version 103 apiVersion string 104 105 configSchema *hclspec.Spec 106 config map[string]interface{} 107 msgpackConfig []byte 108 } 109 110 // NewPluginLoader returns an instance of a plugin loader or an error if the 111 // plugins could not be loaded 112 func NewPluginLoader(config *PluginLoaderConfig) (*PluginLoader, error) { 113 if err := validateConfig(config); err != nil { 114 return nil, fmt.Errorf("invalid plugin loader configuration passed: %v", err) 115 } 116 117 // Convert the versions 118 supportedVersions := make(map[string][]*version.Version, len(config.SupportedVersions)) 119 for pType, versions := range config.SupportedVersions { 120 converted, err := convertVersions(versions) 121 if err != nil { 122 return nil, err 123 } 124 supportedVersions[pType] = converted 125 } 126 127 logger := config.Logger.Named("plugin_loader").With("plugin_dir", config.PluginDir) 128 l := &PluginLoader{ 129 logger: logger, 130 supportedVersions: supportedVersions, 131 pluginDir: config.PluginDir, 132 plugins: make(map[PluginID]*pluginInfo), 133 } 134 135 if err := l.init(config); err != nil { 136 return nil, fmt.Errorf("failed to initialize plugin loader: %v", err) 137 } 138 139 return l, nil 140 } 141 142 // Dispense returns a plugin instance, loading it either internally or by 143 // launching an external plugin. 144 func (l *PluginLoader) Dispense(name, pluginType string, config *base.AgentConfig, logger log.Logger) (PluginInstance, error) { 145 id := PluginID{ 146 Name: name, 147 PluginType: pluginType, 148 } 149 pinfo, ok := l.plugins[id] 150 if !ok { 151 return nil, fmt.Errorf("unknown plugin with name %q and type %q", name, pluginType) 152 } 153 154 // If the plugin is internal, launch via the factory 155 var instance PluginInstance 156 if pinfo.factory != nil { 157 ctx, cancel := context.WithCancel(context.Background()) 158 instance = &internalPluginInstance{ 159 instance: pinfo.factory(ctx, logger), 160 apiVersion: pinfo.apiVersion, 161 killFn: cancel, 162 } 163 } else { 164 var err error 165 instance, err = l.dispensePlugin(pinfo.baseInfo.Type, pinfo.apiVersion, pinfo.exePath, pinfo.args, nil, logger) 166 if err != nil { 167 return nil, fmt.Errorf("failed to launch plugin: %v", err) 168 } 169 } 170 171 // Cast to the base type and set the config 172 b, ok := instance.Plugin().(base.BasePlugin) 173 if !ok { 174 return nil, fmt.Errorf("plugin %s doesn't implement base plugin interface", id) 175 } 176 177 c := &base.Config{ 178 PluginConfig: pinfo.msgpackConfig, 179 AgentConfig: config, 180 ApiVersion: pinfo.apiVersion, 181 } 182 183 if err := b.SetConfig(c); err != nil { 184 return nil, fmt.Errorf("setting config for plugin %s failed: %v", id, err) 185 } 186 187 return instance, nil 188 } 189 190 // Reattach reattaches to a previously launched external plugin. 191 func (l *PluginLoader) Reattach(name, pluginType string, config *plugin.ReattachConfig) (PluginInstance, error) { 192 return l.dispensePlugin(pluginType, "", "", nil, config, l.logger) 193 } 194 195 // dispensePlugin is used to launch or reattach to an external plugin. 196 func (l *PluginLoader) dispensePlugin( 197 pluginType, apiVersion, cmd string, args []string, reattach *plugin.ReattachConfig, 198 logger log.Logger) (PluginInstance, error) { 199 200 var pluginCmd *exec.Cmd 201 if cmd != "" && reattach != nil { 202 return nil, fmt.Errorf("both launch command and reattach config specified") 203 } else if cmd == "" && reattach == nil { 204 return nil, fmt.Errorf("one of launch command or reattach config must be specified") 205 } else if cmd != "" { 206 pluginCmd = exec.Command(cmd, args...) 207 } 208 209 client := plugin.NewClient(&plugin.ClientConfig{ 210 HandshakeConfig: base.Handshake, 211 Plugins: getPluginMap(pluginType, logger), 212 Cmd: pluginCmd, 213 AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, 214 Logger: logger, 215 Reattach: reattach, 216 }) 217 218 // Connect via RPC 219 rpcClient, err := client.Client() 220 if err != nil { 221 client.Kill() 222 return nil, err 223 } 224 225 // Request the plugin 226 raw, err := rpcClient.Dispense(pluginType) 227 if err != nil { 228 client.Kill() 229 return nil, err 230 } 231 232 instance := &externalPluginInstance{ 233 client: client, 234 instance: raw, 235 } 236 237 if apiVersion != "" { 238 instance.apiVersion = apiVersion 239 } else { 240 // We do not know the API version since we are reattaching, so discover 241 // it 242 bplugin := raw.(base.BasePlugin) 243 244 // Retrieve base plugin information 245 i, err := bplugin.PluginInfo() 246 if err != nil { 247 return nil, fmt.Errorf("failed to get plugin info for plugin: %v", err) 248 } 249 250 apiVersion, err := l.selectApiVersion(i) 251 if err != nil { 252 return nil, fmt.Errorf("failed to validate API versions %v for plugin %s: %v", i.PluginApiVersions, i.Name, err) 253 } 254 if apiVersion == "" { 255 return nil, fmt.Errorf("failed to reattach to plugin because supported API versions for the plugin and Nomad do not overlap") 256 } 257 258 instance.apiVersion = apiVersion 259 } 260 261 return instance, nil 262 } 263 264 // getPluginMap returns a plugin map based on the type of plugin being launched. 265 func getPluginMap(pluginType string, logger log.Logger) map[string]plugin.Plugin { 266 pmap := map[string]plugin.Plugin{ 267 base.PluginTypeBase: &base.PluginBase{}, 268 } 269 270 switch pluginType { 271 case base.PluginTypeDevice: 272 pmap[base.PluginTypeDevice] = &device.PluginDevice{} 273 case base.PluginTypeDriver: 274 pmap[base.PluginTypeDriver] = drivers.NewDriverPlugin(nil, logger) 275 } 276 277 return pmap 278 } 279 280 // Catalog returns the catalog of all plugins 281 func (l *PluginLoader) Catalog() map[string][]*base.PluginInfoResponse { 282 c := make(map[string][]*base.PluginInfoResponse, 3) 283 for id, info := range l.plugins { 284 c[id.PluginType] = append(c[id.PluginType], info.baseInfo) 285 } 286 return c 287 }