Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[exporter/datadogexporter] Migrating datadog exporter to use aws sdk …
Browse files Browse the repository at this point in the history
…v2 (open-telemetry#36797)

<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description
Migrating datadog exporter to use aws sdk v2

<!-- Issue number (e.g. open-telemetry#1234) or full URL to issue, if applicable. -->
#### Link to tracking issue
refer to open-telemetry#36699 

<!--Describe what testing was performed and which tests were added.-->
#### Testing
passes tests.
updated mock client implementations to support the new SDK interfaces.

<!--Describe the documentation added.-->
#### Documentation
no need to update.

<!--Please delete paragraphs that you did not use before submitting.-->
LZiHaN authored Jan 10, 2025
1 parent f6f4d48 commit 32dc3be
Showing 16 changed files with 480 additions and 149 deletions.
16 changes: 14 additions & 2 deletions connector/datadogconnector/go.mod
Original file line number Diff line number Diff line change
@@ -115,7 +115,20 @@ require (
github.com/alecthomas/participle/v2 v2.1.1 // indirect
github.com/antchfx/xmlquery v1.4.3 // indirect
github.com/antchfx/xpath v1.3.3 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.28.6 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ec2 v1.196.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/briandowns/spinner v1.23.0 // indirect
@@ -165,7 +178,6 @@ require (
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
30 changes: 28 additions & 2 deletions connector/datadogconnector/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion exporter/datadogexporter/go.mod
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ require (
github.com/DataDog/opentelemetry-mapping-go/pkg/quantile v0.22.0
github.com/DataDog/sketches-go v1.4.6 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0
github.com/aws/aws-sdk-go v1.55.5
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/cenkalti/backoff/v4 v4.3.0
github.com/google/go-cmp v0.6.0
github.com/open-telemetry/opentelemetry-collector-contrib/connector/datadogconnector v0.117.0
@@ -91,6 +91,10 @@ require (
)

require (
github.com/aws/aws-sdk-go-v2 v1.32.7
github.com/aws/aws-sdk-go-v2/config v1.28.6
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22
github.com/aws/aws-sdk-go-v2/service/ec2 v1.196.0
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/datadog v0.117.0
go.opentelemetry.io/collector/component/componenttest v0.117.0
go.opentelemetry.io/collector/consumer/consumererror v0.117.0
@@ -157,6 +161,16 @@ require (
github.com/antchfx/xmlquery v1.4.3 // indirect
github.com/antchfx/xpath v1.3.3 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect
28 changes: 28 additions & 0 deletions exporter/datadogexporter/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions exporter/datadogexporter/integrationtest/go.mod
Original file line number Diff line number Diff line change
@@ -127,6 +127,20 @@ require (
github.com/antchfx/xpath v1.3.3 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.28.6 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ec2 v1.196.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/briandowns/spinner v1.23.0 // indirect
28 changes: 28 additions & 0 deletions exporter/datadogexporter/integrationtest/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 40 additions & 29 deletions exporter/datadogexporter/internal/hostmetadata/internal/ec2/ec2.go
Original file line number Diff line number Diff line change
@@ -7,14 +7,16 @@ package ec2 // import "github.com/open-telemetry/opentelemetry-collector-contrib
import (
"context"
"fmt"
"io"
"strings"
"sync"

"github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes/source"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/hostmetadata/provider"
@@ -42,31 +44,43 @@ func isDefaultHostname(hostname string) bool {

// GetHostInfo gets the hostname info from EC2 metadata
func GetHostInfo(ctx context.Context, logger *zap.Logger) (hostInfo *HostInfo) {
sess, err := session.NewSession()
hostInfo = &HostInfo{}

cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
logger.Warn("Failed to build AWS session", zap.Error(err))
logger.Warn("Failed to build AWS config", zap.Error(err))
return
}

meta := ec2metadata.New(sess)
client := imds.NewFromConfig(cfg)

if !meta.AvailableWithContext(ctx) {
logger.Debug("EC2 Metadata not available")
// Check if metadata service is available by trying to retrieve instance ID
_, err = client.GetMetadata(ctx, &imds.GetMetadataInput{
Path: "instance-id",
})
if err != nil {
logger.Debug("EC2 Metadata service is not available", zap.Error(err))
return
}

if idDoc, err := meta.GetInstanceIdentityDocumentWithContext(ctx); err == nil {
idDoc, err := client.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{})
if err == nil {
hostInfo.InstanceID = idDoc.InstanceID
} else {
logger.Warn("Failed to get EC2 instance id document", zap.Error(err))
}

if ec2Hostname, err := meta.GetMetadataWithContext(ctx, "hostname"); err == nil {
hostInfo.EC2Hostname = ec2Hostname
metadataOutput, err := client.GetMetadata(ctx, &imds.GetMetadataInput{Path: "hostname"})
if err != nil {
logger.Warn("Failed to retrieve EC2 hostname", zap.Error(err))
} else {
logger.Warn("Failed to get EC2 hostname", zap.Error(err))
defer metadataOutput.Content.Close()
hostnameBytes, readErr := io.ReadAll(metadataOutput.Content)
if readErr != nil {
logger.Warn("Failed to read EC2 hostname content", zap.Error(readErr))
} else {
hostInfo.EC2Hostname = string(hostnameBytes)
}
}

return
@@ -94,13 +108,13 @@ type Provider struct {
}

func NewProvider(logger *zap.Logger) (*Provider, error) {
sess, err := session.NewSession()
cfg, err := config.LoadDefaultConfig(context.Background())
if err != nil {
return nil, err
}
return &Provider{
logger: logger,
detector: ec2provider.NewProvider(sess),
detector: ec2provider.NewProvider(cfg),
}, nil
}

@@ -129,23 +143,20 @@ func (p *Provider) instanceTags(ctx context.Context) (*ec2.DescribeTagsOutput, e
// Similar to:
// - https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/39dbc1ac8/processor/resourcedetectionprocessor/internal/aws/ec2/ec2.go#L118-L151
// - https://github.com/DataDog/datadog-agent/blob/1b4afdd6a03e8fabcc169b924931b2bb8935dab9/pkg/util/ec2/ec2_tags.go#L104-L134
sess, err := session.NewSession(&aws.Config{
Region: aws.String(meta.Region),
})
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion(meta.Region),
)
if err != nil {
return nil, fmt.Errorf("failed to build AWS session: %w", err)
return nil, fmt.Errorf("failed to load AWS config: %w", err)
}

svc := ec2.New(sess)
return svc.DescribeTagsWithContext(ctx,
&ec2.DescribeTagsInput{
Filters: []*ec2.Filter{{
Name: aws.String("resource-id"),
Values: []*string{
aws.String(meta.InstanceID),
},
}},
})
client := ec2.NewFromConfig(cfg)
return client.DescribeTags(ctx, &ec2.DescribeTagsInput{
Filters: []types.Filter{{
Name: aws.String("resource-id"),
Values: []string{meta.InstanceID},
}},
})
}

// clusterNameFromTags gets the AWS EC2 Cluster name from the tags on an EC2 instance.
Original file line number Diff line number Diff line change
@@ -5,7 +5,8 @@ package ec2
import (
"testing"

"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
)
@@ -56,7 +57,7 @@ func TestClusterNameFromEC2Tags(t *testing.T) {
name: "missing cluster name tag",
ec2Tags: &ec2.DescribeTagsOutput{
NextToken: strp("NextToken"),
Tags: []*ec2.TagDescription{
Tags: []types.TagDescription{
{Key: strp("some key"), Value: strp("some value")},
},
},
@@ -66,7 +67,7 @@ func TestClusterNameFromEC2Tags(t *testing.T) {
name: "cluster name tag only has the prefix",
ec2Tags: &ec2.DescribeTagsOutput{
NextToken: strp("NextToken"),
Tags: []*ec2.TagDescription{
Tags: []types.TagDescription{
{Key: strp("some key"), Value: strp("some value")},
{Key: strp("kubernetes.io/cluster/"), Value: strp("some value")},
},
@@ -77,7 +78,7 @@ func TestClusterNameFromEC2Tags(t *testing.T) {
name: "cluster name is available",
ec2Tags: &ec2.DescribeTagsOutput{
NextToken: strp("NextToken"),
Tags: []*ec2.TagDescription{
Tags: []types.TagDescription{
{Key: strp("some key"), Value: strp("some value")},
{Key: strp("kubernetes.io/cluster/myclustername"), Value: strp("some value")},
},
42 changes: 32 additions & 10 deletions internal/metadataproviders/aws/ec2/metadata.go
Original file line number Diff line number Diff line change
@@ -5,37 +5,59 @@ package ec2 // import "github.com/open-telemetry/opentelemetry-collector-contrib

import (
"context"
"fmt"
"io"

"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
)

type Provider interface {
Get(ctx context.Context) (ec2metadata.EC2InstanceIdentityDocument, error)
Get(ctx context.Context) (imds.InstanceIdentityDocument, error)
Hostname(ctx context.Context) (string, error)
InstanceID(ctx context.Context) (string, error)
}

type metadataClient struct {
metadata *ec2metadata.EC2Metadata
client *imds.Client
}

var _ Provider = (*metadataClient)(nil)

func NewProvider(sess *session.Session) Provider {
func NewProvider(cfg aws.Config) Provider {
return &metadataClient{
metadata: ec2metadata.New(sess),
client: imds.NewFromConfig(cfg),
}
}

func (c *metadataClient) getMetadata(ctx context.Context, path string) (string, error) {
output, err := c.client.GetMetadata(ctx, &imds.GetMetadataInput{Path: path})
if err != nil {
return "", fmt.Errorf("failed to get %s from IMDS: %w", path, err)
}
defer output.Content.Close()

data, err := io.ReadAll(output.Content)
if err != nil {
return "", fmt.Errorf("failed to read %s response: %w", path, err)
}

return string(data), nil
}

func (c *metadataClient) InstanceID(ctx context.Context) (string, error) {
return c.metadata.GetMetadataWithContext(ctx, "instance-id")
return c.getMetadata(ctx, "instance-id")
}

func (c *metadataClient) Hostname(ctx context.Context) (string, error) {
return c.metadata.GetMetadataWithContext(ctx, "hostname")
return c.getMetadata(ctx, "hostname")
}

func (c *metadataClient) Get(ctx context.Context) (ec2metadata.EC2InstanceIdentityDocument, error) {
return c.metadata.GetInstanceIdentityDocumentWithContext(ctx)
func (c *metadataClient) Get(ctx context.Context) (imds.InstanceIdentityDocument, error) {
output, err := c.client.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{})
if err != nil {
return imds.InstanceIdentityDocument{}, fmt.Errorf("failed to get instance identity document: %w", err)
}

return output.InstanceIdentityDocument, nil
}
208 changes: 171 additions & 37 deletions internal/metadataproviders/aws/ec2/metadata_test.go
Original file line number Diff line number Diff line change
@@ -4,64 +4,198 @@
package ec2

import (
"bytes"
"context"
"fmt"
"io"
"reflect"
"testing"
"time"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/awstesting/mock"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
)

func TestMetadataProviderGetError(t *testing.T) {
type args struct {
ctx context.Context
sess *session.Session
type ImdsGetMetadataAPI interface {
GetMetadata(ctx context.Context, params *imds.GetMetadataInput, optFns ...func(*imds.Options)) (*imds.GetMetadataOutput, error)
}

type ImdsInstanceIdentityDocumentAPI interface {
GetInstanceIdentityDocument(ctx context.Context, params *imds.GetInstanceIdentityDocumentInput, optFns ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error)
}

func GetMetadataFromImds(ctx context.Context, api ImdsGetMetadataAPI, path string) ([]byte, error) {
output, err := api.GetMetadata(ctx, &imds.GetMetadataInput{
Path: path,
})
if err != nil {
return nil, err
}
defer output.Content.Close()

return io.ReadAll(output.Content)
}

func GetInstanceIdentityDocumentFromImds(ctx context.Context, api ImdsInstanceIdentityDocumentAPI) (imds.InstanceIdentityDocument, error) {
output, err := api.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{})
if err != nil {
return imds.InstanceIdentityDocument{}, err
}
tests := []struct {
name string
args args

return output.InstanceIdentityDocument, nil
}

type mockGetMetadataAPI func(ctx context.Context, params *imds.GetMetadataInput, optFns ...func(*imds.Options)) (*imds.GetMetadataOutput, error)

func (m mockGetMetadataAPI) GetMetadata(ctx context.Context, params *imds.GetMetadataInput, optFns ...func(*imds.Options)) (*imds.GetMetadataOutput, error) {
return m(ctx, params, optFns...)
}

type mockInstanceIdentityDocumentAPI func(ctx context.Context, params *imds.GetInstanceIdentityDocumentInput, optFns ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error)

func (m mockInstanceIdentityDocumentAPI) GetInstanceIdentityDocument(ctx context.Context, params *imds.GetInstanceIdentityDocumentInput, optFns ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error) {
return m(ctx, params, optFns...)
}

func TestGetMetadataFromImds(t *testing.T) {
cases := []struct {
name string
client func(t *testing.T) ImdsGetMetadataAPI
path string
expect []byte
wantErr bool
}{
{
name: "mock session",
args: args{
ctx: context.Background(),
sess: mock.Session,
name: "Successfully retrieves InstanceID metadata",
client: func(t *testing.T) ImdsGetMetadataAPI {
return mockGetMetadataAPI(func(_ context.Context, params *imds.GetMetadataInput, _ ...func(*imds.Options)) (*imds.GetMetadataOutput, error) {
t.Helper()
if e, a := "instance-id", params.Path; e != a {
t.Errorf("expected Path: %v, got: %v", e, a)
}
return &imds.GetMetadataOutput{
Content: io.NopCloser(bytes.NewReader([]byte("this is the body foo bar baz"))),
}, nil
})
},
path: "instance-id",
expect: []byte("this is the body foo bar baz"),
wantErr: false,
},
{
name: "Successfully retrieves Hostname metadata",
client: func(t *testing.T) ImdsGetMetadataAPI {
return mockGetMetadataAPI(func(_ context.Context, params *imds.GetMetadataInput, _ ...func(*imds.Options)) (*imds.GetMetadataOutput, error) {
t.Helper()
if e, a := "hostname", params.Path; e != a {
t.Errorf("expected Path: %v, got: %v", e, a)
}
return &imds.GetMetadataOutput{
Content: io.NopCloser(bytes.NewReader([]byte("this is the body foo bar baz"))),
}, nil
})
},
path: "hostname",
expect: []byte("this is the body foo bar baz"),
wantErr: false,
},
{
name: "Path is empty",
client: func(t *testing.T) ImdsGetMetadataAPI {
return mockGetMetadataAPI(func(_ context.Context, params *imds.GetMetadataInput, _ ...func(*imds.Options)) (*imds.GetMetadataOutput, error) {
t.Helper()
if params.Path == "" {
return nil, fmt.Errorf("Path cannot be empty")
}
return nil, nil
})
},
path: "",
expect: nil,
wantErr: true,
},
}
for _, tt := range tests {

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
c := NewProvider(tt.args.sess)
_, err := c.Get(tt.args.ctx)
assert.Error(t, err)
ctx := context.TODO()
content, err := GetMetadataFromImds(ctx, tt.client(t), tt.path)
if (err != nil) != tt.wantErr {
t.Fatalf("expected error: %v, got: %v", tt.wantErr, err)
}
if !tt.wantErr && !bytes.Equal(tt.expect, content) {
t.Errorf("expected content: %v, got: %v", string(tt.expect), string(content))
}
})
}
}

func TestMetadataProvider_available(t *testing.T) {
type fields struct{}
type args struct {
ctx context.Context
sess *session.Session
}
tests := []struct {
name string
fields fields
args args
want error
func TestInstanceIdentityDocumentFromImds(t *testing.T) {
cases := []struct {
name string
client func(t *testing.T) ImdsInstanceIdentityDocumentAPI
expect imds.InstanceIdentityDocument
wantErr bool
}{
{
name: "mock session",
fields: fields{},
args: args{ctx: context.Background(), sess: mock.Session},
want: nil,
name: "Successfully retrieves Instance Identity Document",
client: func(t *testing.T) ImdsInstanceIdentityDocumentAPI {
return mockInstanceIdentityDocumentAPI(func(_ context.Context, _ *imds.GetInstanceIdentityDocumentInput, _ ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error) {
t.Helper()
return &imds.GetInstanceIdentityDocumentOutput{
InstanceIdentityDocument: imds.InstanceIdentityDocument{
DevpayProductCodes: []string{"code1", "code2"},
MarketplaceProductCodes: []string{"market1"},
AvailabilityZone: "us-west-2a",
PrivateIP: "192.168.1.1",
Version: "2017-09-30",
Region: "us-west-2",
InstanceID: "i-1234567890abcdef0",
BillingProducts: []string{"prod1"},
InstanceType: "t2.micro",
AccountID: "123456789012",
PendingTime: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC),
ImageID: "ami-abcdef1234567890",
KernelID: "",
RamdiskID: "",
Architecture: "x86_64",
},
}, nil
})
},
expect: imds.InstanceIdentityDocument{
DevpayProductCodes: []string{"code1", "code2"},
MarketplaceProductCodes: []string{"market1"},
AvailabilityZone: "us-west-2a",
PrivateIP: "192.168.1.1",
Version: "2017-09-30",
Region: "us-west-2",
InstanceID: "i-1234567890abcdef0",
BillingProducts: []string{"prod1"},
InstanceType: "t2.micro",
AccountID: "123456789012",
PendingTime: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC),
ImageID: "ami-abcdef1234567890",
KernelID: "",
RamdiskID: "",
Architecture: "x86_64",
},
wantErr: false,
},
}
for _, tt := range tests {

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
c := NewProvider(tt.args.sess)
_, err := c.InstanceID(tt.args.ctx)
assert.ErrorIs(t, err, tt.want)
ctx := context.TODO()
document, err := GetInstanceIdentityDocumentFromImds(ctx, tt.client(t))
if (err != nil) != tt.wantErr {
t.Fatalf("expected error: %v, got: %v", tt.wantErr, err)
}

if !tt.wantErr {
if !reflect.DeepEqual(document, tt.expect) {
t.Errorf("expected document: %+v, got: %+v", tt.expect, document)
}
}
})
}
}
5 changes: 3 additions & 2 deletions internal/metadataproviders/go.mod
Original file line number Diff line number Diff line change
@@ -4,7 +4,8 @@ go 1.22.0

require (
github.com/Showmax/go-fqdn v1.0.0
github.com/aws/aws-sdk-go v1.55.5
github.com/aws/aws-sdk-go-v2 v1.32.7
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22
github.com/docker/docker v27.4.1+incompatible
github.com/hashicorp/consul/api v1.31.0
github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig v0.117.0
@@ -22,6 +23,7 @@ require (
require (
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.5.0 // indirect
@@ -53,7 +55,6 @@ require (
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
12 changes: 6 additions & 6 deletions internal/metadataproviders/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions processor/resourcedetectionprocessor/go.mod
Original file line number Diff line number Diff line change
@@ -6,6 +6,10 @@ require (
cloud.google.com/go/compute/metadata v0.6.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0
github.com/aws/aws-sdk-go v1.55.5
github.com/aws/aws-sdk-go-v2 v1.32.7
github.com/aws/aws-sdk-go-v2/config v1.28.6
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22
github.com/aws/aws-sdk-go-v2/service/ec2 v1.196.0
github.com/google/go-cmp v0.6.0
github.com/hashicorp/consul/api v1.31.0
github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/ecsutil v0.117.0
@@ -42,6 +46,16 @@ require (
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Showmax/go-fqdn v1.0.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.5.0 // indirect
28 changes: 28 additions & 0 deletions processor/resourcedetectionprocessor/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 20 additions & 22 deletions processor/resourcedetectionprocessor/internal/aws/ec2/ec2.go
Original file line number Diff line number Diff line change
@@ -9,10 +9,10 @@ import (
"net/http"
"regexp"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/processor"
conventions "go.opentelemetry.io/collector/semconv/v1.6.1"
@@ -32,21 +32,21 @@ const (
var _ internal.Detector = (*Detector)(nil)

type ec2ifaceBuilder interface {
buildClient(region string, client *http.Client) (ec2iface.EC2API, error)
buildClient(ctx context.Context, region string, client *http.Client) (ec2.DescribeTagsAPIClient, error)
}

type ec2ClientBuilder struct{}

func (e *ec2ClientBuilder) buildClient(region string, client *http.Client) (ec2iface.EC2API, error) {
sess, err := session.NewSession(&aws.Config{
Region: aws.String(region),
HTTPClient: client,
},
func (e *ec2ClientBuilder) buildClient(ctx context.Context, region string, client *http.Client) (ec2.DescribeTagsAPIClient, error) {
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion(region),
config.WithHTTPClient(client),
)
if err != nil {
return nil, err
}
return ec2.New(sess), nil

return ec2.NewFromConfig(cfg), nil
}

type Detector struct {
@@ -59,7 +59,7 @@ type Detector struct {

func NewDetector(set processor.Settings, dcfg internal.DetectorConfig) (internal.Detector, error) {
cfg := dcfg.(Config)
sess, err := session.NewSession()
awsConfig, err := config.LoadDefaultConfig(context.Background())
if err != nil {
return nil, err
}
@@ -69,7 +69,7 @@ func NewDetector(set processor.Settings, dcfg internal.DetectorConfig) (internal
}

return &Detector{
metadataProvider: ec2provider.NewProvider(sess),
metadataProvider: ec2provider.NewProvider(awsConfig),
tagKeyRegexes: tagKeyRegexes,
logger: set.Logger,
rb: metadata.NewResourceBuilder(cfg.ResourceAttributes),
@@ -106,12 +106,12 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem

if len(d.tagKeyRegexes) != 0 {
httpClient := getClientConfig(ctx, d.logger)
ec2Client, err := d.ec2ClientBuilder.buildClient(meta.Region, httpClient)
ec2Client, err := d.ec2ClientBuilder.buildClient(ctx, meta.Region, httpClient)
if err != nil {
d.logger.Warn("failed to build ec2 client", zap.Error(err))
return res, conventions.SchemaURL, nil
}
tags, err := fetchEC2Tags(ec2Client, meta.InstanceID, d.tagKeyRegexes)
tags, err := fetchEC2Tags(ctx, ec2Client, meta.InstanceID, d.tagKeyRegexes)
if err != nil {
d.logger.Warn("failed fetching ec2 instance tags", zap.Error(err))
} else {
@@ -132,13 +132,11 @@ func getClientConfig(ctx context.Context, logger *zap.Logger) *http.Client {
return client
}

func fetchEC2Tags(svc ec2iface.EC2API, instanceID string, tagKeyRegexes []*regexp.Regexp) (map[string]string, error) {
ec2Tags, err := svc.DescribeTags(&ec2.DescribeTagsInput{
Filters: []*ec2.Filter{{
Name: aws.String("resource-id"),
Values: []*string{
aws.String(instanceID),
},
func fetchEC2Tags(ctx context.Context, svc ec2.DescribeTagsAPIClient, instanceID string, tagKeyRegexes []*regexp.Regexp) (map[string]string, error) {
ec2Tags, err := svc.DescribeTags(ctx, &ec2.DescribeTagsInput{
Filters: []types.Filter{{
Name: aws.String("resource-id"),
Values: []string{instanceID},
}},
})
if err != nil {
68 changes: 34 additions & 34 deletions processor/resourcedetectionprocessor/internal/aws/ec2/ec2_test.go
Original file line number Diff line number Diff line change
@@ -10,9 +10,10 @@ import (
"regexp"
"testing"

"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pcommon"
@@ -26,7 +27,7 @@ import (
var errUnavailable = errors.New("ec2metadata unavailable")

type mockMetadata struct {
retIDDoc ec2metadata.EC2InstanceIdentityDocument
retIDDoc imds.InstanceIdentityDocument
retErrIDDoc error

retHostname string
@@ -39,13 +40,13 @@ var _ ec2provider.Provider = (*mockMetadata)(nil)

type mockClientBuilder struct{}

func (e *mockClientBuilder) buildClient(_ string, _ *http.Client) (ec2iface.EC2API, error) {
func (e *mockClientBuilder) buildClient(_ context.Context, _ string, _ *http.Client) (ec2.DescribeTagsAPIClient, error) {
return &mockEC2Client{}, nil
}

type mockClientBuilderError struct{}

func (e *mockClientBuilderError) buildClient(_ string, _ *http.Client) (ec2iface.EC2API, error) {
func (e *mockClientBuilderError) buildClient(_ context.Context, _ string, _ *http.Client) (ec2.DescribeTagsAPIClient, error) {
return &mockEC2ClientError{}, nil
}

@@ -56,9 +57,9 @@ func (mm mockMetadata) InstanceID(_ context.Context) (string, error) {
return "", nil
}

func (mm mockMetadata) Get(_ context.Context) (ec2metadata.EC2InstanceIdentityDocument, error) {
func (mm mockMetadata) Get(_ context.Context) (imds.InstanceIdentityDocument, error) {
if mm.retErrIDDoc != nil {
return ec2metadata.EC2InstanceIdentityDocument{}, mm.retErrIDDoc
return imds.InstanceIdentityDocument{}, mm.retErrIDDoc
}
return mm.retIDDoc, nil
}
@@ -111,36 +112,35 @@ func TestNewDetector(t *testing.T) {
}

// Define a mock client to mock connecting to an EC2 instance
type mockEC2ClientError struct {
ec2iface.EC2API
}
type mockEC2ClientError struct{}

// override the DescribeTags function to mock the output from an actual EC2 instance
func (m *mockEC2ClientError) DescribeTags(_ *ec2.DescribeTagsInput) (*ec2.DescribeTagsOutput, error) {
func (m *mockEC2ClientError) DescribeTags(_ context.Context, _ *ec2.DescribeTagsInput, _ ...func(*ec2.Options)) (*ec2.DescribeTagsOutput, error) {
return nil, errors.New("Error fetching tags")
}

type mockEC2Client struct {
ec2iface.EC2API
}
type mockEC2Client struct{}

// override the DescribeTags function to mock the output from an actual EC2 instance
func (m *mockEC2Client) DescribeTags(input *ec2.DescribeTagsInput) (*ec2.DescribeTagsOutput, error) {
if *input.Filters[0].Values[0] == "error" {
func (m *mockEC2Client) DescribeTags(_ context.Context, input *ec2.DescribeTagsInput, _ ...func(*ec2.Options)) (*ec2.DescribeTagsOutput, error) {
if len(input.Filters) > 0 && len(input.Filters[0].Values) > 0 && input.Filters[0].Values[0] == "error" {
return nil, errors.New("error")
}

tag1 := "tag1"
tag2 := "tag2"
resource1 := "resource1"
val1 := "val1"
val2 := "val2"
resourceType := "type"

return &ec2.DescribeTagsOutput{
Tags: []*ec2.TagDescription{
{Key: &tag1, ResourceId: &resource1, ResourceType: &resourceType, Value: &val1},
{Key: &tag2, ResourceId: &resource1, ResourceType: &resourceType, Value: &val2},
Tags: []ec2types.TagDescription{
{
Key: aws.String("tag1"),
ResourceId: aws.String("resource1"),
ResourceType: "type",
Value: aws.String("val1"),
},
{
Key: aws.String("tag2"),
ResourceId: aws.String("resource1"),
ResourceType: "type",
Value: aws.String("val2"),
},
},
}, nil
}
@@ -164,7 +164,7 @@ func TestDetector_Detect(t *testing.T) {
{
name: "success",
fields: fields{metadataProvider: &mockMetadata{
retIDDoc: ec2metadata.EC2InstanceIdentityDocument{
retIDDoc: imds.InstanceIdentityDocument{
Region: "us-west-2",
AccountID: "account1234",
AvailabilityZone: "us-west-2a",
@@ -194,7 +194,7 @@ func TestDetector_Detect(t *testing.T) {
{
name: "success with tags",
fields: fields{metadataProvider: &mockMetadata{
retIDDoc: ec2metadata.EC2InstanceIdentityDocument{
retIDDoc: imds.InstanceIdentityDocument{
Region: "us-west-2",
AccountID: "account1234",
AvailabilityZone: "us-west-2a",
@@ -228,7 +228,7 @@ func TestDetector_Detect(t *testing.T) {
{
name: "success without tags returned from describeTags",
fields: fields{metadataProvider: &mockMetadata{
retIDDoc: ec2metadata.EC2InstanceIdentityDocument{
retIDDoc: imds.InstanceIdentityDocument{
Region: "us-west-2",
AccountID: "account1234",
AvailabilityZone: "us-west-2a",
@@ -259,7 +259,7 @@ func TestDetector_Detect(t *testing.T) {
{
name: "endpoint not available",
fields: fields{metadataProvider: &mockMetadata{
retIDDoc: ec2metadata.EC2InstanceIdentityDocument{},
retIDDoc: imds.InstanceIdentityDocument{},
retErrIDDoc: errors.New("should not be called"),
isAvailable: false,
}},
@@ -270,7 +270,7 @@ func TestDetector_Detect(t *testing.T) {
{
name: "get fails",
fields: fields{metadataProvider: &mockMetadata{
retIDDoc: ec2metadata.EC2InstanceIdentityDocument{},
retIDDoc: imds.InstanceIdentityDocument{},
retErrIDDoc: errors.New("get failed"),
isAvailable: true,
}},
@@ -281,7 +281,7 @@ func TestDetector_Detect(t *testing.T) {
{
name: "hostname fails",
fields: fields{metadataProvider: &mockMetadata{
retIDDoc: ec2metadata.EC2InstanceIdentityDocument{},
retIDDoc: imds.InstanceIdentityDocument{},
retHostname: "",
retErrHostname: errors.New("hostname failed"),
isAvailable: true,
@@ -354,7 +354,7 @@ func TestEC2Tags(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &mockEC2Client{}
output, err := fetchEC2Tags(m, tt.resourceID, tt.tagKeyRegexes)
output, err := fetchEC2Tags(context.Background(), m, tt.resourceID, tt.tagKeyRegexes)
if tt.shouldError {
assert.Error(t, err)
return

0 comments on commit 32dc3be

Please sign in to comment.