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