| @@ -126,14 +126,24 @@ func newRoot() *cobra.Command { |
| 126 | 126 | const serverPath = "gitbay.server_path" |
| 127 | 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 | 134 | // passOpts describes how one CLI command maps onto the server command. |
| 130 | 135 | type passOpts struct { |
| 131 | 136 | server []string // server-side command path |
| 132 | 137 | needsRepo bool // prepend inferred owner/name unless given |
| 133 | 138 | stdinOK bool // wire local stdin through when --file - asks for it |
| 134 | 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 |
| 136 | | inferSource bool // --source defaults to the checked-out branch inside a clone |
| 140 | // stdinWhat names the payload for the prompt shown when stdin is a |
| 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 | 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 | 168 | Annotations: map[string]string{ |
| 159 | 169 | serverPath: strings.Join(o.server, " "), |
| 160 | 170 | stdinMode: o.stdinModeName(), |
| 171 | stdinWhat: o.stdinWhat, |
| 161 | 172 | }, |
| 162 | 173 | DisableFlagParsing: true, |
| 163 | 174 | RunE: func(cmd *cobra.Command, args []string) error { |
| @@ -231,7 +242,16 @@ func runPass(o passOpts, args []string) int { |
| 231 | 242 | } |
| 232 | 243 | if stdin == nil || isEmptyReader(stdin) { |
| 233 | 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 | 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 | 327 | func authCmd() *cobra.Command { |
| 308 | 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 | 330 | // keys add always reads stdin on the server; wire it through directly. |
| 311 | 331 | keysAdd.RunE = func(cmd *cobra.Command, args []string) error { |
| 312 | 332 | t, err := resolveTarget() |
| 313 | 333 | if err != nil { |
| 314 | 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 | 341 | return nil |
| 318 | 342 | } |
| 319 | 343 | pgpAdd := &cobra.Command{ |
| 320 | 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 | 350 | DisableFlagParsing: true, |
| 323 | 351 | RunE: func(cmd *cobra.Command, args []string) error { |
| 324 | 352 | t, err := resolveTarget() |
| 325 | 353 | if err != nil { |
| 326 | 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 | 361 | return nil |
| 330 | 362 | }, |
| 331 | 363 | } |
| @@ -391,7 +423,7 @@ func repoCmd() *cobra.Command { |
| 391 | 423 | pass("import-issues", "import GitHub issue/PR history: --from <ghowner/ghrepo> [--token-stdin]", |
| 392 | 424 | passOpts{server: []string{"repo", "import-issues"}, needsRepo: true, stdinOK: true}), |
| 393 | 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 | 427 | pass("list", "list deploy keys", passOpts{server: []string{"repo", "deploy-key", "list"}, needsRepo: true}), |
| 396 | 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 | 440 | pass("status", "show check state and what is behind", passOpts{server: []string{"repo", "deps", "status"}, needsRepo: true}), |
| 409 | 441 | ), |
| 410 | 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 | 444 | pass("list", "list secret names", passOpts{server: []string{"repo", "secret", "list"}, needsRepo: true}), |
| 413 | 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 | 505 | pass("show", "show a release with assets: <tag>", passOpts{server: []string{"release", "show"}, needsRepo: true}), |
| 474 | 506 | pass("delete", "delete a release and its assets: <tag> --yes", passOpts{server: []string{"release", "delete"}, needsRepo: true}), |
| 475 | 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 | 509 | pass("get", "download to stdout: <tag> <filename> > file", passOpts{server: []string{"release", "asset", "get"}, needsRepo: true}), |
| 478 | 510 | pass("remove", "remove an asset: <tag> <filename>", passOpts{server: []string{"release", "asset", "remove"}, needsRepo: true}), |
| 479 | 511 | ), |