| @@ -126,14 +126,24 @@ func newRoot() *cobra.Command { |
| 126 | const serverPath = "gitbay.server_path" |
126 | const serverPath = "gitbay.server_path" |
| 127 | const stdinMode = "gitbay.stdin_mode" |
127 | const stdinMode = "gitbay.stdin_mode" |
| 128 | |
128 | |
| |
129 | // stdinWhat carries the payload's name onto the command tree so the |
| |
130 | // coverage test can assert every stdin-payload command has one; without |
| |
131 | // it the terminal prompt says the unhelpful word "input" (#150). |
| |
132 | const stdinWhat = "gitbay.stdin_what" |
| |
133 | |
| 129 | // passOpts describes how one CLI command maps onto the server command. |
134 | // passOpts describes how one CLI command maps onto the server command. |
| 130 | type passOpts struct { |
135 | type passOpts struct { |
| 131 | server []string // server-side command path |
136 | server []string // server-side command path |
| 132 | needsRepo bool // prepend inferred owner/name unless given |
137 | needsRepo bool // prepend inferred owner/name unless given |
| 133 | stdinOK bool // wire local stdin through when --file - asks for it |
138 | stdinOK bool // wire local stdin through when --file - asks for it |
| 134 | alwaysStdin bool // stdin is the payload, named by no flag: a bare redirect |
139 | alwaysStdin bool // stdin is the payload, named by no flag: a bare redirect |
| 135 | editor string // open $EDITOR for a body when none given |
140 | // stdinWhat names the payload for the prompt shown when stdin is a |
| 136 | inferSource bool // --source defaults to the checked-out branch inside a clone |
141 | // terminal; stdinSecret hides the input and takes one line, for a |
| |
142 | // value that should not reach the scrollback. |
| |
143 | stdinWhat string |
| |
144 | stdinSecret bool |
| |
145 | editor string // open $EDITOR for a body when none given |
| |
146 | inferSource bool // --source defaults to the checked-out branch inside a clone |
| 137 | } |
147 | } |
| 138 | |
148 | |
| 139 | // pass builds a passthrough command. Flags are parsed by the server, which |
149 | // pass builds a passthrough command. Flags are parsed by the server, which |
| @@ -158,6 +168,7 @@ func pass(use, short string, o passOpts) *cobra.Command { |
| 158 | Annotations: map[string]string{ |
168 | Annotations: map[string]string{ |
| 159 | serverPath: strings.Join(o.server, " "), |
169 | serverPath: strings.Join(o.server, " "), |
| 160 | stdinMode: o.stdinModeName(), |
170 | stdinMode: o.stdinModeName(), |
| |
171 | stdinWhat: o.stdinWhat, |
| 161 | }, |
172 | }, |
| 162 | DisableFlagParsing: true, |
173 | DisableFlagParsing: true, |
| 163 | RunE: func(cmd *cobra.Command, args []string) error { |
174 | RunE: func(cmd *cobra.Command, args []string) error { |
| @@ -231,7 +242,16 @@ func runPass(o passOpts, args []string) int { |
| 231 | } |
242 | } |
| 232 | if stdin == nil || isEmptyReader(stdin) { |
243 | if stdin == nil || isEmptyReader(stdin) { |
| 233 | if o.alwaysStdin || (o.stdinOK && usesStdin(args)) { |
244 | if o.alwaysStdin || (o.stdinOK && usesStdin(args)) { |
| 234 | stdin = os.Stdin |
245 | what := o.stdinWhat |
| |
246 | if what == "" { |
| |
247 | what = "input" |
| |
248 | } |
| |
249 | r, err := stdinPayload(os.Stdin, what, o.stdinSecret) |
| |
250 | if err != nil { |
| |
251 | fmt.Fprintln(os.Stderr, "gitbay:", err) |
| |
252 | return protocol.ExitFailure |
| |
253 | } |
| |
254 | stdin = r |
| 235 | } |
255 | } |
| 236 | } |
256 | } |
| 237 | return runSSH(t, append(o.server, args...), stdin) |
257 | return runSSH(t, append(o.server, args...), stdin) |
| @@ -306,26 +326,38 @@ func local(use, short string, fn func(args []string) int) *cobra.Command { |
| 306 | |
326 | |
| 307 | func authCmd() *cobra.Command { |
327 | func authCmd() *cobra.Command { |
| 308 | keysAdd := pass("add", "register an SSH public key (reads the key from stdin or --file -)", |
328 | keysAdd := pass("add", "register an SSH public key (reads the key from stdin or --file -)", |
| 309 | passOpts{server: []string{"keys", "add"}, alwaysStdin: true}) |
329 | passOpts{server: []string{"keys", "add"}, alwaysStdin: true, stdinWhat: "an SSH public key"}) |
| 310 | // keys add always reads stdin on the server; wire it through directly. |
330 | // keys add always reads stdin on the server; wire it through directly. |
| 311 | keysAdd.RunE = func(cmd *cobra.Command, args []string) error { |
331 | keysAdd.RunE = func(cmd *cobra.Command, args []string) error { |
| 312 | t, err := resolveTarget() |
332 | t, err := resolveTarget() |
| 313 | if err != nil { |
333 | if err != nil { |
| 314 | return err |
334 | return err |
| 315 | } |
335 | } |
| 316 | os.Exit(runSSH(t, append([]string{"keys", "add"}, args...), os.Stdin)) |
336 | in, err := stdinPayload(os.Stdin, "an SSH public key", false) |
| |
337 | if err != nil { |
| |
338 | return err |
| |
339 | } |
| |
340 | os.Exit(runSSH(t, append([]string{"keys", "add"}, args...), in)) |
| 317 | return nil |
341 | return nil |
| 318 | } |
342 | } |
| 319 | pgpAdd := &cobra.Command{ |
343 | pgpAdd := &cobra.Command{ |
| 320 | Use: "add", Short: "register an OpenPGP public key (armored, on stdin)", |
344 | Use: "add", Short: "register an OpenPGP public key (armored, on stdin)", |
| 321 | Annotations: map[string]string{serverPath: "pgp add"}, |
345 | Annotations: map[string]string{ |
| |
346 | serverPath: "pgp add", |
| |
347 | stdinMode: "always", |
| |
348 | stdinWhat: "an armored OpenPGP public key", |
| |
349 | }, |
| 322 | DisableFlagParsing: true, |
350 | DisableFlagParsing: true, |
| 323 | RunE: func(cmd *cobra.Command, args []string) error { |
351 | RunE: func(cmd *cobra.Command, args []string) error { |
| 324 | t, err := resolveTarget() |
352 | t, err := resolveTarget() |
| 325 | if err != nil { |
353 | if err != nil { |
| 326 | return err |
354 | return err |
| 327 | } |
355 | } |
| 328 | os.Exit(runSSH(t, append([]string{"pgp", "add"}, args...), os.Stdin)) |
356 | in, err := stdinPayload(os.Stdin, "an armored OpenPGP public key", false) |
| |
357 | if err != nil { |
| |
358 | return err |
| |
359 | } |
| |
360 | os.Exit(runSSH(t, append([]string{"pgp", "add"}, args...), in)) |
| 329 | return nil |
361 | return nil |
| 330 | }, |
362 | }, |
| 331 | } |
363 | } |
| @@ -391,7 +423,7 @@ func repoCmd() *cobra.Command { |
| 391 | pass("import-issues", "import GitHub issue/PR history: --from <ghowner/ghrepo> [--token-stdin]", |
423 | pass("import-issues", "import GitHub issue/PR history: --from <ghowner/ghrepo> [--token-stdin]", |
| 392 | passOpts{server: []string{"repo", "import-issues"}, needsRepo: true, stdinOK: true}), |
424 | passOpts{server: []string{"repo", "import-issues"}, needsRepo: true, stdinOK: true}), |
| 393 | group("deploy-key", "repository-bound CI keys", |
425 | group("deploy-key", "repository-bound CI keys", |
| 394 | pass("add", "bind a key: [--rw] < key.pub", passOpts{server: []string{"repo", "deploy-key", "add"}, needsRepo: true, alwaysStdin: true}), |
426 | pass("add", "bind a key: [--rw] < key.pub", passOpts{server: []string{"repo", "deploy-key", "add"}, needsRepo: true, alwaysStdin: true, stdinWhat: "an SSH public key"}), |
| 395 | pass("list", "list deploy keys", passOpts{server: []string{"repo", "deploy-key", "list"}, needsRepo: true}), |
427 | pass("list", "list deploy keys", passOpts{server: []string{"repo", "deploy-key", "list"}, needsRepo: true}), |
| 396 | pass("remove", "remove a deploy key: <fingerprint>", passOpts{server: []string{"repo", "deploy-key", "remove"}, needsRepo: true}), |
428 | pass("remove", "remove a deploy key: <fingerprint>", passOpts{server: []string{"repo", "deploy-key", "remove"}, needsRepo: true}), |
| 397 | ), |
429 | ), |
| @@ -408,7 +440,7 @@ func repoCmd() *cobra.Command { |
| 408 | pass("status", "show check state and what is behind", passOpts{server: []string{"repo", "deps", "status"}, needsRepo: true}), |
440 | pass("status", "show check state and what is behind", passOpts{server: []string{"repo", "deps", "status"}, needsRepo: true}), |
| 409 | ), |
441 | ), |
| 410 | group("secret", "build secrets (values on stdin, injected into build env)", |
442 | group("secret", "build secrets (values on stdin, injected into build env)", |
| 411 | pass("set", "set a secret: <NAME> (value on stdin)", passOpts{server: []string{"repo", "secret", "set"}, needsRepo: true, alwaysStdin: true}), |
443 | pass("set", "set a secret: <NAME> (value on stdin)", passOpts{server: []string{"repo", "secret", "set"}, needsRepo: true, alwaysStdin: true, stdinWhat: "the secret value", stdinSecret: true}), |
| 412 | pass("list", "list secret names", passOpts{server: []string{"repo", "secret", "list"}, needsRepo: true}), |
444 | pass("list", "list secret names", passOpts{server: []string{"repo", "secret", "list"}, needsRepo: true}), |
| 413 | pass("remove", "remove a secret: <NAME>", passOpts{server: []string{"repo", "secret", "remove"}, needsRepo: true}), |
445 | pass("remove", "remove a secret: <NAME>", passOpts{server: []string{"repo", "secret", "remove"}, needsRepo: true}), |
| 414 | ), |
446 | ), |
| @@ -473,7 +505,7 @@ func releaseCmd() *cobra.Command { |
| 473 | pass("show", "show a release with assets: <tag>", passOpts{server: []string{"release", "show"}, needsRepo: true}), |
505 | pass("show", "show a release with assets: <tag>", passOpts{server: []string{"release", "show"}, needsRepo: true}), |
| 474 | pass("delete", "delete a release and its assets: <tag> --yes", passOpts{server: []string{"release", "delete"}, needsRepo: true}), |
506 | pass("delete", "delete a release and its assets: <tag> --yes", passOpts{server: []string{"release", "delete"}, needsRepo: true}), |
| 475 | group("asset", "binary assets on a release", |
507 | group("asset", "binary assets on a release", |
| 476 | pass("add", "upload from stdin: <tag> <filename> < file", passOpts{server: []string{"release", "asset", "add"}, needsRepo: true, alwaysStdin: true}), |
508 | pass("add", "upload from stdin: <tag> <filename> < file", passOpts{server: []string{"release", "asset", "add"}, needsRepo: true, alwaysStdin: true, stdinWhat: "the asset's bytes"}), |
| 477 | pass("get", "download to stdout: <tag> <filename> > file", passOpts{server: []string{"release", "asset", "get"}, needsRepo: true}), |
509 | pass("get", "download to stdout: <tag> <filename> > file", passOpts{server: []string{"release", "asset", "get"}, needsRepo: true}), |
| 478 | pass("remove", "remove an asset: <tag> <filename>", passOpts{server: []string{"release", "asset", "remove"}, needsRepo: true}), |
510 | pass("remove", "remove an asset: <tag> <filename>", passOpts{server: []string{"release", "asset", "remove"}, needsRepo: true}), |
| 479 | ), |
511 | ), |