diff --git a/.gitignore b/.gitignore
index 94b5d700..6f39f896 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,7 +32,9 @@ coverage.html
# Project specific
weaver/weaver
weavedns/weavedns
+weaveproxy/weaveproxy
sigproxy/sigproxy
+weavewait/weavewait
tools/bin
tools/build
releases
@@ -40,6 +42,8 @@ weave.tar
weavedns.tar
weaveexec.tar
weaveexec/weave
+weaveexec/weaveproxy
+weaveexec/weavewait
weaveexec/sigproxy
weaveexec/docker*.tgz
Vagrantfile.local
diff --git a/Makefile b/Makefile
index 601e6430..463ba6cd 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,9 @@ DOCKERHUB_USER=weaveworks
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
WEAVER_EXE=weaver/weaver
WEAVEDNS_EXE=weavedns/weavedns
+WEAVEPROXY_EXE=weaveproxy/weaveproxy
SIGPROXY_EXE=sigproxy/sigproxy
+WEAVEWAIT_EXE=weavewait/weavewait
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
WEAVEDNS_IMAGE=$(DOCKERHUB_USER)/weavedns
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
@@ -27,9 +29,9 @@ all: $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
travis: $(WEAVER_EXE) $(WEAVEDNS_EXE)
update:
- go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE))
+ go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE)) ./$(dir $(WEAVEPROXY_EXE))
-$(WEAVER_EXE) $(WEAVEDNS_EXE): common/*.go
+$(WEAVER_EXE) $(WEAVEDNS_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE): common/*.go
go get -tags netgo ./$(@D)
go build -ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo -o $@ ./$(@D)
@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
@@ -43,6 +45,8 @@ $(WEAVER_EXE) $(WEAVEDNS_EXE): common/*.go
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go weaver/main.go
$(WEAVEDNS_EXE): nameserver/*.go weavedns/main.go
+$(WEAVEPROXY_EXE): proxy/*.go weaveproxy/main.go
+$(WEAVEWAIT_EXE): weavewait/*.go weavewait/main.go
# Sigproxy needs separate rule as it fails the netgo check in the main
# build stanza due to not importing net package
@@ -57,9 +61,11 @@ $(WEAVEDNS_EXPORT): weavedns/Dockerfile $(WEAVEDNS_EXE)
$(SUDO) docker build -t $(WEAVEDNS_IMAGE) weavedns
$(SUDO) docker save $(WEAVEDNS_IMAGE):latest > $@
-$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE)
+$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE)
cp weave weaveexec/weave
- cp sigproxy/sigproxy weaveexec/sigproxy
+ cp $(SIGPROXY_EXE) weaveexec/sigproxy
+ cp $(WEAVEWAIT_EXE) weaveexec/weavewait
+ cp $(WEAVEPROXY_EXE) weaveexec/weaveproxy
cp $(DOCKER_DISTRIB) weaveexec/docker.tgz
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) weaveexec
$(SUDO) docker save $(WEAVEEXEC_IMAGE):latest > $@
@@ -92,7 +98,7 @@ publish: $(PUBLISH)
clean:
-$(SUDO) docker rmi $(WEAVER_IMAGE) $(WEAVEDNS_IMAGE) $(WEAVEEXEC_IMAGE)
- rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
+ rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
build:
$(SUDO) go clean -i net
diff --git a/proxy/client.go b/proxy/client.go
new file mode 100644
index 00000000..f0733262
--- /dev/null
+++ b/proxy/client.go
@@ -0,0 +1,167 @@
+package proxy
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httputil"
+
+ "github.com/fsouza/go-dockerclient"
+ . "github.com/weaveworks/weave/common"
+)
+
+type client struct {
+ interceptor
+ Dial func() (net.Conn, error)
+}
+
+func newClient(dial func() (net.Conn, error), i interceptor) *client {
+ return &client{
+ interceptor: i,
+ Dial: dial,
+ }
+}
+
+func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ err := c.InterceptRequest(r)
+ if err == docker.ErrNoSuchImage {
+ http.NotFound(w, r)
+ return
+ }
+ if err != nil {
+ http.Error(w, "Unable to intercept request", http.StatusInternalServerError)
+ Warning.Print("Error intercepting request: ", err)
+ return
+ }
+
+ conn, err := c.Dial()
+ if err != nil {
+ http.Error(w, "Could not connect to target", http.StatusInternalServerError)
+ Warning.Print(err)
+ return
+ }
+ client := httputil.NewClientConn(conn, nil)
+ defer client.Close()
+
+ resp, err := client.Do(r)
+ if err != nil && err != httputil.ErrPersistEOF {
+ http.Error(w, fmt.Sprintf("Could not make request to target: %v", err), http.StatusInternalServerError)
+ Warning.Print("Error forwarding request: ", err)
+ return
+ }
+ err = c.InterceptResponse(resp)
+ if err != nil {
+ http.Error(w, "Unable to intercept response", http.StatusInternalServerError)
+ Warning.Print("Error intercepting response: ", err)
+ return
+ }
+
+ hdr := w.Header()
+ for k, vs := range resp.Header {
+ for _, v := range vs {
+ hdr.Add(k, v)
+ }
+ }
+ Debug.Printf("Response from target: %s %v", resp.Status, w.Header())
+
+ if resp.Header.Get("Content-Type") == "application/vnd.docker.raw-stream" {
+ doRawStream(w, resp, client)
+ } else if resp.TransferEncoding != nil && resp.TransferEncoding[0] == "chunked" {
+ doChunkedResponse(w, resp, client)
+ } else {
+ w.WriteHeader(resp.StatusCode)
+ if _, err := io.Copy(w, resp.Body); err != nil {
+ Warning.Print(err)
+ }
+ }
+}
+
+func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
+ down, downBuf, up, rem, err := hijack(w, client)
+ if err != nil {
+ http.Error(w, "Unable to hijack connection for raw stream mode", http.StatusInternalServerError)
+ return
+ }
+ defer down.Close()
+ defer up.Close()
+
+ if _, err := down.Write([]byte("HTTP/1.1 200 OK\n")); err != nil {
+ Warning.Print(err)
+ return
+ }
+
+ if err := resp.Header.Write(down); err != nil {
+ Warning.Print(err)
+ return
+ }
+
+ if _, err := down.Write([]byte("\n")); err != nil {
+ Warning.Print(err)
+ return
+ }
+
+ upDone := make(chan struct{})
+ downDone := make(chan struct{})
+ go copyStream(down, io.MultiReader(rem, up), upDone)
+ go copyStream(up, downBuf, downDone)
+ <-upDone
+ <-downDone
+}
+
+func copyStream(dst io.Writer, src io.Reader, done chan struct{}) {
+ defer close(done)
+ if _, err := io.Copy(dst, src); err != nil {
+ Warning.Print(err)
+ }
+ if c, ok := dst.(interface {
+ CloseWrite() error
+ }); ok {
+ if err := c.CloseWrite(); err != nil {
+ Warning.Printf("Error closing connection: %s", err)
+ }
+ }
+}
+
+func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
+ // Because we can't go back to request/response after we
+ // hijack the connection, we need to close it and make the
+ // client open another.
+ w.Header().Add("Connection", "close")
+ w.WriteHeader(resp.StatusCode)
+
+ down, _, up, rem, err := hijack(w, client)
+ if err != nil {
+ http.Error(w, "Unable to hijack response stream for chunked response", http.StatusInternalServerError)
+ return
+ }
+ defer up.Close()
+ defer down.Close()
+ // Copy the chunked response body to downstream,
+ // stopping at the end of the chunked section.
+ rawResponseBody := io.MultiReader(rem, up)
+ if _, err := io.Copy(ioutil.Discard, httputil.NewChunkedReader(io.TeeReader(rawResponseBody, down))); err != nil {
+ http.Error(w, "Error copying chunked response body", http.StatusInternalServerError)
+ return
+ }
+ resp.Trailer.Write(down)
+ // a chunked response ends with a CRLF
+ down.Write([]byte("\r\n"))
+}
+
+func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn, downBuf *bufio.ReadWriter, up net.Conn, rem io.Reader, err error) {
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ err = errors.New("Unable to cast to Hijack")
+ return
+ }
+ down, downBuf, err = hj.Hijack()
+ if err != nil {
+ return
+ }
+ up, rem = client.Hijack()
+ return
+}
diff --git a/proxy/common.go b/proxy/common.go
new file mode 100644
index 00000000..4d29d95e
--- /dev/null
+++ b/proxy/common.go
@@ -0,0 +1,28 @@
+package proxy
+
+import (
+ "os/exec"
+ "strings"
+
+ "github.com/fsouza/go-dockerclient"
+ . "github.com/weaveworks/weave/common"
+)
+
+func callWeave(args ...string) ([]byte, error) {
+ args = append([]string{"--local"}, args...)
+ Debug.Print("Calling weave", args)
+ cmd := exec.Command("./weave", args...)
+ cmd.Env = []string{"PROCFS=/hostproc", "PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}
+ out, err := cmd.CombinedOutput()
+ return out, err
+}
+
+func weaveCIDRsFromConfig(config *docker.Config) ([]string, bool) {
+ for _, e := range config.Env {
+ if strings.HasPrefix(e, "WEAVE_CIDR=") {
+ result := strings.Trim(e[11:], " ")
+ return strings.Split(strings.TrimSpace(result), " "), result != ""
+ }
+ }
+ return nil, false
+}
diff --git a/proxy/create_container_interceptor.go b/proxy/create_container_interceptor.go
new file mode 100644
index 00000000..35841a9b
--- /dev/null
+++ b/proxy/create_container_interceptor.go
@@ -0,0 +1,105 @@
+package proxy
+
+import (
+ "bytes"
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+
+ "github.com/fsouza/go-dockerclient"
+)
+
+type createContainerInterceptor struct {
+ client *docker.Client
+ withDNS bool
+ dockerBridgeIP string
+}
+
+type createContainerRequestBody struct {
+ *docker.Config
+ HostConfig *docker.HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
+ MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
+}
+
+func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
+ body, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return err
+ }
+ r.Body.Close()
+
+ container := createContainerRequestBody{}
+ if err := json.Unmarshal(body, &container); err != nil {
+ return err
+ }
+
+ if _, ok := weaveCIDRsFromConfig(container.Config); ok {
+ container.HostConfig.VolumesFrom = append(container.HostConfig.VolumesFrom, "weaveproxy")
+ if err := i.setWeaveWaitEntrypoint(container.Config); err != nil {
+ return err
+ }
+ if err := i.setWeaveDNS(&container); err != nil {
+ return err
+ }
+ }
+
+ newBody, err := json.Marshal(container)
+ if err != nil {
+ return err
+ }
+ r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
+ r.ContentLength = int64(len(newBody))
+
+ return nil
+}
+
+func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error {
+ var image *docker.Config
+ if !configHasEntrypoint(container) || !configHasCmd(container) {
+ imageInfo, err := i.client.InspectImage(container.Image)
+ if err != nil {
+ return err
+ }
+ image = imageInfo.Config
+ }
+
+ var entry, command []string
+ if configHasEntrypoint(container) {
+ entry = container.Entrypoint
+ } else if configHasEntrypoint(image) {
+ entry = image.Entrypoint
+ }
+ if configHasCmd(container) {
+ command = container.Cmd
+ } else if configHasCmd(image) {
+ command = image.Cmd
+ }
+
+ container.Entrypoint = []string{"/home/weavewait/weavewait"}
+ container.Cmd = append(entry, command...)
+ return nil
+}
+
+func configHasEntrypoint(c *docker.Config) bool {
+ return c != nil && len(c.Entrypoint) > 0
+}
+func configHasCmd(c *docker.Config) bool {
+ return c != nil && len(c.Cmd) > 0
+}
+
+func (i *createContainerInterceptor) setWeaveDNS(container *createContainerRequestBody) error {
+ if !i.withDNS {
+ return nil
+ }
+
+ container.HostConfig.DNS = append(container.HostConfig.DNS, i.dockerBridgeIP)
+
+ if len(container.HostConfig.DNSSearch) == 0 {
+ container.HostConfig.DNSSearch = []string{"."}
+ }
+ return nil
+}
+
+func (i *createContainerInterceptor) InterceptResponse(r *http.Response) error {
+ return nil
+}
diff --git a/proxy/interceptor.go b/proxy/interceptor.go
new file mode 100644
index 00000000..69d903e2
--- /dev/null
+++ b/proxy/interceptor.go
@@ -0,0 +1,19 @@
+package proxy
+
+import "net/http"
+
+type interceptor interface {
+ InterceptRequest(*http.Request) error
+ InterceptResponse(*http.Response) error
+}
+
+type nullInterceptor struct {
+}
+
+func (i nullInterceptor) InterceptRequest(r *http.Request) error {
+ return nil
+}
+
+func (i nullInterceptor) InterceptResponse(r *http.Response) error {
+ return nil
+}
diff --git a/proxy/proxy.go b/proxy/proxy.go
new file mode 100644
index 00000000..7681230e
--- /dev/null
+++ b/proxy/proxy.go
@@ -0,0 +1,80 @@
+package proxy
+
+import (
+ "net"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strings"
+
+ "github.com/fsouza/go-dockerclient"
+ . "github.com/weaveworks/weave/common"
+)
+
+var (
+ containerCreateRegexp = regexp.MustCompile("/v[0-9\\.]*/containers/create")
+ containerStartRegexp = regexp.MustCompile("^/v[0-9\\.]*/containers/[^/]*/(re)?start$")
+)
+
+type Proxy struct {
+ Dial func() (net.Conn, error)
+ client *docker.Client
+ withDNS bool
+ dockerBridgeIP string
+}
+
+func NewProxy(targetURL string, withDNS bool) (*Proxy, error) {
+ u, err := url.Parse(targetURL)
+ if err != nil {
+ return nil, err
+ }
+
+ var dockerBridgeIP []byte
+ if withDNS {
+ dockerBridgeIP, err = callWeave("docker-bridge-ip")
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ client, err := docker.NewClient(targetURL)
+ if err != nil {
+ return nil, err
+ }
+
+ targetAddr := ""
+ switch u.Scheme {
+ case "tcp":
+ targetAddr = u.Host
+ case "unix":
+ targetAddr = u.Path
+ }
+
+ return &Proxy{
+ Dial: func() (net.Conn, error) {
+ return net.Dial(u.Scheme, targetAddr)
+ },
+ client: client,
+ withDNS: withDNS,
+ dockerBridgeIP: string(dockerBridgeIP),
+ }, nil
+}
+
+func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ Info.Printf("%s %s", r.Method, r.URL)
+ path := r.URL.Path
+ switch {
+ case containerCreateRegexp.MatchString(path):
+ proxy.serveWithInterceptor(&createContainerInterceptor{proxy.client, proxy.withDNS, proxy.dockerBridgeIP}, w, r)
+ case containerStartRegexp.MatchString(path):
+ proxy.serveWithInterceptor(&startContainerInterceptor{proxy.client, proxy.withDNS}, w, r)
+ case strings.HasPrefix(path, "/weave"):
+ w.WriteHeader(http.StatusOK)
+ default:
+ proxy.serveWithInterceptor(&nullInterceptor{}, w, r)
+ }
+}
+
+func (proxy *Proxy) serveWithInterceptor(i interceptor, w http.ResponseWriter, r *http.Request) {
+ newClient(proxy.Dial, i).ServeHTTP(w, r)
+}
diff --git a/proxy/start_container_interceptor.go b/proxy/start_container_interceptor.go
new file mode 100644
index 00000000..f2e361e4
--- /dev/null
+++ b/proxy/start_container_interceptor.go
@@ -0,0 +1,50 @@
+package proxy
+
+import (
+ "net/http"
+ "regexp"
+ "strings"
+
+ "github.com/fsouza/go-dockerclient"
+ . "github.com/weaveworks/weave/common"
+)
+
+var containerIDRegexp = regexp.MustCompile("^/v[0-9\\.]*/containers/([^/]*)/.*")
+
+type startContainerInterceptor struct {
+ client *docker.Client
+ withDNS bool
+}
+
+func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error {
+ return nil
+}
+
+func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
+ if subs := containerIDRegexp.FindStringSubmatch(r.Request.URL.Path); subs == nil {
+ Warning.Printf("No container id found in request with path %s", r.Request.URL.Path)
+ return nil
+ }
+ containerID := subs[1]
+
+ container, err := i.client.InspectContainer(containerID)
+ if err != nil {
+ Warning.Printf("Error inspecting container %s: %v", containerID, err)
+ return nil
+ }
+
+ cidrs, ok := weaveCIDRsFromConfig(container.Config)
+ if !ok {
+ Debug.Print("No Weave CIDR, ignoring")
+ return nil
+ }
+ Info.Printf("Container %s was started with CIDR \"%s\"", containerID, strings.Join(cidrs, " "))
+ args := []string{"attach"}
+ args = append(args, cidrs...)
+ args = append(args, containerID)
+ if _, err := callWeave(args...); err != nil {
+ Warning.Printf("Attaching container %s to weave failed: %v", containerID, err)
+ return nil
+ }
+ return nil
+}
diff --git a/site/features.md b/site/features.md
index 1e0ecab5..dfee4b13 100644
--- a/site/features.md
+++ b/site/features.md
@@ -24,6 +24,7 @@ example](https://github.com/weaveworks/weave#example):
* [Fault tolerance](#fault-tolerance)
* [Automatic IP address management](#ipam)
* [Automatic discovery with WeaveDNS](#dns)
+ * [Proxy](#proxy)
### Virtual Ethernet Switch
@@ -403,4 +404,11 @@ to enable and use this feature.
WeaveDNS is a distributed DNS service for weave networks, enabling
containers to address each other by name rather than IP address. Find
-out more about weaveDNS from its [documentation](weavedns.html).
+out more about WeaveDNS from its [documentation](weavedns.html).
+
+### Proxy
+
+The weave proxy enables you to use the docker command-line utility, allowing
+weave-enabled containers to be started in the foreground, and to wait for the
+weave network interface before running. Find out more about the weave proxy
+from its [documentation](proxy.html).
diff --git a/site/proxy.md b/site/proxy.md
new file mode 100644
index 00000000..c1d0daa0
--- /dev/null
+++ b/site/proxy.md
@@ -0,0 +1,59 @@
+---
+title: Weave Proxy
+layout: default
+---
+
+# Weave Proxy
+
+Instead of the weave command-line utility, you may prefer to use the standard
+docker command-line utility to manage containers. This will allow containers to
+be started in foreground mode. It will also ensure that the weave network
+interface is available before running your container.
+
+## Setup
+
+To do this, while still connecting containers to the weave network, you can use
+the weave proxy. We can start the proxy with
+
+ host1$ weave launch-proxy
+
+By default, the proxy will connect to docker at `unix:///var/run/docker.sock`,
+and listen on port 12375. However, we can adjust the connection to docker via
+the `-H` argument. All docker commands can be run via the proxy, so it is safe
+to globally adjust your `DOCKER_HOST`.
+
+ host1$ export DOCKER_HOST="tcp://host1:12375"
+ host1$ docker ps
+
+## Usage
+
+When containers are created via the weave proxy, their entrypoint will be
+modified to wait for the weave network interface to become available. When they are
+started via the weave proxy, any IP addresses and networks specified in the
+`WEAVE_CIDR` environment variable will be attached. We can create and start a
+container via the weave proxy with
+
+ host1$ docker run -e WEAVE_CIDR=10.2.1.1/24 -ti ubuntu /bin/sh
+
+or, equivalently with
+
+ host1$ docker create -e WEAVE_CIDR=10.2.1.1/24 -ti ubuntu /bin/sh
+ 5ef831df61d50a1a49272357155a976595e7268e590f0a2c75693337b14e1382
+ host1$ docker start 5ef831df61d50a1a49272357155a976595e7268e590f0a2c75693337b14e1382
+
+Multiple IP addresses and networks can be supplied in the WEAVE_CIDR variable by space-separating them, as in `WEAVE_CIDR=10.2.1.1/24 10.2.2.1/24`.
+
+## Usage with WeaveDNS
+
+Containers started via the proxy can be automatically configured to use WeaveDNS for name resolution. To accomplish this we need to launch the proxy with the `--with-dns` option
+
+ host1$ weave launch
+ host1$ weave launch-dns 10.2.254.1/24
+ host1$ weave launch-proxy --with-dns
+
+With this done, any containers launched through the proxy will use weaveDNS for name resolution. WeaveDNS is used in addition to any dns servers specified via the `--dns` option. More details on weaveDNS can be found in the [weaveDNS documentation](weavedns.html).
+
+## Limitations
+
+* The proxy does not currently support TLS.
+* If you have a firewall, you will need to make sure port 12375 is open.
diff --git a/test/400_weave_local_test.sh b/test/400_weave_local_test.sh
index c4f9eab4..d86e2ece 100755
--- a/test/400_weave_local_test.sh
+++ b/test/400_weave_local_test.sh
@@ -10,7 +10,7 @@ run_on $HOST1 sudo ./weave --local launch -iprange 10.2.5.0/24
assert_raises "docker_on $HOST1 ps | grep weave"
run_on $HOST1 sudo ./weave --local run 10.2.6.5/24 -ti --name=c1 gliderlabs/alpine /bin/sh
-assert_raises "exec_on $HOST1 c1 ifconfig | grep ethwe"
+assert_raises "exec_on $HOST1 c1 ip link show ethwe"
run_on $HOST1 sudo ./weave --local run -ti --name=c2 gliderlabs/alpine /bin/sh
assert_raises "exec_on $HOST1 c2 ifconfig | grep ethwe"
diff --git a/test/600_proxy_docker_py_test.sh b/test/600_proxy_docker_py_test.sh
new file mode 100755
index 00000000..dc5e3282
--- /dev/null
+++ b/test/600_proxy_docker_py_test.sh
@@ -0,0 +1,16 @@
+#! /bin/bash
+
+. ./config.sh
+
+start_suite "Run docker-py test suite against the proxy"
+
+weave_on $HOST1 launch-proxy
+assert_raises "docker_on $HOST1 ps | grep weaveproxy"
+assert_raises "docker_on $HOST1 run \
+ -e NOT_ON_HOST=true \
+ -e DOCKER_HOST=tcp://172.17.42.1:12375 \
+ -v /tmp:/tmp \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ joffrey/docker-py python tests/integration_test.py"
+
+end_suite
diff --git a/test/610_proxy_wait_for_weave_test.sh b/test/610_proxy_wait_for_weave_test.sh
new file mode 100755
index 00000000..21504432
--- /dev/null
+++ b/test/610_proxy_wait_for_weave_test.sh
@@ -0,0 +1,15 @@
+#! /bin/bash
+
+. ./config.sh
+
+start_suite "Proxy waits for weave to be ready before running container commands"
+weave_on $HOST1 launch-proxy
+BASE_IMAGE=busybox
+# Ensure the base image does not exist, so that it will be pulled
+if (docker_on $HOST1 images $BASE_IMAGE | grep $BASE_IMAGE); then
+ docker_on $HOST1 rmi $BASE_IMAGE
+fi
+
+assert_raises "docker_proxy_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.1/24' $BASE_IMAGE ip link show ethwe | grep 'state UP'"
+
+end_suite
diff --git a/test/620_proxy_entrypoint_inheritance_test.sh b/test/620_proxy_entrypoint_inheritance_test.sh
new file mode 100755
index 00000000..2bb2fce6
--- /dev/null
+++ b/test/620_proxy_entrypoint_inheritance_test.sh
@@ -0,0 +1,14 @@
+#! /bin/bash
+
+. ./config.sh
+
+start_suite "Proxy uses entrypoint from the image with weavewait"
+weave_on $HOST1 launch-proxy
+docker_on $HOST1 build -t inspect-ethwe - <<- EOF
+ FROM gliderlabs/alpine
+ ENTRYPOINT ["ip", "link", "show", "ethwe"]
+EOF
+
+assert_raises "docker_proxy_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.1/24' inspect-ethwe | grep 'state UP'"
+
+end_suite
diff --git a/test/630_proxy_dns_test.sh b/test/630_proxy_dns_test.sh
new file mode 100755
index 00000000..e4b94fee
--- /dev/null
+++ b/test/630_proxy_dns_test.sh
@@ -0,0 +1,18 @@
+#! /bin/bash
+
+. ./config.sh
+
+C1=10.2.0.78
+C2=10.2.0.34
+NAME=seetwo.weave.local
+
+start_suite "Proxy registers containers with dns"
+
+weave_on $HOST1 launch-dns 10.2.254.1/24
+weave_on $HOST1 launch-proxy --with-dns
+docker_proxy_on $HOST1 run -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $NAME gliderlabs/alpine /bin/sh
+docker_proxy_on $HOST1 run -e WEAVE_CIDR=$C1/24 -dt --name=c1 aanand/docker-dnsutils /bin/sh
+
+assert_dns_record $HOST1 c1 $NAME $C2
+
+end_suite
diff --git a/test/640_proxy_restart_reattaches_test.sh b/test/640_proxy_restart_reattaches_test.sh
new file mode 100755
index 00000000..01017764
--- /dev/null
+++ b/test/640_proxy_restart_reattaches_test.sh
@@ -0,0 +1,20 @@
+#! /bin/bash
+
+. ./config.sh
+
+C1=10.2.0.78
+C2=10.2.0.34
+NAME=seetwo.weave.local
+
+start_suite "Proxy restart reattaches networking to containers"
+
+weave_on $HOST1 launch-dns 10.2.254.1/24
+weave_on $HOST1 launch-proxy --with-dns
+docker_proxy_on $HOST1 run -e WEAVE_CIDR=$C2/24 -dt --name=c2 -h $NAME gliderlabs/alpine /bin/sh
+docker_proxy_on $HOST1 run -e WEAVE_CIDR=$C1/24 -dt --name=c1 aanand/docker-dnsutils /bin/sh
+
+docker_proxy_on $HOST1 restart c2
+assert_raises "exec_on $HOST1 c2 ip link show ethwe | grep 'state UP'"
+assert_dns_record $HOST1 c1 $NAME $C2
+
+end_suite
diff --git a/test/650_proxy_entrypoint_override_test.sh b/test/650_proxy_entrypoint_override_test.sh
new file mode 100755
index 00000000..4f826408
--- /dev/null
+++ b/test/650_proxy_entrypoint_override_test.sh
@@ -0,0 +1,14 @@
+#! /bin/bash
+
+. ./config.sh
+
+start_suite "Proxy allows overridden entrypoint from the container with weavewait"
+weave_on $HOST1 launch-proxy
+docker_on $HOST1 build -t false - <<- EOF
+ FROM gliderlabs/alpine
+ ENTRYPOINT ["/bin/false"]
+EOF
+
+assert_raises "docker_proxy_on $HOST1 run -e 'WEAVE_CIDR=10.2.1.1/24' --entrypoint '/sbin/ip' false link show ethwe | grep 'state UP'"
+
+end_suite
diff --git a/test/config.sh b/test/config.sh
index 3983c826..48de3afb 100644
--- a/test/config.sh
+++ b/test/config.sh
@@ -76,6 +76,13 @@ docker_on() {
docker -H tcp://$host:2375 "$@"
}
+docker_proxy_on() {
+ host=$1
+ shift 1
+ greyly echo "Docker (with proxy) on $host: $@" >&2
+ docker -H tcp://$host:12375 "$@"
+}
+
weave_on() {
host=$1
shift 1
diff --git a/weave b/weave
index 5cf1da6e..744df836 100755
--- a/weave
+++ b/weave
@@ -27,6 +27,7 @@ usage() {
echo "weave setup"
echo "weave launch [-password ] [-nickname ] [-iprange ] ..."
echo "weave launch-dns "
+ echo "weave launch-proxy [-H ] [--with-dns]"
echo "weave connect "
echo "weave forget "
echo "weave run [--with-dns] [ ...] ..."
@@ -679,6 +680,63 @@ get_ipam_cidr() {
fi
}
+######################################################################
+# weave proxy helpers
+######################################################################
+
+PROXY_PORT=12375
+PROXY_CONTAINER_NAME=weaveproxy
+
+proxy_host() {
+ case $DOCKER_HOST in
+ tcp://*)
+ PROXY_HOST=$(echo $DOCKER_HOST | sed -e "s|tcp://\([^:]*\):[0-9]*|\1|")
+ ;;
+ *)
+ PROXY_HOST="localhost"
+ ;;
+ esac
+}
+
+wait_for_proxy_to_boot() {
+ proxy_host
+ for n in `seq 1 10`; do
+ if curl --max-time 1 --connect-timeout 1 -s -X GET http://$PROXY_HOST:$PROXY_PORT/weave ; then
+ return 0
+ fi
+ sleep 1
+ done
+ echo "Proxy did not start within 10 seconds" >&2
+ return 1
+}
+
+ensure_proxy_running() {
+ case $(docker inspect --format '{{.State.Running}} {{.Config.Image}}' $PROXY_CONTAINER_NAME 2>/dev/null) in
+ "true $EXEC_IMAGE")
+ return 0
+ ;;
+ true*)
+ echo "Proxy already running with unknown image" >&2
+ exit 1
+ ;;
+ false*)
+ docker rm -f $PROXY_CONTAINER_NAME >/dev/null
+ ;;
+ esac
+ # Set WEAVEPROXY_DOCKER_ARGS in the environment in order to supply
+ # additional parameters, such as resource limits, to docker
+ # when launching the weave proxy container.
+ PROXY_CONTAINER=$(docker run --privileged --net=host -d \
+ --name=$PROXY_CONTAINER_NAME \
+ -p $PROXY_PORT:$PROXY_PORT/tcp -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /proc:/hostproc -e PROCFS=/hostproc \
+ --entrypoint=/home/weave/weaveproxy \
+ $WEAVE_DOCKER_ARGS $WEAVEPROXY_DOCKER_ARGS \
+ $EXEC_IMAGE "$@")
+
+ wait_for_proxy_to_boot
+}
+
##########################################################################################
[ $(id -u) = 0 ] || {
@@ -807,6 +865,10 @@ case "$COMMAND" in
populate_dns
echo $DNS_CONTAINER
;;
+ launch-proxy)
+ ensure_proxy_running "$@"
+ echo $PROXY_CONTAINER
+ ;;
connect)
[ $# -eq 1 ] || usage
http_call $CONTAINER_NAME $HTTP_PORT POST /connect -d "peer=$1"
@@ -859,6 +921,10 @@ case "$COMMAND" in
dns_args "$@"
echo -n $DNS_ARGS
;;
+ docker-bridge-ip)
+ docker_bridge_ip
+ echo -n $DOCKER_BRIDGE_IP
+ ;;
start)
collect_cidr_args "$@"
shift $CIDR_COUNT
diff --git a/weaveexec/Dockerfile b/weaveexec/Dockerfile
index e7134926..65b0f47a 100644
--- a/weaveexec/Dockerfile
+++ b/weaveexec/Dockerfile
@@ -15,6 +15,10 @@ WORKDIR /home/weave
ADD ./weave /home/weave/
ADD ./sigproxy /home/weave/
+ADD ./weaveproxy /home/weave/
+ADD ./weavewait /home/weavewait/weavewait
ADD ./docker.tgz /
+VOLUME /home/weavewait
+
ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"]
diff --git a/weaveproxy/main.go b/weaveproxy/main.go
new file mode 100644
index 00000000..2a395ae1
--- /dev/null
+++ b/weaveproxy/main.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+
+ "code.google.com/p/getopt"
+ . "github.com/weaveworks/weave/common"
+ "github.com/weaveworks/weave/proxy"
+)
+
+var (
+ defaultTarget = "unix:///var/run/docker.sock"
+ defaultListen = ":12375"
+)
+
+func main() {
+ var target, listen string
+ var withDNS, debug bool
+
+ getopt.BoolVarLong(&debug, "debug", 'd', "log debugging information")
+ getopt.StringVar(&target, 'H', fmt.Sprintf("docker daemon URL to proxy (default %s)", defaultTarget))
+ getopt.StringVar(&listen, 'L', fmt.Sprintf("address on which to listen (default %s)", defaultListen))
+ getopt.BoolVarLong(&withDNS, "with-dns", 'w', "instruct created containers to use weaveDNS as their nameserver")
+ getopt.Parse()
+
+ if target == "" {
+ target = defaultTarget
+ }
+
+ if listen == "" {
+ listen = defaultListen
+ }
+
+ if debug {
+ InitDefaultLogging(true)
+ }
+
+ p, err := proxy.NewProxy(target, withDNS)
+ if err != nil {
+ Error.Fatalf("Could not start proxy: %s", err)
+ }
+ s := &http.Server{
+ Addr: listen,
+ Handler: p,
+ }
+
+ Info.Printf("Listening on %s", listen)
+ Info.Printf("Proxying %s", target)
+
+ if err := s.ListenAndServe(); err != nil {
+ Error.Fatalf("Could not listen on %s: %s", listen, err)
+ }
+}
diff --git a/weavewait/main.go b/weavewait/main.go
new file mode 100644
index 00000000..c4f52549
--- /dev/null
+++ b/weavewait/main.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+
+ "github.com/weaveworks/weave/net"
+)
+
+func main() {
+ if _, err := net.EnsureInterface("ethwe", 20); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ }
+
+ if len(os.Args) <= 1 {
+ os.Exit(0)
+ }
+ cmd := exec.Command(os.Args[1], os.Args[2:]...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ os.Exit(1)
+ }
+}