Add verbose flag to api cmd (#7826)
This commit is contained in:
parent
fd7f987e58
commit
508065b72d
7 changed files with 120 additions and 117 deletions
|
|
@ -23,6 +23,7 @@ type HTTPClientOptions struct {
|
|||
EnableCache bool
|
||||
Log io.Writer
|
||||
LogColorize bool
|
||||
LogVerboseHTTP bool
|
||||
SkipAcceptHeaders bool
|
||||
}
|
||||
|
||||
|
|
@ -35,10 +36,15 @@ func NewHTTPClient(opts HTTPClientOptions) (*http.Client, error) {
|
|||
LogIgnoreEnv: true,
|
||||
}
|
||||
|
||||
if debugEnabled, debugValue := utils.IsDebugEnabled(); debugEnabled {
|
||||
debugEnabled, debugValue := utils.IsDebugEnabled()
|
||||
if strings.Contains(debugValue, "api") {
|
||||
opts.LogVerboseHTTP = true
|
||||
}
|
||||
|
||||
if opts.LogVerboseHTTP || debugEnabled {
|
||||
clientOpts.Log = opts.Log
|
||||
clientOpts.LogColorize = opts.LogColorize
|
||||
clientOpts.LogVerboseHTTP = strings.Contains(debugValue, "api")
|
||||
clientOpts.LogVerboseHTTP = opts.LogVerboseHTTP
|
||||
}
|
||||
|
||||
headers := map[string]string{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -19,16 +18,14 @@ import (
|
|||
|
||||
func TestNewHTTPClient(t *testing.T) {
|
||||
type args struct {
|
||||
config tokenGetter
|
||||
appVersion string
|
||||
setAccept bool
|
||||
config tokenGetter
|
||||
appVersion string
|
||||
setAccept bool
|
||||
logVerboseHTTP bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
envDebug string
|
||||
setGhDebug bool
|
||||
envGhDebug string
|
||||
host string
|
||||
wantHeader map[string]string
|
||||
wantStderr string
|
||||
|
|
@ -36,9 +33,10 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
{
|
||||
name: "github.com with Accept header",
|
||||
args: args{
|
||||
config: tinyConfig{"github.com:oauth_token": "MYTOKEN"},
|
||||
appVersion: "v1.2.3",
|
||||
setAccept: true,
|
||||
config: tinyConfig{"github.com:oauth_token": "MYTOKEN"},
|
||||
appVersion: "v1.2.3",
|
||||
setAccept: true,
|
||||
logVerboseHTTP: false,
|
||||
},
|
||||
host: "github.com",
|
||||
wantHeader: map[string]string{
|
||||
|
|
@ -51,9 +49,10 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
{
|
||||
name: "github.com no Accept header",
|
||||
args: args{
|
||||
config: tinyConfig{"github.com:oauth_token": "MYTOKEN"},
|
||||
appVersion: "v1.2.3",
|
||||
setAccept: false,
|
||||
config: tinyConfig{"github.com:oauth_token": "MYTOKEN"},
|
||||
appVersion: "v1.2.3",
|
||||
setAccept: false,
|
||||
logVerboseHTTP: false,
|
||||
},
|
||||
host: "github.com",
|
||||
wantHeader: map[string]string{
|
||||
|
|
@ -66,9 +65,10 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
{
|
||||
name: "github.com no authentication token",
|
||||
args: args{
|
||||
config: tinyConfig{"example.com:oauth_token": "MYTOKEN"},
|
||||
appVersion: "v1.2.3",
|
||||
setAccept: true,
|
||||
config: tinyConfig{"example.com:oauth_token": "MYTOKEN"},
|
||||
appVersion: "v1.2.3",
|
||||
setAccept: true,
|
||||
logVerboseHTTP: false,
|
||||
},
|
||||
host: "github.com",
|
||||
wantHeader: map[string]string{
|
||||
|
|
@ -81,45 +81,12 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
{
|
||||
name: "github.com in verbose mode",
|
||||
args: args{
|
||||
config: tinyConfig{"github.com:oauth_token": "MYTOKEN"},
|
||||
appVersion: "v1.2.3",
|
||||
setAccept: true,
|
||||
config: tinyConfig{"github.com:oauth_token": "MYTOKEN"},
|
||||
appVersion: "v1.2.3",
|
||||
setAccept: true,
|
||||
logVerboseHTTP: true,
|
||||
},
|
||||
host: "github.com",
|
||||
envDebug: "api",
|
||||
setGhDebug: false,
|
||||
wantHeader: map[string]string{
|
||||
"authorization": "token MYTOKEN",
|
||||
"user-agent": "GitHub CLI v1.2.3",
|
||||
"accept": "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
|
||||
},
|
||||
wantStderr: heredoc.Doc(`
|
||||
* Request at <time>
|
||||
* Request to http://<host>:<port>
|
||||
> GET / HTTP/1.1
|
||||
> Host: github.com
|
||||
> Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
|
||||
> Authorization: token ████████████████████
|
||||
> Content-Type: application/json; charset=utf-8
|
||||
> Time-Zone: <timezone>
|
||||
> User-Agent: GitHub CLI v1.2.3
|
||||
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: <time>
|
||||
|
||||
* Request took <duration>
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "github.com in verbose mode",
|
||||
args: args{
|
||||
config: tinyConfig{"github.com:oauth_token": "MYTOKEN"},
|
||||
appVersion: "v1.2.3",
|
||||
setAccept: true,
|
||||
},
|
||||
host: "github.com",
|
||||
envGhDebug: "api",
|
||||
setGhDebug: true,
|
||||
host: "github.com",
|
||||
wantHeader: map[string]string{
|
||||
"authorization": "token MYTOKEN",
|
||||
"user-agent": "GitHub CLI v1.2.3",
|
||||
|
|
@ -168,19 +135,13 @@ func TestNewHTTPClient(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv("DEBUG", tt.envDebug)
|
||||
if tt.setGhDebug {
|
||||
t.Setenv("GH_DEBUG", tt.envGhDebug)
|
||||
} else {
|
||||
os.Unsetenv("GH_DEBUG")
|
||||
}
|
||||
|
||||
ios, _, _, stderr := iostreams.Test()
|
||||
client, err := NewHTTPClient(HTTPClientOptions{
|
||||
AppVersion: tt.args.appVersion,
|
||||
Config: tt.args.config,
|
||||
Log: ios.ErrOut,
|
||||
SkipAcceptHeaders: !tt.args.setAccept,
|
||||
LogVerboseHTTP: tt.args.logVerboseHTTP,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@ import (
|
|||
)
|
||||
|
||||
type ApiOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
AppVersion string
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Branch func() (string, error)
|
||||
Config func() (config.Config, error)
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
Hostname string
|
||||
RequestMethod string
|
||||
|
|
@ -47,20 +52,16 @@ type ApiOptions struct {
|
|||
Template string
|
||||
CacheTTL time.Duration
|
||||
FilterOutput string
|
||||
|
||||
Config func() (config.Config, error)
|
||||
HttpClient func() (*http.Client, error)
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Branch func() (string, error)
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command {
|
||||
opts := ApiOptions{
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
HttpClient: f.HttpClient,
|
||||
AppVersion: f.AppVersion,
|
||||
BaseRepo: f.BaseRepo,
|
||||
Branch: f.Branch,
|
||||
Config: f.Config,
|
||||
IO: f.IOStreams,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -209,7 +210,8 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
|
|||
}
|
||||
|
||||
if err := cmdutil.MutuallyExclusive(
|
||||
"only one of `--template`, `--jq`, or `--silent` may be used",
|
||||
"only one of `--template`, `--jq`, `--silent`, or `--verbose` may be used",
|
||||
opts.Verbose,
|
||||
opts.Silent,
|
||||
opts.FilterOutput != "",
|
||||
opts.Template != "",
|
||||
|
|
@ -237,6 +239,7 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
|
|||
cmd.Flags().StringVarP(&opts.Template, "template", "t", "", "Format JSON output using a Go template; see \"gh help formatting\"")
|
||||
cmd.Flags().StringVarP(&opts.FilterOutput, "jq", "q", "", "Query to select values from the response using jq syntax")
|
||||
cmd.Flags().DurationVar(&opts.CacheTTL, "cache", 0, "Cache the response, e.g. \"3600s\", \"60m\", \"1h\"")
|
||||
cmd.Flags().BoolVar(&opts.Verbose, "verbose", false, "Include full HTTP request and response in the output")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -283,12 +286,33 @@ func apiRun(opts *ApiOptions) error {
|
|||
requestHeaders = append(requestHeaders, "Accept: "+previewNamesToMIMETypes(opts.Previews))
|
||||
}
|
||||
|
||||
httpClient, err := opts.HttpClient()
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.CacheTTL > 0 {
|
||||
httpClient = api.NewCachedHTTPClient(httpClient, opts.CacheTTL)
|
||||
|
||||
if opts.HttpClient == nil {
|
||||
opts.HttpClient = func() (*http.Client, error) {
|
||||
log := opts.IO.ErrOut
|
||||
if opts.Verbose {
|
||||
log = opts.IO.Out
|
||||
}
|
||||
opts := api.HTTPClientOptions{
|
||||
AppVersion: opts.AppVersion,
|
||||
CacheTTL: opts.CacheTTL,
|
||||
Config: cfg.Authentication(),
|
||||
EnableCache: opts.CacheTTL > 0,
|
||||
Log: log,
|
||||
LogColorize: opts.IO.ColorEnabled(),
|
||||
LogVerboseHTTP: opts.Verbose,
|
||||
SkipAcceptHeaders: true,
|
||||
}
|
||||
return api.NewHTTPClient(opts)
|
||||
}
|
||||
}
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !opts.Silent {
|
||||
|
|
@ -304,10 +328,10 @@ func apiRun(opts *ApiOptions) error {
|
|||
if opts.Silent {
|
||||
bodyWriter = io.Discard
|
||||
}
|
||||
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
if opts.Verbose {
|
||||
// httpClient handles output when verbose flag is specified.
|
||||
bodyWriter = io.Discard
|
||||
headersWriter = io.Discard
|
||||
}
|
||||
|
||||
host, _ := cfg.Authentication().DefaultHost()
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -73,6 +74,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -94,6 +96,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -115,6 +118,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -136,6 +140,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -157,6 +162,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -183,6 +189,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -209,6 +216,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -235,6 +243,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -256,6 +265,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: time.Minute * 5,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -277,6 +287,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "hello {{.name}}",
|
||||
FilterOutput: "",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -298,6 +309,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: ".name",
|
||||
Verbose: false,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
|
|
@ -316,6 +328,28 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
cli: "user --jq .foo -t '{{.foo}}'",
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "with verbose",
|
||||
cli: "user --verbose",
|
||||
wants: ApiOptions{
|
||||
Hostname: "",
|
||||
RequestMethod: "GET",
|
||||
RequestMethodPassed: false,
|
||||
RequestPath: "user",
|
||||
RequestInputFile: "",
|
||||
RawFields: []string(nil),
|
||||
MagicFields: []string(nil),
|
||||
RequestHeaders: []string(nil),
|
||||
ShowResponseHeaders: false,
|
||||
Paginate: false,
|
||||
Silent: false,
|
||||
CacheTTL: 0,
|
||||
Template: "",
|
||||
FilterOutput: "",
|
||||
Verbose: true,
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
@ -352,6 +386,7 @@ func Test_NewCmdApi(t *testing.T) {
|
|||
assert.Equal(t, tt.wants.CacheTTL, opts.CacheTTL)
|
||||
assert.Equal(t, tt.wants.Template, opts.Template)
|
||||
assert.Equal(t, tt.wants.FilterOutput, opts.FilterOutput)
|
||||
assert.Equal(t, tt.wants.Verbose, opts.Verbose)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ var ssoURLRE = regexp.MustCompile(`\burl=([^;]+)`)
|
|||
|
||||
func New(appVersion string) *cmdutil.Factory {
|
||||
f := &cmdutil.Factory{
|
||||
AppVersion: appVersion,
|
||||
Config: configFunc(), // No factory dependencies
|
||||
ExecutableName: "gh",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@ package root
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
actionsCmd "github.com/cli/cli/v2/pkg/cmd/actions"
|
||||
aliasCmd "github.com/cli/cli/v2/pkg/cmd/alias"
|
||||
"github.com/cli/cli/v2/pkg/cmd/alias/shared"
|
||||
|
|
@ -150,13 +148,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) (*cobra.Command,
|
|||
cmd.AddCommand(workflowCmd.NewCmdWorkflow(&repoResolvingCmdFactory))
|
||||
cmd.AddCommand(labelCmd.NewCmdLabel(&repoResolvingCmdFactory))
|
||||
cmd.AddCommand(cacheCmd.NewCmdCache(&repoResolvingCmdFactory))
|
||||
|
||||
// the `api` command should not inherit any extra HTTP headers
|
||||
bareHTTPCmdFactory := *f
|
||||
bareHTTPCmdFactory.HttpClient = bareHTTPClient(f, version)
|
||||
bareHTTPCmdFactory.BaseRepo = factory.SmartBaseRepoFunc(&bareHTTPCmdFactory)
|
||||
|
||||
cmd.AddCommand(apiCmd.NewCmdApi(&bareHTTPCmdFactory, nil))
|
||||
cmd.AddCommand(apiCmd.NewCmdApi(&repoResolvingCmdFactory, nil))
|
||||
|
||||
// Help topics
|
||||
var referenceCmd *cobra.Command
|
||||
|
|
@ -222,20 +214,3 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) (*cobra.Command,
|
|||
referenceCmd.SetHelpFunc(longPager(f.IOStreams))
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func bareHTTPClient(f *cmdutil.Factory, version string) func() (*http.Client, error) {
|
||||
return func() (*http.Client, error) {
|
||||
cfg, err := f.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts := api.HTTPClientOptions{
|
||||
AppVersion: version,
|
||||
Config: cfg.Authentication(),
|
||||
Log: f.IOStreams.ErrOut,
|
||||
LogColorize: f.IOStreams.ColorEnabled(),
|
||||
SkipAcceptHeaders: true,
|
||||
}
|
||||
return api.NewHTTPClient(opts)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,19 +17,20 @@ import (
|
|||
)
|
||||
|
||||
type Factory struct {
|
||||
IOStreams *iostreams.IOStreams
|
||||
Prompter prompter.Prompter
|
||||
Browser browser.Browser
|
||||
GitClient *git.Client
|
||||
|
||||
HttpClient func() (*http.Client, error)
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Remotes func() (context.Remotes, error)
|
||||
Config func() (config.Config, error)
|
||||
Branch func() (string, error)
|
||||
AppVersion string
|
||||
ExecutableName string
|
||||
|
||||
Browser browser.Browser
|
||||
ExtensionManager extensions.ExtensionManager
|
||||
ExecutableName string
|
||||
GitClient *git.Client
|
||||
IOStreams *iostreams.IOStreams
|
||||
Prompter prompter.Prompter
|
||||
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Branch func() (string, error)
|
||||
Config func() (config.Config, error)
|
||||
HttpClient func() (*http.Client, error)
|
||||
Remotes func() (context.Remotes, error)
|
||||
}
|
||||
|
||||
// Executable is the path to the currently invoked binary
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue