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