Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 1 | package impl |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "fmt" |
| 8 | "io/ioutil" |
| 9 | "os" |
| 10 | "os/exec" |
| 11 | "strings" |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 12 | "time" |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 13 | |
Jiri Simsa | 519c507 | 2014-09-17 21:37:57 -0700 | [diff] [blame] | 14 | "veyron.io/veyron/veyron/lib/cmdline" |
| 15 | "veyron.io/veyron/veyron/services/mgmt/repository" |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 16 | |
Jiri Simsa | 519c507 | 2014-09-17 21:37:57 -0700 | [diff] [blame] | 17 | "veyron.io/veyron/veyron2/context" |
| 18 | "veyron.io/veyron/veyron2/rt" |
| 19 | "veyron.io/veyron/veyron2/services/mgmt/application" |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 20 | ) |
| 21 | |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 22 | func getEnvelopeJSON(ctx context.T, app repository.Application, profiles string) ([]byte, error) { |
| 23 | env, err := app.Match(ctx, strings.Split(profiles, ",")) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 24 | if err != nil { |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 25 | return nil, err |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 26 | } |
| 27 | j, err := json.MarshalIndent(env, "", " ") |
| 28 | if err != nil { |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 29 | return nil, fmt.Errorf("MarshalIndent(%v) failed: %v", env, err) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 30 | } |
| 31 | return j, nil |
| 32 | } |
| 33 | |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 34 | func putEnvelopeJSON(ctx context.T, app repository.Application, profiles string, j []byte) error { |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 35 | var env application.Envelope |
| 36 | if err := json.Unmarshal(j, &env); err != nil { |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 37 | return fmt.Errorf("Unmarshal(%v) failed: %v", string(j), err) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 38 | } |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 39 | if err := app.Put(ctx, strings.Split(profiles, ","), env); err != nil { |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 40 | return err |
| 41 | } |
| 42 | return nil |
| 43 | } |
| 44 | |
| 45 | func promptUser(cmd *cmdline.Command, msg string) string { |
| 46 | fmt.Fprint(cmd.Stdout(), msg) |
| 47 | var answer string |
| 48 | if _, err := fmt.Scanf("%s", &answer); err != nil { |
| 49 | return "" |
| 50 | } |
| 51 | return answer |
| 52 | } |
| 53 | |
| 54 | var cmdMatch = &cmdline.Command{ |
| 55 | Run: runMatch, |
| 56 | Name: "match", |
| 57 | Short: "Shows the first matching envelope that matches the given profiles.", |
| 58 | Long: "Shows the first matching envelope that matches the given profiles.", |
| 59 | ArgsName: "<application> <profiles>", |
| 60 | ArgsLong: ` |
| 61 | <application> is the full name of the application. |
| 62 | <profiles> is a comma-separated list of profiles.`, |
| 63 | } |
| 64 | |
| 65 | func runMatch(cmd *cmdline.Command, args []string) error { |
| 66 | if expected, got := 2, len(args); expected != got { |
Todd Wang | a615e4d | 2014-09-29 16:56:05 -0700 | [diff] [blame] | 67 | return cmd.UsageErrorf("match: incorrect number of arguments, expected %d, got %d", expected, got) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 68 | } |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 69 | name, profiles := args[0], args[1] |
| 70 | app, err := repository.BindApplication(name) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 71 | if err != nil { |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 72 | return fmt.Errorf("BindApplication(%v) failed: %v", name, err) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 73 | } |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 74 | ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute) |
| 75 | defer cancel() |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 76 | j, err := getEnvelopeJSON(ctx, app, profiles) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 77 | if err != nil { |
| 78 | return err |
| 79 | } |
| 80 | fmt.Fprintln(cmd.Stdout(), string(j)) |
| 81 | return nil |
| 82 | } |
| 83 | |
| 84 | var cmdPut = &cmdline.Command{ |
| 85 | Run: runPut, |
| 86 | Name: "put", |
| 87 | Short: "Add the given envelope to the application for the given profiles.", |
| 88 | Long: "Add the given envelope to the application for the given profiles.", |
| 89 | ArgsName: "<application> <profiles> <envelope>", |
| 90 | ArgsLong: ` |
| 91 | <application> is the full name of the application. |
| 92 | <profiles> is a comma-separated list of profiles. |
| 93 | <envelope> is the file that contains a JSON-encoded envelope.`, |
| 94 | } |
| 95 | |
| 96 | func runPut(cmd *cmdline.Command, args []string) error { |
| 97 | if expected, got := 3, len(args); expected != got { |
Todd Wang | a615e4d | 2014-09-29 16:56:05 -0700 | [diff] [blame] | 98 | return cmd.UsageErrorf("put: incorrect number of arguments, expected %d, got %d", expected, got) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 99 | } |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 100 | name, profiles, envelope := args[0], args[1], args[2] |
| 101 | app, err := repository.BindApplication(name) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 102 | if err != nil { |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 103 | return fmt.Errorf("BindApplication(%v) failed: %v", name, err) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 104 | } |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 105 | j, err := ioutil.ReadFile(envelope) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 106 | if err != nil { |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 107 | return fmt.Errorf("ReadFile(%v): %v", envelope, err) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 108 | } |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 109 | ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute) |
| 110 | defer cancel() |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 111 | if err = putEnvelopeJSON(ctx, app, profiles, j); err != nil { |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 112 | return err |
| 113 | } |
Jiri Simsa | 1a15c16 | 2014-08-19 11:36:24 -0700 | [diff] [blame] | 114 | fmt.Fprintln(cmd.Stdout(), "Application envelope added successfully.") |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 115 | return nil |
| 116 | } |
| 117 | |
| 118 | var cmdRemove = &cmdline.Command{ |
| 119 | Run: runRemove, |
| 120 | Name: "remove", |
| 121 | Short: "removes the application envelope for the given profile.", |
| 122 | Long: "removes the application envelope for the given profile.", |
| 123 | ArgsName: "<application> <profile>", |
| 124 | ArgsLong: ` |
| 125 | <application> is the full name of the application. |
| 126 | <profile> is a profile.`, |
| 127 | } |
| 128 | |
| 129 | func runRemove(cmd *cmdline.Command, args []string) error { |
| 130 | if expected, got := 2, len(args); expected != got { |
Todd Wang | a615e4d | 2014-09-29 16:56:05 -0700 | [diff] [blame] | 131 | return cmd.UsageErrorf("remove: incorrect number of arguments, expected %d, got %d", expected, got) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 132 | } |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 133 | name, profile := args[0], args[1] |
| 134 | app, err := repository.BindApplication(name) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 135 | if err != nil { |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 136 | return fmt.Errorf("BindApplication(%v) failed: %v", name, err) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 137 | } |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 138 | ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute) |
| 139 | defer cancel() |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 140 | if err = app.Remove(ctx, profile); err != nil { |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 141 | return err |
| 142 | } |
| 143 | fmt.Fprintln(cmd.Stdout(), "Application envelope removed successfully.") |
| 144 | return nil |
| 145 | } |
| 146 | |
| 147 | var cmdEdit = &cmdline.Command{ |
| 148 | Run: runEdit, |
| 149 | Name: "edit", |
| 150 | Short: "edits the application envelope for the given profile.", |
| 151 | Long: "edits the application envelope for the given profile.", |
| 152 | ArgsName: "<application> <profile>", |
| 153 | ArgsLong: ` |
| 154 | <application> is the full name of the application. |
| 155 | <profile> is a profile.`, |
| 156 | } |
| 157 | |
| 158 | func runEdit(cmd *cmdline.Command, args []string) error { |
| 159 | if expected, got := 2, len(args); expected != got { |
Todd Wang | a615e4d | 2014-09-29 16:56:05 -0700 | [diff] [blame] | 160 | return cmd.UsageErrorf("edit: incorrect number of arguments, expected %d, got %d", expected, got) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 161 | } |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 162 | name, profile := args[0], args[1] |
| 163 | app, err := repository.BindApplication(name) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 164 | if err != nil { |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 165 | return fmt.Errorf("BindApplication(%v) failed: %v", name, err) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 166 | } |
| 167 | f, err := ioutil.TempFile("", "application-edit-") |
| 168 | if err != nil { |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 169 | return fmt.Errorf("TempFile() failed: %v", err) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 170 | } |
| 171 | fileName := f.Name() |
| 172 | f.Close() |
| 173 | defer os.Remove(fileName) |
| 174 | |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 175 | ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute) |
| 176 | defer cancel() |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 177 | envData, err := getEnvelopeJSON(ctx, app, profile) |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 178 | if err != nil { |
| 179 | return err |
| 180 | } |
| 181 | if err = ioutil.WriteFile(fileName, envData, os.FileMode(0644)); err != nil { |
| 182 | return err |
| 183 | } |
| 184 | editor := os.Getenv("EDITOR") |
| 185 | if len(editor) == 0 { |
| 186 | editor = "nano" |
| 187 | } |
| 188 | for { |
| 189 | c := exec.Command("sh", "-c", fmt.Sprintf("%s %s", editor, fileName)) |
| 190 | c.Stdin = os.Stdin |
| 191 | c.Stdout = os.Stdout |
| 192 | c.Stderr = os.Stderr |
| 193 | if err := c.Run(); err != nil { |
| 194 | return fmt.Errorf("failed to run %s %s", editor, fileName) |
| 195 | } |
| 196 | newData, err := ioutil.ReadFile(fileName) |
| 197 | if err != nil { |
| 198 | fmt.Fprintf(cmd.Stdout(), "Error: %v\n", err) |
| 199 | if ans := promptUser(cmd, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" { |
| 200 | continue |
| 201 | } |
| 202 | return errors.New("aborted") |
| 203 | } |
| 204 | if bytes.Compare(envData, newData) == 0 { |
| 205 | fmt.Fprintln(cmd.Stdout(), "Nothing changed") |
| 206 | return nil |
| 207 | } |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 208 | if err = putEnvelopeJSON(ctx, app, profile, newData); err != nil { |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 209 | fmt.Fprintf(cmd.Stdout(), "Error: %v\n", err) |
| 210 | if ans := promptUser(cmd, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" { |
| 211 | continue |
| 212 | } |
| 213 | return errors.New("aborted") |
| 214 | } |
| 215 | break |
| 216 | } |
| 217 | fmt.Fprintln(cmd.Stdout(), "Application envelope updated successfully.") |
| 218 | return nil |
| 219 | } |
| 220 | |
| 221 | func Root() *cmdline.Command { |
| 222 | return &cmdline.Command{ |
| 223 | Name: "application", |
Jiri Simsa | ddbfebb | 2014-06-20 15:56:06 -0700 | [diff] [blame] | 224 | Short: "Command-line tool for interacting with the veyron application repository", |
| 225 | Long: "Command-line tool for interacting with the veyron application repository", |
Robin Thellend | 32c438b | 2014-05-22 17:28:00 -0700 | [diff] [blame] | 226 | Children: []*cmdline.Command{cmdMatch, cmdPut, cmdRemove, cmdEdit}, |
| 227 | } |
| 228 | } |