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}