run built-in provisioners in-process

Use the new provisioner interfaces, and run the built-in provisioners
in-process.
This commit is contained in:
James Bardin 2020-11-25 17:22:40 -05:00
parent 256a7ec95a
commit 5e089c2c09
5 changed files with 10 additions and 211 deletions

View File

@ -1,97 +0,0 @@
package command
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/plugin"
"github.com/kardianos/osext"
)
// InternalPluginCommand is a Command implementation that allows plugins to be
// compiled into the main Terraform binary and executed via a subcommand.
type InternalPluginCommand struct {
Meta
}
const TFSPACE = "-TFSPACE-"
// BuildPluginCommandString builds a special string for executing internal
// plugins. It has the following format:
//
// /path/to/terraform-TFSPACE-internal-plugin-TFSPACE-terraform-provider-aws
//
// We split the string on -TFSPACE- to build the command executor. The reason we
// use -TFSPACE- is so we can support spaces in the /path/to/terraform part.
func BuildPluginCommandString(pluginType, pluginName string) (string, error) {
terraformPath, err := osext.Executable()
if err != nil {
return "", err
}
parts := []string{terraformPath, "internal-plugin", pluginType, pluginName}
return strings.Join(parts, TFSPACE), nil
}
// Internal plugins do not support any CLI args, but we do receive flags that
// main.go:mergeEnvArgs has merged in from EnvCLI. Instead of making main.go
// aware of this exception, we strip all flags from our args. Flags are easily
// identified by the '-' prefix, ensured by the cli package used.
func StripArgFlags(args []string) []string {
argsNoFlags := []string{}
for i := range args {
if !strings.HasPrefix(args[i], "-") {
argsNoFlags = append(argsNoFlags, args[i])
}
}
return argsNoFlags
}
func (c *InternalPluginCommand) Run(args []string) int {
// strip flags from args, only use subcommands.
args = StripArgFlags(args)
if len(args) != 2 {
log.Printf("Wrong number of args; expected: terraform internal-plugin pluginType pluginName")
return 1
}
pluginType := args[0]
pluginName := args[1]
log.SetPrefix(fmt.Sprintf("%s-%s (internal) ", pluginName, pluginType))
switch pluginType {
case "provisioner":
pluginFunc, found := InternalProvisioners[pluginName]
if !found {
log.Printf("[ERROR] Could not load provisioner: %s", pluginName)
return 1
}
log.Printf("[INFO] Starting provisioner plugin %s", pluginName)
plugin.Serve(&plugin.ServeOpts{
ProvisionerFunc: pluginFunc,
})
default:
log.Printf("[ERROR] Invalid plugin type %s", pluginType)
return 1
}
return 0
}
func (c *InternalPluginCommand) Help() string {
helpText := `
Usage: terraform internal-plugin pluginType pluginName
Runs an internally-compiled version of a plugin from the terraform binary.
NOTE: this is an internal command and you should not call it yourself.
`
return strings.TrimSpace(helpText)
}
func (c *InternalPluginCommand) Synopsis() string {
return "internal plugin command"
}

View File

@ -1,20 +0,0 @@
//
// This file is automatically generated by scripts/generate-plugins.go -- Do not edit!
//
package command
import (
fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file"
localexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/local-exec"
remoteexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/remote-exec"
"github.com/hashicorp/terraform/plugin"
)
var InternalProviders = map[string]plugin.ProviderFunc{}
var InternalProvisioners = map[string]plugin.ProvisionerFunc{
"file": fileprovisioner.Provisioner,
"local-exec": localexecprovisioner.Provisioner,
"remote-exec": remoteexecprovisioner.Provisioner,
}

View File

@ -1,55 +0,0 @@
package command
import (
"testing"
)
func TestInternalPlugin_InternalProviders(t *testing.T) {
m := new(Meta)
providers := m.internalProviders()
// terraform is the only provider moved back to internal
for _, name := range []string{"terraform"} {
pf, ok := providers[name]
if !ok {
t.Errorf("Expected to find %s in InternalProviders", name)
}
provider, err := pf()
if err != nil {
t.Fatal(err)
}
if provider == nil {
t.Fatal("provider factory returned a nil provider")
}
}
}
func TestInternalPlugin_InternalProvisioners(t *testing.T) {
for _, name := range []string{"file", "local-exec", "remote-exec"} {
if _, ok := InternalProvisioners[name]; !ok {
t.Errorf("Expected to find %s in InternalProvisioners", name)
}
}
}
func TestInternalPlugin_BuildPluginCommandString(t *testing.T) {
actual, err := BuildPluginCommandString("provisioner", "remote-exec")
if err != nil {
t.Fatalf(err.Error())
}
expected := "-TFSPACE-internal-plugin-TFSPACE-provisioner-TFSPACE-remote-exec"
if actual[len(actual)-len(expected):] != expected {
t.Errorf("Expected command to end with %s; got:\n%s\n", expected, actual)
}
}
func TestInternalPlugin_StripArgFlags(t *testing.T) {
actual := StripArgFlags([]string{"provisioner", "remote-exec", "-var-file=my_vars.tfvars", "-flag"})
expected := []string{"provisioner", "remote-exec"}
// Must be same length and order.
if len(actual) != len(expected) || expected[0] != actual[0] || actual[1] != actual[1] {
t.Fatalf("Expected args to be exactly '%s', got '%s'", expected, actual)
}
}

View File

@ -9,11 +9,13 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strings"
plugin "github.com/hashicorp/go-plugin"
"github.com/kardianos/osext"
fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file"
localexec "github.com/hashicorp/terraform/builtin/provisioners/local-exec"
remoteexec "github.com/hashicorp/terraform/builtin/provisioners/remote-exec"
"github.com/hashicorp/terraform/internal/logging"
tfplugin "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery"
@ -134,8 +136,8 @@ func (m *Meta) provisionerFactories() map[string]provisioners.Factory {
// Wire up the internal provisioners first. These might be overridden
// by discovered provisioners below.
for name := range InternalProvisioners {
factories[name] = internalProvisionerFactory(discovery.PluginMeta{Name: name})
for name, factory := range internalProvisionerFactories() {
factories[name] = factory
}
byName := plugins.ByName()
@ -151,29 +153,6 @@ func (m *Meta) provisionerFactories() map[string]provisioners.Factory {
return factories
}
func internalPluginClient(kind, name string) (*plugin.Client, error) {
cmdLine, err := BuildPluginCommandString(kind, name)
if err != nil {
return nil, err
}
// See the docstring for BuildPluginCommandString for why we need to do
// this split here.
cmdArgv := strings.Split(cmdLine, TFSPACE)
cfg := &plugin.ClientConfig{
Cmd: exec.Command(cmdArgv[0], cmdArgv[1:]...),
HandshakeConfig: tfplugin.Handshake,
Managed: true,
VersionedPlugins: tfplugin.VersionedPlugins,
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
AutoMTLS: enableProviderAutoMTLS,
Logger: logging.NewLogger(kind),
}
return plugin.NewClient(cfg), nil
}
func provisionerFactory(meta discovery.PluginMeta) provisioners.Factory {
return func() (provisioners.Interface, error) {
cfg := &plugin.ClientConfig{
@ -190,13 +169,11 @@ func provisionerFactory(meta discovery.PluginMeta) provisioners.Factory {
}
}
func internalProvisionerFactory(meta discovery.PluginMeta) provisioners.Factory {
return func() (provisioners.Interface, error) {
client, err := internalPluginClient("provisioner", meta.Name)
if err != nil {
return nil, fmt.Errorf("[WARN] failed to build command line for internal plugin %q: %s", meta.Name, err)
}
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()),
}
}

View File

@ -194,12 +194,6 @@ func initCommands(
}, nil
},
"internal-plugin": func() (cli.Command, error) {
return &command.InternalPluginCommand{
Meta: meta,
}, nil
},
"login": func() (cli.Command, error) {
return &command.LoginCommand{
Meta: meta,