Merge pull request #13524 from calavera/plugin_json_spec

Plugins JSON spec.
This commit is contained in:
Brian Goff 2015-06-30 15:44:48 -04:00
commit f80a5673c1
6 changed files with 145 additions and 43 deletions

View file

@ -5,12 +5,13 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/sockets"
"github.com/docker/docker/pkg/tlsconfig"
) )
const ( const (
@ -18,11 +19,18 @@ const (
defaultTimeOut = 30 defaultTimeOut = 30
) )
func NewClient(addr string) *Client { func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
tr := &http.Transport{} tr := &http.Transport{}
c, err := tlsconfig.Client(tlsConfig)
if err != nil {
return nil, err
}
tr.TLSClientConfig = c
protoAndAddr := strings.Split(addr, "://") protoAndAddr := strings.Split(addr, "://")
configureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1]) sockets.ConfigureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1])
return &Client{&http.Client{Transport: tr}, protoAndAddr[1]} return &Client{&http.Client{Transport: tr}, protoAndAddr[1]}, nil
} }
type Client struct { type Client struct {
@ -96,18 +104,3 @@ func backoff(retries int) time.Duration {
func abort(start time.Time, timeOff time.Duration) bool { func abort(start time.Time, timeOff time.Duration) bool {
return timeOff+time.Since(start) > time.Duration(defaultTimeOut)*time.Second return timeOff+time.Since(start) > time.Duration(defaultTimeOut)*time.Second
} }
func configureTCPTransport(tr *http.Transport, proto, addr string) {
// Why 32? See https://github.com/docker/docker/pull/8035.
timeout := 32 * time.Second
if proto == "unix" {
// No need for compression in local communications.
tr.DisableCompression = true
tr.Dial = func(_, _ string) (net.Conn, error) {
return net.DialTimeout(proto, addr, timeout)
}
} else {
tr.Proxy = http.ProxyFromEnvironment
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
}
}

View file

@ -7,6 +7,8 @@ import (
"reflect" "reflect"
"testing" "testing"
"time" "time"
"github.com/docker/docker/pkg/tlsconfig"
) )
var ( var (
@ -27,7 +29,7 @@ func teardownRemotePluginServer() {
} }
func TestFailedConnection(t *testing.T) { func TestFailedConnection(t *testing.T) {
c := NewClient("tcp://127.0.0.1:1") c, _ := NewClient("tcp://127.0.0.1:1", tlsconfig.Options{InsecureSkipVerify: true})
err := c.callWithRetry("Service.Method", nil, nil, false) err := c.callWithRetry("Service.Method", nil, nil, false)
if err == nil { if err == nil {
t.Fatal("Unexpected successful connection") t.Fatal("Unexpected successful connection")
@ -51,7 +53,7 @@ func TestEchoInputOutput(t *testing.T) {
io.Copy(w, r.Body) io.Copy(w, r.Body)
}) })
c := NewClient(addr) c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
var output Manifest var output Manifest
err := c.Call("Test.Echo", m, &output) err := c.Call("Test.Echo", m, &output)
if err != nil { if err != nil {

View file

@ -1,6 +1,7 @@
package plugins package plugins
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -37,25 +38,25 @@ func (l *LocalRegistry) Plugin(name string) (*Plugin, error) {
filepath := filepath.Join(l.path, name) filepath := filepath.Join(l.path, name)
specpath := filepath + ".spec" specpath := filepath + ".spec"
if fi, err := os.Stat(specpath); err == nil { if fi, err := os.Stat(specpath); err == nil {
return readPluginInfo(specpath, fi) return readPluginSpecInfo(specpath, fi)
} }
socketpath := filepath + ".sock" socketpath := filepath + ".sock"
if fi, err := os.Stat(socketpath); err == nil { if fi, err := os.Stat(socketpath); err == nil {
return readPluginInfo(socketpath, fi) return readPluginSocketInfo(socketpath, fi)
} }
jsonpath := filepath + ".json"
if _, err := os.Stat(jsonpath); err == nil {
return readPluginJSONInfo(name, jsonpath)
}
return nil, ErrNotFound return nil, ErrNotFound
} }
func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) { func readPluginSpecInfo(path string, fi os.FileInfo) (*Plugin, error) {
name := strings.Split(fi.Name(), ".")[0] name := strings.Split(fi.Name(), ".")[0]
if fi.Mode()&os.ModeSocket != 0 {
return &Plugin{
Name: name,
Addr: "unix://" + path,
}, nil
}
content, err := ioutil.ReadFile(path) content, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
@ -71,8 +72,34 @@ func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) {
return nil, fmt.Errorf("Unknown protocol") return nil, fmt.Errorf("Unknown protocol")
} }
return &Plugin{ return newLocalPlugin(name, addr), nil
Name: name, }
Addr: addr,
}, nil func readPluginSocketInfo(path string, fi os.FileInfo) (*Plugin, error) {
name := strings.Split(fi.Name(), ".")[0]
if fi.Mode()&os.ModeSocket == 0 {
return nil, fmt.Errorf("%s is not a socket", path)
}
return newLocalPlugin(name, "unix://"+path), nil
}
func readPluginJSONInfo(name, path string) (*Plugin, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var p Plugin
if err := json.NewDecoder(f).Decode(&p); err != nil {
return nil, err
}
p.Name = name
if len(p.TLSConfig.CAFile) == 0 {
p.TLSConfig.InsecureSkipVerify = true
}
return &p, nil
} }

View file

@ -61,7 +61,7 @@ func TestLocalSocket(t *testing.T) {
} }
func TestFileSpecPlugin(t *testing.T) { func TestFileSpecPlugin(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-test") tmpdir, err := ioutil.TempDir("", "docker-test-")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -102,3 +102,51 @@ func TestFileSpecPlugin(t *testing.T) {
} }
} }
} }
func TestFileJSONSpecPlugin(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-test-")
if err != nil {
t.Fatal(err)
}
p := filepath.Join(tmpdir, "example.json")
spec := `{
"Name": "plugin-example",
"Addr": "https://example.com/docker/plugin",
"TLSConfig": {
"CAFile": "/usr/shared/docker/certs/example-ca.pem",
"CertFile": "/usr/shared/docker/certs/example-cert.pem",
"KeyFile": "/usr/shared/docker/certs/example-key.pem"
}
}`
if err = ioutil.WriteFile(p, []byte(spec), 0644); err != nil {
t.Fatal(err)
}
r := newLocalRegistry(tmpdir)
plugin, err := r.Plugin("example")
if err != nil {
t.Fatal(err)
}
if plugin.Name != "example" {
t.Fatalf("Expected plugin `plugin-example`, got %s\n", plugin.Name)
}
if plugin.Addr != "https://example.com/docker/plugin" {
t.Fatalf("Expected plugin addr `https://example.com/docker/plugin`, got %s\n", plugin.Addr)
}
if plugin.TLSConfig.CAFile != "/usr/shared/docker/certs/example-ca.pem" {
t.Fatalf("Expected plugin CA `/usr/shared/docker/certs/example-ca.pem`, got %s\n", plugin.TLSConfig.CAFile)
}
if plugin.TLSConfig.CertFile != "/usr/shared/docker/certs/example-cert.pem" {
t.Fatalf("Expected plugin Certificate `/usr/shared/docker/certs/example-cert.pem`, got %s\n", plugin.TLSConfig.CertFile)
}
if plugin.TLSConfig.KeyFile != "/usr/shared/docker/certs/example-key.pem" {
t.Fatalf("Expected plugin Key `/usr/shared/docker/certs/example-key.pem`, got %s\n", plugin.TLSConfig.KeyFile)
}
}

View file

@ -5,6 +5,7 @@ import (
"sync" "sync"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/tlsconfig"
) )
var ( var (
@ -26,22 +27,36 @@ type Manifest struct {
} }
type Plugin struct { type Plugin struct {
Name string Name string `json:"-"`
Addr string Addr string
Client *Client TLSConfig tlsconfig.Options
Manifest *Manifest Client *Client `json:"-"`
Manifest *Manifest `json:"-"`
}
func newLocalPlugin(name, addr string) *Plugin {
return &Plugin{
Name: name,
Addr: addr,
TLSConfig: tlsconfig.Options{InsecureSkipVerify: true},
}
} }
func (p *Plugin) activate() error { func (p *Plugin) activate() error {
m := new(Manifest) c, err := NewClient(p.Addr, p.TLSConfig)
p.Client = NewClient(p.Addr)
err := p.Client.Call("Plugin.Activate", nil, m)
if err != nil { if err != nil {
return err return err
} }
p.Client = c
m := new(Manifest)
if err = p.Client.Call("Plugin.Activate", nil, m); err != nil {
return err
}
logrus.Debugf("%s's manifest: %v", p.Name, m) logrus.Debugf("%s's manifest: %v", p.Name, m)
p.Manifest = m p.Manifest = m
for _, iface := range m.Implements { for _, iface := range m.Implements {
handler, handled := extpointHandlers[iface] handler, handled := extpointHandlers[iface]
if !handled { if !handled {

View file

@ -3,6 +3,8 @@ package sockets
import ( import (
"crypto/tls" "crypto/tls"
"net" "net"
"net/http"
"time"
"github.com/docker/docker/pkg/listenbuffer" "github.com/docker/docker/pkg/listenbuffer"
) )
@ -18,3 +20,18 @@ func NewTcpSocket(addr string, tlsConfig *tls.Config, activate <-chan struct{})
} }
return l, nil return l, nil
} }
func ConfigureTCPTransport(tr *http.Transport, proto, addr string) {
// Why 32? See https://github.com/docker/docker/pull/8035.
timeout := 32 * time.Second
if proto == "unix" {
// No need for compression in local communications.
tr.DisableCompression = true
tr.Dial = func(_, _ string) (net.Conn, error) {
return net.DialTimeout(proto, addr, timeout)
}
} else {
tr.Proxy = http.ProxyFromEnvironment
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
}
}