veyron/tools/application: add manual entry of envelope

When adding (i.e. "putting") a new envelope, allow the user to manually
enter the data if the file argument is not present.

Change-Id: I418795a6114fd3dfa0a8f8e382622d02814564c9
diff --git a/tools/application/impl.go b/tools/application/impl.go
index 026f2c3..ef0bf9e 100644
--- a/tools/application/impl.go
+++ b/tools/application/impl.go
@@ -17,7 +17,9 @@
 	"v.io/lib/cmdline"
 )
 
-func getEnvelopeJSON(ctx *context.T, app repository.ApplicationClientMethods, profiles string) ([]byte, error) {
+func getEnvelopeJSON(app repository.ApplicationClientMethods, profiles string) ([]byte, error) {
+	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	defer cancel()
 	env, err := app.Match(ctx, strings.Split(profiles, ","))
 	if err != nil {
 		return nil, err
@@ -29,11 +31,13 @@
 	return j, nil
 }
 
-func putEnvelopeJSON(ctx *context.T, app repository.ApplicationClientMethods, profiles string, j []byte) error {
+func putEnvelopeJSON(app repository.ApplicationClientMethods, profiles string, j []byte) error {
 	var env application.Envelope
 	if err := json.Unmarshal(j, &env); err != nil {
 		return fmt.Errorf("Unmarshal(%v) failed: %v", string(j), err)
 	}
+	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	defer cancel()
 	if err := app.Put(ctx, strings.Split(profiles, ","), env); err != nil {
 		return err
 	}
@@ -66,9 +70,7 @@
 	}
 	name, profiles := args[0], args[1]
 	app := repository.ApplicationClient(name)
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
-	defer cancel()
-	j, err := getEnvelopeJSON(ctx, app, profiles)
+	j, err := getEnvelopeJSON(app, profiles)
 	if err != nil {
 		return err
 	}
@@ -81,29 +83,40 @@
 	Name:     "put",
 	Short:    "Add the given envelope to the application for the given profiles.",
 	Long:     "Add the given envelope to the application for the given profiles.",
-	ArgsName: "<application> <profiles> <envelope>",
+	ArgsName: "<application> <profiles> [<envelope>]",
 	ArgsLong: `
 <application> is the full name of the application.
 <profiles> is a comma-separated list of profiles.
-<envelope> is the file that contains a JSON-encoded envelope.`,
+<envelope> is the file that contains a JSON-encoded envelope. If this file is
+not provided, the user will be prompted to enter the data manually.`,
 }
 
 func runPut(cmd *cmdline.Command, args []string) error {
-	if expected, got := 3, len(args); expected != got {
-		return cmd.UsageErrorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
+	if got := len(args); got != 2 && got != 3 {
+		return cmd.UsageErrorf("put: incorrect number of arguments, expected 2 or 3, got %d", got)
 	}
-	name, profiles, envelope := args[0], args[1], args[2]
+	name, profiles := args[0], args[1]
 	app := repository.ApplicationClient(name)
-	j, err := ioutil.ReadFile(envelope)
-	if err != nil {
-		return fmt.Errorf("ReadFile(%v): %v", envelope, err)
+	if len(args) == 3 {
+		envelope := args[2]
+		j, err := ioutil.ReadFile(envelope)
+		if err != nil {
+			return fmt.Errorf("ReadFile(%v): %v", envelope, err)
+		}
+		if err = putEnvelopeJSON(app, profiles, j); err != nil {
+			return err
+		}
+		fmt.Fprintln(cmd.Stdout(), "Application envelope added successfully.")
+		return nil
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
-	defer cancel()
-	if err = putEnvelopeJSON(ctx, app, profiles, j); err != nil {
+	env := application.Envelope{Args: []string{}, Env: []string{}, Packages: map[string]string{}}
+	j, err := json.MarshalIndent(env, "", "  ")
+	if err != nil {
+		return fmt.Errorf("MarshalIndent() failed: %v", err)
+	}
+	if err := editAndPutEnvelopeJSON(cmd, app, profiles, j); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Application envelope added successfully.")
 	return nil
 }
 
@@ -150,6 +163,18 @@
 	}
 	name, profile := args[0], args[1]
 	app := repository.ApplicationClient(name)
+
+	envData, err := getEnvelopeJSON(app, profile)
+	if err != nil {
+		return err
+	}
+	if err := editAndPutEnvelopeJSON(cmd, app, profile, envData); err != nil {
+		return err
+	}
+	return nil
+}
+
+func editAndPutEnvelopeJSON(cmd *cmdline.Command, app repository.ApplicationClientMethods, profile string, envData []byte) error {
 	f, err := ioutil.TempFile("", "application-edit-")
 	if err != nil {
 		return fmt.Errorf("TempFile() failed: %v", err)
@@ -157,13 +182,6 @@
 	fileName := f.Name()
 	f.Close()
 	defer os.Remove(fileName)
-
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
-	defer cancel()
-	envData, err := getEnvelopeJSON(ctx, app, profile)
-	if err != nil {
-		return err
-	}
 	if err = ioutil.WriteFile(fileName, envData, os.FileMode(0644)); err != nil {
 		return err
 	}
@@ -191,7 +209,7 @@
 			fmt.Fprintln(cmd.Stdout(), "Nothing changed")
 			return nil
 		}
-		if err = putEnvelopeJSON(ctx, app, profile, newData); err != nil {
+		if err = putEnvelopeJSON(app, profile, newData); err != nil {
 			fmt.Fprintf(cmd.Stdout(), "Error: %v\n", err)
 			if ans := promptUser(cmd, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" {
 				continue