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