Skip to content

Commit

Permalink
[aws detector] Remove error when instance metadata is unavailable
Browse files Browse the repository at this point in the history
> Note the failure to detect any resource information MUST NOT be
>  considered an error

When instance metadata is not available, this should not be treated
 as an error.

Small refactoring required to allow tests to mock the client, which
 should be _mostly_ non-impacting for consumers.
  • Loading branch information
dackroyd committed Oct 14, 2020
1 parent fc663ca commit 8807fae
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

## Fixed

- `/detectors/aws` no longer fails if instance metadata is not available (e.g. not running in AWS) (#401)

## [0.13.0] - 2020-10-09

## Added
Expand Down
28 changes: 23 additions & 5 deletions detectors/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package aws

import (
"context"
"errors"

"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
Expand All @@ -27,21 +26,27 @@ import (
)

// AWS collects resource information of AWS computing instances
type AWS struct{}
type AWS struct {
c client
}

type client interface {
Available() bool
GetInstanceIdentityDocument() (ec2metadata.EC2InstanceIdentityDocument, error)
}

// compile time assertion that AWS implements the resource.Detector interface.
var _ resource.Detector = (*AWS)(nil)

// Detect detects associated resources when running in AWS environment.
func (aws *AWS) Detect(ctx context.Context) (*resource.Resource, error) {
session, err := session.NewSession()
client, err := aws.client()
if err != nil {
return nil, err
}

client := ec2metadata.New(session)
if !client.Available() {
return nil, errors.New("unavailable EC2 client")
return nil, nil
}

doc, err := client.GetInstanceIdentityDocument()
Expand All @@ -58,3 +63,16 @@ func (aws *AWS) Detect(ctx context.Context) (*resource.Resource, error) {

return resource.New(labels...), nil
}

func (aws *AWS) client() (client, error) {
if aws.c != nil {
return aws.c, nil
}

s, err := session.NewSession()
if err != nil {
return nil, err
}

return ec2metadata.New(s), nil
}
118 changes: 118 additions & 0 deletions detectors/aws/aws_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aws

import (
"context"
"errors"
"testing"
"time"

"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/semconv"
)

func TestAWS_Detect(t *testing.T) {
type fields struct {
Client client
}

type want struct {
Error string
Resource *resource.Resource
}

testTable := map[string]struct {
Fields fields
Want want
}{
"Unavailable": {
Fields: fields{Client: &clientMock{}},
},
"Instance ID Error": {
Fields: fields{
Client: &clientMock{available: true, idDoc: func() (ec2metadata.EC2InstanceIdentityDocument, error) {
return ec2metadata.EC2InstanceIdentityDocument{}, errors.New("id not available")
}},
},
Want: want{Error: "id not available"},
},
"Instance ID Available": {
Fields: fields{
Client: &clientMock{available: true, idDoc: func() (ec2metadata.EC2InstanceIdentityDocument, error) {
// Example from https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
doc := ec2metadata.EC2InstanceIdentityDocument{
MarketplaceProductCodes: []string{"1abc2defghijklm3nopqrs4tu"},
AvailabilityZone: "us-west-2b",
PrivateIP: "10.158.112.84",
Version: "2017-09-30",
Region: "us-west-2",
InstanceID: "i-1234567890abcdef0",
InstanceType: "t2.micro",
AccountID: "123456789012",
PendingTime: time.Date(2016, time.November, 19, 16, 32, 11, 0, time.UTC),
ImageID: "ami-5fb8c835",
Architecture: "x86_64",
}

return doc, nil
}},
},
Want: want{Resource: resource.New(
semconv.CloudProviderAWS,
semconv.CloudRegionKey.String("us-west-2"),
semconv.CloudAccountIDKey.String("123456789012"),
semconv.HostIDKey.String("i-1234567890abcdef0"),
)},
},
}

for name, tt := range testTable {
tt := tt

t.Run(name, func(t *testing.T) {
t.Parallel()

aws := AWS{c: tt.Fields.Client}

r, err := aws.Detect(context.Background())

if tt.Want.Error != "" {
require.EqualError(t, err, tt.Want.Error, "Error")
return
}

require.NoError(t, err, "Error")
assert.Equal(t, tt.Want.Resource, r, "Resource")
})
}
}

type clientMock struct {
available bool
idDoc func() (ec2metadata.EC2InstanceIdentityDocument, error)
}

func (c *clientMock) Available() bool {
return c.available
}

func (c *clientMock) GetInstanceIdentityDocument() (ec2metadata.EC2InstanceIdentityDocument, error) {
return c.idDoc()
}
1 change: 1 addition & 0 deletions detectors/aws/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.14

require (
github.com/aws/aws-sdk-go v1.35.7
github.com/stretchr/testify v1.6.1
go.opentelemetry.io/otel v0.13.0
go.opentelemetry.io/otel/sdk v0.13.0
)

0 comments on commit 8807fae

Please sign in to comment.