Skip to content

Commit

Permalink
Pass plugin config via stdin
Browse files Browse the repository at this point in the history
  • Loading branch information
carolynvs-msft committed Oct 28, 2019
1 parent b9de25e commit 3cb5cce
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 24 deletions.
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
}

0 comments on commit 3cb5cce

Please sign in to comment.