github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/command/plugins.go (about) 1 package command 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "runtime" 12 13 plugin "github.com/hashicorp/go-plugin" 14 "github.com/kardianos/osext" 15 16 fileprovisioner "github.com/iaas-resource-provision/iaas-rpc/internal/builtin/provisioners/file" 17 localexec "github.com/iaas-resource-provision/iaas-rpc/internal/builtin/provisioners/local-exec" 18 remoteexec "github.com/iaas-resource-provision/iaas-rpc/internal/builtin/provisioners/remote-exec" 19 "github.com/iaas-resource-provision/iaas-rpc/internal/logging" 20 tfplugin "github.com/iaas-resource-provision/iaas-rpc/internal/plugin" 21 "github.com/iaas-resource-provision/iaas-rpc/internal/plugin/discovery" 22 "github.com/iaas-resource-provision/iaas-rpc/internal/provisioners" 23 ) 24 25 // NOTE WELL: The logic in this file is primarily about plugin types OTHER THAN 26 // providers, which use an older set of approaches implemented here. 27 // 28 // The provider-related functions live primarily in meta_providers.go, and 29 // lean on some different underlying mechanisms in order to support automatic 30 // installation and a hierarchical addressing namespace, neither of which 31 // are supported for other plugin types. 32 33 // store the user-supplied path for plugin discovery 34 func (m *Meta) storePluginPath(pluginPath []string) error { 35 if len(pluginPath) == 0 { 36 return nil 37 } 38 39 path := filepath.Join(m.DataDir(), PluginPathFile) 40 41 // remove the plugin dir record if the path was set to an empty string 42 if len(pluginPath) == 1 && (pluginPath[0] == "") { 43 err := os.Remove(path) 44 if !os.IsNotExist(err) { 45 return err 46 } 47 return nil 48 } 49 50 js, err := json.MarshalIndent(pluginPath, "", " ") 51 if err != nil { 52 return err 53 } 54 55 // if this fails, so will WriteFile 56 os.MkdirAll(m.DataDir(), 0755) 57 58 return ioutil.WriteFile(path, js, 0644) 59 } 60 61 // Load the user-defined plugin search path into Meta.pluginPath if the file 62 // exists. 63 func (m *Meta) loadPluginPath() ([]string, error) { 64 js, err := ioutil.ReadFile(filepath.Join(m.DataDir(), PluginPathFile)) 65 if os.IsNotExist(err) { 66 return nil, nil 67 } 68 69 if err != nil { 70 return nil, err 71 } 72 73 var pluginPath []string 74 if err := json.Unmarshal(js, &pluginPath); err != nil { 75 return nil, err 76 } 77 78 return pluginPath, nil 79 } 80 81 // the default location for automatically installed plugins 82 func (m *Meta) pluginDir() string { 83 return filepath.Join(m.DataDir(), "plugins", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)) 84 } 85 86 // pluginDirs return a list of directories to search for plugins. 87 // 88 // Earlier entries in this slice get priority over later when multiple copies 89 // of the same plugin version are found, but newer versions always override 90 // older versions where both satisfy the provider version constraints. 91 func (m *Meta) pluginDirs(includeAutoInstalled bool) []string { 92 // user defined paths take precedence 93 if len(m.pluginPath) > 0 { 94 return m.pluginPath 95 } 96 97 // When searching the following directories, earlier entries get precedence 98 // if the same plugin version is found twice, but newer versions will 99 // always get preference below regardless of where they are coming from. 100 // TODO: Add auto-install dir, default vendor dir and optional override 101 // vendor dir(s). 102 dirs := []string{"."} 103 104 // Look in the same directory as the Terraform executable. 105 // If found, this replaces what we found in the config path. 106 exePath, err := osext.Executable() 107 if err != nil { 108 log.Printf("[ERROR] Error discovering exe directory: %s", err) 109 } else { 110 dirs = append(dirs, filepath.Dir(exePath)) 111 } 112 113 // add the user vendor directory 114 dirs = append(dirs, DefaultPluginVendorDir) 115 116 if includeAutoInstalled { 117 dirs = append(dirs, m.pluginDir()) 118 } 119 dirs = append(dirs, m.GlobalPluginDirs...) 120 121 return dirs 122 } 123 124 func (m *Meta) provisionerFactories() map[string]provisioners.Factory { 125 dirs := m.pluginDirs(true) 126 plugins := discovery.FindPlugins("provisioner", dirs) 127 plugins, _ = plugins.ValidateVersions() 128 129 // For now our goal is to just find the latest version of each plugin 130 // we have on the system. All provisioners should be at version 0.0.0 131 // currently, so there should actually only be one instance of each plugin 132 // name here, even though the discovery interface forces us to pretend 133 // that might not be true. 134 135 factories := make(map[string]provisioners.Factory) 136 137 // Wire up the internal provisioners first. These might be overridden 138 // by discovered provisioners below. 139 for name, factory := range internalProvisionerFactories() { 140 factories[name] = factory 141 } 142 143 byName := plugins.ByName() 144 for name, metas := range byName { 145 // Since we validated versions above and we partitioned the sets 146 // by name, we're guaranteed that the metas in our set all have 147 // valid versions and that there's at least one meta. 148 newest := metas.Newest() 149 150 factories[name] = provisionerFactory(newest) 151 } 152 153 return factories 154 } 155 156 func provisionerFactory(meta discovery.PluginMeta) provisioners.Factory { 157 return func() (provisioners.Interface, error) { 158 cfg := &plugin.ClientConfig{ 159 Cmd: exec.Command(meta.Path), 160 HandshakeConfig: tfplugin.Handshake, 161 VersionedPlugins: tfplugin.VersionedPlugins, 162 Managed: true, 163 Logger: logging.NewLogger("provisioner"), 164 AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, 165 AutoMTLS: enableProviderAutoMTLS, 166 } 167 client := plugin.NewClient(cfg) 168 return newProvisionerClient(client) 169 } 170 } 171 172 func internalProvisionerFactories() map[string]provisioners.Factory { 173 return map[string]provisioners.Factory{ 174 "file": provisioners.FactoryFixed(fileprovisioner.New()), 175 "local-exec": provisioners.FactoryFixed(localexec.New()), 176 "remote-exec": provisioners.FactoryFixed(remoteexec.New()), 177 } 178 } 179 180 func newProvisionerClient(client *plugin.Client) (provisioners.Interface, error) { 181 // Request the RPC client so we can get the provisioner 182 // so we can build the actual RPC-implemented provisioner. 183 rpcClient, err := client.Client() 184 if err != nil { 185 return nil, err 186 } 187 188 raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) 189 if err != nil { 190 return nil, err 191 } 192 193 // store the client so that the plugin can kill the child process 194 p := raw.(*tfplugin.GRPCProvisioner) 195 p.PluginClient = client 196 return p, nil 197 }