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}