veyron/lib/flags: add flag groups.
Change-Id: Ia490369e153564b79d673feaee64d8e3c0e91564
diff --git a/lib/flags/doc.go b/lib/flags/doc.go
index ef8c2c5..49a7360 100644
--- a/lib/flags/doc.go
+++ b/lib/flags/doc.go
@@ -1,13 +1,17 @@
-// Package flags provides flag definitions for commonly used flags and
-// and, where appropriate, implementations of the flag.Value interface
-// for those flags to ensure that only valid values of those flags are
-// supplied. In general, this package will be used by veyron profiles and
-// the runtime implementations, but can also be used by any application
+// Package flags provides definitions for commonly used flags and where
+// appropriate, implementations of the flag.Value interface for those flags
+// to ensure that only valid values of those flags are supplied. Some of these
+// flags may also be specified using environment variables directly for
+// are documented accordingly; in these cases the command line value takes
+// precedence over the environment variable.
+// Flags are defined as 'groups' of related flags so that caller may choose
+// which ones to use without having to be burdened with the full set. The
+// groups may be used directly or via the Flags type that aggregates multiple
+// groups. In all cases, the flags are registered with a supplied
+// flag.FlagSet and hence are not forced onto the command line
+// unless the caller passes in flag.CommandLine as the flag.FlagSet to use.
+//
+// In general, this package will be used by veyron profiles and the
+// runtime implementations, but can also be used by any application
// that wants access to the flags and environment variables it supports.
-//
-// TODO(cnicolaou): move reading of environment variables to here also,
-// flags will override the environment variable settings.
-//
-// TODO(cnicolaou): implement simply subsetting of the flags to be defined
-// and parsed - e.g. for server configs, basic runtime configs/security etc.
package flags
diff --git a/lib/flags/flags.go b/lib/flags/flags.go
index 270b72f..f6c2ef1 100644
--- a/lib/flags/flags.go
+++ b/lib/flags/flags.go
@@ -1,44 +1,170 @@
package flags
-import "flag"
+import (
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+)
+// FlagGroup is the type for identifying groups of related flags.
+type FlagGroup int
+
+const (
+ // Essential identifies the flags and associated environment variables
+ // required by all Vanadium processes. Namely:
+ // --veyron.namespace.root (which may be repeated to supply multiple values)
+ // --veyron.credentials
+ Essential FlagGroup = iota
+ // Listen identifies the flags typically required to configure
+ // ipc.ListenSpec. Namely:
+ // --veyron.tcp.protocol
+ // --veyron.tcp.address
+ // --veyron.proxy
+ Listen
+)
+
+// Flags represents the set of flag groups created by a call to
+// CreateAndRegister.
type Flags struct {
- // The FlagSet passed in as a parameter to New
FlagSet *flag.FlagSet
-
- // As needed to initialize ipc.ListenSpec.
- ListenProtocolFlag TCPProtocolFlag
- ListenAddressFlag IPHostPortFlag
- ListenProxyFlag string
-
- // TODO(cnicolaou): implement these.
- NamespaceRootsFlag string // TODO(cnicolaou): provide flag.Value impl
- CredentialsFlag string // TODO(cnicolaou): provide flag.Value impl
+ groups map[FlagGroup]interface{}
}
-// New returns a new instance of flags.Flags. Calling Parse on the supplied
-// flagSet will populate the fields of the returned flags.Flags with the
-// appropriate values. The currently supported set of flags only allows
-// for configuring one service at a time, but in the future this will
-// be expanded to support multiple services.
-func New(fs *flag.FlagSet) *Flags {
- t := &Flags{FlagSet: fs}
- t.ListenProtocolFlag = TCPProtocolFlag{"tcp"}
- t.ListenAddressFlag = IPHostPortFlag{Port: "0"}
- fs.Var(&t.ListenProtocolFlag, "veyron.tcp.protocol", "protocol to listen with")
- fs.Var(&t.ListenAddressFlag, "veyron.tcp.address", "address to listen on")
- fs.StringVar(&t.ListenProxyFlag, "veyron.proxy", "", "object name of proxy service to use to export services across network boundaries")
- fs.StringVar(&t.NamespaceRootsFlag, "veyron.namespace.roots", "", "colon separated list of roots for the local namespace")
- fs.StringVar(&t.CredentialsFlag, "veyron.credentials", "", "directory to use for storing security credentials")
- return t
+type namespaceRootFlagVar struct {
+ roots []string
}
-// Args returns the unparsed args, as per flag.Args
-func (fs *Flags) Args() []string {
- return fs.FlagSet.Args()
+func (nsr *namespaceRootFlagVar) String() string {
+ return fmt.Sprintf("%v", nsr.roots)
+}
+
+func (nsr *namespaceRootFlagVar) Set(v string) error {
+ nsr.roots = append(nsr.roots, v)
+ return nil
+}
+
+// EssentialFlags contains the values of the Essential flag group.
+type EssentialFlags struct {
+ // NamespaceRoots may be initialized by NAMESPACE_ROOT* enivornment
+ // variables as well as --veyron.namespace.root. The command line
+ // will override the environment.
+ NamespaceRoots []string // TODO(cnicolaou): provide flag.Value impl
+
+ // Credentials may be initialized by the the VEYRON_CREDENTIALS
+ // environment variable. The command line will override the environment.
+ Credentials string // TODO(cnicolaou): provide flag.Value impl
+
+ namespaceRootsFlag namespaceRootFlagVar
+}
+
+// ListenFlags contains the values of the Listen flag group.
+type ListenFlags struct {
+ ListenProtocol TCPProtocolFlag
+ ListenAddress IPHostPortFlag
+ ListenProxy string
+}
+
+// createAndRegisterEssentialFlags creates and registers the EssentialFlags
+// group with the supplied flag.FlagSet.
+func createAndRegisterEssentialFlags(fs *flag.FlagSet) *EssentialFlags {
+ f := &EssentialFlags{}
+ fs.Var(&f.namespaceRootsFlag, "veyron.namespace.root", "local namespace root; can be repeated to provided multiple roots")
+ fs.StringVar(&f.Credentials, "veyron.credentials", "", "directory to use for storing security credentials")
+ return f
+}
+
+// createAndRegisterListenFlags creates and registers the ListenFlags
+// group with the supplied flag.FlagSet.
+func createAndRegisterListenFlags(fs *flag.FlagSet) *ListenFlags {
+ f := &ListenFlags{}
+ f.ListenProtocol = TCPProtocolFlag{"tcp"}
+ f.ListenAddress = IPHostPortFlag{Port: "0"}
+ fs.Var(&f.ListenProtocol, "veyron.tcp.protocol", "protocol to listen with")
+ fs.Var(&f.ListenAddress, "veyron.tcp.address", "address to listen on")
+ fs.StringVar(&f.ListenProxy, "veyron.proxy", "", "object name of proxy service to use to export services across network boundaries")
+ return f
+}
+
+// CreateAndRegister creates a new set of flag groups as specified by the
+// supplied flag group parameters and registers them with the supplied
+// flag.Flagset. The Essential flag group is always included.
+func CreateAndRegister(fs *flag.FlagSet, groups ...FlagGroup) *Flags {
+ f := &Flags{FlagSet: fs, groups: make(map[FlagGroup]interface{})}
+ f.groups[Essential] = createAndRegisterEssentialFlags(fs)
+ for _, s := range groups {
+ switch s {
+ case Essential:
+ // do nothing, always included
+ case Listen:
+ f.groups[Listen] = createAndRegisterListenFlags(fs)
+ }
+ }
+ return f
+}
+
+// EssentialFlags returns the Essential flag subset stored in its Flags
+// instance.
+func (f *Flags) EssentialFlags() EssentialFlags {
+ from := f.groups[Essential].(*EssentialFlags)
+ to := *from
+ to.NamespaceRoots = make([]string, len(from.NamespaceRoots))
+ copy(to.NamespaceRoots, from.NamespaceRoots)
+ return to
+}
+
+// ListenFlags returns a copy of the Listen flag group stored in Flags.
+// This copy will contain default values if the Listen flag group
+// was not specified when CreateAndRegister was called. The HasGroup
+// method can be used for testing to see if any given group was configured.
+func (f *Flags) ListenFlags() ListenFlags {
+ if p := f.groups[Listen]; p != nil {
+ return *(p.(*ListenFlags))
+ }
+ return ListenFlags{}
+}
+
+// HasGroup returns group if the supplied FlagGroup has been created
+// for these Flags.
+func (f *Flags) HasGroup(group FlagGroup) bool {
+ _, present := f.groups[group]
+ return present
+}
+
+// Args returns the unparsed args, as per flag.Args.
+func (f *Flags) Args() []string {
+ return f.FlagSet.Args()
+}
+
+// legacyEnvInit provides support for the legacy NAMESPACE_ROOT? and
+// VEYRON_CREDENTIALS env vars.
+func (es *EssentialFlags) legacyEnvInit() {
+ for _, ev := range os.Environ() {
+ p := strings.SplitN(ev, "=", 2)
+ if len(p) != 2 {
+ continue
+ }
+ k, v := p[0], p[1]
+ if strings.HasPrefix(k, "NAMESPACE_ROOT") {
+ es.NamespaceRoots = append(es.NamespaceRoots, v)
+ }
+ }
+ if creds := os.Getenv("VEYRON_CREDENTIALS"); creds != "" {
+ es.Credentials = creds
+ }
}
// Parse parses the supplied args, as per flag.Parse
-func (fs *Flags) Parse(args []string) error {
- return fs.FlagSet.Parse(args)
+func (f *Flags) Parse(args []string) error {
+ f.groups[Essential].(*EssentialFlags).legacyEnvInit()
+ // TODO(cnicolaou): implement a single env var 'VANADIUM_OPTS'
+ // that can be used to specify any command line.
+ if err := f.FlagSet.Parse(args); err != nil {
+ return err
+ }
+ essential := f.groups[Essential].(*EssentialFlags)
+ if len(essential.namespaceRootsFlag.roots) > 0 {
+ essential.NamespaceRoots = essential.namespaceRootsFlag.roots
+ }
+ return nil
}
diff --git a/lib/flags/flags_test.go b/lib/flags/flags_test.go
index 7b17e99..e8ca273 100644
--- a/lib/flags/flags_test.go
+++ b/lib/flags/flags_test.go
@@ -3,34 +3,41 @@
import (
"flag"
"io/ioutil"
+ "os"
+ "reflect"
"testing"
"veyron.io/veyron/veyron/lib/flags"
)
func TestFlags(t *testing.T) {
- fs := flag.NewFlagSet("test", flag.ContinueOnError)
- fl := flags.New(fs)
-
- addr := "192.168.10.1:0"
- roots := "ab:cd:ef"
- args := []string{"--veyron.tcp.address=" + addr, "--veyron.namespace.roots=" + roots}
- fs.Parse(args)
- if got, want := fl.NamespaceRootsFlag, roots; got != want {
- t.Errorf("got %q, want %q", got, want)
+ fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError))
+ creds := "creddir"
+ roots := []string{"ab:cd:ef"}
+ args := []string{"--veyron.credentials=" + creds, "--veyron.namespace.root=" + roots[0]}
+ fl.Parse(args)
+ es := fl.EssentialFlags()
+ if got, want := es.NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
+ t.Errorf("got %v, want %v", got, want)
}
- if got, want := fl.ListenAddressFlag.String(), addr; got != want {
- t.Errorf("got %q, want %q", got, want)
+ if got, want := es.Credentials, creds; !reflect.DeepEqual(got, want) {
+ t.Errorf("got %v, want %v", got, want)
}
- if got, want := len(fl.Args()), 0; got != want {
- t.Errorf("got %d, want %d", got, want)
+ if got, want := fl.HasGroup(flags.Listen), false; got != want {
+ t.Errorf("got %t, want %t", got, want)
+ }
+ // Make sure we have a deep copy.
+ es.NamespaceRoots[0] = "oooh"
+ es = fl.EssentialFlags()
+ if got, want := es.NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
+ t.Errorf("got %v, want %v", got, want)
}
}
func TestFlagError(t *testing.T) {
fs := flag.NewFlagSet("test", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
- fl := flags.New(fs)
+ fl := flags.CreateAndRegister(fs)
addr := "192.168.10.1:0"
args := []string{"--xxxveyron.tcp.address=" + addr, "not an arg"}
err := fl.Parse(args)
@@ -40,5 +47,73 @@
if got, want := len(fl.Args()), 1; got != want {
t.Errorf("got %d, want %d [args: %v]", got, want, fl.Args())
}
+}
+
+func TestFlagsGroups(t *testing.T) {
+ fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Listen)
+ if got, want := fl.HasGroup(flags.Listen), true; got != want {
+ t.Errorf("got %t, want %t", got, want)
+ }
+ addr := "192.168.10.1:0"
+ roots := []string{"ab:cd:ef"}
+ args := []string{"--veyron.tcp.address=" + addr, "--veyron.namespace.root=" + roots[0]}
+ fl.Parse(args)
+ lf := fl.ListenFlags()
+ if got, want := fl.EssentialFlags().NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := lf.ListenAddress.String(), addr; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+}
+
+const credEnvVar = "VEYRON_CREDENTIALS"
+const rootEnvVar = "NAMESPACE_ROOT"
+const rootEnvVar0 = "NAMESPACE_ROOT0"
+
+func TestEnvVars(t *testing.T) {
+ oldcreds := os.Getenv(credEnvVar)
+ defer os.Setenv(credEnvVar, oldcreds)
+
+ oldroot := os.Getenv(rootEnvVar)
+ oldroot0 := os.Getenv(rootEnvVar0)
+ defer os.Setenv(rootEnvVar, oldroot)
+ defer os.Setenv(rootEnvVar0, oldroot0)
+
+ os.Setenv(credEnvVar, "bar")
+ fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError))
+ if err := fl.Parse([]string{}); err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ es := fl.EssentialFlags()
+ if got, want := es.Credentials, "bar"; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+
+ if err := fl.Parse([]string{"--veyron.credentials=baz"}); err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ es = fl.EssentialFlags()
+ if got, want := es.Credentials, "baz"; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+
+ os.Setenv(rootEnvVar, "a:1")
+ os.Setenv(rootEnvVar0, "a:2")
+ fl = flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError))
+ if err := fl.Parse([]string{}); err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ es = fl.EssentialFlags()
+ if got, want := es.NamespaceRoots, []string{"a:1", "a:2"}; !reflect.DeepEqual(got, want) {
+ t.Errorf("got %q, want %q", got, want)
+ }
+ if err := fl.Parse([]string{"--veyron.namespace.root=b:1", "--veyron.namespace.root=b:2", "--veyron.namespace.root=b:3"}); err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ es = fl.EssentialFlags()
+ if got, want := es.NamespaceRoots, []string{"b:1", "b:2", "b:3"}; !reflect.DeepEqual(got, want) {
+ t.Errorf("got %q, want %q", got, want)
+ }
}
diff --git a/lib/modules/core/echo.go b/lib/modules/core/echo.go
index 5fd3307..3f6ff42 100644
--- a/lib/modules/core/echo.go
+++ b/lib/modules/core/echo.go
@@ -38,11 +38,10 @@
}
func echoServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
- fl, err := ParseCommonFlags(args)
+ fl, args, err := parseListenFlags(args)
if err != nil {
return fmt.Errorf("failed parsing args: %s", err)
}
- args = fl.Args()
if err := checkArgs(args, 2, "<message> <name>"); err != nil {
return err
}
@@ -68,14 +67,7 @@
}
func echoClient(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
- fl, err := ParseCommonFlags(args)
- if err != nil {
- return fmt.Errorf("failed parsing args: %s", err)
- }
- args = fl.Args()
- if err := checkArgs(args, 2, "<name> <message>"); err != nil {
- return err
- }
+ args = args[1:]
name := args[0]
args = args[1:]
client := rt.R().Client()
diff --git a/lib/modules/core/mounttable.go b/lib/modules/core/mounttable.go
index 307dde3..9c4fec0 100644
--- a/lib/modules/core/mounttable.go
+++ b/lib/modules/core/mounttable.go
@@ -34,11 +34,11 @@
func runMT(root bool, stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
r := rt.R()
- fl, err := ParseCommonFlags(args)
+ fl, args, err := parseListenFlags(args)
if err != nil {
return fmt.Errorf("failed parsing args: %s", err)
}
- args = fl.Args()
+ // args = fl.Args()
lspec := initListenSpec(fl)
server, err := r.NewServer(options.ServesMountTable(true))
if err != nil {
diff --git a/lib/modules/core/proxy.go b/lib/modules/core/proxy.go
index 0e24fe1..7290543 100644
--- a/lib/modules/core/proxy.go
+++ b/lib/modules/core/proxy.go
@@ -18,11 +18,10 @@
}
func proxyServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
- fl, err := ParseCommonFlags(args)
+ fl, args, err := parseListenFlags(args)
if err != nil {
return fmt.Errorf("failed parsing args: %s", err)
- }
- args = fl.Args()
+ } // args = fl.Args()
if err := checkArgs(args, -1, ""); err != nil {
return err
}
@@ -31,10 +30,10 @@
if err != nil {
return err
}
-
+ lf := fl.ListenFlags()
// TODO(ashankar): Set the second argument to r.Principal() once the
// old security model is no longer operational.
- proxy, err := proxy.New(rid, nil, fl.ListenProtocolFlag.String(), fl.ListenAddressFlag.String(), "")
+ proxy, err := proxy.New(rid, nil, lf.ListenProtocol.String(), lf.ListenAddress.String(), "")
if err != nil {
return err
}
diff --git a/lib/modules/core/util.go b/lib/modules/core/util.go
index 507b7e3..81b6101 100644
--- a/lib/modules/core/util.go
+++ b/lib/modules/core/util.go
@@ -9,23 +9,22 @@
"veyron.io/veyron/veyron/lib/flags"
)
-// ParseCommonFlags parses the supplied args for the common set of flags
-// and environment variables defined in in the veyron/lib/flags package.
-func ParseCommonFlags(args []string) (*flags.Flags, error) {
+func parseListenFlags(args []string) (*flags.Flags, []string, error) {
fs := flag.NewFlagSet("modules/core", flag.ContinueOnError)
- fl := flags.New(fs)
+ fl := flags.CreateAndRegister(fs, flags.Listen)
if len(args) == 0 {
- return fl, fmt.Errorf("no args supplied")
+ return fl, []string{}, nil
}
err := fl.Parse(args[1:])
- return fl, err
+ return fl, fl.Args(), err
}
func initListenSpec(fl *flags.Flags) ipc.ListenSpec {
+ lf := fl.ListenFlags()
return ipc.ListenSpec{
- Protocol: fl.ListenProtocolFlag.String(),
- Address: fl.ListenAddressFlag.String(),
- Proxy: fl.ListenProxyFlag,
+ Protocol: lf.ListenProtocol.String(),
+ Address: lf.ListenAddress.String(),
+ Proxy: lf.ListenProxy,
}
}
diff --git a/profiles/roaming/roaming.go b/profiles/roaming/roaming.go
index 2f9381f..816b4a1 100644
--- a/profiles/roaming/roaming.go
+++ b/profiles/roaming/roaming.go
@@ -37,7 +37,7 @@
)
func init() {
- commonFlags = flags.New(flag.CommandLine)
+ commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Listen)
rt.RegisterProfile(New())
}
@@ -69,10 +69,11 @@
func (p *profile) Init(rt veyron2.Runtime, publisher *config.Publisher) error {
log := rt.Logger()
+ lf := commonFlags.ListenFlags()
ListenSpec = ipc.ListenSpec{
- Protocol: commonFlags.ListenProtocolFlag.Protocol,
- Address: commonFlags.ListenAddressFlag.String(),
- Proxy: commonFlags.ListenProxyFlag,
+ Protocol: lf.ListenProtocol.Protocol,
+ Address: lf.ListenAddress.String(),
+ Proxy: lf.ListenProxy,
}
// Our address is private, so we test for running on GCE and for its
diff --git a/profiles/static/static.go b/profiles/static/static.go
index 4997720..ba90318 100644
--- a/profiles/static/static.go
+++ b/profiles/static/static.go
@@ -25,7 +25,7 @@
)
func init() {
- commonFlags = flags.New(flag.CommandLine)
+ commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Listen)
rt.RegisterProfile(New())
}
@@ -55,10 +55,11 @@
func (p *static) Init(rt veyron2.Runtime, _ *config.Publisher) error {
log := rt.Logger()
+ lf := commonFlags.ListenFlags()
ListenSpec = ipc.ListenSpec{
- Protocol: commonFlags.ListenProtocolFlag.Protocol,
- Address: commonFlags.ListenAddressFlag.String(),
- Proxy: commonFlags.ListenProxyFlag,
+ Protocol: lf.ListenProtocol.Protocol,
+ Address: lf.ListenAddress.String(),
+ Proxy: lf.ListenProxy,
}
// Our address is private, so we test for running on GCE and for its