bridge_new.go

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