From 4e7ea32baf4553295ea0274c31795f2fd978622c Mon Sep 17 00:00:00 2001 From: Maxime Lagresle Date: Tue, 1 Oct 2024 09:10:37 +0200 Subject: [PATCH 1/3] log request retries --- internal/bitwarden/webapi/client.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/bitwarden/webapi/client.go b/internal/bitwarden/webapi/client.go index fde5c9f..6ed9ab6 100644 --- a/internal/bitwarden/webapi/client.go +++ b/internal/bitwarden/webapi/client.go @@ -78,10 +78,15 @@ func NewClient(serverURL string, opts ...Options) Client { o(c) } c.httpClient.Logger = nil - + c.httpClient.CheckRetry = CustomRetryPolicy return c } +func CustomRetryPolicy(ctx context.Context, resp *http.Response, err error) (bool, error) { + tflog.Trace(ctx, "retry_handler", map[string]interface{}{"status_code": resp.StatusCode, "status_message": resp.Status, "error": err}) + return retryablehttp.DefaultRetryPolicy(ctx, resp, err) +} + type client struct { deviceIdentifier string deviceName string From da8698d6c16c8fd716581b78bbaa0a64e872480c Mon Sep 17 00:00:00 2001 From: Maxime Lagresle Date: Tue, 1 Oct 2024 09:41:07 +0200 Subject: [PATCH 2/3] don't retry on timeouts --- internal/bitwarden/webapi/client.go | 63 ++++++++----------- .../bitwarden/webapi/custom_retry_policy.go | 39 ++++++++++++ internal/bitwarden/webapi/options.go | 23 +++++++ 3 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 internal/bitwarden/webapi/custom_retry_policy.go create mode 100644 internal/bitwarden/webapi/options.go diff --git a/internal/bitwarden/webapi/client.go b/internal/bitwarden/webapi/client.go index 6ed9ab6..7c7f6e7 100644 --- a/internal/bitwarden/webapi/client.go +++ b/internal/bitwarden/webapi/client.go @@ -3,13 +3,14 @@ package webapi import ( "bytes" "context" + "encoding/base64" "encoding/json" "fmt" "io" "mime/multipart" - "net/http" "net/url" "strings" + "time" "github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -47,26 +48,6 @@ type Client interface { Sync(ctx context.Context) (*SyncResponse, error) } -type Options func(c Client) - -func DisableRetries() Options { - return func(c Client) { - c.(*client).httpClient.RetryMax = 0 - } -} - -func WithCustomClient(httpClient http.Client) Options { - return func(c Client) { - c.(*client).httpClient.HTTPClient = &httpClient - } -} - -func WithDeviceIdentifier(deviceIdentifier string) Options { - return func(c Client) { - c.(*client).deviceIdentifier = deviceIdentifier - } -} - func NewClient(serverURL string, opts ...Options) Client { c := &client{ deviceName: deviceName, @@ -79,14 +60,10 @@ func NewClient(serverURL string, opts ...Options) Client { } c.httpClient.Logger = nil c.httpClient.CheckRetry = CustomRetryPolicy + c.httpClient.HTTPClient.Timeout = 10 * time.Second return c } -func CustomRetryPolicy(ctx context.Context, resp *http.Response, err error) (bool, error) { - tflog.Trace(ctx, "retry_handler", map[string]interface{}{"status_code": resp.StatusCode, "status_message": resp.Status, "error": err}) - return retryablehttp.DefaultRetryPolicy(ctx, resp, err) -} - type client struct { deviceIdentifier string deviceName string @@ -304,6 +281,7 @@ func (c *client) LoginWithPassword(ctx context.Context, username, password strin if err != nil { return nil, fmt.Errorf("error preparing login with password request: %w", err) } + httpReq.Header.Add("Auth-Email", base64.StdEncoding.EncodeToString([]byte(username))) tokenResp, err := doRequest[TokenResponse](ctx, c.httpClient, httpReq) if err != nil { @@ -391,9 +369,9 @@ func (c *client) Sync(ctx context.Context) (*SyncResponse, error) { func (c *client) prepareRequest(ctx context.Context, reqMethod, reqUrl string, reqBody interface{}) (*retryablehttp.Request, error) { var httpReq *retryablehttp.Request var err error - contentType := "" - debugInfo := map[string]interface{}{"url": reqUrl, "method": reqMethod} + if reqBody != nil { + contentType := "" var bodyBytes []byte if v, ok := reqBody.(url.Values); ok { bodyBytes = []byte(v.Encode()) @@ -408,7 +386,9 @@ func (c *client) prepareRequest(ctx context.Context, reqMethod, reqUrl string, r } } httpReq, err = retryablehttp.NewRequestWithContext(ctx, reqMethod, reqUrl, bytes.NewBuffer(bodyBytes)) - debugInfo["body"] = string(bodyBytes) + if len(contentType) > 0 { + httpReq.Header.Add("Content-Type", contentType) + } } else { httpReq, err = retryablehttp.NewRequestWithContext(ctx, reqMethod, reqUrl, nil) } @@ -419,17 +399,13 @@ func (c *client) prepareRequest(ctx context.Context, reqMethod, reqUrl string, r if len(c.sessionAccessToken) > 0 { httpReq.Header.Add("authorization", fmt.Sprintf("Bearer %s", c.sessionAccessToken)) } - if len(contentType) > 0 { - httpReq.Header.Add("Content-Type", contentType) - } - - debugInfo["headers"] = httpReq.Header - tflog.Trace(ctx, "Request to Bitwarden server", debugInfo) return httpReq, nil } func doRequest[T any](ctx context.Context, httpClient *retryablehttp.Client, httpReq *retryablehttp.Request) (*T, error) { + logRequest(ctx, httpReq) + resp, err := httpClient.Do(httpReq) if err != nil { return nil, fmt.Errorf("error doing request to '%s': %w", httpReq.URL, err) @@ -458,7 +434,22 @@ func doRequest[T any](ctx context.Context, httpClient *retryablehttp.Client, htt fmt.Printf("Body to unmarshall: %s\n", string(body)) return nil, fmt.Errorf("error unmarshalling response from '%s': %w", httpReq.URL, err) } - tflog.Trace(ctx, "Response from Bitwarden server", map[string]interface{}{"url": httpReq.URL, "body": string(body)}) + tflog.Trace(ctx, "Response from Bitwarden server", map[string]interface{}{"url": httpReq.URL.RequestURI(), "body": string(body)}) return &res, nil } + +func logRequest(ctx context.Context, httpReq *retryablehttp.Request) { + bodyCopy, err := httpReq.BodyBytes() + if err != nil { + tflog.Trace(ctx, "Unable to re-read request body", map[string]interface{}{"error": err}) + } + debugInfo := map[string]interface{}{ + "url": httpReq.URL.RequestURI(), + "method": httpReq.Method, + "headers": httpReq.Header, + "body": string(bodyCopy), + } + + tflog.Trace(ctx, "Request to Bitwarden server ", debugInfo) +} diff --git a/internal/bitwarden/webapi/custom_retry_policy.go b/internal/bitwarden/webapi/custom_retry_policy.go new file mode 100644 index 0000000..9761e1e --- /dev/null +++ b/internal/bitwarden/webapi/custom_retry_policy.go @@ -0,0 +1,39 @@ +package webapi + +import ( + "context" + "net" + "net/http" + + "github.com/hashicorp/go-retryablehttp" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func CustomRetryPolicy(ctx context.Context, resp *http.Response, err error) (bool, error) { + debugInfo := map[string]interface{}{ + "error": err, + } + if resp != nil { + debugInfo["status_code"] = resp.StatusCode + debugInfo["status_message"] = resp.Status + } + + var willRetry bool + var handlerErr error + + if err != nil { + if err == context.DeadlineExceeded { + willRetry = false + } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + willRetry = false + } + } else { + willRetry, handlerErr = retryablehttp.DefaultRetryPolicy(ctx, resp, err) + } + + debugInfo["will_retry"] = willRetry + debugInfo["handler_error"] = handlerErr + tflog.Trace(ctx, "retry_handler", debugInfo) + + return willRetry, handlerErr +} diff --git a/internal/bitwarden/webapi/options.go b/internal/bitwarden/webapi/options.go new file mode 100644 index 0000000..15521ad --- /dev/null +++ b/internal/bitwarden/webapi/options.go @@ -0,0 +1,23 @@ +package webapi + +import "net/http" + +type Options func(c Client) + +func DisableRetries() Options { + return func(c Client) { + c.(*client).httpClient.RetryMax = 0 + } +} + +func WithCustomClient(httpClient http.Client) Options { + return func(c Client) { + c.(*client).httpClient.HTTPClient = &httpClient + } +} + +func WithDeviceIdentifier(deviceIdentifier string) Options { + return func(c Client) { + c.(*client).deviceIdentifier = deviceIdentifier + } +} From 4bf4965729c952ede1adb7701f7c4b3768059bfc Mon Sep 17 00:00:00 2001 From: Maxime Lagresle Date: Tue, 1 Oct 2024 10:01:03 +0200 Subject: [PATCH 3/3] add two headers --- internal/bitwarden/webapi/client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/bitwarden/webapi/client.go b/internal/bitwarden/webapi/client.go index 7c7f6e7..c12cdbc 100644 --- a/internal/bitwarden/webapi/client.go +++ b/internal/bitwarden/webapi/client.go @@ -282,6 +282,7 @@ func (c *client) LoginWithPassword(ctx context.Context, username, password strin return nil, fmt.Errorf("error preparing login with password request: %w", err) } httpReq.Header.Add("Auth-Email", base64.StdEncoding.EncodeToString([]byte(username))) + httpReq.Header.Add("Device-Type", c.deviceType) tokenResp, err := doRequest[TokenResponse](ctx, c.httpClient, httpReq) if err != nil { @@ -399,6 +400,7 @@ func (c *client) prepareRequest(ctx context.Context, reqMethod, reqUrl string, r if len(c.sessionAccessToken) > 0 { httpReq.Header.Add("authorization", fmt.Sprintf("Bearer %s", c.sessionAccessToken)) } + httpReq.Header.Add("Accept", "application/json") return httpReq, nil }