diff --git a/proxy/common.go b/proxy/common.go index 4c0f3fc3..068e1712 100644 --- a/proxy/common.go +++ b/proxy/common.go @@ -90,7 +90,6 @@ func marshalResponseBody(r *http.Response, body interface{}) error { // Stop it being chunked, because that hangs r.TransferEncoding = nil return nil - } func inspectContainerInPath(client *docker.Client, path string) (*docker.Container, error) { diff --git a/proxy/create_container_interceptor.go b/proxy/create_container_interceptor.go index d40efaff..5f154631 100644 --- a/proxy/create_container_interceptor.go +++ b/proxy/create_container_interceptor.go @@ -17,12 +17,6 @@ var ( type createContainerInterceptor struct{ proxy *Proxy } -type createContainerRequestBody struct { - *docker.Config - HostConfig *docker.HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"` - MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` -} - // ErrNoSuchImage replaces docker.NoSuchImage, which does not contain the image // name, which in turn breaks docker clients post 1.7.0 since they expect the // image name to be present in errors. @@ -35,35 +29,45 @@ func (err *ErrNoSuchImage) Error() string { } func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error { - container := createContainerRequestBody{} + container := jsonObject{} if err := unmarshalRequestBody(r, &container); err != nil { return err } - if cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig); err != nil { + hostConfig, err := container.Object("HostConfig") + if err != nil { + return err + } + + networkMode, err := hostConfig.String("NetworkMode") + if err != nil { + return err + } + + env, err := container.StringArray("Env") + if err != nil { + return err + } + + if cidrs, err := i.proxy.weaveCIDRs(networkMode, env); err != nil { Log.Infof("Leaving container alone because %s", err) } else { Log.Infof("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " ")) - if container.HostConfig == nil { - container.HostConfig = &docker.HostConfig{} - } - if container.Config == nil { - container.Config = &docker.Config{} - } - i.proxy.addWeaveWaitVolume(container.HostConfig) - if err := i.setWeaveWaitEntrypoint(container.Config); err != nil { + if err := i.proxy.addWeaveWaitVolume(hostConfig); err != nil { return err } - hostname := r.URL.Query().Get("name") - if i.proxy.Config.HostnameFromLabel != "" { - if labelValue, ok := container.Labels[i.proxy.Config.HostnameFromLabel]; ok { - hostname = labelValue - } + if err := i.setWeaveWaitEntrypoint(container); err != nil { + return err + } + hostname, err := i.containerHostname(r, container) + if err != nil { + return err } - hostname = i.proxy.hostnameMatchRegexp.ReplaceAllString(hostname, i.proxy.HostnameReplacement) if dnsDomain := i.proxy.getDNSDomain(); dnsDomain != "" { - i.setHostname(&container, hostname, dnsDomain) - if err := i.proxy.setWeaveDNS(container.HostConfig, container.Hostname, dnsDomain); err != nil { + if err := i.setHostname(container, hostname, dnsDomain); err != nil { + return err + } + if err := i.proxy.setWeaveDNS(hostConfig, hostname, dnsDomain); err != nil { return err } } @@ -74,50 +78,104 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error { return nil } -func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error { - if len(container.Entrypoint) == 0 { - image, err := i.proxy.client.InspectImage(container.Image) +func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container jsonObject) error { + var entrypoint []string + entrypoint, err := container.StringArray("Entrypoint") + if err != nil { + err, ok := err.(*UnmarshalWrongTypeError) + if !ok { + return err + } + + got, ok := err.Got.(string) + if !ok { + return err + } + + entrypoint = []string{got} + } + + cmd, err := container.StringArray("Cmd") + if err != nil { + return err + } + + if len(entrypoint) == 0 { + containerImage, err := container.String("Image") + if err != nil { + return err + } + + image, err := i.proxy.client.InspectImage(containerImage) if err == docker.ErrNoSuchImage { - return &ErrNoSuchImage{container.Image} + return &ErrNoSuchImage{containerImage} } else if err != nil { return err } - if len(container.Cmd) == 0 { - container.Cmd = image.Config.Cmd + if len(cmd) == 0 { + cmd = image.Config.Cmd + container["Cmd"] = cmd } - if container.Entrypoint == nil { - container.Entrypoint = image.Config.Entrypoint + if entrypoint == nil { + entrypoint = image.Config.Entrypoint + container["Entrypoint"] = entrypoint } } - if len(container.Entrypoint) == 0 && len(container.Cmd) == 0 { + if len(entrypoint) == 0 && len(cmd) == 0 { return ErrNoCommandSpecified } - if len(container.Entrypoint) == 0 || container.Entrypoint[0] != weaveWaitEntrypoint[0] { - container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...) + if len(entrypoint) == 0 || entrypoint[0] != weaveWaitEntrypoint[0] { + container["Entrypoint"] = append(weaveWaitEntrypoint, entrypoint...) } return nil } -func (i *createContainerInterceptor) setHostname(container *createContainerRequestBody, name, dnsDomain string) { - if container.Hostname == "" && name != "" { +func (i *createContainerInterceptor) setHostname(container jsonObject, name, dnsDomain string) error { + hostname, err := container.String("Hostname") + if err != nil { + return err + } + if hostname == "" && name != "" { // Strip trailing period because it's unusual to see it used on the end of a host name trimmedDNSDomain := strings.TrimSuffix(dnsDomain, ".") if len(name)+1+len(trimmedDNSDomain) > MaxDockerHostname { Log.Warningf("Container name [%s] too long to be used as hostname", name) } else { - container.Hostname = name - container.Domainname = trimmedDNSDomain + hostname = name + container["Hostname"] = name + container["Domainname"] = trimmedDNSDomain } } - return + return nil } func (i *createContainerInterceptor) InterceptResponse(r *http.Response) error { return nil } + +func (i *createContainerInterceptor) containerHostname(r *http.Request, container jsonObject) (hostname string, err error) { + hostname = r.URL.Query().Get("name") + if i.proxy.Config.HostnameFromLabel != "" { + hostname, err = i.hostnameFromLabel(hostname, container) + } + hostname = i.proxy.hostnameMatchRegexp.ReplaceAllString(hostname, i.proxy.HostnameReplacement) + return +} + +func (i *createContainerInterceptor) hostnameFromLabel(hostname string, container jsonObject) (string, error) { + label, err := container.String("Labels", i.proxy.Config.HostnameFromLabel) + if err != nil { + return "", err + } + if label == "" { + return hostname, nil + } + + return label, nil +} diff --git a/proxy/create_exec_interceptor.go b/proxy/create_exec_interceptor.go index 1308f9cc..f26eb286 100644 --- a/proxy/create_exec_interceptor.go +++ b/proxy/create_exec_interceptor.go @@ -4,14 +4,13 @@ import ( "net/http" "strings" - "github.com/fsouza/go-dockerclient" . "github.com/weaveworks/weave/common" ) type createExecInterceptor struct{ proxy *Proxy } func (i *createExecInterceptor) InterceptRequest(r *http.Request) error { - options := docker.CreateExecOptions{} + options := jsonObject{} if err := unmarshalRequestBody(r, &options); err != nil { return err } @@ -25,14 +24,19 @@ func (i *createExecInterceptor) InterceptRequest(r *http.Request) error { return nil } - cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig) + cidrs, err := i.proxy.weaveCIDRs(container.HostConfig.NetworkMode, container.Config.Env) if err != nil { Log.Infof("Leaving container %s alone because %s", container.ID, err) return nil } + cmd, err := options.StringArray("Cmd") + if err != nil { + return err + } + Log.Infof("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " ")) - options.Cmd = append(weaveWaitEntrypoint, options.Cmd...) + options["Cmd"] = append(weaveWaitEntrypoint, cmd...) return marshalRequestBody(r, options) } diff --git a/proxy/inspect_container_interceptor.go b/proxy/inspect_container_interceptor.go index 59f9a062..5fb1cb97 100644 --- a/proxy/inspect_container_interceptor.go +++ b/proxy/inspect_container_interceptor.go @@ -17,7 +17,7 @@ func (i *inspectContainerInterceptor) InterceptResponse(r *http.Response) error return nil } - container := map[string]interface{}{} + container := jsonObject{} if err := unmarshalResponseBody(r, &container); err != nil { return err } @@ -29,8 +29,8 @@ func (i *inspectContainerInterceptor) InterceptResponse(r *http.Response) error return marshalResponseBody(r, container) } -func updateContainerNetworkSettings(container map[string]interface{}) error { - containerID, err := lookupString(container, "Id") +func updateContainerNetworkSettings(container jsonObject) error { + containerID, err := container.String("Id") if err != nil { return err } @@ -40,7 +40,7 @@ func updateContainerNetworkSettings(container map[string]interface{}) error { return err } - networkSettings, err := lookupObject(container, "NetworkSettings") + networkSettings, err := container.Object("NetworkSettings") if err != nil { return err } diff --git a/proxy/inspect_exec_interceptor.go b/proxy/inspect_exec_interceptor.go index 89707146..970c3142 100644 --- a/proxy/inspect_exec_interceptor.go +++ b/proxy/inspect_exec_interceptor.go @@ -17,12 +17,12 @@ func (i *inspectExecInterceptor) InterceptResponse(r *http.Response) error { return nil } - exec := map[string]interface{}{} + exec := jsonObject{} if err := unmarshalResponseBody(r, &exec); err != nil { return err } - container, err := lookupObject(exec, "Container") + container, err := exec.Object("Container") if err != nil { return err } diff --git a/proxy/json.go b/proxy/json.go index d3728e18..8e333612 100644 --- a/proxy/json.go +++ b/proxy/json.go @@ -13,12 +13,13 @@ func (e *UnmarshalWrongTypeError) Error() string { return fmt.Sprintf("Wrong type for %s field, expected %s, but got %T", e.Field, e.Expected, e.Got) } -// For parsing json objects. Look up (or create) a child object in the given map. -func lookupObject(m map[string]interface{}, key string) (map[string]interface{}, error) { - iface, ok := m[key] +type jsonObject map[string]interface{} + +func (j jsonObject) Object(key string) (jsonObject, error) { + iface, ok := j[key] if !ok || iface == nil { - result := map[string]interface{}{} - m[key] = result + result := jsonObject{} + j[key] = result return result, nil } @@ -27,12 +28,20 @@ func lookupObject(m map[string]interface{}, key string) (map[string]interface{}, return nil, &UnmarshalWrongTypeError{key, "object", iface} } - return result, nil + return jsonObject(result), nil } // For parsing json objects. Look up (or create) a string in the given map. -func lookupString(m map[string]interface{}, key string) (string, error) { - iface, ok := m[key] +func (j jsonObject) String(key string, ks ...string) (string, error) { + if len(ks) > 0 { + j, err := j.Object(key) + if err != nil { + return "", err + } + return j.String(ks[0], ks[1:]...) + } + + iface, ok := j[key] if !ok || iface == nil { return "", nil } @@ -44,3 +53,27 @@ func lookupString(m map[string]interface{}, key string) (string, error) { return result, nil } + +// For parsing json objects. Look up (or create) a string array in the given map. +func (j jsonObject) StringArray(key string) ([]string, error) { + iface, ok := j[key] + if !ok || iface == nil { + return nil, nil + } + + switch o := iface.(type) { + case []string: + return o, nil + case []interface{}: + result := []string{} + for _, s := range o { + if s, ok := s.(string); ok { + result = append(result, s) + } else { + return nil, &UnmarshalWrongTypeError{key, "array of strings", iface} + } + } + return result, nil + } + return nil, &UnmarshalWrongTypeError{key, "array of strings", iface} +} diff --git a/proxy/json_test.go b/proxy/json_test.go new file mode 100644 index 00000000..0083bebc --- /dev/null +++ b/proxy/json_test.go @@ -0,0 +1,116 @@ +package proxy + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLookupObject(t *testing.T) { + tests := []struct { + root jsonObject + key string + result jsonObject + err error + }{ + { + jsonObject{}, + "a", + jsonObject{}, + nil, + }, + { + jsonObject{"a": map[string]interface{}{"b": int(1)}}, + "a", + jsonObject{"b": int(1)}, + nil, + }, + { + jsonObject{"nonObject": int(1)}, + "nonObject", + nil, + &UnmarshalWrongTypeError{Field: "nonObject", Expected: "object", Got: 1}, + }, + } + for _, test := range tests { + gotResult, gotErr := test.root.Object(test.key) + msg := fmt.Sprintf("%q.Object(%q) => %q, %q", test.root, test.key, gotResult, gotErr) + assert.Equal(t, gotResult, test.result, msg) + assert.Equal(t, gotErr, test.err, msg) + } +} + +func TestLookupString(t *testing.T) { + tests := []struct { + root jsonObject + path []string + result string + err error + }{ + { + jsonObject{}, + []string{"a", "b"}, + "", + nil, + }, + { + jsonObject{"nonString": int(1)}, + []string{"nonString"}, + "", + &UnmarshalWrongTypeError{Field: "nonString", Expected: "string", Got: 1}, + }, + { + jsonObject{"nonObject": int(1)}, + []string{"nonObject", "b"}, + "", + &UnmarshalWrongTypeError{Field: "nonObject", Expected: "object", Got: 1}, + }, + } + for _, test := range tests { + gotResult, gotErr := test.root.String(test.path[0], test.path[1:]...) + msg := fmt.Sprintf("%q.String(%q) => %q, %q", test.root, test.path, gotResult, gotErr) + assert.Equal(t, gotResult, test.result, msg) + assert.Equal(t, gotErr, test.err, msg) + } +} + +func TestLookupStringArray(t *testing.T) { + tests := []struct { + root jsonObject + path string + result []string + err error + }{ + { + jsonObject{}, + "a", + nil, + nil, + }, + { + jsonObject{"a": []string{"foo"}}, + "a", + []string{"foo"}, + nil, + }, + { + jsonObject{"a": []string{}}, + "a", + []string{}, + nil, + }, + { + jsonObject{"string": "foo"}, + "string", + nil, + &UnmarshalWrongTypeError{Field: "string", Expected: "array of strings", Got: "foo"}, + }, + } + for _, test := range tests { + gotResult, gotErr := test.root.StringArray(test.path) + msg := fmt.Sprintf("%q.String(%q) => %q, %q", test.root, test.path, gotResult, gotErr) + assert.Equal(t, gotResult, test.result, msg) + assert.Equal(t, gotErr, test.err, msg) + } +} diff --git a/proxy/proxy.go b/proxy/proxy.go index fc7d1457..8d09292e 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -346,7 +346,7 @@ func (proxy *Proxy) ContainerDied(ident string) { } func (proxy *Proxy) attach(container *docker.Container) error { - cidrs, err := proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig) + cidrs, err := proxy.weaveCIDRs(container.HostConfig.NetworkMode, container.Config.Env) if err != nil { Log.Infof("Leaving container %s alone because %s", container.ID, err) return nil @@ -368,15 +368,11 @@ func (proxy *Proxy) attach(container *docker.Container) error { return nil } -func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *docker.HostConfig) ([]string, error) { - netMode := "" - if hostConfig != nil { - netMode = hostConfig.NetworkMode +func (proxy *Proxy) weaveCIDRs(networkMode string, env []string) ([]string, error) { + if networkMode == "host" || strings.HasPrefix(networkMode, "container:") { + return nil, fmt.Errorf("the container was created with the '--net=%s'", networkMode) } - if netMode == "host" || strings.HasPrefix(netMode, "container:") { - return nil, fmt.Errorf("the container was created with the '--net=%s'", netMode) - } - for _, e := range config.Env { + for _, e := range env { if strings.HasPrefix(e, "WEAVE_CIDR=") { if e[11:] == "none" { return nil, ErrWeaveCIDRNone @@ -390,26 +386,40 @@ func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *dock return nil, nil } -func (proxy *Proxy) addWeaveWaitVolume(hostConfig *docker.HostConfig) { +func (proxy *Proxy) addWeaveWaitVolume(hostConfig jsonObject) error { + configBinds, err := hostConfig.StringArray("Binds") + if err != nil { + return err + } + var binds []string - for _, bind := range hostConfig.Binds { + for _, bind := range configBinds { s := strings.Split(bind, ":") if len(s) >= 2 && s[1] == "/w" { continue } binds = append(binds, bind) } - hostConfig.Binds = append(binds, fmt.Sprintf("%s:/w:ro", proxy.weaveWaitVolume)) + hostConfig["Binds"] = append(binds, fmt.Sprintf("%s:/w:ro", proxy.weaveWaitVolume)) + return nil } -func (proxy *Proxy) setWeaveDNS(hostConfig *docker.HostConfig, hostname, dnsDomain string) error { - hostConfig.DNS = append(hostConfig.DNS, proxy.dockerBridgeIP) +func (proxy *Proxy) setWeaveDNS(hostConfig jsonObject, hostname, dnsDomain string) error { + dns, err := hostConfig.StringArray("Dns") + if err != nil { + return err + } + hostConfig["Dns"] = append(dns, proxy.dockerBridgeIP) - if len(hostConfig.DNSSearch) == 0 { + dnsSearch, err := hostConfig.StringArray("DnsSearch") + if err != nil { + return err + } + if len(dnsSearch) == 0 { if hostname == "" { - hostConfig.DNSSearch = []string{dnsDomain} + hostConfig["DnsSearch"] = []string{dnsDomain} } else { - hostConfig.DNSSearch = []string{"."} + hostConfig["DnsSearch"] = []string{"."} } } diff --git a/proxy/start_container_interceptor.go b/proxy/start_container_interceptor.go index 478bb9c1..bac9b887 100644 --- a/proxy/start_container_interceptor.go +++ b/proxy/start_container_interceptor.go @@ -23,8 +23,8 @@ func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error { return nil } - hostConfig := &docker.HostConfig{} - if err := unmarshalRequestBody(r, hostConfig); err != nil { + hostConfig := map[string]interface{}{} + if err := unmarshalRequestBody(r, &hostConfig); err != nil { return err }