initial support for commands to use external binaries
License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>
Jeromy committed
Nov 11, 2015 at 12:47 UTC
7681b66fee82b4a420d02c4a40fc7d8daf1aff7e
3 files changed
+79
-1
commands/cli/parse.go
+7
@@ -204,6 +204,13 @@ func parseOpts(args []string, root *cmds.Command) (
204
if err != nil {
205
return
206
}
207
+
208
+ // If we've come across an external binary call, pass all the remaining
209
+ // arguments on to it
210
+ if cmd.External {
211
+ stringVals = append(stringVals, args[i+1:]...)
212
+ return
213
+ }
214
} else {
215
stringVals = append(stringVals, arg)
216
}
commands/command.go
+71
@@ -9,9 +9,12 @@ output to the user, including text, JSON, and XML marshallers.
9
package commands
10
11
import (
12
+ "bytes"
13
"errors"
14
"fmt"
15
"io"
16
+ "os"
17
+ "os/exec"
18
"reflect"
19
"strings"
20
@@ -59,6 +62,10 @@ type Command struct {
62
Marshalers map[EncodingType]Marshaler
63
Helptext HelpText
64
65
+ // External denotes that a command is actually an external binary.
66
+ // fewer checks and validations will be performed on such commands.
67
+ External bool
68
+
69
// Type describes the type of the output of the Command's Run Function.
70
// In precise terms, the value of Type is an instance of the return type of
71
// the Run Function.
@@ -262,3 +269,67 @@ func checkArgValue(v string, found bool, def Argument) error {
269
func ClientError(msg string) error {
270
return &Error{Code: ErrClient, Message: msg}
271
}
272
+
273
+func ExternalBinary() *Command {
274
+ return &Command{
275
+ Arguments: []Argument{
276
+ StringArg("args", false, true, "arguments for subcommand"),
277
+ },
278
+ External: true,
279
+ Run: func(req Request, res Response) {
280
+ binname := strings.Join(append([]string{"ipfs"}, req.Path()...), "-")
281
+ _, err := exec.LookPath(binname)
282
+ if err != nil {
283
+ // special case for '--help' on uninstalled binaries.
284
+ if req.Arguments()[0] == "--help" {
285
+ buf := new(bytes.Buffer)
286
+ fmt.Fprintf(buf, "%s is an 'external' command.\n", binname)
287
+ fmt.Fprintf(buf, "it does not currently appear to be installated.\n")
288
+ fmt.Fprintf(buf, "please refer to the ipfs documentation for instructions\n")
289
+ res.SetOutput(buf)
290
+ return
291
+ }
292
+
293
+ res.SetError(fmt.Errorf("%s not installed."), ErrNormal)
294
+ return
295
+ }
296
+
297
+ r, w := io.Pipe()
298
+
299
+ cmd := exec.Command(binname, req.Arguments()...)
300
+
301
+ // TODO: make commands lib be able to pass stdin through daemon
302
+ //cmd.Stdin = req.Stdin()
303
+ cmd.Stdin = io.LimitReader(nil, 0)
304
+ cmd.Stdout = w
305
+ cmd.Stderr = w
306
+
307
+ // setup env of child program
308
+ env := os.Environ()
309
+
310
+ nd, err := req.InvocContext().GetNode()
311
+ if err == nil {
312
+ env = append(env, fmt.Sprintf("IPFS_ONLINE=%t", nd.OnlineMode()))
313
+ }
314
+
315
+ cmd.Env = env
316
+
317
+ err = cmd.Start()
318
+ if err != nil {
319
+ res.SetError(fmt.Errorf("failed to start subcommand: %s", err), ErrNormal)
320
+ return
321
+ }
322
+
323
+ res.SetOutput(r)
324
+
325
+ go func() {
326
+ err = cmd.Wait()
327
+ if err != nil {
328
+ res.SetError(err, ErrNormal)
329
+ }
330
+
331
+ w.Close()
332
+ }()
333
+ },
334
+ }
335
+}
core/commands/root.go
+1
-1
@@ -111,7 +111,7 @@ var rootSubcommands = map[string]*cmds.Command{
111
"tar": TarCmd,
112
"tour": tourCmd,
113
"file": unixfs.UnixFSCmd,
114
- "update": UpdateCmd,
114
+ "update": cmds.ExternalBinary(),
115
"version": VersionCmd,
116
"bitswap": BitswapCmd,
117
}