From ffcd7d8059dfdd4d0f0c337f897de50fc20d747e Mon Sep 17 00:00:00 2001
From: Max Jonas Werner <mail@makk.es>
Date: Tue, 2 Nov 2021 09:42:28 +0100
Subject: [PATCH] feat: prompt for access tokens in 'bootstrap' command

This change adds functionality to both, `bootstrap github` and
`bootstrap gitlab` to prompt the user for the personal access tokens
if those can't be derived from the shell environment. Echoing is
turned off for better privacy.

Instead of having to interactively type the token or manually paste it
from the clipboard, users can also pipe it to Flux which comes in
handy e.g. when executing Flux remotely over an SSH connection:

```
$ echo 'asdf' | flux bootstrap github
```

Otherwise, Flux will prompt the user:

```
$ flux bootstrap github
Please type your GitHub personal access token:
```

closes #2011

Signed-off-by: Max Jonas Werner <mail@makk.es>
---
 cmd/flux/bootstrap_github.go |  6 +++++-
 cmd/flux/bootstrap_gitlab.go |  6 +++++-
 cmd/flux/main.go             | 22 ++++++++++++++++++++++
 go.mod                       |  1 +
 4 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go
index 54b9694f..34f60f6d 100644
--- a/cmd/flux/bootstrap_github.go
+++ b/cmd/flux/bootstrap_github.go
@@ -111,7 +111,11 @@ func init() {
 func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
 	ghToken := os.Getenv(ghTokenEnvVar)
 	if ghToken == "" {
-		return fmt.Errorf("%s environment variable not found", ghTokenEnvVar)
+		var err error
+		ghToken, err = readPasswordFromStdin("Please type your GitHub personal access token: ")
+		if err != nil {
+			return fmt.Errorf("could not read token: %w", err)
+		}
 	}
 
 	if err := bootstrapValidate(); err != nil {
diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go
index 09bde7f3..24baa03a 100644
--- a/cmd/flux/bootstrap_gitlab.go
+++ b/cmd/flux/bootstrap_gitlab.go
@@ -108,7 +108,11 @@ func init() {
 func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
 	glToken := os.Getenv(glTokenEnvVar)
 	if glToken == "" {
-		return fmt.Errorf("%s environment variable not found", glTokenEnvVar)
+		var err error
+		glToken, err = readPasswordFromStdin("Please type your GitLab personal access token: ")
+		if err != nil {
+			return fmt.Errorf("could not read token: %w", err)
+		}
 	}
 
 	if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, gitlabArgs.repository); err != nil || !projectNameIsValid {
diff --git a/cmd/flux/main.go b/cmd/flux/main.go
index 916c4940..86cfba76 100644
--- a/cmd/flux/main.go
+++ b/cmd/flux/main.go
@@ -17,12 +17,15 @@ limitations under the License.
 package main
 
 import (
+	"bufio"
+	"fmt"
 	"log"
 	"os"
 	"path/filepath"
 	"time"
 
 	"github.com/spf13/cobra"
+	"golang.org/x/term"
 	corev1 "k8s.io/api/core/v1"
 	_ "k8s.io/client-go/plugin/pkg/client/auth"
 
@@ -167,3 +170,22 @@ func homeDir() string {
 	}
 	return os.Getenv("USERPROFILE") // windows
 }
+
+func readPasswordFromStdin(prompt string) (string, error) {
+	var out string
+	var err error
+	fmt.Fprint(os.Stdout, prompt)
+	stdinFD := int(os.Stdin.Fd())
+	if term.IsTerminal(stdinFD) {
+		var inBytes []byte
+		inBytes, err = term.ReadPassword(int(os.Stdin.Fd()))
+		out = string(inBytes)
+	} else {
+		out, err = bufio.NewReader(os.Stdin).ReadString('\n')
+	}
+	if err != nil {
+		return "", fmt.Errorf("could not read from stdin: %w", err)
+	}
+	fmt.Println()
+	return out, nil
+}
diff --git a/go.mod b/go.mod
index d12d1795..ca48bb4d 100644
--- a/go.mod
+++ b/go.mod
@@ -29,6 +29,7 @@ require (
 	github.com/spf13/cobra v1.1.3
 	github.com/spf13/pflag v1.0.5
 	golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
+	golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d
 	k8s.io/api v0.22.2
 	k8s.io/apiextensions-apiserver v0.22.2
 	k8s.io/apimachinery v0.22.2
-- 
GitLab