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

Draft plugin system #42

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e7b3d38
Initial plugin system implementation
okushchenko Jun 4, 2017
6803c56
Move env plugin to builtins
okushchenko Jun 4, 2017
4cf2ec7
Fix build
okushchenko Jun 4, 2017
9ea60cc
Improve plugin system
okushchenko Jun 6, 2017
d4913e1
Make plugin system actually work
okushchenko Jun 6, 2017
a840e05
Fix pointer dereference in plugins
okushchenko Jun 6, 2017
4ab91ab
Migrate more backends to plugins
okushchenko Jun 6, 2017
d312075
Migrate all backends to plugins
okushchenko Jun 7, 2017
695772f
Make plugins exit correctly
okushchenko Jun 7, 2017
4382cc7
Improve documentation for plugins
okushchenko Jun 7, 2017
54a815b
Undo fix of ipv6 in template test
okushchenko Jun 7, 2017
cf0f326
Add vendored dependencies for the plugin system
okushchenko Jun 7, 2017
8b8d9c4
Fix typo in docs
okushchenko Jun 7, 2017
984d1f9
Refactor config parsing in modules
okushchenko Jun 12, 2017
2d11723
Refactor logging to be consistent with hashicorp/go-plugin
okushchenko Jun 13, 2017
1611e9a
Update logging dependencies
okushchenko Jun 13, 2017
b17d55b
Expand docs on plugin system
okushchenko Jul 27, 2017
2528759
Fix linter errors, refactoring
okushchenko Jul 27, 2017
2c374a8
Add more comments to plugin package
okushchenko Jul 31, 2017
0f33da6
Remove stopChan parameter from WatchPrefix method of Database interface
okushchenko Aug 2, 2017
69a1743
Add Plugin API Protobufs
okushchenko Aug 3, 2017
0ee5efd
Implement gRPC plugins support
okushchenko Aug 3, 2017
64b30b2
Update vendored dependencies
okushchenko Aug 3, 2017
5160cd6
Update plugin system docs
okushchenko Aug 4, 2017
200ec0e
Update Go version to 1.8
okushchenko Aug 4, 2017
15286bf
Remove DatabaseFunc factory; remove net/rpc plugin support
okushchenko Aug 7, 2017
3326352
Fix client references in tests
okushchenko Aug 7, 2017
66c1539
Replace polling of WatchPrefix with streaming
okushchenko Feb 25, 2018
14a3326
Change the plugin interface, improve error handling
okushchenko Feb 28, 2018
2bcea95
Merge branch 'master' into draft-plugin-system
okushchenko Mar 1, 2018
2304d6c
Refactor logging in plugins
okushchenko Mar 2, 2018
7cfb6b2
Fix logging
okushchenko Mar 18, 2018
5e88924
Fix error handling in the plugin system when confd exits
okushchenko May 3, 2018
5f1357e
Merge branch 'master' into draft-plugin-system
okushchenko May 6, 2018
27df474
Fix tests
okushchenko May 6, 2018
591b904
Fix tests
okushchenko May 6, 2018
3a3e0a1
Fix tests
okushchenko May 6, 2018
2e17582
Fix tests
okushchenko May 6, 2018
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
Prev Previous commit
Next Next commit
Move env plugin to builtins
okushchenko committed Jun 4, 2017

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 6803c564de3c724435e5f8e93213f954023a55cb
71 changes: 49 additions & 22 deletions backends/client.go
Original file line number Diff line number Diff line change
@@ -4,30 +4,59 @@ import (
"os/exec"

plugin "github.com/hashicorp/go-plugin"
"github.com/kelseyhightower/confd/backends/commons"
"github.com/kelseyhightower/confd/confd"
"github.com/kelseyhightower/confd/log"
confdplugin "github.com/kelseyhightower/confd/plugin"
)

// handshakeConfigs are used to just do a basic handshake between
// a plugin and host. If the handshake fails, a user friendly error is shown.
// This prevents users from executing bad plugins or executing a plugin
// directory. It is a UX feature, not a security feature.
var handshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "BASIC_PLUGIN",
MagicCookieValue: "hello",
}

// pluginMap is the map of plugins we can dispense.
var pluginMap = map[string]plugin.Plugin{
"env": &commons.StoreClientPlugin{},
}

// New is used to create a storage client based on our configuration.
func New(config Config) (commons.StoreClient, error) {
func New(config Config) (confd.Database, error) {
// if config.Backend == "" {
// config.Backend = "etcd"
// }
// backendNodes := config.BackendNodes
// log.Info("Backend nodes set to " + strings.Join(backendNodes, ", "))
// switch config.Backend {
// case "consul":
// return consul.New(config.BackendNodes, config.Scheme,
// config.ClientCert, config.ClientKey,
// config.ClientCaKeys)
// case "etcd":
// // Create the etcd client upfront and use it for the life of the process.
// // The etcdClient is an http.Client and designed to be reused.
// return etcd.NewEtcdClient(backendNodes, config.ClientCert, config.ClientKey, config.ClientCaKeys, config.BasicAuth, config.Username, config.Password)
// case "zookeeper":
// return zookeeper.NewZookeeperClient(backendNodes)
// case "rancher":
// return rancher.NewRancherClient(backendNodes)
// case "redis":
// return redis.NewRedisClient(backendNodes, config.ClientKey)
// case "env":
// return env.NewEnvClient()
// case "vault":
// vaultConfig := map[string]string{
// "app-id": config.AppID,
// "user-id": config.UserID,
// "username": config.Username,
// "password": config.Password,
// "token": config.AuthToken,
// "cert": config.ClientCert,
// "key": config.ClientKey,
// "caCert": config.ClientCaKeys,
// }
// return vault.New(backendNodes[0], config.AuthType, vaultConfig)
// case "dynamodb":
// table := config.Table
// log.Info("DynamoDB table set to " + table)
// return dynamodb.NewDynamoDBClient(table)
// case "stackengine":
// return stackengine.NewStackEngineClient(backendNodes, config.Scheme, config.ClientCert, config.ClientKey, config.ClientCaKeys, config.AuthToken)
// }
// return nil, errors.New("Invalid backend")

client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: handshakeConfig,
Plugins: pluginMap,
HandshakeConfig: confdplugin.HandshakeConfig,
Plugins: confdplugin.PluginMap,
Cmd: exec.Command("/Users/oleksa/go/src/github.com/kelseyhightower/confd/bin/plugins"),
})
defer client.Kill()
@@ -44,7 +73,5 @@ func New(config Config) (commons.StoreClient, error) {
log.Fatal(err.Error())
}

// We should have a Greeter now! This feels like a normal interface
// implementation but is in fact over an RPC connection.
return raw.(commons.StoreClient), nil
return raw.(confd.Database), nil
}
75 changes: 0 additions & 75 deletions backends/commons/main.go

This file was deleted.

29 changes: 0 additions & 29 deletions backends/env/main.go

This file was deleted.

9 changes: 9 additions & 0 deletions builtin/bins/env/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import "github.com/hashicorp/terraform/plugin"

func main() {
plugin.Serve(&plugin.ServeOpts{
DatabaseFunc: env.Database,
})
}
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import (
"os"
"strings"

"github.com/kelseyhightower/confd/backends/env/util"
"github.com/kelseyhightower/confd/builtin/databases/env/util"
"github.com/kelseyhightower/confd/log"
)

31 changes: 31 additions & 0 deletions builtin/databases/env/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package env

// package main
//
// import (
// plugin "github.com/hashicorp/go-plugin"
// "github.com/kelseyhightower/confd/backends/commons"
// env "github.com/kelseyhightower/confd/backends/env/plugin"
// )
//
// // handshakeConfigs are used to just do a basic handshake between
// // a plugin and host. If the handshake fails, a user friendly error is shown.
// // This prevents users from executing bad plugins or executing a plugin
// // directory. It is a UX feature, not a security feature.
// var handshakeConfig = plugin.HandshakeConfig{
// ProtocolVersion: 1,
// MagicCookieKey: "BASIC_PLUGIN",
// MagicCookieValue: "hello",
// }
//
// // pluginMap is the map of plugins we can dispense.
// var pluginMap = map[string]plugin.Plugin{
// "env": &commons.DatabasePlugin{Impl: new(env.Client)},
// }
//
// func main() {
// plugin.Serve(&plugin.ServeConfig{
// HandshakeConfig: handshakeConfig,
// Plugins: pluginMap,
// })
// }
Original file line number Diff line number Diff line change
@@ -2,16 +2,14 @@ package util

import "strings"

var replacer = strings.NewReplacer("/", "_")

func Transform(key string) string {
replacer := strings.NewReplacer("/", "_")
k := strings.TrimPrefix(key, "/")
return strings.ToUpper(replacer.Replace(k))
}

var cleanReplacer = strings.NewReplacer("_", "/")

func Clean(key string) string {
cleanReplacer := strings.NewReplacer("_", "/")
newKey := "/" + key
return cleanReplacer.Replace(strings.ToLower(newKey))
}
4 changes: 2 additions & 2 deletions confd.go
Original file line number Diff line number Diff line change
@@ -23,12 +23,12 @@ func main() {

log.Info("Starting confd")

storeClient, err := backends.New(backendsConfig)
database, err := backends.New(backendsConfig)
if err != nil {
log.Fatal(err.Error())
}

templateConfig.StoreClient = storeClient
templateConfig.Database = database
if onetime {
if err := template.Process(templateConfig); err != nil {
log.Fatal(err.Error())
8 changes: 8 additions & 0 deletions confd/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package confd

// The Database interface is implemented by objects that can retrieve
// key/value pairs from a backend store.
type Database interface {
GetValues(keys []string) (map[string]string, error)
WatchPrefix(prefix string, keys []string, waitIndex uint64, stopChan chan bool) (uint64, error)
}
6 changes: 6 additions & 0 deletions docs/plugin-system.md
Original file line number Diff line number Diff line change
@@ -12,3 +12,9 @@ How plugins should be configured?
I prefer number 2 for now, as it's easier to implement and maintain. But it can be changed later on.

Plugin systems is implemented using https://github.com/hashicorp/go-plugin, which is successfully used in Terraform, Packer, Nomad and Vault. It's based on network RPC over local network. Plugins are Go interface implementations, which maps quite good to the current backends implementation.

The core set of plugins is defined in builtin/ directory. They are compiled in the main binary.
To add a new plugin:

Builtin plugins are called using special cli command:
confd internal-plugin <pluginType> <pluginName>
58 changes: 58 additions & 0 deletions internal_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"fmt"
"log"
"strings"

"github.com/kardianos/osext"
"github.com/kelseyhightower/confd/plugin"
)

const CONFDSPACE = "-CONFDSPACE-"

// BuildPluginCommandString builds a special string for executing internal
// plugins. It has the following format:
//
// /path/to/terraform-CONFDSPACE-internal-plugin-CONFDSPACE-terraform-provider-aws
//
// We split the string on -CONFDSPACE- to build the command executor. The reason we
// use -CONFDSPACE- 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, CONFDSPACE), nil
}

func Run(args []string) int {
if len(args) != 2 {
log.Printf("Wrong number of args; expected: confd internal-plugin pluginType pluginName")
return 1
}

pluginType := args[0]
pluginName := args[1]

log.SetPrefix(fmt.Sprintf("%s-%s (internal) ", pluginName, pluginType))

switch pluginType {
case "database":
pluginFunc, found := InternalProviders[pluginName]
if !found {
log.Printf("[ERROR] Could not load provider: %s", pluginName)
return 1
}
log.Printf("[INFO] Starting provider plugin %s", pluginName)
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: pluginFunc,
})
default:
log.Printf("[ERROR] Invalid plugin type %s", pluginType)
return 1
}

return 0
}
10 changes: 10 additions & 0 deletions internal_plugin_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import (
"github.com/kelseyhightower/confd/builtin/databases/env"
"github.com/kelseyhightower/confd/confd"
)

var InternalDatabases = map[string]confd.Database{
"env": &env.Client{},
}
Loading