[proxy] all request modification is done through map[string]interface{}

That way we are less likely fall victim to go-dockerclient, or the API changing.
This commit is contained in:
Paul Bellamy committed 2015-10-12 15:53:07 +01:00
1 parent d323de3c21
commit fbb2c213a9
9 files changed
+298 -78

No files matched your search

-1
View File
@@ -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) {
+98 -40
View File
@@ -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
}
+8 -4
View File
@@ -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)
}
+4 -4
View File
@@ -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
}
+2 -2
View File
@@ -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
}
+41 -8
View File
@@ -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}
}
+116
View File
@@ -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)
}
}
+27 -17
View File
@@ -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{"."}
}
}
+2 -2
View File
@@ -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
}