diff --git a/client.go b/client.go index 555a888..f110250 100644 --- a/client.go +++ b/client.go @@ -11,25 +11,120 @@ package okms import ( "context" + "crypto/tls" "errors" "fmt" + "io" "net/http" + "net/http/httputil" + "os" "reflect" "strings" + "time" "github.com/google/uuid" + "github.com/hashicorp/go-retryablehttp" "github.com/ovh/okms-sdk-go/internal" "github.com/ovh/okms-sdk-go/types" ) +const DefaultHTTPClientTimeout = 30 * time.Second + // RestAPIClient is the main client to the KMS rest api. type RestAPIClient struct { inner internal.ClientWithResponsesInterface customHeaders map[string]string } -// NewRestAPIClientWithHttp creates and initialize a new HTTP connection to the KMS at url `endpoint` -// using the provided [http.Client]. +// LeveledLogger represents loggers that can be used inside the client. +type LeveledLogger retryablehttp.LeveledLogger + +// ClientConfig is used to configure Rest clients created using NewRestAPIClient(). +type ClientConfig struct { + Timeout *time.Duration + Retry *RetryConfig + Logger LeveledLogger + TlsCfg *tls.Config + Middleware func(http.RoundTripper) http.RoundTripper +} + +type RetryConfig struct { + RetryMax int + RetryWaitMin time.Duration + RetryWaitMax time.Duration +} + +type debugTransport struct { + next http.RoundTripper + out io.Writer +} + +// RoundTrip implements http.RoundTripper. +func (t *debugTransport) RoundTrip(r *http.Request) (*http.Response, error) { + data, _ := httputil.DumpRequestOut(r, true) + fmt.Fprintf(os.Stderr, "REQUEST:\n%s\n", data) + resp, err := t.next.RoundTrip(r) + if err != nil { + return resp, err + } + data, _ = httputil.DumpResponse(resp, true) + fmt.Fprintf(os.Stderr, "RESPONSE:\n%s\n", data) + return resp, nil +} + +// DebugTransport creates an http client middleware that will dump all the HTTP resquests and +// responses to the giver io.Writer. It can be passed to ClientConfig.Middleware. +func DebugTransport(out io.Writer) func(http.RoundTripper) http.RoundTripper { + return func(rt http.RoundTripper) http.RoundTripper { + if rt == nil { + rt = http.DefaultTransport + } + if out == nil { + out = os.Stderr + } + return &debugTransport{ + next: rt, + out: out, + } + } +} + +// NewRestAPIClient creates and initializes a new HTTP connection to the KMS at url `endpoint` +// using the provided client configuration. It allows configuring retries, timeouts and loggers. +func NewRestAPIClient(endpoint string, clientCfg ClientConfig) (*RestAPIClient, error) { + client := retryablehttp.NewClient() + client.HTTPClient.Timeout = DefaultHTTPClientTimeout + client.Logger = nil + + client.HTTPClient.Transport.(*http.Transport).TLSClientConfig = clientCfg.TlsCfg + if clientCfg.Logger != nil { + client.Logger = clientCfg.Logger + } + + if clientCfg.Timeout != nil { + client.HTTPClient.Timeout = *clientCfg.Timeout + } + + if clientCfg.Retry != nil { + client.RetryMax = clientCfg.Retry.RetryMax + if clientCfg.Retry.RetryWaitMin > 0 { + client.RetryWaitMin = clientCfg.Retry.RetryWaitMin + } + if clientCfg.Retry.RetryWaitMax > 0 { + client.RetryWaitMax = clientCfg.Retry.RetryWaitMax + } + } + if clientCfg.Middleware != nil { + client.HTTPClient.Transport = clientCfg.Middleware(client.HTTPClient.Transport) + } + + client.ErrorHandler = retryablehttp.PassthroughErrorHandler + + return NewRestAPIClientWithHttp(endpoint, client.StandardClient()) +} + +// NewRestAPIClientWithHttp is a lower level constructor to create and initialize a new HTTP +// connection to the KMS at url `endpoint` using the provided [http.Client]. // // The client must be configured with an appropriate tls.Config using client TLS certificates for authentication. func NewRestAPIClientWithHttp(endpoint string, c *http.Client) (*RestAPIClient, error) { diff --git a/go.mod b/go.mod index feb25d1..25cf213 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,12 @@ go 1.23.0 require ( github.com/google/uuid v1.6.0 + github.com/hashicorp/go-retryablehttp v0.7.7 github.com/oapi-codegen/runtime v1.1.1 golang.org/x/crypto v0.28.0 ) -require github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect +require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect +) diff --git a/go.sum b/go.sum index 8005216..4b0f228 100644 --- a/go.sum +++ b/go.sum @@ -5,9 +5,21 @@ github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvF github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -19,5 +31,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=