Merge "ref/services/groups: a command-line client for interacting with the group server"
diff --git a/services/groups/groups/doc.go b/services/groups/groups/doc.go
new file mode 100644
index 0000000..fec9b80
--- /dev/null
+++ b/services/groups/groups/doc.go
@@ -0,0 +1,140 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+Command groups creates and manages Vanadium groups of blessing patterns.
+
+Usage:
+ groups <command>
+
+The groups commands are:
+ create Creates a blessing pattern group
+ delete Delete a blessing group
+ add Adds a blessing pattern to a group
+ remove Removes a blessing pattern from a group
+ relate Relate a set of blessing to a group
+ get Returns entries of a group
+ help Display help for commands or topics
+
+The global flags are:
+ -v23.metadata=<just specify -v23.metadata to activate>
+ Displays metadata for the program and exits.
+
+Groups create - Creates a blessing pattern group
+
+Creates a blessing pattern group.
+
+Usage:
+ groups create [flags] <von> <patterns...>
+
+<von> is the vanadium object name of the group to create
+
+<patterns...> is a list of blessing pattern chunks
+
+The groups create flags are:
+ -permissions=
+ Path to a permissions file
+
+Groups delete - Delete a blessing group
+
+Delete a blessing group.
+
+Usage:
+ groups delete [flags] <von>
+
+<von> is the vanadium object name of the group
+
+The groups delete flags are:
+ -version=
+ Identifies group version
+
+Groups add - Adds a blessing pattern to a group
+
+Adds a blessing pattern to a group.
+
+Usage:
+ groups add [flags] <von> <pattern>
+
+<von> is the vanadium object name of the group
+
+<pattern> is the blessing pattern chunk to add
+
+The groups add flags are:
+ -version=
+ Identifies group version
+
+Groups remove - Removes a blessing pattern from a group
+
+Removes a blessing pattern from a group.
+
+Usage:
+ groups remove [flags] <von> <pattern>
+
+<von> is the vanadium object name of the group
+
+<pattern> is the blessing pattern chunk to add
+
+The groups remove flags are:
+ -version=
+ Identifies group version
+
+Groups relate - Relate a set of blessing to a group
+
+Relate a set of blessing to a group.
+
+NOTE: This command exists primarily for debugging purposes. In particular,
+invocations of the Relate RPC are expected to be mainly issued by the
+authorization logic.
+
+Usage:
+ groups relate [flags] <von> <blessings>
+
+<von> is the vanadium object name of the group
+
+<blessings...> is a list of blessings
+
+The groups relate flags are:
+ -approximation=under
+ Identifies the type of approximation to use; supported values = (under, over)
+ -version=
+ Identifies group version
+
+Groups get - Returns entries of a group
+
+Returns entries of a group.
+
+Usage:
+ groups get <von>
+
+<von> is the vanadium object name of the group
+
+Groups help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+ groups help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The groups help flags are:
+ -style=compact
+ The formatting style for help output:
+ compact - Good for compact cmdline output.
+ full - Good for cmdline output, shows all global flags.
+ godoc - Good for godoc processing.
+ Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+ Format output to this target width in runes, or unlimited if width < 0.
+ Defaults to the terminal width if available. Override the default by setting
+ the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/groups/groups/main.go b/services/groups/groups/main.go
new file mode 100644
index 0000000..e15b7dc
--- /dev/null
+++ b/services/groups/groups/main.go
@@ -0,0 +1,206 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "v.io/v23/context"
+ "v.io/v23/security/access"
+ "v.io/v23/services/groups"
+ "v.io/x/lib/cmdline"
+ "v.io/x/ref/lib/v23cmd"
+)
+
+var (
+ flagPermFile string
+ flagVersion string
+ flagApproximation string
+
+ cmdCreate = &cmdline.Command{
+ Name: "create",
+ Short: "Creates a blessing pattern group",
+ Long: "Creates a blessing pattern group.",
+ ArgsName: "<von> <patterns...>",
+ ArgsLong: `
+<von> is the vanadium object name of the group to create
+
+<patterns...> is a list of blessing pattern chunks
+`,
+ Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+ // Process command-line arguments.
+ if want, got := 1, len(args); want > got {
+ return env.UsageErrorf("create: unexpected number of arguments, want at least %d, got %d", want, got)
+ }
+ von := args[0]
+ var patterns []groups.BlessingPatternChunk
+ for _, pattern := range args[1:] {
+ patterns = append(patterns, groups.BlessingPatternChunk(pattern))
+ }
+ // Process command-line flags.
+ var permissions access.Permissions
+ if flagPermFile != "" {
+ file, err := os.Open(flagPermFile)
+ if err != nil {
+ return fmt.Errorf("Open(%v) failed: %v", flagPermFile, err)
+ }
+ permissions, err = access.ReadPermissions(file)
+ if err != nil {
+ return err
+ }
+ }
+ // Invoke the "create" RPC.
+ client := groups.GroupClient(von)
+ return client.Create(ctx, permissions, patterns)
+ }),
+ }
+
+ cmdDelete = &cmdline.Command{
+ Name: "delete",
+ Short: "Delete a blessing group",
+ Long: "Delete a blessing group.",
+ ArgsName: "<von>",
+ ArgsLong: "<von> is the vanadium object name of the group",
+ Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+ // Process command-line arguments.
+ if want, got := 1, len(args); want != got {
+ return env.UsageErrorf("delete: unexpected number of arguments, want %d, got %d", want, got)
+ }
+ von := args[0]
+ // Invoke the "delete" RPC.
+ client := groups.GroupClient(von)
+ return client.Delete(ctx, flagVersion)
+ }),
+ }
+
+ cmdAdd = &cmdline.Command{
+ Name: "add",
+ Short: "Adds a blessing pattern to a group",
+ Long: "Adds a blessing pattern to a group.",
+ ArgsName: "<von> <pattern>",
+ ArgsLong: `
+<von> is the vanadium object name of the group
+
+<pattern> is the blessing pattern chunk to add
+`,
+ Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+ // Process command-line arguments.
+ if want, got := 2, len(args); want != got {
+ return env.UsageErrorf("add: unexpected number of arguments, want %d, got %d", want, got)
+ }
+ von, pattern := args[0], args[1]
+ // Invoke the "add" RPC.
+ client := groups.GroupClient(von)
+ return client.Add(ctx, groups.BlessingPatternChunk(pattern), flagVersion)
+ }),
+ }
+
+ cmdRemove = &cmdline.Command{
+ Name: "remove",
+ Short: "Removes a blessing pattern from a group",
+ Long: "Removes a blessing pattern from a group.",
+ ArgsName: "<von> <pattern>",
+ ArgsLong: `
+<von> is the vanadium object name of the group
+
+<pattern> is the blessing pattern chunk to add
+`,
+ Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+ // Process command-line arguments.
+ if want, got := 2, len(args); want != got {
+ return env.UsageErrorf("remove: unexpected number of arguments, want %d, got %d", want, got)
+ }
+ von, pattern := args[0], args[1]
+ // Invoke the "remove" RPC.
+ client := groups.GroupClient(von)
+ return client.Remove(ctx, groups.BlessingPatternChunk(pattern), flagVersion)
+ }),
+ }
+
+ cmdRelate = &cmdline.Command{
+ Name: "relate",
+ Short: "Relate a set of blessing to a group",
+ Long: `
+Relate a set of blessing to a group.
+
+NOTE: This command exists primarily for debugging purposes. In
+particular, invocations of the Relate RPC are expected to be mainly
+issued by the authorization logic.
+`,
+ ArgsName: "<von> <blessings>",
+ ArgsLong: `
+<von> is the vanadium object name of the group
+
+<blessings...> is a list of blessings
+`,
+ Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+ // Process command-line arguments.
+ if want, got := 1, len(args); want > got {
+ return env.UsageErrorf("relate: unexpected number of arguments, want at least %d, got %d", want, got)
+ }
+ von := args[0]
+ blessings := make(map[string]struct{}, len(args[1:]))
+ for _, blessing := range args[1:] {
+ blessings[blessing] = struct{}{}
+ }
+ // Process command-line flags.
+ var hint groups.ApproximationType
+ switch flagApproximation {
+ case "under":
+ hint = groups.ApproximationTypeUnder
+ case "over":
+ hint = groups.ApproximationTypeOver
+ }
+ // Invoke the "relate" RPC.
+ client := groups.GroupClient(von)
+ remainder, approximations, version, err := client.Relate(ctx, blessings, hint, flagVersion, nil)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(env.Stdout, "remainder = %v, approximations = %v, version = %v\n", remainder, approximations, version)
+ return nil
+ }),
+ }
+
+ cmdGet = &cmdline.Command{
+ Name: "get",
+ Short: "Returns entries of a group",
+ Long: "Returns entries of a group.",
+ ArgsName: "<von>",
+ ArgsLong: "<von> is the vanadium object name of the group",
+ Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+ // TODO(jsimsa): Implement once the corresponding server-side
+ // API is designed.
+ return fmt.Errorf(`the "groups get ..." sub-command is not implemented`)
+ }),
+ }
+
+ cmdRoot = &cmdline.Command{
+ Name: "groups",
+ Short: "creates and manages Vanadium groups of blessing patterns",
+ Long: "Command groups creates and manages Vanadium groups of blessing patterns.",
+ Children: []*cmdline.Command{cmdCreate, cmdDelete, cmdAdd, cmdRemove, cmdRelate, cmdGet},
+ }
+)
+
+func init() {
+ cmdCreate.Flags.StringVar(&flagPermFile, "permissions", "", "Path to a permissions file")
+ cmdDelete.Flags.StringVar(&flagVersion, "version", "", "Identifies group version")
+ cmdAdd.Flags.StringVar(&flagVersion, "version", "", "Identifies group version")
+ cmdRemove.Flags.StringVar(&flagVersion, "version", "", "Identifies group version")
+ cmdRelate.Flags.StringVar(&flagVersion, "version", "", "Identifies group version")
+ cmdRelate.Flags.StringVar(&flagApproximation, "approximation", "under",
+ "Identifies the type of approximation to use; supported values = (under, over)",
+ )
+}
+
+func main() {
+ cmdline.HideGlobalFlagsExcept()
+ cmdline.Main(cmdRoot)
+}
diff --git a/services/groups/groups/main_test.go b/services/groups/groups/main_test.go
new file mode 100644
index 0000000..f36de00
--- /dev/null
+++ b/services/groups/groups/main_test.go
@@ -0,0 +1,216 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "testing"
+ "unicode"
+ "unicode/utf8"
+
+ "v.io/v23"
+ "v.io/v23/context"
+ "v.io/v23/naming"
+ "v.io/v23/rpc"
+ "v.io/v23/security/access"
+ "v.io/v23/services/groups"
+ "v.io/x/lib/cmdline"
+ "v.io/x/ref/lib/v23cmd"
+ _ "v.io/x/ref/runtime/factories/generic"
+ "v.io/x/ref/test"
+)
+
+var group map[string]struct{}
+var buffer bytes.Buffer
+
+type mock struct{}
+
+func (mock) Create(ctx *context.T, call rpc.ServerCall, perms access.Permissions, entries []groups.BlessingPatternChunk) error {
+ fmt.Fprintf(&buffer, "Create(%v, %v) was called", perms, entries)
+ group = make(map[string]struct{}, len(entries))
+ for _, entry := range entries {
+ group[string(entry)] = struct{}{}
+ }
+ return nil
+}
+
+func (mock) Delete(ctx *context.T, call rpc.ServerCall, version string) error {
+ fmt.Fprintf(&buffer, "Delete(%v) was called", version)
+ group = nil
+ return nil
+}
+
+func (mock) Add(ctx *context.T, call rpc.ServerCall, entry groups.BlessingPatternChunk, version string) error {
+ fmt.Fprintf(&buffer, "Add(%v, %v) was called", entry, version)
+ group[string(entry)] = struct{}{}
+ return nil
+}
+
+func (mock) Remove(ctx *context.T, call rpc.ServerCall, entry groups.BlessingPatternChunk, version string) error {
+ fmt.Fprintf(&buffer, "Remove(%v, %v) was called", entry, version)
+ delete(group, string(entry))
+ return nil
+}
+
+func (mock) Relate(ctx *context.T, call rpc.ServerCall, blessings map[string]struct{}, hint groups.ApproximationType, version string, visitedGroups map[string]struct{}) (map[string]struct{}, []groups.Approximation, string, error) {
+ fmt.Fprintf(&buffer, "Relate(%v, %v, %v, %v) was called", blessings, hint, version, visitedGroups)
+ return nil, nil, "123", nil
+}
+
+func (mock) Get(ctx *context.T, call rpc.ServerCall, req groups.GetRequest, version string) (groups.GetResponse, string, error) {
+ return groups.GetResponse{}, "", nil
+}
+
+func (mock) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
+ return nil
+}
+
+func (mock) GetPermissions(ctx *context.T, call rpc.ServerCall) (access.Permissions, string, error) {
+ return nil, "", nil
+}
+
+func capitalize(s string) string {
+ if s == "" {
+ return ""
+ }
+ rune, size := utf8.DecodeRuneInString(s)
+ return string(unicode.ToUpper(rune)) + s[size:]
+}
+
+func startServer(ctx *context.T, t *testing.T) (rpc.Server, naming.Endpoint) {
+ server, err := v23.NewServer(ctx)
+ if err != nil {
+ t.Fatalf("NewServer failed: %v", err)
+ }
+ l := v23.GetListenSpec(ctx)
+ endpoints, err := server.Listen(l)
+ if err != nil {
+ t.Fatalf("Listen(%s) failed: %v", l, err)
+ }
+ unpublished := ""
+ if err := server.Serve(unpublished, groups.GroupServer(&mock{}), nil); err != nil {
+ t.Fatalf("Serve(%v) failed: %v", unpublished, err)
+ }
+ return server, endpoints[0]
+}
+
+func stopServer(t *testing.T, server rpc.Server) {
+ if err := server.Stop(); err != nil {
+ t.Errorf("Stop() failed: %v", err)
+ }
+}
+
+func TestGroupClient(t *testing.T) {
+ ctx, shutdown := test.V23Init()
+ defer shutdown()
+
+ server, endpoint := startServer(ctx, t)
+ defer stopServer(t, server)
+
+ // Test the "create" command.
+ {
+ var stdout, stderr bytes.Buffer
+ env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+ patterns := []string{"alice", "bob"}
+ args := append([]string{"create", naming.JoinAddressName(endpoint.String(), "")}, patterns...)
+ if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+ t.Fatalf("run failed: %v\n%v", err, stderr.String())
+ }
+ if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+ if got, want := buffer.String(), fmt.Sprintf("Create(%v, %v) was called", access.Permissions{}, patterns); got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := len(group), 2; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ buffer.Reset()
+ }
+
+ // Test the "add" command.
+ {
+ var stdout, stderr bytes.Buffer
+ env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+ pattern, version := "charlie", "123"
+ args := []string{"add", "-version=" + version, naming.JoinAddressName(endpoint.String(), ""), pattern}
+ if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+ t.Fatalf("run failed: %v\n%v", err, stderr.String())
+ }
+ if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+ if got, want := buffer.String(), fmt.Sprintf("Add(%v, %v) was called", pattern, version); got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := len(group), 3; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ buffer.Reset()
+ }
+
+ // Test the "remove" command.
+ {
+ var stdout, stderr bytes.Buffer
+ env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+ pattern, version := "bob", "123"
+ args := []string{"remove", "-version=" + version, naming.JoinAddressName(endpoint.String(), ""), "bob"}
+ if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+ t.Fatalf("run failed: %v\n%v", err, stderr.String())
+ }
+ if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+ if got, want := buffer.String(), fmt.Sprintf("Remove(%v, %v) was called", pattern, version); got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := len(group), 2; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ buffer.Reset()
+ }
+
+ // Test the "delete" command.
+ {
+ var stdout, stderr bytes.Buffer
+ env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+ version := "123"
+ args := []string{"delete", "-version=" + version, naming.JoinAddressName(endpoint.String(), "")}
+ if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+ t.Fatalf("run failed: %v\n%v", err, stderr.String())
+ }
+ if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+ if got, want := buffer.String(), fmt.Sprintf("Delete(%v) was called", version); got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ if got, want := len(group), 0; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ buffer.Reset()
+ }
+
+ // Test the "relate" command.
+ {
+ var stdout, stderr bytes.Buffer
+ env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+ hint, version := "over", "123"
+ args := []string{"relate", "-approximation=" + hint, "-version=" + version, naming.JoinAddressName(endpoint.String(), "")}
+ if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+ t.Fatalf("run failed: %v\n%v", err, stderr.String())
+ }
+ if got, want := strings.TrimSpace(stdout.String()), "remainder = map[], approximations = [], version = 123"; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+ empty := map[string]struct{}{}
+ if got, want := buffer.String(), fmt.Sprintf("Relate(%v, %v, %v, %v) was called", empty, capitalize(hint), version, empty); got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ buffer.Reset()
+ }
+}