[OpenTelemetry] Add traces to init command (#2665)

Signed-off-by: James Humphries <james@james-humphries.co.uk>
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
Co-authored-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
James Humphries
2025-04-25 12:40:48 +01:00
committed by GitHub
parent 7fa40db6af
commit d92d4f9c11
23 changed files with 476 additions and 192 deletions

View File

@@ -24,10 +24,16 @@ import (
"github.com/hashicorp/go-retryablehttp"
svchost "github.com/hashicorp/terraform-svchost"
svcauth "github.com/hashicorp/terraform-svchost/auth"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
otelAttr "go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/trace"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/httpclient"
"github.com/opentofu/opentofu/internal/logging"
"github.com/opentofu/opentofu/internal/tracing"
"github.com/opentofu/opentofu/internal/tracing/traceattrs"
"github.com/opentofu/opentofu/version"
)
@@ -82,6 +88,8 @@ func newRegistryClient(baseURL *url.URL, creds svcauth.HostCredentials) *registr
retryableClient.RequestLogHook = requestLogHook
retryableClient.ErrorHandler = maxRetryErrorHandler
retryableClient.HTTPClient.Transport = otelhttp.NewTransport(retryableClient.HTTPClient.Transport)
retryableClient.Logger = log.New(logging.LogOutput(), "", log.Flags())
return &registryClient{
@@ -99,6 +107,13 @@ func newRegistryClient(baseURL *url.URL, creds svcauth.HostCredentials) *registr
// ErrUnauthorized if the registry responds with 401 or 403 status codes, or
// ErrQueryFailed for any other protocol or operational problem.
func (c *registryClient) ProviderVersions(ctx context.Context, addr addrs.Provider) (map[string][]string, []string, error) {
ctx, span := tracing.Tracer().Start(ctx,
"List Versions",
trace.WithAttributes(
otelAttr.String(traceattrs.ProviderAddress, addr.String()),
),
)
defer span.End()
endpointPath, err := url.Parse(path.Join(addr.Namespace, addr.Type, "versions"))
if err != nil {
// Should never happen because we're constructing this from
@@ -106,6 +121,7 @@ func (c *registryClient) ProviderVersions(ctx context.Context, addr addrs.Provid
return nil, nil, err
}
endpointURL := c.baseURL.ResolveReference(endpointPath)
span.SetAttributes(semconv.URLFull(endpointURL.String()))
req, err := retryablehttp.NewRequest("GET", endpointURL.String(), nil)
if err != nil {
return nil, nil, err
@@ -115,7 +131,9 @@ func (c *registryClient) ProviderVersions(ctx context.Context, addr addrs.Provid
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, nil, c.errQueryFailed(addr, err)
errResult := c.errQueryFailed(addr, err)
tracing.SetSpanError(span, errResult)
return nil, nil, errResult
}
defer resp.Body.Close()
@@ -123,13 +141,19 @@ func (c *registryClient) ProviderVersions(ctx context.Context, addr addrs.Provid
case http.StatusOK:
// Great!
case http.StatusNotFound:
return nil, nil, ErrRegistryProviderNotKnown{
err := ErrRegistryProviderNotKnown{
Provider: addr,
}
tracing.SetSpanError(span, err)
return nil, nil, err
case http.StatusUnauthorized, http.StatusForbidden:
return nil, nil, c.errUnauthorized(addr.Hostname)
err := c.errUnauthorized(addr.Hostname)
tracing.SetSpanError(span, err)
return nil, nil, err
default:
return nil, nil, c.errQueryFailed(addr, errors.New(resp.Status))
err := c.errQueryFailed(addr, errors.New(resp.Status))
tracing.SetSpanError(span, err)
return nil, nil, err
}
// We ignore the platforms portion of the response body, because the
@@ -146,7 +170,9 @@ func (c *registryClient) ProviderVersions(ctx context.Context, addr addrs.Provid
dec := json.NewDecoder(resp.Body)
if err := dec.Decode(&body); err != nil {
return nil, nil, c.errQueryFailed(addr, err)
errResult := c.errQueryFailed(addr, err)
tracing.SetSpanError(span, errResult)
return nil, nil, errResult
}
if len(body.Versions) == 0 {
@@ -181,12 +207,23 @@ func (c *registryClient) PackageMeta(ctx context.Context, provider addrs.Provide
target.OS,
target.Arch,
))
ctx, span := tracing.Tracer().Start(ctx,
"Fetch metadata",
trace.WithAttributes(
otelAttr.String(traceattrs.ProviderAddress, provider.String()),
otelAttr.String(traceattrs.ProviderVersion, version.String()),
))
defer span.End()
if err != nil {
// Should never happen because we're constructing this from
// already-validated components.
return PackageMeta{}, err
}
endpointURL := c.baseURL.ResolveReference(endpointPath)
span.SetAttributes(
semconv.URLFull(endpointURL.String()),
)
req, err := retryablehttp.NewRequest("GET", endpointURL.String(), nil)
if err != nil {
@@ -197,6 +234,7 @@ func (c *registryClient) PackageMeta(ctx context.Context, provider addrs.Provide
resp, err := c.httpClient.Do(req)
if err != nil {
tracing.SetSpanError(span, err)
return PackageMeta{}, c.errQueryFailed(provider, err)
}
defer resp.Body.Close()
@@ -328,7 +366,7 @@ func (c *registryClient) PackageMeta(ctx context.Context, provider addrs.Provide
if shasumsURL.Scheme != "http" && shasumsURL.Scheme != "https" {
return PackageMeta{}, fmt.Errorf("registry response includes invalid SHASUMS URL: must use http or https scheme")
}
document, err := c.getFile(shasumsURL)
document, err := c.getFile(ctx, shasumsURL)
if err != nil {
return PackageMeta{}, c.errQueryFailed(
provider,
@@ -343,7 +381,7 @@ func (c *registryClient) PackageMeta(ctx context.Context, provider addrs.Provide
if signatureURL.Scheme != "http" && signatureURL.Scheme != "https" {
return PackageMeta{}, fmt.Errorf("registry response includes invalid SHASUMS signature URL: must use http or https scheme")
}
signature, err := c.getFile(signatureURL)
signature, err := c.getFile(ctx, signatureURL)
if err != nil {
return PackageMeta{}, c.errQueryFailed(
provider,
@@ -434,8 +472,14 @@ func (c *registryClient) errUnauthorized(hostname svchost.Hostname) error {
}
}
func (c *registryClient) getFile(url *url.URL) ([]byte, error) {
resp, err := c.httpClient.Get(url.String())
func (c *registryClient) getFile(ctx context.Context, url *url.URL) ([]byte, error) {
req, err := retryablehttp.NewRequest("GET", url.String(), nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}