bridge_configure.go

  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
 26var bridgeExampleFilePath = "commands/bridge_configure_example.go"
 27
 28func GenBridgeConfig() error {
 29	var exampleText strings.Builder
 30	exampleText.WriteString("`")
 31	exampleText.WriteString(`# Interactive example
 32[1]: github
 33[2]: gitlab
 34[3]: jira
 35[4]: launchpad-preview
 36
 37target: 1
 38name [default]: default
 39
 40Detected projects:
 41[1]: github.com/a-hilaly/git-bug
 42[2]: github.com/MichaelMure/git-bug
 43
 44[0]: Another project
 45
 46Select option: 1
 47
 48[1]: user provided token
 49[2]: interactive token creation
 50Select option: 1
 51
 52You can generate a new token by visiting https://github.com/settings/tokens.
 53Choose 'Generate new token' and set the necessary access scope for your repository.
 54
 55The access scope depend on the type of repository.
 56Public:
 57	- 'public_repo': to be able to read public repositories
 58Private:
 59	- 'repo'       : to be able to read private repositories
 60
 61Enter token: 87cf5c03b64029f18ea5f9ca5679daa08ccbd700
 62Successfully configured bridge: default`)
 63	targets := bridge.Targets()
 64	for i, b := range targets {
 65		_, err := fmt.Fprintf(&exampleText, "# For %s\ngit bug bridge configure \\\n", strings.Title(strings.Split(b, ".")[0]))
 66		if err != nil {
 67			return err
 68		}
 69		params, err := bridge.ValidParams(b)
 70		if err != nil {
 71			return fmt.Errorf("Encountered error when getting bridge parameters: %q", err)
 72		}
 73		for _, param := range params {
 74			_, err = fmt.Fprintf(&exampleText, "    --%s=PLACEHOLDERTEXT \\\n", param)
 75			if err != nil {
 76				return err
 77			}
 78		}
 79		if i < len(targets)-1 {
 80			_, err = fmt.Fprintf(&exampleText, "\n\n")
 81			if err != nil {
 82				return err
 83			}
 84		}
 85	}
 86	exampleText.WriteString("`")
 87
 88	_ = os.Remove(bridgeExampleFilePath)
 89
 90	f, err := os.Create(bridgeExampleFilePath)
 91	if err != nil {
 92		return err
 93	}
 94	defer f.Close()
 95
 96	_, err = f.WriteString(`// Code generated by go generate; DO NOT EDIT.
 97
 98package commands
 99
100var bridgeConfigureExample =`)
101	if err != nil {
102		return err
103	}
104	_, err = f.WriteString(exampleText.String())
105	if err != nil {
106		return err
107	}
108
109	return nil
110}
111
112func newBridgeConfigureCommand() *cobra.Command {
113	env := newEnv()
114	options := bridgeConfigureOptions{}
115
116	cmd := &cobra.Command{
117		Use:   "configure",
118		Short: "Configure a new bridge.",
119		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.`,
120		Example:  bridgeConfigureExample,
121		PreRunE:  loadBackend(env),
122		PostRunE: closeBackend(env),
123		RunE: func(cmd *cobra.Command, args []string) error {
124			return runBridgeConfigure(env, options)
125		},
126	}
127
128	flags := cmd.Flags()
129	flags.SortFlags = false
130
131	flags.StringVarP(&options.name, "name", "n", "", "A distinctive name to identify the bridge")
132	flags.StringVarP(&options.target, "target", "t", "",
133		fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ",")))
134	flags.StringVarP(&options.params.URL, "url", "u", "", "The URL of the remote repository")
135	flags.StringVarP(&options.params.BaseURL, "base-url", "b", "", "The base URL of your remote issue tracker")
136	flags.StringVarP(&options.params.Login, "login", "l", "", "The login on your remote issue tracker")
137	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\")")
138	flags.StringVar(&options.token, "token", "", "A raw authentication token for the remote issue tracker")
139	flags.BoolVar(&options.tokenStdin, "token-stdin", false, "Will read the token from stdin and ignore --token")
140	flags.StringVarP(&options.params.Owner, "owner", "o", "", "The owner of the remote repository")
141	flags.StringVarP(&options.params.Project, "project", "p", "", "The name of the remote repository")
142
143	return cmd
144}
145
146func runBridgeConfigure(env *Env, opts bridgeConfigureOptions) error {
147	var err error
148
149	if (opts.tokenStdin || opts.token != "" || opts.params.CredPrefix != "") &&
150		(opts.name == "" || opts.target == "") {
151		return fmt.Errorf("you must provide a bridge name and target to configure a bridge with a credential")
152	}
153
154	// early fail
155	if opts.params.CredPrefix != "" {
156		if _, err := auth.LoadWithPrefix(env.repo, opts.params.CredPrefix); err != nil {
157			return err
158		}
159	}
160
161	switch {
162	case opts.tokenStdin:
163		reader := bufio.NewReader(os.Stdin)
164		token, err := reader.ReadString('\n')
165		if err != nil {
166			return fmt.Errorf("reading from stdin: %v", err)
167		}
168		opts.params.TokenRaw = strings.TrimSpace(token)
169	case opts.token != "":
170		opts.params.TokenRaw = opts.token
171	}
172
173	if opts.target == "" {
174		opts.target, err = promptTarget()
175		if err != nil {
176			return err
177		}
178	}
179
180	if opts.name == "" {
181		opts.name, err = promptName(env.repo)
182		if err != nil {
183			return err
184		}
185	}
186
187	b, err := bridge.NewBridge(env.backend, opts.target, opts.name)
188	if err != nil {
189		return err
190	}
191
192	err = b.Configure(opts.params)
193	if err != nil {
194		return err
195	}
196
197	env.out.Printf("Successfully configured bridge: %s\n", opts.name)
198	return nil
199}
200
201func promptTarget() (string, error) {
202	// TODO: use the reusable prompt from the input package
203	targets := bridge.Targets()
204
205	for {
206		for i, target := range targets {
207			fmt.Printf("[%d]: %s\n", i+1, target)
208		}
209		fmt.Printf("target: ")
210
211		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
212
213		if err != nil {
214			return "", err
215		}
216
217		line = strings.TrimSpace(line)
218
219		index, err := strconv.Atoi(line)
220		if err != nil || index <= 0 || index > len(targets) {
221			fmt.Println("invalid input")
222			continue
223		}
224
225		return targets[index-1], nil
226	}
227}
228
229func promptName(repo repository.RepoConfig) (string, error) {
230	// TODO: use the reusable prompt from the input package
231	const defaultName = "default"
232
233	defaultExist := core.BridgeExist(repo, defaultName)
234
235	for {
236		if defaultExist {
237			fmt.Printf("name: ")
238		} else {
239			fmt.Printf("name [%s]: ", defaultName)
240		}
241
242		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
243		if err != nil {
244			return "", err
245		}
246
247		line = strings.TrimSpace(line)
248
249		name := line
250		if defaultExist && name == "" {
251			continue
252		}
253
254		if name == "" {
255			name = defaultName
256		}
257
258		if !core.BridgeExist(repo, name) {
259			return name, nil
260		}
261
262		fmt.Println("a bridge with the same name already exist")
263	}
264}