Merge pull request #56 from aanand/orchard

This commit is contained in:
Solomon Hykes 2014-06-08 14:05:41 -07:00
commit 991a109959
4 changed files with 170 additions and 40 deletions

View File

@ -18,5 +18,6 @@ func New() *beam.Object {
backends.Bind("forward", Forward())
backends.Bind("exec", Exec())
backends.Bind("dockerserver", DockerServer())
backends.Bind("orchard", Orchard())
return beam.Obj(backends)
}

View File

@ -114,10 +114,9 @@ func (s *cloud) Install(eng *engine.Engine) error {
}
}
host := fmt.Sprintf("tcp://localhost:%d", localPort)
client, err := newClient(host, apiVersion)
if err != nil {
return job.Errorf("Unexpected error: %#v", err)
}
client := newClient()
client.setURL(host)
client.version = apiVersion
//job.Eng.Register("inspect", func(job *engine.Job) engine.Status {
// resp, err := client.call("GET", "/containers/
job.Eng.Register("create", func(job *engine.Job) engine.Status {
@ -128,17 +127,17 @@ func (s *cloud) Install(eng *engine.Engine) error {
data, err := json.Marshal(container)
resp, err := client.call("POST", "/containers/create", string(data))
if err != nil {
return job.Errorf("%s: post: %v", client.URL.String(), err)
return job.Errorf("post: %v", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return job.Errorf("%s: read body: %#v", client.URL.String(), err)
return job.Errorf("read body: %#v", err)
}
var containerOut Container
err = json.Unmarshal([]byte(body), &containerOut)
_, err = job.Printf("%s\n", containerOut.Id)
if err != nil {
return job.Errorf("%s: write body: %#v", client.URL.String(), err)
return job.Errorf("write body: %#v", err)
}
log.Printf("%s", string(body))
return engine.StatusOK
@ -148,11 +147,11 @@ func (s *cloud) Install(eng *engine.Engine) error {
path := fmt.Sprintf("/containers/%s/start", job.Args[0])
resp, err := client.call("POST", path, "{\"Binds\":[],\"ContainerIDFile\":\"\",\"LxcConf\":[],\"Privileged\":false,\"PortBindings\":{},\"Links\":null,\"PublishAllPorts\":false,\"Dns\":null,\"DnsSearch\":[],\"VolumesFrom\":[]}")
if err != nil {
return job.Errorf("%s: post: %v", client.URL.String(), err)
return job.Errorf("post: %v", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return job.Errorf("%s: read body: %#v", client.URL.String(), err)
return job.Errorf("read body: %#v", err)
}
log.Printf("%s", string(body))
return engine.StatusOK
@ -169,17 +168,17 @@ func (s *cloud) Install(eng *engine.Engine) error {
)
resp, err := client.call("GET", path, "")
if err != nil {
return job.Errorf("%s: get: %v", client.URL.String(), err)
return job.Errorf("get: %v", err)
}
// FIXME: check for response error
c := engine.NewTable("Created", 0)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return job.Errorf("%s: read body: %v", client.URL.String(), err)
return job.Errorf("read body: %v", err)
}
fmt.Printf("---> '%s'\n", body)
if _, err := c.ReadListFrom(body); err != nil {
return job.Errorf("%s: readlist: %v", client.URL.String(), err)
return job.Errorf("readlist: %v", err)
}
c.WriteListTo(job.Stdout)
return engine.StatusOK
@ -191,7 +190,7 @@ func (s *cloud) Install(eng *engine.Engine) error {
resp, err := client.call("DELETE", path, "")
if err != nil {
return job.Errorf("%s: delete: %v", client.URL.String(), err)
return job.Errorf("delete: %v", err)
}
log.Printf("%#v", resp)
return engine.StatusOK
@ -203,7 +202,7 @@ func (s *cloud) Install(eng *engine.Engine) error {
resp, err := client.call("POST", path, "")
if err != nil {
return job.Errorf("%s: delete: %v", client.URL.String(), err)
return job.Errorf("delete: %v", err)
}
log.Printf("%#v", resp)
return engine.StatusOK

View File

@ -17,15 +17,16 @@ import (
)
func Forward() beam.Sender {
return ForwardWithClient(newClient())
}
func ForwardWithClient(client *client) beam.Sender {
backend := beam.NewServer()
backend.OnSpawn(beam.Handler(func(ctx *beam.Message) error {
if len(ctx.Args) != 1 {
return fmt.Errorf("forward: spawn takes exactly 1 argument, got %d", len(ctx.Args))
}
client, err := newClient(ctx.Args[0], "v1.11")
if err != nil {
return fmt.Errorf("%v", err)
}
client.setURL(ctx.Args[0])
f := &forwarder{
client: client,
Server: beam.NewServer(),
@ -34,7 +35,7 @@ func Forward() beam.Sender {
f.Server.OnStart(beam.Handler(f.start))
f.Server.OnLs(beam.Handler(f.ls))
f.Server.OnSpawn(beam.Handler(f.spawn))
_, err = ctx.Ret.Send(&beam.Message{Verb: beam.Ack, Ret: f.Server})
_, err := ctx.Ret.Send(&beam.Message{Verb: beam.Ack, Ret: f.Server})
return err
}))
return backend
@ -67,23 +68,23 @@ func (f *forwarder) start(ctx *beam.Message) error {
func (f *forwarder) ls(ctx *beam.Message) error {
resp, err := f.client.call("GET", "/containers/json", "")
if err != nil {
return fmt.Errorf("%s: get: %v", f.client.URL.String(), err)
return fmt.Errorf("get: %v", err)
}
// FIXME: check for response error
c := engine.NewTable("Created", 0)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("%s: read body: %v", f.client.URL.String(), err)
return fmt.Errorf("read body: %v", err)
}
if _, err := c.ReadListFrom(body); err != nil {
return fmt.Errorf("%s: readlist: %v", f.client.URL.String(), err)
return fmt.Errorf("readlist: %v", err)
}
names := []string{}
for _, env := range c.Data {
names = append(names, env.GetList("Names")[0][1:])
}
if _, err := ctx.Ret.Send(&beam.Message{Verb: beam.Set, Args: names}); err != nil {
return fmt.Errorf("%s: send response: %v", f.client.URL.String(), err)
return fmt.Errorf("send response: %v", err)
}
return nil
}
@ -218,23 +219,27 @@ func (c *container) get(ctx *beam.Message) error {
}
type client struct {
URL *url.URL
proto string
addr string
version string
transport *http.Transport
urlHost string
scheme string
version string
}
func newClient(peer, version string) (*client, error) {
u, err := url.Parse(peer)
if err != nil {
return nil, err
func newClient() *client {
return &client{
transport: &http.Transport{},
urlHost: "dummy.host",
scheme: "http",
version: "v1.11",
}
c := &client{
URL: u,
version: version,
}
func (c *client) setURL(url string) {
parts := strings.SplitN(url, "://", 2)
proto, host := parts[0], parts[1]
c.transport.Dial = func(_, _ string) (net.Conn, error) {
return net.Dial(proto, host)
}
c.URL.Scheme = "http"
return c, nil
}
func (c *client) call(method, path, body string) (*http.Response, error) {
@ -243,13 +248,14 @@ func (c *client) call(method, path, body string) (*http.Response, error) {
if err != nil {
return nil, err
}
u.Host = c.URL.Host
u.Scheme = c.URL.Scheme
u.Host = c.urlHost
u.Scheme = c.scheme
req, err := http.NewRequest(method, u.String(), strings.NewReader(body))
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
httpClient := &http.Client{Transport: c.transport}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
@ -258,7 +264,7 @@ func (c *client) call(method, path, body string) (*http.Response, error) {
func (c *client) hijack(method, path string, in io.ReadCloser, stdout, stderr io.Writer) error {
path = fmt.Sprintf("/%s%s", c.version, path)
dial, err := net.Dial("tcp", c.URL.Host)
dial, err := c.transport.Dial("ignored", "ignored")
if err != nil {
return err
}

124
backends/orchard.go Normal file
View File

@ -0,0 +1,124 @@
package backends
import (
"github.com/docker/libswarm/beam"
"github.com/orchardup/go-orchard/api"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
)
func Orchard() beam.Sender {
backend := beam.NewServer()
backend.OnSpawn(beam.Handler(func(ctx *beam.Message) error {
if len(ctx.Args) != 2 {
return fmt.Errorf("orchard: spawn expects 2 arguments: API token and name of host")
}
apiToken, hostName := ctx.Args[0], ctx.Args[1]
apiClient := &api.HTTPClient{
BaseURL: "https://api.orchardup.com/v2",
Token: apiToken,
}
host, err := apiClient.GetHost(hostName)
if err != nil {
return err
}
url := fmt.Sprintf("tcp://%s:4243", host.IPAddress)
tlsConfig, err := getTLSConfig([]byte(host.ClientCert), []byte(host.ClientKey))
if err != nil {
return err
}
client := newClient()
client.scheme = "https"
client.transport.TLSClientConfig = tlsConfig
client.urlHost = host.IPAddress
forwardBackend := beam.Obj(ForwardWithClient(client))
forwardInstance, err := forwardBackend.Spawn(url)
if err != nil {
return err
}
_, err = ctx.Ret.Send(&beam.Message{Verb: beam.Ack, Ret: forwardInstance})
return err
}))
return backend
}
func getTLSConfig(clientCertPEMData, clientKeyPEMData []byte) (*tls.Config, error) {
certPool := x509.NewCertPool()
certChainPath := os.Getenv("ORCHARD_HOST_CA")
if certChainPath != "" {
certChainData, err := ioutil.ReadFile(certChainPath)
if err != nil {
return nil, err
}
certPool.AppendCertsFromPEM(certChainData)
} else {
certPool.AppendCertsFromPEM([]byte(orchardCerts))
}
clientCert, err := tls.X509KeyPair(clientCertPEMData, clientKeyPEMData)
if err != nil {
return nil, err
}
config := new(tls.Config)
config.RootCAs = certPool
config.Certificates = []tls.Certificate{clientCert}
config.BuildNameToCertificate()
return config, nil
}
var orchardCerts string = `-----BEGIN CERTIFICATE-----
MIIDizCCAnOgAwIBAgIJANOkcdAljaXsMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNV
BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xIjAgBgNVBAoMGU9yY2hhcmQgTGFib3Jh
dG9yaWVzIEx0ZC4xGDAWBgNVBAMMD09yY2hhcmQgUm9vdCBDQTAeFw0xMzA5MTAx
OTU4MDZaFw0xODA5MDkxOTU4MDZaMFwxCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZM
b25kb24xIjAgBgNVBAoMGU9yY2hhcmQgTGFib3JhdG9yaWVzIEx0ZC4xGDAWBgNV
BAMMD09yY2hhcmQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAKE2uHYhJyUxTa/DgXE2Ru85RJptv7YypqL6/hEthhSb30e8c/UZ2RGAupgr
2KhP2B/c78d7hMIX09rbc/Z7TpnV4T3ERuguDJ6jz6NjLKDsW8VVBMx4imcE0hHB
ZrhO+cEuBBufw+iW83uNIkzzIVVlgZ6o9jGXEY61D+SNNxtsMEIkjh+5/JxdeRvK
PSHhBJ2VJpCRWpvuhEuCc5Qlz6PkWEbCchEe8Mzy6Zy8FzT4q4t+ztryWTUavR9s
8lv3N7vHMo4R/r1M+VtjlXzutV8S83avrUQ48woTGBULyfXisbSN6snCXf4VJx8E
UU48FjQBZbKfgV+2ut4f2oUdkNsCAwEAAaNQME4wHQYDVR0OBBYEFBM7bwyZ7n42
9q3EmgaOU7PGa4xtMB8GA1UdIwQYMBaAFBM7bwyZ7n429q3EmgaOU7PGa4xtMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADZ5YUYc3WfiKWg1VqmBj0EE
wVhy43rId0ruMwcNgyDesCvIDJy5Y9XsIRnRIIeM3tm0MF+fGlmOiN1AvX0KeiTM
7RtPYFawl62aGrDvo0CdZTxCYSRcLvUhgGiftEnqRijawecnk5BhcP+g5Zxe8b+L
DzqbCwG9AQ9M2NAxWdbaBJwAL8qceKklVGWOEpjEYiF4zdNQoLfW+lygVBvKZEwF
By4x8aTPlf8MMMn2ogk4Js8ZcjmvP9fBlA09ecD1DO9lNWMXpDyoB504qTPGoLoj
u6XKHvAXEXvMzLHp7qABqZXDgKIcr8wAqnmqlnFHpLjd+bV7bGMHjGM8EeIdt8I=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDjTCCAnWgAwIBAgIJANOkcdAljaXwMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNV
BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xIjAgBgNVBAoMGU9yY2hhcmQgTGFib3Jh
dG9yaWVzIEx0ZC4xGDAWBgNVBAMMD09yY2hhcmQgUm9vdCBDQTAeFw0xMzA5MTIx
ODE5MTRaFw0xODA5MTExODE5MTRaMF4xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZM
b25kb24xIjAgBgNVBAoMGU9yY2hhcmQgTGFib3JhdG9yaWVzIEx0ZC4xGjAYBgNV
BAMMEU9yY2hhcmQgRG9ja2VyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA1T5SrhAZI9N3k8lESi9mYJ9aAqaNESF1qGqWh4Vk2ek0J9X76tPXTK0/
mn9kVMhHFusSHw6EV9imORIdJd9ivdqfMpEPeBuYNuZTNQYsVPP//ZwBPA5+dVOK
dBH+OjgLne8oHIgNM1lRZQlTWrE9FrD11VVnTNcI3VfuDjPD7z5FeYb+gRQx5/u4
gp+xLfglquCzbaPRqQ9FhPB7MFkiQDfZuZieAWZ4QOLJb4za582OX2Gl6mUbIOc7
TQcxeifIwUnSBunq8ER6donjfLy/vJUMBITw4LfzVFpuDnki5FI1DzY+GaVzRSOn
JiGT+WxPH2ydgDieKL1cqB7i+/6o4QIDAQABo1AwTjAdBgNVHQ4EFgQUp/8F8VUv
P+hIDEOf/fzXSujHjYAwHwYDVR0jBBgwFoAUEztvDJnufjb2rcSaBo5Ts8ZrjG0w
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAcbK4gNKRipaDBUwUWU7Z
YXP+npiGEaKxEgBf2J0FFi0yfKRTdACj42vWTZI2m26A1be04xrXZhBKPm4+zN6U
SjEjl+jIKSR7E0QvFv9fFJ0hWl5CDcg8EJdFckgawH2MfSBb4qjN4MDygtOet35Z
VmA7V3AaHa7d2xP+dyER+qP5/ysWzHqliephEpDj4QQIK+bQWwBXj91LhRsyHdn7
VBHS10FcJegbD86SLb85U7zFPZ+vWClDLWwh2hN7ApAGjJgQrfHFhanLi51MIkM2
FHytSi4zdRd+9nEbSVc4t1CL/llaSFk7W77hMFZpq+J+ih6aC0echGQIoXfdHfSM
Qg==
-----END CERTIFICATE-----`