Repository: hetznercloud/hcloud-go
Pull request #642 (2025-03-26 09:35:51)

fix: http transport ignored when using instrumentation
Configuration that set a custom HTTP client transport would be ignored. For example in this snippet, the `otelhttp.NewTransport` would not come into effect.

```go
httpClient := &http.Client{
	Transport: otelhttp.NewTransport(
		http.DefaultTransport,
	),
}

opts := []hcloud.ClientOption{
	hcloud.WithInstrumentation(reg),
	hcloud.WithHTTPClient(httpClient),
}

client := hcloud.NewClient(opts...)
```

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/642?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud) Report
Attention: Patch coverage is `62.50000%` with `3 lines` in your changes missing coverage. Please review.
> Project coverage is 79.18%. Comparing base [(`bdb3d26`)](https://app.codecov.io/gh/hetznercloud/hcloud-go/commit/bdb3d26a1dec87c7708c6094b1143cb409c8f48c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud) to head [(`b1cde02`)](https://app.codecov.io/gh/hetznercloud/hcloud-go/commit/b1cde02f35651467b2415828c6988be2f1c3f689?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud).

| [Files with missing lines](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/642?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud) | Patch % | Lines |
|---|---|---|
| [hcloud/internal/instrumentation/metrics.go](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/642?src=pr&el=tree&filepath=hcloud%2Finternal%2Finstrumentation%2Fmetrics.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud#diff-aGNsb3VkL2ludGVybmFsL2luc3RydW1lbnRhdGlvbi9tZXRyaWNzLmdv) | 50.00% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/642?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #642      +/-   ##
==========================================
- Coverage   79.28%   79.18%   -0.10%     
==========================================
  Files          50       50              
  Lines        4407     4411       +4     
==========================================
- Hits         3494     3493       -1     
- Misses        699      703       +4     
- Partials      214      215       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/642?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/hcloud/client.go b/hcloud/client.go
index baac293..a56defe 100644
--- a/hcloud/client.go
+++ b/hcloud/client.go
@@ -267,7 +267,7 @@ func NewClient(options ...ClientOption) *Client {
 	client.buildUserAgent()
 	if client.instrumentationRegistry != nil {
 		i := instrumentation.New("api", client.instrumentationRegistry)
-		client.httpClient.Transport = i.InstrumentedRoundTripper()
+		client.httpClient.Transport = i.InstrumentedRoundTripper(client.httpClient.Transport)
 	}
 
 	client.handler = assembleHandlerChain(client)
diff --git a/hcloud/internal/instrumentation/metrics.go b/hcloud/internal/instrumentation/metrics.go
index d706b89..4496d2b 100644
--- a/hcloud/internal/instrumentation/metrics.go
+++ b/hcloud/internal/instrumentation/metrics.go
@@ -25,7 +25,12 @@ func New(subsystemIdentifier string, instrumentationRegistry prometheus.Register
 }
 
 // InstrumentedRoundTripper returns an instrumented round tripper.
-func (i *Instrumenter) InstrumentedRoundTripper() http.RoundTripper {
+func (i *Instrumenter) InstrumentedRoundTripper(transport http.RoundTripper) http.RoundTripper {
+	// By default, http client would use DefaultTransport on nil, but we internally are relying on it being configured
+	if transport == nil {
+		transport = http.DefaultTransport
+	}
+
 	inFlightRequestsGauge := registerOrReuse(
 		i.instrumentationRegistry,
 		prometheus.NewGauge(prometheus.GaugeOpts{
@@ -60,7 +65,7 @@ func (i *Instrumenter) InstrumentedRoundTripper() http.RoundTripper {
 	return promhttp.InstrumentRoundTripperInFlight(inFlightRequestsGauge,
 		promhttp.InstrumentRoundTripperDuration(requestLatencyHistogram,
 			i.instrumentRoundTripperEndpoint(requestsPerEndpointCounter,
-				http.DefaultTransport,
+				transport,
 			),
 		),
 	)
diff --git a/hcloud/metadata/client.go b/hcloud/metadata/client.go
index 45704ca..f5ddeff 100644
--- a/hcloud/metadata/client.go
+++ b/hcloud/metadata/client.go
@@ -75,7 +75,7 @@ func NewClient(options ...ClientOption) *Client {
 
 	if client.instrumentationRegistry != nil {
 		i := instrumentation.New("metadata", client.instrumentationRegistry)
-		client.httpClient.Transport = i.InstrumentedRoundTripper()
+		client.httpClient.Transport = i.InstrumentedRoundTripper(client.httpClient.Transport)
 	}
 	return client
 }


Test patch:
diff --git a/hcloud/internal/instrumentation/metrics_test.go b/hcloud/internal/instrumentation/metrics_test.go
index a8405d5..d0d8f7f 100644
--- a/hcloud/internal/instrumentation/metrics_test.go
+++ b/hcloud/internal/instrumentation/metrics_test.go
@@ -1,6 +1,7 @@
 package instrumentation
 
 import (
+	"net/http"
 	"testing"
 
 	"github.com/prometheus/client_golang/prometheus"
@@ -12,8 +13,8 @@ func TestMultipleInstrumentedClients(t *testing.T) {
 
 	t.Run("should not panic", func(_ *testing.T) {
 		// Following code should run without panicking
-		New("test", reg).InstrumentedRoundTripper()
-		New("test", reg).InstrumentedRoundTripper()
+		New("test", reg).InstrumentedRoundTripper(http.DefaultTransport)
+		New("test", reg).InstrumentedRoundTripper(http.DefaultTransport)
 	})
 }