Skip to content

Commit

Permalink
create pkg/datadog/api and move static and full API validation here
Browse files Browse the repository at this point in the history
  • Loading branch information
jackgopack4 committed Feb 26, 2025
1 parent 6c94334 commit fd3df3c
Show file tree
Hide file tree
Showing 10 changed files with 568 additions and 83 deletions.
29 changes: 29 additions & 0 deletions .chloggen/pkg-datadog-add-api-package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: pkg/datadog

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: add pkg/datadog/api submodule; refactor online API key validation for shared Datadog components

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [38223, 38234]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: |
StaticAPIKeyCheck and FullAPIKeyCheck are now available in pkg/datadog/api for use by shared Datadog components.
The online API key validation logic has been refactored to use these new functions.
# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
31 changes: 14 additions & 17 deletions exporter/datadogexporter/logs_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/datadog/clientutil"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/datadog/hostmetadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/datadog/scrub"
pkgdatadogapi "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/datadog/api"
)

const (
Expand Down Expand Up @@ -69,23 +70,19 @@ func newLogsExporter(
// create Datadog client
// validation endpoint is provided by Metrics
errchan := make(chan error)
var metricsAPI *datadogV2.MetricsApi
if isMetricExportV2Enabled() {
apiClient := clientutil.CreateAPIClient(
params.BuildInfo,
cfg.Metrics.TCPAddrConfig.Endpoint,
cfg.ClientConfig)
go func() { errchan <- clientutil.ValidateAPIKey(ctx, string(cfg.API.Key), params.Logger, apiClient) }()
metricsAPI = datadogV2.NewMetricsApi(apiClient)
} else {
client := clientutil.CreateZorkianClient(string(cfg.API.Key), cfg.Metrics.TCPAddrConfig.Endpoint)
go func() { errchan <- clientutil.ValidateAPIKeyZorkian(params.Logger, client) }()
}
// validate the apiKey
if cfg.API.FailOnInvalidKey {
if err := <-errchan; err != nil {
return nil, err
}
metricsAPI, _, err := pkgdatadogapi.FullAPIKeyCheck(
ctx,
string(cfg.API.Key),
&errchan,
params.BuildInfo,
isMetricExportV2Enabled(),
cfg.API.FailOnInvalidKey,
cfg.Metrics.TCPAddrConfig.Endpoint,
params.Logger,
cfg.ClientConfig,
)
if err != nil {
return nil, err
}

translator, err := logsmapping.NewTranslator(params.TelemetrySettings, attributesTranslator, otelSource)
Expand Down
37 changes: 18 additions & 19 deletions exporter/datadogexporter/metrics_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/datadog/hostmetadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/datadog/scrub"
pkgdatadog "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/datadog"
pkgdatadogapi "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/datadog/api"
)

type metricsExporter struct {
Expand Down Expand Up @@ -94,27 +95,25 @@ func newMetricsExporter(
metadataReporter: metadataReporter,
gatewayUsage: gatewayUsage,
}
// Call Full API Key validation in pkg/datadog/api to validate API key and create API client
errchan := make(chan error)
if isMetricExportV2Enabled() {
apiClient := clientutil.CreateAPIClient(
params.BuildInfo,
cfg.Metrics.TCPAddrConfig.Endpoint,
cfg.ClientConfig)
go func() { errchan <- clientutil.ValidateAPIKey(ctx, string(cfg.API.Key), params.Logger, apiClient) }()
exporter.metricsAPI = datadogV2.NewMetricsApi(apiClient)
} else {
client := clientutil.CreateZorkianClient(string(cfg.API.Key), cfg.Metrics.TCPAddrConfig.Endpoint)
client.ExtraHeader["User-Agent"] = clientutil.UserAgent(params.BuildInfo)
client.HttpClient = clientutil.NewHTTPClient(cfg.ClientConfig)
go func() { errchan <- clientutil.ValidateAPIKeyZorkian(params.Logger, client) }()
exporter.client = client
}
if cfg.API.FailOnInvalidKey {
err = <-errchan
if err != nil {
return nil, err
}
metricsAPI, client, err := pkgdatadogapi.FullAPIKeyCheck(
ctx,
string(cfg.API.Key),
&errchan,
params.BuildInfo,
isMetricExportV2Enabled(),
cfg.API.FailOnInvalidKey,
cfg.Metrics.TCPAddrConfig.Endpoint,
params.Logger,
cfg.ClientConfig,
)
if err != nil {
return nil, err
}
exporter.metricsAPI = metricsAPI
exporter.client = client

return exporter, nil
}

Expand Down
41 changes: 21 additions & 20 deletions exporter/datadogexporter/traces_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/datadog/clientutil"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/datadog/hostmetadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/datadog/scrub"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/datadog"
pkgdatadog "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/datadog"
pkgdatadogapi "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/datadog/api"
)

var traceCustomHTTPFeatureGate = featuregate.GlobalRegistry().MustRegister(
Expand Down Expand Up @@ -82,23 +83,23 @@ func newTracesExporter(
}
// client to send running metric to the backend & perform API key validation
errchan := make(chan error)
if isMetricExportV2Enabled() {
apiClient := clientutil.CreateAPIClient(
params.BuildInfo,
cfg.Metrics.TCPAddrConfig.Endpoint,
cfg.ClientConfig)
go func() { errchan <- clientutil.ValidateAPIKey(ctx, string(cfg.API.Key), params.Logger, apiClient) }()
exp.metricsAPI = datadogV2.NewMetricsApi(apiClient)
} else {
client := clientutil.CreateZorkianClient(string(cfg.API.Key), cfg.Metrics.TCPAddrConfig.Endpoint)
go func() { errchan <- clientutil.ValidateAPIKeyZorkian(params.Logger, client) }()
exp.client = client
}
if cfg.API.FailOnInvalidKey {
if err := <-errchan; err != nil {
return nil, err
}
metricsAPI, client, err := pkgdatadogapi.FullAPIKeyCheck(
ctx,
string(cfg.API.Key),
&errchan,
params.BuildInfo,
isMetricExportV2Enabled(),
cfg.API.FailOnInvalidKey,
cfg.Metrics.TCPAddrConfig.Endpoint,
params.Logger,
cfg.ClientConfig,
)
if err != nil {
return nil, err
}
exp.metricsAPI = metricsAPI
exp.client = client

return exp, nil
}

Expand Down Expand Up @@ -234,7 +235,7 @@ func newTraceAgentConfig(ctx context.Context, params exporter.Settings, cfg *Con
return clientutil.NewHTTPClient(cfg.ClientConfig)
}
}
if datadog.OperationAndResourceNameV2FeatureGate.IsEnabled() {
if pkgdatadog.OperationAndResourceNameV2FeatureGate.IsEnabled() {
acfg.Features["enable_operation_and_resource_name_logic_v2"] = struct{}{}
} else {
params.Logger.Info("Please enable feature gate datadog.EnableOperationAndResourceNameV2 for improved operation and resource name logic. This feature will be enabled by default in the future - if you have Datadog monitors or alerts set on operation/resource names, you may need to migrate them to the new convention.")
Expand All @@ -251,9 +252,9 @@ func newTraceAgentConfig(ctx context.Context, params exporter.Settings, cfg *Con
if cfg.Traces.ComputeTopLevelBySpanKind {
acfg.Features["enable_otlp_compute_top_level_by_span_kind"] = struct{}{}
}
if !datadog.ReceiveResourceSpansV2FeatureGate.IsEnabled() {
if !pkgdatadog.ReceiveResourceSpansV2FeatureGate.IsEnabled() {
acfg.Features["disable_receive_resource_spans_v2"] = struct{}{}
}
tracelog.SetLogger(&datadog.Zaplogger{Logger: params.Logger}) // TODO: This shouldn't be a singleton
tracelog.SetLogger(&pkgdatadog.Zaplogger{Logger: params.Logger}) // TODO: This shouldn't be a singleton
return acfg, nil
}
84 changes: 84 additions & 0 deletions pkg/datadog/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright The OpenTelemetryAuthors
// SPDX-License-Identifier: Apache-2.0

package api // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/datadog/api"

import (
"context"
"errors"
"fmt"
"regexp"
"strings"

"github.com/DataDog/datadog-api-client-go/v2/api/datadogV2"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/datadog/clientutil"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.uber.org/zap"
zorkian "gopkg.in/zorkian/go-datadog-api.v2"
)

const (
// NonHexChars is a regex of characters that are always invalid in a Datadog API key
NonHexChars = "[^0-9a-fA-F]"
)

var (
// ErrAPIKeyFormat is returned if API key contains invalid characters
ErrAPIKeyFormat = errors.New("api::key contains invalid characters")
// ErrUnsetAPIKey is returned when the API key is not set.
ErrUnsetAPIKey = errors.New("api.key is not set")
// NonHexRegex is a regex of characters that are always invalid in a Datadog API key
NonHexRegex = regexp.MustCompile(NonHexChars)
)

// StaticAPIKeyCheck does offline validation of API Key, checking for non-empty
// and ensuring all values are valid hex characters.
func StaticAPIKeyCheck(key string) error {
if key == "" {
return ErrUnsetAPIKey
}
invalidAPIKeyChars := NonHexRegex.FindAllString(key, -1)
if len(invalidAPIKeyChars) > 0 {
return fmt.Errorf("%w: invalid characters: %s", ErrAPIKeyFormat, strings.Join(invalidAPIKeyChars, ", "))
}
return nil
}

// FullAPIKeyCheck is a helper function that validates the API key online
// and returns an API client as well as an error via a passed channel if it is invalid.
// Note: The Zorkian client portion is deprecated and will be removed in a future version
func FullAPIKeyCheck(
ctx context.Context,
apiKey string,
errchan *chan error,
buildInfo component.BuildInfo,
isMetricExportV2Enabled bool,
failOnInvalidKey bool,
endpoint string,
logger *zap.Logger,
clientConfig confighttp.ClientConfig,
) (apiClient *datadogV2.MetricsApi, zorkianClient *zorkian.Client, err error) {
if isMetricExportV2Enabled {
client := clientutil.CreateAPIClient(
buildInfo,
endpoint,
clientConfig,
)
go func() { *errchan <- clientutil.ValidateAPIKey(ctx, apiKey, logger, client) }()
apiClient = datadogV2.NewMetricsApi(client)
zorkianClient = nil
} else {
zorkianClient = clientutil.CreateZorkianClient(apiKey, endpoint)
go func() { *errchan <- clientutil.ValidateAPIKeyZorkian(logger, zorkianClient) }()
apiClient = nil
}
// validate apiKey
if failOnInvalidKey {
err = <-*errchan
if err != nil {
return nil, nil, err
}
}
return apiClient, zorkianClient, nil
}
Loading

0 comments on commit fd3df3c

Please sign in to comment.