1package commands
  2
  3import (
  4	"bufio"
  5	"fmt"
  6	"os"
  7	"strconv"
  8	"strings"
  9
 10	"github.com/spf13/cobra"
 11
 12	"github.com/MichaelMure/git-bug/bridge"
 13	"github.com/MichaelMure/git-bug/bridge/core"
 14	"github.com/MichaelMure/git-bug/bridge/core/auth"
 15	"github.com/MichaelMure/git-bug/repository"
 16)
 17
 18type bridgeConfigureOptions struct {
 19	name       string
 20	target     string
 21	params     core.BridgeParams
 22	token      string
 23	tokenStdin bool
 24}
 25
 26func newBridgeConfigureCommand() *cobra.Command {
 27	env := newEnv()
 28	options := bridgeConfigureOptions{}
 29
 30	cmd := &cobra.Command{
 31		Use:   "configure",
 32		Short: "Configure a new bridge.",
 33		Long: `	Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge.`,
 34		Example: `# Interactive example
 35[1]: github
 36[2]: gitlab
 37[3]: jira
 38[4]: launchpad-preview
 39
 40target: 1
 41name [default]: default
 42
 43Detected projects:
 44[1]: github.com/a-hilaly/git-bug
 45[2]: github.com/MichaelMure/git-bug
 46
 47[0]: Another project
 48
 49Select option: 1
 50
 51[1]: user provided token
 52[2]: interactive token creation
 53Select option: 1
 54
 55You can generate a new token by visiting https://github.com/settings/tokens.
 56Choose 'Generate new token' and set the necessary access scope for your repository.
 57
 58The access scope depend on the type of repository.
 59Public:
 60	- 'public_repo': to be able to read public repositories
 61Private:
 62	- 'repo'       : to be able to read private repositories
 63
 64Enter token: 87cf5c03b64029f18ea5f9ca5679daa08ccbd700
 65Successfully configured bridge: default
 66
 67# For GitHub
 68git bug bridge configure \
 69    --name=default \
 70    --target=github \
 71    --owner=$(OWNER) \
 72    --project=$(PROJECT) \
 73    --token=$(TOKEN)
 74
 75# For Launchpad
 76git bug bridge configure \
 77    --name=default \
 78    --target=launchpad-preview \
 79	--url=https://bugs.launchpad.net/ubuntu/
 80
 81# For Gitlab
 82git bug bridge configure \
 83    --name=default \
 84    --target=github \
 85    --url=https://github.com/michaelmure/git-bug \
 86    --token=$(TOKEN)`,
 87		PreRunE:  loadBackend(env),
 88		PostRunE: closeBackend(env),
 89		RunE: func(cmd *cobra.Command, args []string) error {
 90			return runBridgeConfigure(env, options)
 91		},
 92	}
 93
 94	flags := cmd.Flags()
 95	flags.SortFlags = false
 96
 97	flags.StringVarP(&options.name, "name", "n", "", "A distinctive name to identify the bridge")
 98	flags.StringVarP(&options.target, "target", "t", "",
 99		fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ",")))
100	flags.StringVarP(&options.params.URL, "url", "u", "", "The URL of the remote repository")
101	flags.StringVarP(&options.params.BaseURL, "base-url", "b", "", "The base URL of your remote issue tracker")
102	flags.StringVarP(&options.params.Login, "login", "l", "", "The login on your remote issue tracker")
103	flags.StringVarP(&options.params.CredPrefix, "credential", "c", "", "The identifier or prefix of an already known credential for your remote issue tracker (see \"git-bug bridge auth\")")
104	flags.StringVar(&options.token, "token", "", "A raw authentication token for the remote issue tracker")
105	flags.BoolVar(&options.tokenStdin, "token-stdin", false, "Will read the token from stdin and ignore --token")
106	flags.StringVarP(&options.params.Owner, "owner", "o", "", "The owner of the remote repository")
107	flags.StringVarP(&options.params.Project, "project", "p", "", "The name of the remote repository")
108
109	return cmd
110}
111
112func runBridgeConfigure(env *Env, opts bridgeConfigureOptions) error {
113	var err error
114
115	if (opts.tokenStdin || opts.token != "" || opts.params.CredPrefix != "") &&
116		(opts.name == "" || opts.target == "") {
117		return fmt.Errorf("you must provide a bridge name and target to configure a bridge with a credential")
118	}
119
120	// early fail
121	if opts.params.CredPrefix != "" {
122		if _, err := auth.LoadWithPrefix(env.repo, opts.params.CredPrefix); err != nil {
123			return err
124		}
125	}
126
127	switch {
128	case opts.tokenStdin:
129		reader := bufio.NewReader(os.Stdin)
130		token, err := reader.ReadString('\n')
131		if err != nil {
132			return fmt.Errorf("reading from stdin: %v", err)
133		}
134		opts.params.TokenRaw = strings.TrimSpace(token)
135	case opts.token != "":
136		opts.params.TokenRaw = opts.token
137	}
138
139	if opts.target == "" {
140		opts.target, err = promptTarget()
141		if err != nil {
142			return err
143		}
144	}
145
146	if opts.name == "" {
147		opts.name, err = promptName(env.repo)
148		if err != nil {
149			return err
150		}
151	}
152
153	b, err := bridge.NewBridge(env.backend, opts.target, opts.name)
154	if err != nil {
155		return err
156	}
157
158	err = b.Configure(opts.params)
159	if err != nil {
160		return err
161	}
162
163	env.out.Printf("Successfully configured bridge: %s\n", opts.name)
164	return nil
165}
166
167func promptTarget() (string, error) {
168	// TODO: use the reusable prompt from the input package
169	targets := bridge.Targets()
170
171	for {
172		for i, target := range targets {
173			fmt.Printf("[%d]: %s\n", i+1, target)
174		}
175		fmt.Printf("target: ")
176
177		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
178
179		if err != nil {
180			return "", err
181		}
182
183		line = strings.TrimSpace(line)
184
185		index, err := strconv.Atoi(line)
186		if err != nil || index <= 0 || index > len(targets) {
187			fmt.Println("invalid input")
188			continue
189		}
190
191		return targets[index-1], nil
192	}
193}
194
195func promptName(repo repository.RepoConfig) (string, error) {
196	// TODO: use the reusable prompt from the input package
197	const defaultName = "default"
198
199	defaultExist := core.BridgeExist(repo, defaultName)
200
201	for {
202		if defaultExist {
203			fmt.Printf("name: ")
204		} else {
205			fmt.Printf("name [%s]: ", defaultName)
206		}
207
208		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
209		if err != nil {
210			return "", err
211		}
212
213		line = strings.TrimSpace(line)
214
215		name := line
216		if defaultExist && name == "" {
217			continue
218		}
219
220		if name == "" {
221			name = defaultName
222		}
223
224		if !core.BridgeExist(repo, name) {
225			return name, nil
226		}
227
228		fmt.Println("a bridge with the same name already exist")
229	}
230}