blob: 66d7b05f2d0cedbde6d912c700dd1790c2b9bcaf [file] [log] [blame]
Todd Wang06eeab02015-01-20 11:34:14 -08001// The following enables go generate to generate the doc.go file.
2//go:generate go run $VANADIUM_ROOT/release/go/src/v.io/lib/cmdline/testdata/gendoc.go .
3
4package main
5
6import (
7 "fmt"
8 "io"
Matt Rosencrantz92303e12015-01-21 09:02:42 -08009 "os"
Todd Wang06eeab02015-01-20 11:34:14 -080010 "strings"
11 "time"
12
13 "v.io/core/veyron2"
14 "v.io/core/veyron2/context"
15 "v.io/core/veyron2/ipc/reserved"
16 "v.io/core/veyron2/rt"
17 "v.io/core/veyron2/vdl"
18 "v.io/core/veyron2/vdl/build"
19 "v.io/core/veyron2/vdl/codegen/vdlgen"
20 "v.io/core/veyron2/vdl/compile"
21 "v.io/core/veyron2/vdl/vdlroot/src/signature"
22 "v.io/lib/cmdline"
23
24 _ "v.io/core/veyron/profiles"
25)
26
27var runtime veyron2.Runtime
28
29func main() {
30 var err error
31 runtime, err = rt.New()
32 if err != nil {
33 panic(err)
34 }
Matt Rosencrantz92303e12015-01-21 09:02:42 -080035 exitCode := cmdVRPC.Main()
36 runtime.Cleanup()
37 os.Exit(exitCode)
Todd Wang06eeab02015-01-20 11:34:14 -080038}
39
40var cmdVRPC = &cmdline.Command{
41 Name: "vrpc",
42 Short: "Vanadium Remote Procedure Call tool",
43 Long: `
44The vrpc tool provides command-line access to Vanadium servers via Remote
45Procedure Call.
46`,
47 // TODO(toddw): Add cmdServe, which will take an interface as input, and set
48 // up a server capable of handling the given methods. When a request is
49 // received, it'll allow the user to respond via stdin.
50 Children: []*cmdline.Command{cmdSignature, cmdCall, cmdIdentify},
51}
52
53const serverDesc = `
54<server> identifies a Vanadium server. It can either be the object address of
55the server, or an object name that will be resolved to an end-point.
56`
57
58var cmdSignature = &cmdline.Command{
59 Run: runSignature,
60 Name: "signature",
61 Short: "Describe the interfaces of a Vanadium server",
62 Long: `
63Signature connects to the Vanadium server identified by <server>.
64
65If no [method] is provided, returns all interfaces implemented by the server.
66
67If a [method] is provided, returns the signature of just that method.
68`,
69 ArgsName: "<server> [method]",
70 ArgsLong: serverDesc + `
71[method] is the optional server method name.
72`,
73}
74
75var cmdCall = &cmdline.Command{
76 Run: runCall,
77 Name: "call",
78 Short: "Call a method of a Vanadium server",
79 Long: `
80Call connects to the Vanadium server identified by <server> and calls the
81<method> with the given positional [args...], returning results on stdout.
82
83TODO(toddw): stdin is read for streaming arguments sent to the server. An EOF
84on stdin (e.g. via ^D) causes the send stream to be closed.
85
86Regardless of whether the call is streaming, the main goroutine blocks for
87streaming and positional results received from the server.
88
89All input arguments (both positional and streaming) are specified as VDL
90expressions, with commas separating multiple expressions. Positional arguments
91may also be specified as separate command-line arguments. Streaming arguments
92may also be specified as separate newline-terminated expressions.
93
94The method signature is always retrieved from the server as a first step. This
95makes it easier to input complex typed arguments, since the top-level type for
96each argument is implicit and doesn't need to be specified.
97`,
98 ArgsName: "<server> <method> [args...]",
99 ArgsLong: serverDesc + `
100<method> is the server method to call.
101
102[args...] are the positional input arguments, specified as VDL expressions.
103`,
104}
105
106var cmdIdentify = &cmdline.Command{
107 Run: runIdentify,
108 Name: "identify",
109 Short: "Reveal blessings presented by a Vanadium server",
110 Long: `
111Identify connects to the Vanadium server identified by <server> and dumps out
112the blessings presented by that server (and the subset of those that are
113considered valid by the principal running this tool) to standard output.
114`,
115 ArgsName: "<server>",
116 ArgsLong: serverDesc,
117}
118
119func runSignature(cmd *cmdline.Command, args []string) error {
120 // Error-check args.
121 var server, method string
122 switch len(args) {
123 case 1:
124 server = args[0]
125 case 2:
126 server, method = args[0], args[1]
127 default:
128 return cmd.UsageErrorf("wrong number of arguments")
129 }
130 // Get the interface or method signature, and pretty-print. We print the
131 // named types after the signatures, to aid in readability.
132 ctx, cancel := context.WithTimeout(runtime.NewContext(), time.Minute)
133 defer cancel()
134 var types signature.NamedTypes
135 if method != "" {
136 methodSig, err := reserved.MethodSignature(ctx, server, method)
137 if err != nil {
138 return fmt.Errorf("MethodSignature failed: %v", err)
139 }
140 methodSig.Print(cmd.Stdout(), &types)
141 fmt.Fprintln(cmd.Stdout())
142 types.Print(cmd.Stdout())
143 return nil
144 }
145 ifacesSig, err := reserved.Signature(ctx, server)
146 if err != nil {
147 return fmt.Errorf("Signature failed: %v", err)
148 }
149 for i, iface := range ifacesSig {
150 if i > 0 {
151 fmt.Fprintln(cmd.Stdout())
152 }
153 iface.Print(cmd.Stdout(), &types)
154 fmt.Fprintln(cmd.Stdout())
155 }
156 types.Print(cmd.Stdout())
157 return nil
158}
159
160func runCall(cmd *cmdline.Command, args []string) error {
161 // Error-check args, and set up argsdata with a comma-separated list of
162 // arguments, allowing each individual arg to already be comma-separated.
163 //
164 // TODO(toddw): Should we just space-separate the args instead?
165 if len(args) < 2 {
166 return cmd.UsageErrorf("must specify <server> and <method>")
167 }
168 server, method := args[0], args[1]
169 var argsdata string
170 for _, arg := range args[2:] {
171 arg := strings.TrimSpace(arg)
172 if argsdata == "" || strings.HasSuffix(argsdata, ",") || strings.HasPrefix(arg, ",") {
173 argsdata += arg
174 } else {
175 argsdata += "," + arg
176 }
177 }
178 // Get the method signature and parse args.
179 ctx, cancel := context.WithTimeout(runtime.NewContext(), time.Minute)
180 defer cancel()
181 methodSig, err := reserved.MethodSignature(ctx, server, method)
182 if err != nil {
183 return fmt.Errorf("MethodSignature failed: %v", err)
184 }
185 inargs, err := parseInArgs(argsdata, methodSig)
186 if err != nil {
187 // TODO: Print signature and example.
188 return err
189 }
190 // Start the method call.
191 call, err := veyron2.GetClient(ctx).StartCall(ctx, server, method, inargs)
192 if err != nil {
193 return fmt.Errorf("StartCall failed: %v", err)
194 }
195 // TODO(toddw): Fire off a goroutine to handle streaming inputs.
196 // Handle streaming results.
197StreamingResultsLoop:
198 for {
199 var item *vdl.Value
200 switch err := call.Recv(&item); {
201 case err == io.EOF:
202 break StreamingResultsLoop
203 case err != nil:
204 return fmt.Errorf("call.Recv failed: %v", err)
205 }
206 fmt.Fprintf(cmd.Stdout(), "<< %v\n", vdlgen.TypedConst(item, "", nil))
207 }
208 // Finish the method call.
209 outargs := make([]*vdl.Value, len(methodSig.OutArgs))
210 outptrs := make([]interface{}, len(outargs))
211 for i := range outargs {
212 outptrs[i] = &outargs[i]
213 }
214 if err := call.Finish(outptrs...); err != nil {
215 return fmt.Errorf("call.Finish failed: %v", err)
216 }
217 // Handle application errors, reported as a final error out-arg.
218 //
219 // TODO(toddw): Change call.Finish to report the error directly.
220 outlen := len(outargs)
221 if outlen > 0 && methodSig.OutArgs[outlen-1].Type == vdl.ErrorType {
222 if errarg := outargs[outlen-1]; !errarg.IsNil() {
223 return fmt.Errorf(vdlgen.TypedConst(errarg, "", nil))
224 }
225 outargs = outargs[:outlen-1]
226 }
227 // Pretty-print results.
228 for i, arg := range outargs {
229 if i > 0 {
230 fmt.Fprint(cmd.Stdout(), " ")
231 }
232 fmt.Fprint(cmd.Stdout(), vdlgen.TypedConst(arg, "", nil))
233 }
234 fmt.Fprintln(cmd.Stdout())
235 return nil
236}
237
238func parseInArgs(argsdata string, methodSig signature.Method) ([]interface{}, error) {
239 if len(methodSig.InArgs) == 0 {
240 return nil, nil
241 }
242 var intypes []*vdl.Type
243 for _, inarg := range methodSig.InArgs {
244 intypes = append(intypes, inarg.Type)
245 }
246 env := compile.NewEnv(-1)
247 inargs := build.BuildExprs(argsdata, intypes, env)
248 if err := env.Errors.ToError(); err != nil {
249 return nil, fmt.Errorf("can't parse in-args:\n%v", err)
250 }
251 if got, want := len(inargs), len(methodSig.InArgs); got != want {
252 return nil, fmt.Errorf("got %d args, want %d", got, want)
253 }
254 // Translate []*vdl.Value to []interface, with each item still *vdl.Value.
255 var ret []interface{}
256 for _, arg := range inargs {
257 ret = append(ret, arg)
258 }
259 return ret, nil
260}
261
262func runIdentify(cmd *cmdline.Command, args []string) error {
263 if len(args) != 1 {
264 return cmd.UsageErrorf("wrong number of arguments")
265 }
266 server := args[0]
267 ctx, cancel := context.WithTimeout(runtime.NewContext(), time.Minute)
268 defer cancel()
269 // The method name does not matter - only interested in authentication,
270 // not in actually making an RPC.
271 call, err := veyron2.GetClient(ctx).StartCall(ctx, server, "", nil)
272 if err != nil {
273 return fmt.Errorf(`client.StartCall(%q, "", nil) failed with %v`, server, err)
274 }
275 valid, presented := call.RemoteBlessings()
276 fmt.Fprintf(cmd.Stdout(), "PRESENTED: %v\nVALID: %v\n", presented, valid)
277 return nil
278}