Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass plugin config via stdin #776

Merged
merged 3 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 31 additions & 1 deletion pkg/config/datastore.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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.
Expand Down
77 changes: 54 additions & 23 deletions pkg/instance-storage/provider/provider.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
39 changes: 39 additions & 0 deletions pkg/plugins/plugins.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package plugins

import (
"errors"
"fmt"
"strings"

"github.com/hashicorp/go-plugin"
)

Expand All @@ -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
}
9 changes: 7 additions & 2 deletions vendor/github.com/hashicorp/go-plugin/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.