Skip to content

Commit

Permalink
Add HTTP User-Agent header to all requests in SDK (#336)
Browse files Browse the repository at this point in the history
* Add user agent to all HTTP requests
* Add panic if no User-Agent is set in API call during tests
  • Loading branch information
Didainius authored Oct 7, 2020
1 parent 332c4fa commit 002215c
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
* Deprecated Vdc.UploadMediaImage because it no longer works with API V32.0+ [#330](https://github.com/vmware/go-vcloud-director/pull/330)
* Add methods `vapp.AddNewVMWithComputePolicy`, `org.GetVdcComputePolicyById`, `adminOrg.GetVdcComputePolicyById`, `org.GetAllVdcComputePolicies`, `adminOrg.GetAllVdcComputePolicies`, `adminOrg.CreateVdcComputePolicy`, `vdcComputePolicy.Update`, `vdcComputePolicy.Delete`, `adminVdc.GetAllAssignedVdcComputePolicies` and `adminVdc.SetAssignedComputePolicies` [#334] (https://github.com/vmware/go-vcloud-director/pull/334)
* Introduce NSX-T support for adminOrg.CreateOrgVdc() [#332](https://github.com/vmware/go-vcloud-director/pull/332)
* Add HTTP User-Agent header `go-vcloud-director` to all API calls and allow to customize it using
`WithHttpUserAgent` configuration options function [#336](https://github.com/vmware/go-vcloud-director/pull/336)

## 2.8.0 (June 30, 2020)

Expand Down
17 changes: 16 additions & 1 deletion govcd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ type Client struct {
// ID is used as Relaying Party Trust identifier.
CustomAdfsRptId string

// UserAgent to send for API queries. Standard format is described as:
// "User-Agent: <product> / <product-version> <comment>"
UserAgent string

supportedVersions SupportedVersions // Versions from /api/versions endpoint
}

Expand Down Expand Up @@ -212,6 +216,8 @@ func (cli *Client) newRequest(params map[string]string, notEncodedParams map[str
}
}

setHttpUserAgent(cli.UserAgent, req)

// Avoids passing data if the logging of requests is disabled
if util.LogHttpRequest {
payload := ""
Expand Down Expand Up @@ -537,7 +543,7 @@ func (client *Client) executeRequest(pathURL, requestType, contentType, errorMes
}

// ExecuteRequestWithCustomError sends the request and checks for 2xx response. If the returned status code
// was not as expected - the returned error will be unmarshaled to `errType` which implements Go's standard `error`
// was not as expected - the returned error will be unmarshalled to `errType` which implements Go's standard `error`
// interface.
func (client *Client) ExecuteRequestWithCustomError(pathURL, requestType, contentType, errorMessage string,
payload interface{}, errType error) (*http.Response, error) {
Expand Down Expand Up @@ -604,6 +610,8 @@ func executeRequestCustomErr(pathURL string, params map[string]string, requestTy
req.Header.Add("Content-Type", contentType)
}

setHttpUserAgent(client.UserAgent, req)

resp, err := client.Http.Do(req)
if err != nil {
return resp, err
Expand All @@ -612,6 +620,13 @@ func executeRequestCustomErr(pathURL string, params map[string]string, requestTy
return checkRespWithErrType(types.BodyTypeXML, resp, err, errType)
}

// setHttpUserAgent adds User-Agent string to HTTP request. When supplied string is empty - header will not be set
func setHttpUserAgent(userAgent string, req *http.Request) {
if userAgent != "" {
req.Header.Set("User-Agent", userAgent)
}
}

func isMessageWithPlaceHolder(message string) bool {
err := fmt.Errorf(message, "test error")
return !strings.Contains(err.Error(), "%!(EXTRA")
Expand Down
14 changes: 13 additions & 1 deletion govcd/api_vcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ func NewVCDClient(vcdEndpoint url.URL, insecure bool, options ...VCDClientOption
vcdClient := &VCDClient{
Client: Client{
APIVersion: "32.0", // supported by 9.7+
VCDHREF: vcdEndpoint,
// UserAgent cannot embed exact version by default because this is source code and is supposed to be used by programs,
// but any client can customize or disable it at all using WithHttpUserAgent() configuration options function.
UserAgent: "go-vcloud-director",
VCDHREF: vcdEndpoint,
Http: http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Expand Down Expand Up @@ -245,3 +248,12 @@ func WithSamlAdfs(useSaml bool, customAdfsRptId string) VCDClientOption {
return nil
}
}

// WithHttpUserAgent allows to specify HTTP user-agent which can be useful for statistics tracking.
// By default User-Agent is set to "go-vcloud-director". It can be unset by supplying empty value.
func WithHttpUserAgent(userAgent string) VCDClientOption {
return func(vcdClient *VCDClient) error {
vcdClient.Client.UserAgent = userAgent
return nil
}
}
8 changes: 8 additions & 0 deletions govcd/api_vcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,12 @@ func (vcd *TestVCD) SetUpSuite(check *C) {
}
vcd.config = config

// This library sets HTTP User-Agent to be `go-vcloud-director` by default and all HTTP calls
// expected to contain this header. An explicit test cannot capture future HTTP requests, but
// of them should use logging so this should be a good 'gate' to ensure ALL HTTP calls going out
// of this library do include HTTP User-Agent.
util.TogglePanicEmptyUserAgent(true)

if vcd.config.Logging.Enabled {
util.EnableLogging = true
if vcd.config.Logging.LogFileName != "" {
Expand Down Expand Up @@ -1584,6 +1590,8 @@ func (vcd *TestVCD) Test_NewRequestWitNotEncodedParamsWithApiVersion(check *C) {
req := vcd.client.Client.NewRequestWitNotEncodedParamsWithApiVersion(nil, map[string]string{"type": "media",
"filter": "name==any"}, http.MethodGet, queryUlr, nil, apiVersion)

check.Assert(req.Header.Get("User-Agent"), Equals, vcd.client.Client.UserAgent)

resp, err := checkResp(vcd.client.Client.Http.Do(req))
check.Assert(err, IsNil)

Expand Down
2 changes: 2 additions & 0 deletions govcd/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,8 @@ func (client *Client) newOpenApiRequest(apiVersion string, params url.Values, me
// Inject JSON mime type
req.Header.Add("Content-Type", types.JSONMime)

setHttpUserAgent(client.UserAgent, req)

// Avoids passing data if the logging of requests is disabled
if util.LogHttpRequest {
payload := ""
Expand Down
16 changes: 16 additions & 0 deletions util/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,22 @@ var (
// Flag indicating that a log file is open
// logOpen bool = false

// PanicEmptyUserAgent will panic if Request header does not have HTTP User-Agent set This
// is generally useful in tests and is off by default.
PanicEmptyUserAgent bool = false

// Text lines used for logging of http requests and responses
lineLength int = 80
dashLine string = strings.Repeat("-", lineLength)
hashLine string = strings.Repeat("#", lineLength)
)

// TogglePanicEmptyUserAgent allows to enable Panic in test if HTTP User-Agent is missing. This
// generally is useful in tests and is off by default.
func TogglePanicEmptyUserAgent(willPanic bool) {
PanicEmptyUserAgent = willPanic
}

func newLogger(logpath string) *log.Logger {
var err error
var file *os.File
Expand Down Expand Up @@ -266,6 +276,11 @@ func includeFunction(caller string) bool {

// Logs the essentials of a HTTP request
func ProcessRequestOutput(caller, operation, url, payload string, req *http.Request) {
// Special behavior for testing that all requests get HTTP User-Agent set
if PanicEmptyUserAgent && req.Header.Get("User-Agent") == "" {
panic(fmt.Sprintf("empty User-Agent detected in API call to '%s'", url))
}

if !LogHttpRequest {
return
}
Expand All @@ -286,6 +301,7 @@ func ProcessRequestOutput(caller, operation, url, payload string, req *http.Requ
}
Logger.Printf("Req header:\n")
logSanitizedHeader(req.Header)

}

// Logs the essentials of a HTTP response
Expand Down

0 comments on commit 002215c

Please sign in to comment.