Skip to content
Client configuration

Client configuration

Every setting is a functional option of NewClient (and of NewClientFromEnv). Their full list is in the API reference.

Timeout

The HTTP client managed by the SDK times requests out after 30 seconds by default:

client, err := sonar.NewClient(nil,
	sonar.WithBaseURL("https://sonarqube.example.com/api/"),
	sonar.WithToken("my-token"),
	sonar.WithTimeout(time.Minute),
)
if err != nil {
	panic(err)
}

Example on pkg.go.dev

HTTP client

To share an HTTP client with the rest of an application, pass it with WithHTTPClient. The timeout of that client then applies, WithTimeout is ignored.

httpClient := &http.Client{Timeout: time.Minute}

client, err := sonar.NewClient(nil,
	sonar.WithBaseURL("https://sonarqube.example.com/api/"),
	sonar.WithToken("my-token"),
	sonar.WithHTTPClient(httpClient),
)
if err != nil {
	panic(err)
}

Example on pkg.go.dev

Transport

WithTransportConfig tunes the connection pool and the TLS settings of the SDK-managed transport:

client, err := sonar.NewClient(nil,
	sonar.WithBaseURL("https://sonarqube.example.com/api/"),
	sonar.WithToken("my-token"),
	sonar.WithTransportConfig(sonar.TransportConfig{
		TLSClientConfig:     &tls.Config{MinVersion: tls.VersionTLS12},
		MaxIdleConns:        20,
		IdleConnTimeout:     90 * time.Second,
		TLSHandshakeTimeout: 10 * time.Second,
	}),
)
if err != nil {
	panic(err)
}

Example on pkg.go.dev

Never set InsecureSkipVerify outside of a trusted local instance: it disables the verification of the server certificate, which exposes the token and the source code metadata exchanged with SonarQube to man-in-the-middle attacks.

Retries

Retries are disabled by default. WithRetry enables them, with exponential backoff and full jitter, for the listed status codes and for network errors:

client, err := sonar.NewClient(nil,
	sonar.WithBaseURL("https://sonarqube.example.com/api/"),
	sonar.WithToken("my-token"),
	sonar.WithRetry(sonar.RetryOptions{
		MaxAttempts:          3,
		InitialDelay:         100 * time.Millisecond,
		MaxDelay:             2 * time.Second,
		RetryableStatusCodes: []int{502, 503, 504},
	}),
)
if err != nil {
	panic(err)
}

Example on pkg.go.dev

Only idempotent methods (GET, HEAD, PUT, DELETE, …) are retried. RetryNonIdempotent extends retries to POST and PATCH: enable it only for endpoints that are safe to repeat, as a retried request can otherwise create a resource twice.

Two options bound how long a request can take when the server misbehaves:

  • RetryableError chooses which transport errors are retried, all of them when unset. Each attempt can take as long as the transport’s own timeouts allow, so a server that stops answering costs that much per attempt. Returning false for timeouts fails the request after the first one:

    sonar.WithRetry(sonar.RetryOptions{
    	MaxAttempts:          3,
    	InitialDelay:         500 * time.Millisecond,
    	MaxDelay:             5 * time.Second,
    	RetryableStatusCodes: []int{502, 503, 504},
    	RetryableError: func(err error) bool {
    		var netErr net.Error
    
    		return !errors.As(err, &netErr) || !netErr.Timeout()
    	},
    })
  • MaxRetryAfter limits the Retry-After wait honoured on a 429, which MaxDelay does not cap. A response asking for longer is returned to the caller right away, rather than retried early or waited out. There is no limit when unset.

Middleware and observability

WithMiddleware wraps the transport with http.RoundTripper middleware, the place for request logging, metrics or tracing. Middleware sits inside the retry layer, so it observes every attempt, not only the first one:

loggingMiddleware := func(next http.RoundTripper) http.RoundTripper {
	return roundTripperFunc(func(req *http.Request) (*http.Response, error) {
		start := time.Now()
		resp, err := next.RoundTrip(req)
		log.Printf("sonar %s %s (%s)", req.Method, req.URL.Path, time.Since(start))

		return resp, err
	})
}

client, err := sonar.NewClient(nil,
	sonar.WithBaseURL("https://sonar.example.com/api/"),
	sonar.WithToken("my-token"),
	sonar.WithMiddleware(loggingMiddleware),
)
if err != nil {
	panic(err)
}

Example on pkg.go.dev

For OpenTelemetry tracing, wrap otelhttp.NewTransport the same way:

otelMiddleware := func(next http.RoundTripper) http.RoundTripper {
	return otelhttp.NewTransport(next)
}

client, err := sonar.NewClient(nil,
	sonar.WithBaseURL("https://sonar.example.com/api/"),
	sonar.WithToken(token),
	sonar.WithMiddleware(otelMiddleware),
)

User agent

client, err := sonar.NewClient(nil,
	sonar.WithBaseURL("https://sonarqube.example.com/api/"),
	sonar.WithToken("my-token"),
	sonar.WithUserAgent("my-tool/1.0"),
)
if err != nil {
	panic(err)
}

Example on pkg.go.dev