veyron/lib/flags: add --veyron.acl flag

Change-Id: Ib58a9995d0450f33f6c0d5fac561c72edadd8233
diff --git a/lib/flags/flags.go b/lib/flags/flags.go
index b1313e1..b8a1473 100644
--- a/lib/flags/flags.go
+++ b/lib/flags/flags.go
@@ -24,9 +24,13 @@
 	// --veyron.tcp.address
 	// --veyron.proxy
 	Listen
+	// --veyron.acl (which may be repeated to supply multiple values)
+	ACL
 )
 
 const defaultNamespaceRoot = "/proxy.envyor.com:8101"
+const defaultACLName = "veyron"
+const defaultACLFile = "acl.json"
 
 // Flags represents the set of flag groups created by a call to
 // CreateAndRegister.
@@ -46,7 +50,7 @@
 
 func (nsr *namespaceRootFlagVar) Set(v string) error {
 	if !nsr.isSet {
-		// override the default value and
+		// override the default value
 		nsr.isSet = true
 		nsr.roots = []string{}
 	}
@@ -54,6 +58,30 @@
 	return nil
 }
 
+type aclFlagVar struct {
+	isSet bool
+	files map[string]string
+}
+
+func (aclf *aclFlagVar) String() string {
+	return fmt.Sprintf("%v", aclf.files)
+}
+
+func (aclf *aclFlagVar) Set(v string) error {
+	if !aclf.isSet {
+		// override the default value
+		aclf.isSet = true
+		aclf.files = make(map[string]string)
+	}
+	parts := strings.SplitN(v, ":", 2)
+	if len(parts) != 2 {
+		return fmt.Errorf("%q is not in 'name:file' format", v)
+	}
+	name, file := parts[0], parts[1]
+	aclf.files[name] = file
+	return nil
+}
+
 // RuntimeFlags contains the values of the Runtime flag group.
 type RuntimeFlags struct {
 	// NamespaceRoots may be initialized by NAMESPACE_ROOT* enivornment
@@ -68,6 +96,17 @@
 	namespaceRootsFlag namespaceRootFlagVar
 }
 
+// ACLFlags contains the values of the ACLFlags flag group.
+type ACLFlags struct {
+	flag aclFlagVar
+}
+
+// ACLFile returns the file which is presumed to contain ACL information
+// associated with the supplied name parameter.
+func (af ACLFlags) ACLFile(name string) string {
+	return af.flag.files[name]
+}
+
 // ListenFlags contains the values of the Listen flag group.
 type ListenFlags struct {
 	ListenProtocol TCPProtocolFlag
@@ -85,11 +124,19 @@
 	} else {
 		f.namespaceRootsFlag.roots = roots
 	}
+
 	fs.Var(&f.namespaceRootsFlag, "veyron.namespace.root", "local namespace root; can be repeated to provided multiple roots")
 	fs.StringVar(&f.Credentials, "veyron.credentials", creds, "directory to use for storing security credentials")
 	return f
 }
 
+func createAndRegisterACLFlags(fs *flag.FlagSet) *ACLFlags {
+	f := &ACLFlags{}
+	f.flag.files = map[string]string{defaultACLName: defaultACLFile}
+	fs.Var(&f.flag, "veyron.acl", "specify an acl file as <name>:<aclfile>")
+	return f
+}
+
 // createAndRegisterListenFlags creates and registers the ListenFlags
 // group with the supplied flag.FlagSet.
 func createAndRegisterListenFlags(fs *flag.FlagSet) *ListenFlags {
@@ -116,6 +163,8 @@
 			f.groups[Runtime] = createAndRegisterRuntimeFlags(fs)
 		case Listen:
 			f.groups[Listen] = createAndRegisterListenFlags(fs)
+		case ACL:
+			f.groups[ACL] = createAndRegisterACLFlags(fs)
 		}
 	}
 	return f
@@ -145,6 +194,17 @@
 	return ListenFlags{}
 }
 
+// ACLFlags returns a copy of the ACL flag group stored in Flags.
+// This copy will contain default values if the ACL 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) ACLFlags() ACLFlags {
+	if p := f.groups[ACL]; p != nil {
+		return *(p.(*ACLFlags))
+	}
+	return ACLFlags{}
+}
+
 // HasGroup returns group if the supplied FlagGroup has been created
 // for these Flags.
 func (f *Flags) HasGroup(group FlagGroup) bool {
diff --git a/lib/flags/flags_test.go b/lib/flags/flags_test.go
index c802942..ee9c32a 100644
--- a/lib/flags/flags_test.go
+++ b/lib/flags/flags_test.go
@@ -14,11 +14,11 @@
 func TestFlags(t *testing.T) {
 	fs := flag.NewFlagSet("test", flag.ContinueOnError)
 	if flags.CreateAndRegister(fs) != nil {
-		t.Errorf("should have failed")
+		t.Fatalf("should have returned a nil value")
 	}
 	fl := flags.CreateAndRegister(fs, flags.Runtime)
 	if fl == nil {
-		t.Fatalf("should have returned a non-nil value")
+		t.Errorf("should have failed")
 	}
 	creds := "creddir"
 	roots := []string{"ab:cd:ef"}
@@ -42,6 +42,26 @@
 	}
 }
 
+func TestACLFlags(t *testing.T) {
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	fl := flags.CreateAndRegister(fs, flags.Runtime, flags.ACL)
+	args := []string{"--veyron.acl=veyron:foo.json", "--veyron.acl=bar:bar.json", "--veyron.acl=baz:bar:baz.json"}
+	fl.Parse(args)
+	aclf := fl.ACLFlags()
+	if got, want := aclf.ACLFile("veyron"), "foo.json"; got != want {
+		t.Errorf("got %t, want %t", got, want)
+	}
+	if got, want := aclf.ACLFile("bar"), "bar.json"; got != want {
+		t.Errorf("got %t, want %t", got, want)
+	}
+	if got, want := aclf.ACLFile("wombat"), ""; got != want {
+		t.Errorf("got %t, want %t", got, want)
+	}
+	if got, want := aclf.ACLFile("baz"), "bar:baz.json"; got != want {
+		t.Errorf("got %t, want %t", got, want)
+	}
+}
+
 func TestFlagError(t *testing.T) {
 	fs := flag.NewFlagSet("test", flag.ContinueOnError)
 	fs.SetOutput(ioutil.Discard)
@@ -55,6 +75,15 @@
 	if got, want := len(fl.Args()), 1; got != want {
 		t.Errorf("got %d, want %d [args: %v]", got, want, fl.Args())
 	}
+
+	fs = flag.NewFlagSet("test", flag.ContinueOnError)
+	fs.SetOutput(ioutil.Discard)
+	fl = flags.CreateAndRegister(fs, flags.ACL)
+	args = []string{"--veyron.acl=noname"}
+	err = fl.Parse(args)
+	if err == nil {
+		t.Fatalf("expected this to fail!")
+	}
 }
 
 func TestFlagsGroups(t *testing.T) {
@@ -138,7 +167,7 @@
 	os.Setenv(rootEnvVar, "")
 	os.Setenv(rootEnvVar0, "")
 
-	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime)
+	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime, flags.ACL)
 	if err := fl.Parse([]string{}); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
@@ -146,4 +175,8 @@
 	if got, want := rtf.NamespaceRoots, []string{"/proxy.envyor.com:8101"}; !reflect.DeepEqual(got, want) {
 		t.Errorf("got %q, want %q", got, want)
 	}
+	aclf := fl.ACLFlags()
+	if got, want := aclf.ACLFile("veyron"), "acl.json"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
 }