diff --git a/Gopkg.lock b/Gopkg.lock index a39388e60..7b67c9f23 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -786,15 +786,16 @@ version = "v1.0.0" [[projects]] - digest = "1:446e0a4f73191887866ac789d1fd210e3408bb8eb8aeaf199d5f3a73ff63c108" + branch = "accept-stdin" + digest = "1:366d0ebbb08fe1b0e3b58b871d14b2a72b67fc87da712103c0340651a014cd23" name = "github.com/hashicorp/go-plugin" packages = [ ".", "internal/plugin", ] pruneopts = "NUT" - revision = "9e3e1c37db188a1acb66561ee0ed4bf4d5e77554" - version = "v1.0.1" + revision = "8fe113736efa529bc9eb72a46ea2129e9c39011e" + source = "github.com/carolynvs/go-plugin" [[projects]] digest = "1:0b06ffe0c0764e413a6738e3f045d6bb14117359aef80a09f8c60fbff2ecad6b" diff --git a/Gopkg.toml b/Gopkg.toml index 74a84420f..5402512ba 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -98,7 +98,9 @@ [[constraint]] name = "github.com/hashicorp/go-plugin" - version = "^v1.0.0" + #version = "^v1.0.0" + source = "github.com/carolynvs/go-plugin" + branch = "accept-stdin" [prune] non-go = true diff --git a/pkg/config/datastore.go b/pkg/config/datastore.go index b66f51c21..7bae2b8b0 100644 --- a/pkg/config/datastore.go +++ b/pkg/config/datastore.go @@ -1,10 +1,20 @@ package config +import "errors" + // Data is the data stored in PORTER_HOME/porter.toml|yaml|json type Data struct { // Only define fields here that you need to access from code // Values are dynamically applied to flags and don't need to be defined - InstanceStoragePlugin string `mapstructure:"instance-storage-plugin"` + InstanceStoragePlugin string `mapstructure:"instance-storage-plugin"` + DefaultInstanceStore string `mapstructure:"default-instance-store"` + InstanceStores []InstanceStore `mapstructure:"instance-store"` +} + +type InstanceStore struct { + Name string `mapstructure:"name"` + PluginSubkey string `mapstructure:"plugin"` + Config map[string]interface{} `mapstructure:"config"` } func (d *Data) GetInstanceStoragePlugin() string { @@ -15,6 +25,26 @@ func (d *Data) GetInstanceStoragePlugin() string { return d.InstanceStoragePlugin } +func (d *Data) GetDefaultInstanceStore() string { + if d == nil { + return "" + } + + return d.DefaultInstanceStore +} + +func (d *Data) GetInstanceStore(name string) (InstanceStore, error) { + if d != nil { + for _, is := range d.InstanceStores { + if is.Name == name { + return is, nil + } + } + } + + return InstanceStore{}, errors.New("instance-store %q not defined") +} + var _ DataStoreLoaderFunc = NoopDataLoader // NoopDataLoader skips loading the datastore. diff --git a/pkg/instance-storage/provider/provider.go b/pkg/instance-storage/provider/provider.go index f2bf93bd6..a32d65b25 100644 --- a/pkg/instance-storage/provider/provider.go +++ b/pkg/instance-storage/provider/provider.go @@ -1,9 +1,10 @@ package instancestorageprovider import ( - "fmt" + "bytes" + "encoding/json" + "io" "os/exec" - "strings" "github.com/deislabs/cnab-go/claim" "github.com/deislabs/cnab-go/utils/crud" @@ -42,38 +43,27 @@ func NewPluginDelegator(c *config.Config) *PluginDelegator { } func (d *PluginDelegator) connect() (crud.Store, func(), error) { - pluginId := d.Config.Data.GetInstanceStoragePlugin() - parts := strings.Split(pluginId, ".") - isInternal := false - if len(parts) == 1 { - isInternal = true - } else if len(parts) > 2 { - return nil, nil, errors.New("invalid config value for instance-storage-plugin, can only have two parts PLUGIN_BINARY.IMPLEMENTATION_KEY") - } + pluginKey, config, err := d.selectInstanceStoragePlugin() + pluginKey.Interface = claimstore.PluginKey var pluginCommand *exec.Cmd - if isInternal { - pluginImpl := parts[0] - pluginKey := fmt.Sprintf("%s.porter.%s", claimstore.PluginKey, pluginImpl) + if pluginKey.IsInternal { porterPath, err := d.GetPorterPath() if err != nil { return nil, nil, errors.Wrap(err, "could not determine the path to the porter client") } - pluginCommand = d.NewCommand(porterPath, "plugin", "run", pluginKey) + pluginCommand = d.NewCommand(porterPath, "plugin", "run", pluginKey.String()) } else { - pluginBinary := parts[0] - pluginImpl := parts[1] - pluginKey := fmt.Sprintf("%s.%s.%s", claimstore.PluginKey, pluginBinary, pluginImpl) - pluginPath, err := d.GetPluginPath(pluginBinary) + pluginPath, err := d.GetPluginPath(pluginKey.Binary) if err != nil { return nil, nil, err } - pluginCommand = d.NewCommand(pluginPath, "run", pluginKey) + pluginCommand = d.NewCommand(pluginPath, "run", pluginKey.String()) } + pluginCommand.Stdin = config - // Create an hclog.Logger logger := hclog.New(&hclog.LoggerOptions{ Name: "porter", Output: d.Err, @@ -98,21 +88,62 @@ func (d *PluginDelegator) connect() (crud.Store, func(), error) { rpcClient, err := client.Client() if err != nil { cleanup() - return nil, nil, errors.Wrapf(err, "could not connect to the %s plugin", pluginId) + return nil, nil, errors.Wrapf(err, "could not connect to the %s plugin", pluginKey) } // Request the plugin raw, err := rpcClient.Dispense(claimstore.PluginKey) if err != nil { cleanup() - return nil, nil, errors.Wrapf(err, "could not connect to the %s plugin", pluginId) + return nil, nil, errors.Wrapf(err, "could not connect to the %s plugin", pluginKey) } store, ok := raw.(crud.Store) if !ok { cleanup() - return nil, nil, errors.Errorf("the interface exposed by the %s plugin was not instancestorage.ClaimStore", pluginId) + return nil, nil, errors.Errorf("the interface exposed by the %s plugin was not instancestorage.ClaimStore", pluginKey) } return store, cleanup, nil } + +// selectInstanceStoragePlugin picks the plugin to use and loads its configuration. +func (d *PluginDelegator) selectInstanceStoragePlugin() (plugins.PluginKey, io.Reader, error) { + var pluginId string + var config interface{} + + defaultStore := d.Config.Data.GetDefaultInstanceStore() + if defaultStore != "" { + is, err := d.Config.Data.GetInstanceStore(defaultStore) + if err != nil { + return plugins.PluginKey{}, nil, err + } + pluginId = is.PluginSubkey + config = is.Config + } + + if pluginId == "" { + pluginId = d.Config.Data.GetInstanceStoragePlugin() + } + + key, err := plugins.ParsePluginKey(pluginId) + if err != nil { + return plugins.PluginKey{}, nil, err + } + + configInput, err := d.writePluginConfig(config) + return key, configInput, err +} + +func (d *PluginDelegator) writePluginConfig(config interface{}) (io.Reader, error) { + if config == nil { + return &bytes.Buffer{}, nil + } + + b, err := json.Marshal(config) + if err != nil { + return nil, errors.Wrapf(err, "could not marshal plugin config %#v", config) + } + + return bytes.NewBuffer(b), nil +} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 6ad59dc73..7c5eb3391 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -1,6 +1,10 @@ package plugins import ( + "errors" + "fmt" + "strings" + "github.com/hashicorp/go-plugin" ) @@ -10,3 +14,38 @@ var HandshakeConfig = plugin.HandshakeConfig{ MagicCookieKey: "PORTER", MagicCookieValue: "bbc2dd71-def4-4311-906e-e98dc27208ce", } + +type PluginKey struct { + Binary string + Interface string + Implementation string + IsInternal bool +} + +func (k PluginKey) String() string { + return fmt.Sprintf("%s.%s.%s", k.Interface, k.Binary, k.Implementation) +} + +func ParsePluginKey(value string) (PluginKey, error) { + var key PluginKey + + parts := strings.Split(value, ".") + + switch len(parts) { + case 1: + key.IsInternal = true + key.Binary = "porter" + key.Implementation = parts[0] + case 2: + key.Binary = parts[0] + key.Implementation = parts[1] + case 3: + key.Interface = parts[0] + key.Binary = parts[1] + key.Implementation = parts[2] + default: + return PluginKey{}, errors.New("invalid plugin key %q, allowed format is [INTERFACE].BINARY.IMPLEMENTATION") + } + + return key, nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/client.go b/vendor/github.com/hashicorp/go-plugin/client.go index bc56559c6..a6c81543e 100644 --- a/vendor/github.com/hashicorp/go-plugin/client.go +++ b/vendor/github.com/hashicorp/go-plugin/client.go @@ -526,7 +526,9 @@ func (c *Client) Start() (addr net.Addr, err error) { cmd := c.config.Cmd cmd.Env = append(cmd.Env, os.Environ()...) cmd.Env = append(cmd.Env, env...) - cmd.Stdin = os.Stdin + if cmd.Stdin == nil { + cmd.Stdin = os.Stdin + } cmdStdout, err := cmd.StdoutPipe() if err != nil { @@ -788,7 +790,10 @@ func (c *Client) reattach() (net.Addr, error) { // Verify the process still exists. If not, then it is an error p, err := os.FindProcess(c.config.Reattach.Pid) if err != nil { - return nil, err + // On Unix systems, FindProcess never returns an error. + // On Windows, for non-existent pids it returns: + // os.SyscallError - 'OpenProcess: the paremter is incorrect' + return nil, ErrProcessNotFound } // Attempt to connect to the addr since on Unix systems FindProcess