Skip to content

Commit 3ecb9de

Browse files
author
wilso199
authored
Adding a hostname flag option for use with gh api
1 parent 4b395c5 commit 3ecb9de

File tree

5 files changed

+105
-14
lines changed

5 files changed

+105
-14
lines changed

pkg/cmd/api/api.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import (
2121
"github.com/cli/cli/pkg/cmdutil"
2222
"github.com/cli/cli/pkg/iostreams"
2323
"github.com/cli/cli/pkg/jsoncolor"
24+
"github.com/cli/cli/utils"
2425
"github.com/spf13/cobra"
2526
)
2627

2728
type ApiOptions struct {
2829
IO *iostreams.IOStreams
2930

31+
Hostname string
3032
RequestMethod string
3133
RequestMethodPassed bool
3234
RequestPath string
@@ -101,7 +103,7 @@ original query accepts an '$endCursor: String' variable and that it fetches the
101103
}
102104
}
103105
'
104-
106+
105107
$ gh api graphql --paginate -f query='
106108
query($endCursor: String) {
107109
viewer {
@@ -128,6 +130,12 @@ original query accepts an '$endCursor: String' variable and that it fetches the
128130
opts.RequestPath = args[0]
129131
opts.RequestMethodPassed = c.Flags().Changed("method")
130132

133+
if c.Flags().Changed("hostname") {
134+
if err := utils.HostnameValidator(opts.Hostname); err != nil {
135+
return &cmdutil.FlagError{Err: fmt.Errorf("error parsing --hostname: %w", err)}
136+
}
137+
}
138+
131139
if opts.Paginate && !strings.EqualFold(opts.RequestMethod, "GET") && opts.RequestPath != "graphql" {
132140
return &cmdutil.FlagError{Err: errors.New(`the '--paginate' option is not supported for non-GET requests`)}
133141
}
@@ -142,6 +150,7 @@ original query accepts an '$endCursor: String' variable and that it fetches the
142150
},
143151
}
144152

153+
cmd.Flags().StringVar(&opts.Hostname, "hostname", "", "The hostname of the GitHub instance for the request")
145154
cmd.Flags().StringVarP(&opts.RequestMethod, "method", "X", "GET", "The HTTP method for the request")
146155
cmd.Flags().StringArrayVarP(&opts.MagicFields, "field", "F", nil, "Add a parameter of inferred type")
147156
cmd.Flags().StringArrayVarP(&opts.RawFields, "raw-field", "f", nil, "Add a string parameter")
@@ -206,6 +215,9 @@ func apiRun(opts *ApiOptions) error {
206215
}
207216

208217
host := ghinstance.OverridableDefault()
218+
if opts.Hostname != "" {
219+
host = opts.Hostname
220+
}
209221

210222
hasNextPage := true
211223
for hasNextPage {

pkg/cmd/api/api_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func Test_NewCmdApi(t *testing.T) {
3131
name: "no flags",
3232
cli: "graphql",
3333
wants: ApiOptions{
34+
Hostname: "",
3435
RequestMethod: "GET",
3536
RequestMethodPassed: false,
3637
RequestPath: "graphql",
@@ -48,6 +49,7 @@ func Test_NewCmdApi(t *testing.T) {
4849
name: "override method",
4950
cli: "repos/octocat/Spoon-Knife -XDELETE",
5051
wants: ApiOptions{
52+
Hostname: "",
5153
RequestMethod: "DELETE",
5254
RequestMethodPassed: true,
5355
RequestPath: "repos/octocat/Spoon-Knife",
@@ -65,6 +67,7 @@ func Test_NewCmdApi(t *testing.T) {
6567
name: "with fields",
6668
cli: "graphql -f query=QUERY -F body=@file.txt",
6769
wants: ApiOptions{
70+
Hostname: "",
6871
RequestMethod: "GET",
6972
RequestMethodPassed: false,
7073
RequestPath: "graphql",
@@ -82,6 +85,7 @@ func Test_NewCmdApi(t *testing.T) {
8285
name: "with headers",
8386
cli: "user -H 'accept: text/plain' -i",
8487
wants: ApiOptions{
88+
Hostname: "",
8589
RequestMethod: "GET",
8690
RequestMethodPassed: false,
8791
RequestPath: "user",
@@ -99,6 +103,7 @@ func Test_NewCmdApi(t *testing.T) {
99103
name: "with pagination",
100104
cli: "repos/OWNER/REPO/issues --paginate",
101105
wants: ApiOptions{
106+
Hostname: "",
102107
RequestMethod: "GET",
103108
RequestMethodPassed: false,
104109
RequestPath: "repos/OWNER/REPO/issues",
@@ -116,6 +121,7 @@ func Test_NewCmdApi(t *testing.T) {
116121
name: "with silenced output",
117122
cli: "repos/OWNER/REPO/issues --silent",
118123
wants: ApiOptions{
124+
Hostname: "",
119125
RequestMethod: "GET",
120126
RequestMethodPassed: false,
121127
RequestPath: "repos/OWNER/REPO/issues",
@@ -138,6 +144,7 @@ func Test_NewCmdApi(t *testing.T) {
138144
name: "GraphQL pagination",
139145
cli: "-XPOST graphql --paginate",
140146
wants: ApiOptions{
147+
Hostname: "",
141148
RequestMethod: "POST",
142149
RequestMethodPassed: true,
143150
RequestPath: "graphql",
@@ -160,6 +167,7 @@ func Test_NewCmdApi(t *testing.T) {
160167
name: "with request body from file",
161168
cli: "user --input myfile",
162169
wants: ApiOptions{
170+
Hostname: "",
163171
RequestMethod: "GET",
164172
RequestMethodPassed: false,
165173
RequestPath: "user",
@@ -178,10 +186,29 @@ func Test_NewCmdApi(t *testing.T) {
178186
cli: "",
179187
wantsErr: true,
180188
},
189+
{
190+
name: "with hostname",
191+
cli: "graphql --hostname tom.petty",
192+
wants: ApiOptions{
193+
Hostname: "tom.petty",
194+
RequestMethod: "GET",
195+
RequestMethodPassed: false,
196+
RequestPath: "graphql",
197+
RequestInputFile: "",
198+
RawFields: []string(nil),
199+
MagicFields: []string(nil),
200+
RequestHeaders: []string(nil),
201+
ShowResponseHeaders: false,
202+
Paginate: false,
203+
Silent: false,
204+
},
205+
wantsErr: false,
206+
},
181207
}
182208
for _, tt := range tests {
183209
t.Run(tt.name, func(t *testing.T) {
184210
cmd := NewCmdApi(f, func(o *ApiOptions) error {
211+
assert.Equal(t, tt.wants.Hostname, o.Hostname)
185212
assert.Equal(t, tt.wants.RequestMethod, o.RequestMethod)
186213
assert.Equal(t, tt.wants.RequestMethodPassed, o.RequestMethodPassed)
187214
assert.Equal(t, tt.wants.RequestPath, o.RequestPath)

pkg/cmd/auth/login/login.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm
8888
}
8989

9090
if cmd.Flags().Changed("hostname") {
91-
if err := hostnameValidator(opts.Hostname); err != nil {
91+
if err := utils.HostnameValidator(opts.Hostname); err != nil {
9292
return &cmdutil.FlagError{Err: fmt.Errorf("error parsing --hostname: %w", err)}
9393
}
9494
}
@@ -166,7 +166,7 @@ func loginRun(opts *LoginOptions) error {
166166
if isEnterprise {
167167
err := prompt.SurveyAskOne(&survey.Input{
168168
Message: "GHE hostname:",
169-
}, &hostname, survey.WithValidator(hostnameValidator))
169+
}, &hostname, survey.WithValidator(utils.HostnameValidator))
170170
if err != nil {
171171
return fmt.Errorf("could not prompt: %w", err)
172172
}
@@ -307,17 +307,6 @@ func loginRun(opts *LoginOptions) error {
307307
return nil
308308
}
309309

310-
func hostnameValidator(v interface{}) error {
311-
val := v.(string)
312-
if len(strings.TrimSpace(val)) < 1 {
313-
return errors.New("a value is required")
314-
}
315-
if strings.ContainsRune(val, '/') || strings.ContainsRune(val, ':') {
316-
return errors.New("invalid hostname")
317-
}
318-
return nil
319-
}
320-
321310
func getAccessTokenTip(hostname string) string {
322311
ghHostname := hostname
323312
if ghHostname == "" {

utils/utils.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package utils
22

33
import (
4+
"errors"
45
"fmt"
56
"io"
67
"net/url"
@@ -28,6 +29,21 @@ func OpenInBrowser(url string) error {
2829
return err
2930
}
3031

32+
func HostnameValidator(v interface{}) error {
33+
hostname, valid := v.(string)
34+
if !valid {
35+
return errors.New("hostname is not a string")
36+
}
37+
38+
if len(strings.TrimSpace(hostname)) < 1 {
39+
return errors.New("a value is required")
40+
}
41+
if strings.ContainsRune(hostname, '/') || strings.ContainsRune(hostname, ':') {
42+
return errors.New("invalid hostname")
43+
}
44+
return nil
45+
}
46+
3147
func Pluralize(num int, thing string) string {
3248
if num == 1 {
3349
return fmt.Sprintf("%d %s", num, thing)

utils/utils_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package utils
33
import (
44
"testing"
55
"time"
6+
7+
"github.com/stretchr/testify/assert"
68
)
79

810
func TestFuzzyAgo(t *testing.T) {
@@ -36,3 +38,48 @@ func TestFuzzyAgo(t *testing.T) {
3638
}
3739
}
3840
}
41+
42+
func TestHostnameValidator(t *testing.T) {
43+
tests := []struct {
44+
name string
45+
input interface{}
46+
wantsErr bool
47+
}{
48+
{
49+
name: "valid hostname",
50+
input: "internal.instance",
51+
wantsErr: false,
52+
},
53+
{
54+
name: "hostname with slashes",
55+
input: "//internal.instance",
56+
wantsErr: true,
57+
},
58+
{
59+
name: "empty hostname",
60+
input: " ",
61+
wantsErr: true,
62+
},
63+
{
64+
name: "hostname with colon",
65+
input: "internal.instance:2205",
66+
wantsErr: true,
67+
},
68+
{
69+
name: "non-string hostname",
70+
input: 62,
71+
wantsErr: true,
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
err := HostnameValidator(tt.input)
78+
if tt.wantsErr {
79+
assert.Error(t, err)
80+
return
81+
}
82+
assert.Equal(t, nil, err)
83+
})
84+
}
85+
}

0 commit comments

Comments
 (0)