| package command |
| |
| import ( |
| "fmt" |
| "log" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| |
| plugin "github.com/hashicorp/go-plugin" |
| "github.com/kardianos/osext" |
| |
| fileprovisioner "github.com/hashicorp/terraform/internal/builtin/provisioners/file" |
| localexec "github.com/hashicorp/terraform/internal/builtin/provisioners/local-exec" |
| remoteexec "github.com/hashicorp/terraform/internal/builtin/provisioners/remote-exec" |
| "github.com/hashicorp/terraform/internal/logging" |
| tfplugin "github.com/hashicorp/terraform/internal/plugin" |
| "github.com/hashicorp/terraform/internal/plugin/discovery" |
| "github.com/hashicorp/terraform/internal/provisioners" |
| ) |
| |
| // NOTE WELL: The logic in this file is primarily about plugin types OTHER THAN |
| // providers, which use an older set of approaches implemented here. |
| // |
| // The provider-related functions live primarily in meta_providers.go, and |
| // lean on some different underlying mechanisms in order to support automatic |
| // installation and a hierarchical addressing namespace, neither of which |
| // are supported for other plugin types. |
| |
| // store the user-supplied path for plugin discovery |
| func (m *Meta) storePluginPath(pluginPath []string) error { |
| if len(pluginPath) == 0 { |
| return nil |
| } |
| |
| m.fixupMissingWorkingDir() |
| |
| // remove the plugin dir record if the path was set to an empty string |
| if len(pluginPath) == 1 && (pluginPath[0] == "") { |
| return m.WorkingDir.SetForcedPluginDirs(nil) |
| } |
| |
| return m.WorkingDir.SetForcedPluginDirs(pluginPath) |
| } |
| |
| // Load the user-defined plugin search path into Meta.pluginPath if the file |
| // exists. |
| func (m *Meta) loadPluginPath() ([]string, error) { |
| m.fixupMissingWorkingDir() |
| return m.WorkingDir.ForcedPluginDirs() |
| } |
| |
| // the default location for automatically installed plugins |
| func (m *Meta) pluginDir() string { |
| return filepath.Join(m.DataDir(), "plugins", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)) |
| } |
| |
| // pluginDirs return a list of directories to search for plugins. |
| // |
| // Earlier entries in this slice get priority over later when multiple copies |
| // of the same plugin version are found, but newer versions always override |
| // older versions where both satisfy the provider version constraints. |
| func (m *Meta) pluginDirs(includeAutoInstalled bool) []string { |
| // user defined paths take precedence |
| if len(m.pluginPath) > 0 { |
| return m.pluginPath |
| } |
| |
| // When searching the following directories, earlier entries get precedence |
| // if the same plugin version is found twice, but newer versions will |
| // always get preference below regardless of where they are coming from. |
| // TODO: Add auto-install dir, default vendor dir and optional override |
| // vendor dir(s). |
| dirs := []string{"."} |
| |
| // Look in the same directory as the Terraform executable. |
| // If found, this replaces what we found in the config path. |
| exePath, err := osext.Executable() |
| if err != nil { |
| log.Printf("[ERROR] Error discovering exe directory: %s", err) |
| } else { |
| dirs = append(dirs, filepath.Dir(exePath)) |
| } |
| |
| // add the user vendor directory |
| dirs = append(dirs, DefaultPluginVendorDir) |
| |
| if includeAutoInstalled { |
| dirs = append(dirs, m.pluginDir()) |
| } |
| dirs = append(dirs, m.GlobalPluginDirs...) |
| |
| return dirs |
| } |
| |
| func (m *Meta) provisionerFactories() map[string]provisioners.Factory { |
| dirs := m.pluginDirs(true) |
| plugins := discovery.FindPlugins("provisioner", dirs) |
| plugins, _ = plugins.ValidateVersions() |
| |
| // For now our goal is to just find the latest version of each plugin |
| // we have on the system. All provisioners should be at version 0.0.0 |
| // currently, so there should actually only be one instance of each plugin |
| // name here, even though the discovery interface forces us to pretend |
| // that might not be true. |
| |
| factories := make(map[string]provisioners.Factory) |
| |
| // Wire up the internal provisioners first. These might be overridden |
| // by discovered provisioners below. |
| for name, factory := range internalProvisionerFactories() { |
| factories[name] = factory |
| } |
| |
| byName := plugins.ByName() |
| for name, metas := range byName { |
| // Since we validated versions above and we partitioned the sets |
| // by name, we're guaranteed that the metas in our set all have |
| // valid versions and that there's at least one meta. |
| newest := metas.Newest() |
| |
| factories[name] = provisionerFactory(newest) |
| } |
| |
| return factories |
| } |
| |
| func provisionerFactory(meta discovery.PluginMeta) provisioners.Factory { |
| return func() (provisioners.Interface, error) { |
| cfg := &plugin.ClientConfig{ |
| Cmd: exec.Command(meta.Path), |
| HandshakeConfig: tfplugin.Handshake, |
| VersionedPlugins: tfplugin.VersionedPlugins, |
| Managed: true, |
| Logger: logging.NewLogger("provisioner"), |
| AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, |
| AutoMTLS: enableProviderAutoMTLS, |
| SyncStdout: logging.PluginOutputMonitor(fmt.Sprintf("%s:stdout", meta.Name)), |
| SyncStderr: logging.PluginOutputMonitor(fmt.Sprintf("%s:stderr", meta.Name)), |
| } |
| client := plugin.NewClient(cfg) |
| return newProvisionerClient(client) |
| } |
| } |
| |
| func internalProvisionerFactories() map[string]provisioners.Factory { |
| return map[string]provisioners.Factory{ |
| "file": provisioners.FactoryFixed(fileprovisioner.New()), |
| "local-exec": provisioners.FactoryFixed(localexec.New()), |
| "remote-exec": provisioners.FactoryFixed(remoteexec.New()), |
| } |
| } |
| |
| func newProvisionerClient(client *plugin.Client) (provisioners.Interface, error) { |
| // Request the RPC client so we can get the provisioner |
| // so we can build the actual RPC-implemented provisioner. |
| rpcClient, err := client.Client() |
| if err != nil { |
| return nil, err |
| } |
| |
| raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) |
| if err != nil { |
| return nil, err |
| } |
| |
| // store the client so that the plugin can kill the child process |
| p := raw.(*tfplugin.GRPCProvisioner) |
| p.PluginClient = client |
| return p, nil |
| } |