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