Skip to content

Commit

Permalink
Implemented aws.ecs.* and aws.logs.* resource attributes
Browse files Browse the repository at this point in the history
in go.opentelemetry.io/detectors/aws/ecs for ECS Metadata v4
  • Loading branch information
Michele Mancioppi committed Sep 17, 2022
1 parent c4876c3 commit f379b94
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 9 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]

### Added

- Retrieve the [`aws.ecs.*`](https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud_provider/aws/ecs/) and [`aws.logs.*`](https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud_provider/aws/logs/) resource attributes in `go.opentelemetry.io/detectors/aws/ecs` based on the Amazon ECS Metadata v4 endpoint.

## [1.10.0/0.35.0/0.5.0]

### Changed
Expand Down
49 changes: 44 additions & 5 deletions detectors/aws/ecs/ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ package ecs // import "go.opentelemetry.io/contrib/detectors/aws/ecs"
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"strings"

ecsmetadata "github.com/brunoscheufler/aws-ecs-metadata-go"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
Expand All @@ -35,10 +39,12 @@ const (
)

var (
empty = resource.Empty()
errCannotReadContainerID = errors.New("failed to read container ID from cGroupFile")
errCannotReadContainerName = errors.New("failed to read hostname")
errCannotReadCGroupFile = errors.New("ECS resource detector failed to read cGroupFile")
empty = resource.Empty()
errCannotReadContainerID = errors.New("failed to read container ID from cGroupFile")
errCannotReadContainerName = errors.New("failed to read hostname")
errCannotReadCGroupFile = errors.New("ECS resource detector failed to read cGroupFile")
errCannotRetrieveMetadataV4 = errors.New("ECS resource detector failed to retrieve metadata from the ECS Metada v4 container endpoint")
errCannotRetrieveMetadataV4Task = errors.New("ECS resource detector failed to retrieve metadata from the ECS Metada v4 task endpoint")
)

// Create interface for methods needing to be mocked.
Expand All @@ -63,7 +69,9 @@ var _ resource.Detector = (*resourceDetector)(nil)

// NewResourceDetector returns a resource detector that will detect AWS ECS resources.
func NewResourceDetector() resource.Detector {
return &resourceDetector{utils: ecsDetectorUtils{}}
return &resourceDetector{
utils: ecsDetectorUtils{},
}
}

// Detect finds associated resources when running on ECS environment.
Expand All @@ -89,6 +97,37 @@ func (detector *resourceDetector) Detect(ctx context.Context) (*resource.Resourc
semconv.ContainerIDKey.String(containerID),
}

if len(metadataURIV4) > 0 {
containerMetadata, err := ecsmetadata.GetContainerV4(ctx, &http.Client{})
if err != nil {
return empty, err
}
attributes = append(
attributes,
semconv.AWSECSContainerARNKey.String(containerMetadata.ContainerARN),
)

taskMetadata, err := ecsmetadata.GetTaskV4(ctx, &http.Client{})
if err != nil {
return empty, err
}

clusterArn := taskMetadata.Cluster
if !strings.HasPrefix(clusterArn, "arn:") {
baseArn := containerMetadata.ContainerARN[:strings.LastIndex(containerMetadata.ContainerARN, ":")]
clusterArn = fmt.Sprintf("%s:cluster/%s", baseArn, clusterArn)
}

attributes = append(
attributes,
semconv.AWSECSClusterARNKey.String(clusterArn),
semconv.AWSECSLaunchtypeKey.String(taskMetadata.LaunchType),
semconv.AWSECSTaskARNKey.String(taskMetadata.TaskARN),
semconv.AWSECSTaskFamilyKey.String(taskMetadata.Family),
semconv.AWSECSTaskRevisionKey.String(taskMetadata.Revision),
)
}

return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil
}

Expand Down
55 changes: 51 additions & 4 deletions detectors/aws/ecs/ecs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ package ecs

import (
"context"
http "net/http"
"net/http/httptest"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -42,11 +45,10 @@ func (detectorUtils *MockDetectorUtils) getContainerName() (string, error) {
return args.String(0), args.Error(1)
}

// successfully return resource when process is running on Amazon ECS environment.
func TestDetect(t *testing.T) {
// succesfully return resource when process is running on Amazon ECS environment with no Metadata v4.
func TestDetectV3(t *testing.T) {
os.Clearenv()
_ = os.Setenv(metadataV3EnvVar, "3")
_ = os.Setenv(metadataV4EnvVar, "4")

detectorUtils := new(MockDetectorUtils)

Expand All @@ -63,7 +65,52 @@ func TestDetect(t *testing.T) {
detector := &resourceDetector{utils: detectorUtils}
res, _ := detector.Detect(context.Background())

assert.Equal(t, res, expectedResource, "Resource returned is incorrect")
assert.Equal(t, expectedResource, res, "Resource returned is incorrect")
}

// succesfully return resource when process is running on Amazon ECS environment with Metadata v4.
func TestDetectV4(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
if strings.HasSuffix(req.URL.String(), "/task") {
content, err := os.ReadFile("testdata/metadatav4-response-task.json")
if err == nil {
res.Write(content)
}
} else {
content, err := os.ReadFile("testdata/metadatav4-response-container.json")
if err == nil {
res.Write(content)
}
}
}))
defer func() { testServer.Close() }()

os.Clearenv()
_ = os.Setenv(metadataV3EnvVar, "3")
_ = os.Setenv(metadataV4EnvVar, testServer.URL)

detectorUtils := new(MockDetectorUtils)

detectorUtils.On("getContainerName").Return("container-Name", nil)
detectorUtils.On("getContainerID").Return("0123456789A", nil)

attributes := []attribute.KeyValue{
semconv.CloudProviderAWS,
semconv.CloudPlatformAWSECS,
semconv.ContainerNameKey.String("container-Name"),
semconv.ContainerIDKey.String("0123456789A"),
semconv.AWSECSContainerARNKey.String("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"),
semconv.AWSECSClusterARNKey.String("arn:aws:ecs:us-west-2:111122223333:cluster/default"),
semconv.AWSECSLaunchtypeKey.String("EC2"),
semconv.AWSECSTaskARNKey.String("arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c"),
semconv.AWSECSTaskFamilyKey.String("curltest"),
semconv.AWSECSTaskRevisionKey.String("26"),
}
expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...)
detector := &resourceDetector{utils: detectorUtils}
res, _ := detector.Detect(context.Background())

assert.Equal(t, expectedResource, res, "Resource returned is incorrect")
}

// returns empty resource when detector cannot read container ID.
Expand Down
1 change: 1 addition & 0 deletions detectors/aws/ecs/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module go.opentelemetry.io/contrib/detectors/aws/ecs
go 1.17

require (
github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20220806202253-1e93a1d357d6
github.com/stretchr/testify v1.8.0
go.opentelemetry.io/otel v1.10.0
go.opentelemetry.io/otel/sdk v1.10.0
Expand Down
2 changes: 2 additions & 0 deletions detectors/aws/ecs/go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20220806202253-1e93a1d357d6 h1:Izqf3e8tWDmFCnSRdmtej33tKPWlPYIvPPXqcI9qimM=
github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20220806202253-1e93a1d357d6/go.mod h1:5BBxNAuQFkDjY3l9UHlJ8NnnM6NEi0bocZR2xc3xGxU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
44 changes: 44 additions & 0 deletions detectors/aws/ecs/testdata/metadatav4-response-container.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"DockerId": "ea32192c8553fbff06c9340478a2ff089b2bb5646fb718b4ee206641c9086d66",
"Name": "curl",
"DockerName": "ecs-curltest-24-curl-cca48e8dcadd97805600",
"Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest",
"ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553",
"Labels": {
"com.amazonaws.ecs.cluster": "default",
"com.amazonaws.ecs.container-name": "curl",
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/8f03e41243824aea923aca126495f665",
"com.amazonaws.ecs.task-definition-family": "curltest",
"com.amazonaws.ecs.task-definition-version": "24"
},
"DesiredStatus": "RUNNING",
"KnownStatus": "RUNNING",
"Limits": {
"CPU": 10,
"Memory": 128
},
"CreatedAt": "2020-10-02T00:15:07.620912337Z",
"StartedAt": "2020-10-02T00:15:08.062559351Z",
"Type": "NORMAL",
"LogDriver": "awslogs",
"LogOptions": {
"awslogs-create-group": "true",
"awslogs-group": "/ecs/metadata",
"awslogs-region": "us-west-2",
"awslogs-stream": "ecs/curl/8f03e41243824aea923aca126495f665"
},
"ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9",
"Networks": [
{
"NetworkMode": "awsvpc",
"IPv4Addresses": [
"10.0.2.100"
],
"AttachmentIndex": 0,
"MACAddress": "0e:9e:32:c7:48:85",
"IPv4SubnetCIDRBlock": "10.0.2.0/24",
"PrivateDNSName": "ip-10-0-2-100.us-west-2.compute.internal",
"SubnetGatewayIpv4Address": "10.0.2.1/24"
}
]
}
94 changes: 94 additions & 0 deletions detectors/aws/ecs/testdata/metadatav4-response-task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"Cluster": "default",
"TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
"Family": "curltest",
"Revision": "26",
"DesiredStatus": "RUNNING",
"KnownStatus": "RUNNING",
"PullStartedAt": "2020-10-02T00:43:06.202617438Z",
"PullStoppedAt": "2020-10-02T00:43:06.31288465Z",
"AvailabilityZone": "us-west-2d",
"LaunchType": "EC2",
"Containers": [
{
"DockerId": "598cba581fe3f939459eaba1e071d5c93bb2c49b7d1ba7db6bb19deeb70d8e38",
"Name": "~internal~ecs~pause",
"DockerName": "ecs-curltest-26-internalecspause-e292d586b6f9dade4a00",
"Image": "amazon/amazon-ecs-pause:0.1.0",
"ImageID": "",
"Labels": {
"com.amazonaws.ecs.cluster": "default",
"com.amazonaws.ecs.container-name": "~internal~ecs~pause",
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
"com.amazonaws.ecs.task-definition-family": "curltest",
"com.amazonaws.ecs.task-definition-version": "26"
},
"DesiredStatus": "RESOURCES_PROVISIONED",
"KnownStatus": "RESOURCES_PROVISIONED",
"Limits": {
"CPU": 0,
"Memory": 0
},
"CreatedAt": "2020-10-02T00:43:05.602352471Z",
"StartedAt": "2020-10-02T00:43:06.076707576Z",
"Type": "CNI_PAUSE",
"Networks": [
{
"NetworkMode": "awsvpc",
"IPv4Addresses": [
"10.0.2.61"
],
"AttachmentIndex": 0,
"MACAddress": "0e:10:e2:01:bd:91",
"IPv4SubnetCIDRBlock": "10.0.2.0/24",
"PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal",
"SubnetGatewayIpv4Address": "10.0.2.1/24"
}
]
},
{
"DockerId": "ee08638adaaf009d78c248913f629e38299471d45fe7dc944d1039077e3424ca",
"Name": "curl",
"DockerName": "ecs-curltest-26-curl-a0e7dba5aca6d8cb2e00",
"Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest",
"ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553",
"Labels": {
"com.amazonaws.ecs.cluster": "default",
"com.amazonaws.ecs.container-name": "curl",
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
"com.amazonaws.ecs.task-definition-family": "curltest",
"com.amazonaws.ecs.task-definition-version": "26"
},
"DesiredStatus": "RUNNING",
"KnownStatus": "RUNNING",
"Limits": {
"CPU": 10,
"Memory": 128
},
"CreatedAt": "2020-10-02T00:43:06.326590752Z",
"StartedAt": "2020-10-02T00:43:06.767535449Z",
"Type": "NORMAL",
"LogDriver": "awslogs",
"LogOptions": {
"awslogs-create-group": "true",
"awslogs-group": "/ecs/metadata",
"awslogs-region": "us-west-2",
"awslogs-stream": "ecs/curl/158d1c8083dd49d6b527399fd6414f5c"
},
"ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/abb51bdd-11b4-467f-8f6c-adcfe1fe059d",
"Networks": [
{
"NetworkMode": "awsvpc",
"IPv4Addresses": [
"10.0.2.61"
],
"AttachmentIndex": 0,
"MACAddress": "0e:10:e2:01:bd:91",
"IPv4SubnetCIDRBlock": "10.0.2.0/24",
"PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal",
"SubnetGatewayIpv4Address": "10.0.2.1/24"
}
]
}
]
}

0 comments on commit f379b94

Please sign in to comment.