From e71164060f37b03d430347b7d78f9cfe4ab1c281 Mon Sep 17 00:00:00 2001 From: Adam Connelly Date: Fri, 23 Sep 2022 19:48:43 +0200 Subject: [PATCH] Allow custom HTTP client for Connection I've added the ability to specify a custom HTTP client when creating a Connection. This is important because the various Azure DevOps clients make requests during construction to get the resource area info. This meant that some requests would be made with the default HTTP client even when specifying a custom one for the git client. --- azuredevops/client.go | 17 +++++++++++------ azuredevops/connection.go | 26 ++++++++++++++++++-------- azuredevops/connection_options.go | 20 ++++++++++++++++++++ 3 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 azuredevops/connection_options.go diff --git a/azuredevops/client.go b/azuredevops/client.go index 921af58c..46a9dd38 100644 --- a/azuredevops/client.go +++ b/azuredevops/client.go @@ -53,12 +53,17 @@ var baseUserAgent = "go/" + runtime.Version() + " (" + runtime.GOOS + " " + runt // NewClient provides an Azure DevOps client // and copies the TLS config and timeout from the supplied connection func NewClient(connection *Connection, baseUrl string) *Client { - httpClient := &http.Client{} - if connection.TlsConfig != nil { - httpClient.Transport = &http.Transport{TLSClientConfig: connection.TlsConfig} - } - if connection.Timeout != nil { - httpClient.Timeout = *connection.Timeout + var httpClient *http.Client + if connection.httpClient != nil { + httpClient = connection.httpClient + } else { + httpClient = &http.Client{} + if connection.TlsConfig != nil { + httpClient.Transport = &http.Transport{TLSClientConfig: connection.TlsConfig} + } + if connection.Timeout != nil { + httpClient.Timeout = *connection.Timeout + } } return NewClientWithOptions(connection, baseUrl, WithHTTPClient(httpClient)) diff --git a/azuredevops/connection.go b/azuredevops/connection.go index eb76f53c..7fed209a 100644 --- a/azuredevops/connection.go +++ b/azuredevops/connection.go @@ -7,6 +7,7 @@ import ( "context" "crypto/tls" "encoding/base64" + "net/http" "strings" "sync" "time" @@ -16,21 +17,29 @@ import ( // Creates a new Azure DevOps connection instance using a personal access token. func NewPatConnection(organizationUrl string, personalAccessToken string) *Connection { - authorizationString := CreateBasicAuthHeaderValue("", personalAccessToken) - organizationUrl = normalizeUrl(organizationUrl) - return &Connection{ - AuthorizationString: authorizationString, - BaseUrl: organizationUrl, - SuppressFedAuthRedirect: true, - } + return NewConnectionWithOptions(organizationUrl, WithConnectionPersonalAccessToken(personalAccessToken)) } +// NewAnonymousConnection creates a new connection without specifying any authentication. This +// is equivalent to calling NewConnectionWithOptions without specifying any options. func NewAnonymousConnection(organizationUrl string) *Connection { + return NewConnectionWithOptions(organizationUrl) +} + +// NewConnectionWithOptions creates a new connection using the specified options. See WithConnection*() functions. +func NewConnectionWithOptions(organizationUrl string, options ...ConnectionOptionFunc) *Connection { organizationUrl = normalizeUrl(organizationUrl) - return &Connection{ + connection := &Connection{ BaseUrl: organizationUrl, SuppressFedAuthRedirect: true, + httpClient: &http.Client{}, + } + + for _, fn := range options { + fn(connection) } + + return connection } type Connection struct { @@ -45,6 +54,7 @@ type Connection struct { clientCacheLock sync.RWMutex resourceAreaCache map[uuid.UUID]ResourceAreaInfo resourceAreaCacheLock sync.RWMutex + httpClient *http.Client } func CreateBasicAuthHeaderValue(username, password string) string { diff --git a/azuredevops/connection_options.go b/azuredevops/connection_options.go new file mode 100644 index 00000000..74ab8866 --- /dev/null +++ b/azuredevops/connection_options.go @@ -0,0 +1,20 @@ +package azuredevops + +import "net/http" + +type ConnectionOptionFunc func(*Connection) + +// WithConnectionHTTPClient sets the HTTP client that will be used by the connection. +func WithConnectionHTTPClient(client *http.Client) ConnectionOptionFunc { + return func(c *Connection) { + c.httpClient = client + } +} + +// WithConnectionPersonalAccessToken specifies the PAT to use for authentication with Azure DevOps. +func WithConnectionPersonalAccessToken(personalAccessToken string) ConnectionOptionFunc { + return func(c *Connection) { + authorizationString := CreateBasicAuthHeaderValue("", personalAccessToken) + c.AuthorizationString = authorizationString + } +}