diff --git a/command/connect/proxy/flag_upstreams.go b/command/connect/proxy/flag_upstreams.go new file mode 100644 index 000000000..02ce3a3bb --- /dev/null +++ b/command/connect/proxy/flag_upstreams.go @@ -0,0 +1,54 @@ +package proxy + +import ( + "fmt" + "strconv" + "strings" + + "github.com/hashicorp/consul/connect/proxy" +) + +// FlagUpstreams implements the flag.Value interface and allows specifying +// the -upstream flag multiple times and keeping track of the name of the +// upstream and the local port. +// +// The syntax of the value is "name:addr" where addr can be "port" or +// "host:port". Examples: "db:8181", "db:127.0.0.10:8282", etc. +type FlagUpstreams map[string]proxy.UpstreamConfig + +func (f *FlagUpstreams) String() string { + return fmt.Sprintf("%v", *f) +} + +func (f *FlagUpstreams) Set(value string) error { + idx := strings.Index(value, ":") + if idx == -1 { + return fmt.Errorf("Upstream value should be name:addr in %q", value) + } + + addr := "" + name := value[:idx] + portRaw := value[idx+1:] + if idx := strings.Index(portRaw, ":"); idx != -1 { + addr = portRaw[:idx] + portRaw = portRaw[idx+1:] + } + + port, err := strconv.ParseInt(portRaw, 0, 0) + if err != nil { + return err + } + + if *f == nil { + *f = make(map[string]proxy.UpstreamConfig) + } + + (*f)[name] = proxy.UpstreamConfig{ + LocalBindAddress: addr, + LocalBindPort: int(port), + DestinationName: name, + DestinationType: "service", + } + + return nil +} diff --git a/command/connect/proxy/flag_upstreams_test.go b/command/connect/proxy/flag_upstreams_test.go new file mode 100644 index 000000000..d43c49d03 --- /dev/null +++ b/command/connect/proxy/flag_upstreams_test.go @@ -0,0 +1,106 @@ +package proxy + +import ( + "flag" + "testing" + + "github.com/hashicorp/consul/connect/proxy" + "github.com/stretchr/testify/require" +) + +func TestFlagUpstreams_impl(t *testing.T) { + var _ flag.Value = new(FlagUpstreams) +} + +func TestFlagUpstreams(t *testing.T) { + cases := []struct { + Name string + Input []string + Expected map[string]proxy.UpstreamConfig + Error string + }{ + { + "bad format", + []string{"foo"}, + nil, + "should be name:addr", + }, + + { + "port not int", + []string{"db:hello"}, + nil, + "invalid syntax", + }, + + { + "4 parts", + []string{"db:127.0.0.1:8181:foo"}, + nil, + "invalid syntax", + }, + + { + "single value", + []string{"db:8181"}, + map[string]proxy.UpstreamConfig{ + "db": proxy.UpstreamConfig{ + LocalBindPort: 8181, + DestinationName: "db", + DestinationType: "service", + }, + }, + "", + }, + + { + "address specified", + []string{"db:127.0.0.55:8181"}, + map[string]proxy.UpstreamConfig{ + "db": proxy.UpstreamConfig{ + LocalBindAddress: "127.0.0.55", + LocalBindPort: 8181, + DestinationName: "db", + DestinationType: "service", + }, + }, + "", + }, + + { + "repeat value, overwrite", + []string{"db:8181", "db:8282"}, + map[string]proxy.UpstreamConfig{ + "db": proxy.UpstreamConfig{ + LocalBindPort: 8282, + DestinationName: "db", + DestinationType: "service", + }, + }, + "", + }, + } + + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + require := require.New(t) + + var actual map[string]proxy.UpstreamConfig + f := (*FlagUpstreams)(&actual) + + var err error + for _, input := range tc.Input { + err = f.Set(input) + // Note we only test the last error. This could make some + // test failures confusing but it shouldn't be too bad. + } + if tc.Error != "" { + require.Error(err) + require.Contains(err.Error(), tc.Error) + return + } + + require.Equal(tc.Expected, actual) + }) + } +} diff --git a/command/connect/proxy/proxy.go b/command/connect/proxy/proxy.go index 83406e0fb..fc9e65b86 100644 --- a/command/connect/proxy/proxy.go +++ b/command/connect/proxy/proxy.go @@ -42,6 +42,8 @@ type cmd struct { cfgFile string proxyID string pprofAddr string + service string + upstreams map[string]proxyImpl.UpstreamConfig } func (c *cmd) init() { @@ -66,6 +68,14 @@ func (c *cmd) init() { "Enable debugging via pprof. Providing a host:port (or just ':port') "+ "enables profiling HTTP endpoints on that address.") + c.flags.StringVar(&c.service, "service", "", + "Name of the service this proxy is representing.") + + c.flags.Var((*FlagUpstreams)(&c.upstreams), "upstream", + "Upstream service to support connecting to. The format should be "+ + "'name:addr', such as 'db:8181'. This will make 'db' available "+ + "on port 8181.") + c.http = &flags.HTTPFlags{} flags.Merge(c.flags, c.http.ClientFlags()) flags.Merge(c.flags, c.http.ServerFlags()) @@ -158,13 +168,27 @@ func (c *cmd) configWatcher(client *api.Client) (proxyImpl.ConfigWatcher, error) } // Use the configured proxy ID - if c.proxyID == "" { + if c.proxyID != "" { + return proxyImpl.NewAgentConfigWatcher(client, c.proxyID, c.logger) + } + + // Otherwise, we're representing a manually specified service. + if c.service == "" { return nil, fmt.Errorf( "-service or -proxy-id must be specified so that proxy can " + "configure itself.") } - return proxyImpl.NewAgentConfigWatcher(client, c.proxyID, c.logger) + // Convert our upstreams to a slice of configurations + upstreams := make([]proxyImpl.UpstreamConfig, 0, len(c.upstreams)) + for _, u := range c.upstreams { + upstreams = append(upstreams, u) + } + + return proxyImpl.NewStaticConfigWatcher(&proxyImpl.Config{ + ProxiedServiceName: c.service, + Upstreams: upstreams, + }), nil } func (c *cmd) Synopsis() string {