Merge pull request #200 from MichaelMure/bridge-token-stdin

Michael Muré created

bridge: support --token-stdin

Change summary

bridge/core/bridge.go              |  9 ++++---
bridge/github/config.go            | 40 +++++++++++++++++++++++++++++++
bridge/gitlab/config.go            | 20 ++++++++++++++-
bridge/launchpad/config.go         |  8 +++++
commands/bridge_configure.go       | 12 ++-------
doc/man/git-bug-bridge-configure.1 | 15 ++++++++++-
doc/md/git-bug_bridge_configure.md | 12 ++++++++-
misc/bash_completion/git-bug       |  2 +
misc/powershell_completion/git-bug |  1 
misc/zsh_completion/git-bug        |  1 
10 files changed, 99 insertions(+), 21 deletions(-)

Detailed changes

bridge/core/bridge.go 🔗

@@ -31,10 +31,11 @@ var bridgeImpl map[string]reflect.Type
 // BridgeParams holds parameters to simplify the bridge configuration without
 // having to make terminal prompts.
 type BridgeParams struct {
-	Owner   string
-	Project string
-	URL     string
-	Token   string
+	Owner      string
+	Project    string
+	URL        string
+	Token      string
+	TokenStdin bool
 }
 
 // Bridge is a wrapper around a BridgeImpl that will bind low-level

bridge/github/config.go 🔗

@@ -21,6 +21,7 @@ import (
 
 	"github.com/MichaelMure/git-bug/bridge/core"
 	"github.com/MichaelMure/git-bug/repository"
+	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 const (
@@ -37,13 +38,18 @@ var (
 	ErrBadProjectURL = errors.New("bad project url")
 )
 
-func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) {
+func (g *Github) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) {
 	conf := make(core.Configuration)
 	var err error
 	var token string
 	var owner string
 	var project string
 
+	if (params.Token != "" || params.TokenStdin) &&
+		(params.URL == "" && (params.Project == "" || params.Owner == "")) {
+		return nil, fmt.Errorf("you must provide a project URL or Owner/Name to configure this bridge with a token")
+	}
+
 	// getting owner and project name
 	if params.Owner != "" && params.Project != "" {
 		// first try to use params if both or project and owner are provided
@@ -85,6 +91,13 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) (
 	if params.Token != "" {
 		token = params.Token
 
+	} else if params.TokenStdin {
+		reader := bufio.NewReader(os.Stdin)
+		token, err = reader.ReadString('\n')
+		if err != nil {
+			return nil, fmt.Errorf("reading from stdin: %v", err)
+		}
+		token = strings.TrimSuffix(token, "\n")
 	} else {
 		token, err = promptTokenOptions(owner, project)
 		if err != nil {
@@ -106,6 +119,11 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) (
 	conf[keyOwner] = owner
 	conf[keyProject] = project
 
+	err = g.ValidateConfig(conf)
+	if err != nil {
+		return nil, err
+	}
+
 	return conf, nil
 }
 
@@ -505,6 +523,16 @@ func validateProject(owner, project, token string) (bool, error) {
 }
 
 func promptPassword() (string, error) {
+	termState, err := terminal.GetState(int(syscall.Stdin))
+	if err != nil {
+		return "", err
+	}
+
+	cancel := interrupt.RegisterCleaner(func() error {
+		return terminal.Restore(int(syscall.Stdin), termState)
+	})
+	defer cancel()
+
 	for {
 		fmt.Print("password: ")
 
@@ -526,6 +554,16 @@ func promptPassword() (string, error) {
 }
 
 func prompt2FA() (string, error) {
+	termState, err := terminal.GetState(int(syscall.Stdin))
+	if err != nil {
+		return "", err
+	}
+
+	cancel := interrupt.RegisterCleaner(func() error {
+		return terminal.Restore(int(syscall.Stdin), termState)
+	})
+	defer cancel()
+
 	for {
 		fmt.Print("two-factor authentication code: ")
 

bridge/gitlab/config.go 🔗

@@ -20,7 +20,7 @@ var (
 	ErrBadProjectURL = errors.New("bad project url")
 )
 
-func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) {
+func (g *Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) {
 	if params.Project != "" {
 		fmt.Println("warning: --project is ineffective for a gitlab bridge")
 	}
@@ -33,6 +33,10 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) (
 	var url string
 	var token string
 
+	if (params.Token != "" || params.TokenStdin) && params.URL == "" {
+		return nil, fmt.Errorf("you must provide a project URL to configure this bridge with a token")
+	}
+
 	// get project url
 	if params.URL != "" {
 		url = params.URL
@@ -54,6 +58,13 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) (
 	// get user token
 	if params.Token != "" {
 		token = params.Token
+	} else if params.TokenStdin {
+		reader := bufio.NewReader(os.Stdin)
+		token, err = reader.ReadString('\n')
+		if err != nil {
+			return nil, fmt.Errorf("reading from stdin: %v", err)
+		}
+		token = strings.TrimSuffix(token, "\n")
 	} else {
 		token, err = promptToken()
 		if err != nil {
@@ -71,10 +82,15 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) (
 	conf[keyToken] = token
 	conf[core.KeyTarget] = target
 
+	err = g.ValidateConfig(conf)
+	if err != nil {
+		return nil, err
+	}
+
 	return conf, nil
 }
 
-func (*Gitlab) ValidateConfig(conf core.Configuration) error {
+func (g *Gitlab) ValidateConfig(conf core.Configuration) error {
 	if v, ok := conf[core.KeyTarget]; !ok {
 		return fmt.Errorf("missing %s key", core.KeyTarget)
 	} else if v != target {

bridge/launchpad/config.go 🔗

@@ -22,7 +22,7 @@ const (
 	defaultTimeout = 60 * time.Second
 )
 
-func (*Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) {
+func (l *Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) {
 	if params.Token != "" {
 		fmt.Println("warning: --token is ineffective for a Launchpad bridge")
 	}
@@ -63,6 +63,12 @@ func (*Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams
 
 	conf[keyProject] = project
 	conf[core.KeyTarget] = target
+
+	err = l.ValidateConfig(conf)
+	if err != nil {
+		return nil, err
+	}
+
 	return conf, nil
 }
 

commands/bridge_configure.go 🔗

@@ -6,10 +6,8 @@ import (
 	"os"
 	"strconv"
 	"strings"
-	"syscall"
 
 	"github.com/spf13/cobra"
-	"golang.org/x/crypto/ssh/terminal"
 
 	"github.com/MichaelMure/git-bug/bridge"
 	"github.com/MichaelMure/git-bug/bridge/core"
@@ -36,15 +34,10 @@ func runBridgeConfigure(cmd *cobra.Command, args []string) error {
 	defer backend.Close()
 	interrupt.RegisterCleaner(backend.Close)
 
-	termState, err := terminal.GetState(int(syscall.Stdin))
-	if err != nil {
-		return err
+	if (bridgeParams.TokenStdin || bridgeParams.Token != "") && (bridgeConfigureName == "" || bridgeConfigureTarget == "") {
+		return fmt.Errorf("you must provide a bridge name and target to configure a bridge with a token")
 	}
 
-	interrupt.RegisterCleaner(func() error {
-		return terminal.Restore(int(syscall.Stdin), termState)
-	})
-
 	if bridgeConfigureTarget == "" {
 		bridgeConfigureTarget, err = promptTarget()
 		if err != nil {
@@ -192,6 +185,7 @@ func init() {
 	bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.URL, "url", "u", "", "The URL of the target repository")
 	bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Owner, "owner", "o", "", "The owner of the target repository")
 	bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Token, "token", "T", "", "The authentication token for the API")
+	bridgeConfigureCmd.Flags().BoolVar(&bridgeParams.TokenStdin, "token-stdin", false, "Will read the token from stdin and ignore --token")
 	bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Project, "project", "p", "", "The name of the target repository")
 	bridgeConfigureCmd.Flags().SortFlags = false
 }

doc/man/git-bug-bridge-configure.1 🔗

@@ -47,6 +47,10 @@ Token configuration can be directly passed with the \-\-token flag or in the ter
 \fB\-T\fP, \fB\-\-token\fP=""
     The authentication token for the API
 
+.PP
+\fB\-\-token\-stdin\fP[=false]
+    Will read the token from stdin and ignore \-\-token
+
 .PP
 \fB\-p\fP, \fB\-\-project\fP=""
     The name of the target repository
@@ -91,7 +95,7 @@ Private:
 Enter token: 87cf5c03b64029f18ea5f9ca5679daa08ccbd700
 Successfully configured bridge: default
 
-# For Github
+# For GitHub
 git bug bridge configure \\
     \-\-name=default \\
     \-\-target=github \\
@@ -103,7 +107,14 @@ git bug bridge configure \\
 git bug bridge configure \\
     \-\-name=default \\
     \-\-target=launchpad\-preview \\
-    \-\-url=https://bugs.launchpad.net/ubuntu/
+	\-\-url=https://bugs.launchpad.net/ubuntu/
+
+# For Gitlab
+git bug bridge configure \\
+    \-\-name=default \\
+    \-\-target=github \\
+    \-\-url=https://github.com/michaelmure/git\-bug \\
+    \-\-token=$(TOKEN)
 
 .fi
 .RE

doc/md/git-bug_bridge_configure.md 🔗

@@ -45,7 +45,7 @@ Private:
 Enter token: 87cf5c03b64029f18ea5f9ca5679daa08ccbd700
 Successfully configured bridge: default
 
-# For Github
+# For GitHub
 git bug bridge configure \
     --name=default \
     --target=github \
@@ -57,7 +57,14 @@ git bug bridge configure \
 git bug bridge configure \
     --name=default \
     --target=launchpad-preview \
-    --url=https://bugs.launchpad.net/ubuntu/
+	--url=https://bugs.launchpad.net/ubuntu/
+
+# For Gitlab
+git bug bridge configure \
+    --name=default \
+    --target=github \
+    --url=https://github.com/michaelmure/git-bug \
+    --token=$(TOKEN)
 ```
 
 ### Options
@@ -68,6 +75,7 @@ git bug bridge configure \
   -u, --url string       The URL of the target repository
   -o, --owner string     The owner of the target repository
   -T, --token string     The authentication token for the API
+      --token-stdin      Will read the token from stdin and ignore --token
   -p, --project string   The name of the target repository
   -h, --help             help for configure
 ```

misc/bash_completion/git-bug 🔗

@@ -321,6 +321,8 @@ _git-bug_bridge_configure()
     two_word_flags+=("--token")
     two_word_flags+=("-T")
     local_nonpersistent_flags+=("--token=")
+    flags+=("--token-stdin")
+    local_nonpersistent_flags+=("--token-stdin")
     flags+=("--project=")
     two_word_flags+=("--project")
     two_word_flags+=("-p")

misc/powershell_completion/git-bug 🔗

@@ -65,6 +65,7 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock {
             [CompletionResult]::new('--owner', 'owner', [CompletionResultType]::ParameterName, 'The owner of the target repository')
             [CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'The authentication token for the API')
             [CompletionResult]::new('--token', 'token', [CompletionResultType]::ParameterName, 'The authentication token for the API')
+            [CompletionResult]::new('--token-stdin', 'token-stdin', [CompletionResultType]::ParameterName, 'Will read the token from stdin and ignore --token')
             [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'The name of the target repository')
             [CompletionResult]::new('--project', 'project', [CompletionResultType]::ParameterName, 'The name of the target repository')
             break

misc/zsh_completion/git-bug 🔗

@@ -146,6 +146,7 @@ function _git-bug_bridge_configure {
     '(-u --url)'{-u,--url}'[The URL of the target repository]:' \
     '(-o --owner)'{-o,--owner}'[The owner of the target repository]:' \
     '(-T --token)'{-T,--token}'[The authentication token for the API]:' \
+    '--token-stdin[Will read the token from stdin and ignore --token]' \
     '(-p --project)'{-p,--project}'[The name of the target repository]:'
 }