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 PostRunE: closeBackend(env),
90 RunE: func(cmd *cobra.Command, args []string) error {
91 return runBridgeConfigure(env, options)
92 },
93 }
94
95 flags := cmd.Flags()
96 flags.SortFlags = false
97
98 flags.StringVarP(&options.name, "name", "n", "", "A distinctive name to identify the bridge")
99 flags.StringVarP(&options.target, "target", "t", "",
100 fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(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}