Skip to content

Commit 645d23a

Browse files
authored
Add Amazon ECS Resource Detector (#466)
* add ECS resource Detector * add comments to methods * refactored naming convention and methods to use mocking client * added comments to tests * refactor env variables with more clearer names * update changelog and rename variables
1 parent d648427 commit 645d23a

File tree

6 files changed

+250
-0
lines changed

6 files changed

+250
-0
lines changed

.github/dependabot.yml

+10
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ updates:
3737
schedule:
3838
interval: "weekly"
3939
day: "sunday"
40+
-
41+
package-ecosystem: "gomod"
42+
directory: "/detectors/aws/ecs"
43+
labels:
44+
- dependencies
45+
- go
46+
- "Skip Changelog"
47+
schedule:
48+
interval: "weekly"
49+
day: "sunday"
4050
-
4151
package-ecosystem: "gomod"
4252
directory: "/detectors/gcp"

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1414

1515
- `otelhttp.{Get,Head,Post,PostForm}` convenience wrappers for their `http` counterparts. (#390)
1616
- The AWS detector now adds the cloud zone, host image ID, host type, and host name to the returned `Resource`. (#410)
17+
- Add Amazon ECS Resource Detector for AWS X-Ray. (#466)
1718

1819
### Changed
1920

detectors/aws/ecs/ecs.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ecs
16+
17+
import (
18+
"context"
19+
"errors"
20+
"io/ioutil"
21+
"os"
22+
"strings"
23+
24+
"go.opentelemetry.io/otel/label"
25+
"go.opentelemetry.io/otel/sdk/resource"
26+
"go.opentelemetry.io/otel/semconv"
27+
)
28+
29+
const (
30+
TypeStr = "ecs"
31+
metadataV3EnvVar = "ECS_CONTAINER_METADATA_URI"
32+
metadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4"
33+
containerIDLength = 64
34+
defaultCgroupPath = "/proc/self/cgroup"
35+
)
36+
37+
var (
38+
empty = resource.Empty()
39+
errCannotReadContainerID = errors.New("failed to read container ID from cGroupFile")
40+
errCannotReadCGroupFile = errors.New("ECS resource detector failed to read cGroupFile")
41+
errNotOnECS = errors.New("process is not on ECS, cannot detect environment variables from ECS")
42+
)
43+
44+
// Create interface for methods needing to be mocked
45+
type detectorUtils interface {
46+
getContainerName() (string, error)
47+
getContainerID() (string, error)
48+
}
49+
50+
// struct implements detectorUtils interface
51+
type ecsDetectorUtils struct{}
52+
53+
// resource detector collects resource information from Elastic Container Service environment
54+
type ResourceDetector struct {
55+
utils detectorUtils
56+
}
57+
58+
// compile time assertion that ecsDetectorUtils implements detectorUtils interface
59+
var _ detectorUtils = (*ecsDetectorUtils)(nil)
60+
61+
// compile time assertion that resource detector implements the resource.Detector interface.
62+
var _ resource.Detector = (*ResourceDetector)(nil)
63+
64+
// Detect finds associated resources when running on ECS environment.
65+
func (detector *ResourceDetector) Detect(ctx context.Context) (*resource.Resource, error) {
66+
metadataURIV3 := os.Getenv(metadataV3EnvVar)
67+
metadataURIV4 := os.Getenv(metadataV4EnvVar)
68+
69+
if len(metadataURIV3) == 0 && len(metadataURIV4) == 0 {
70+
return empty, errNotOnECS
71+
}
72+
hostName, err := detector.utils.getContainerName()
73+
if err != nil {
74+
return empty, err
75+
}
76+
containerID, err := detector.utils.getContainerID()
77+
if err != nil {
78+
return empty, err
79+
}
80+
labels := []label.KeyValue{
81+
semconv.ContainerNameKey.String(hostName),
82+
semconv.ContainerIDKey.String(containerID),
83+
}
84+
85+
return resource.NewWithAttributes(labels...), nil
86+
}
87+
88+
// returns docker container ID from default c group path
89+
func (ecsUtils ecsDetectorUtils) getContainerID() (string, error) {
90+
fileData, err := ioutil.ReadFile(defaultCgroupPath)
91+
if err != nil {
92+
return "", errCannotReadCGroupFile
93+
}
94+
splitData := strings.Split(strings.TrimSpace(string(fileData)), "\n")
95+
for _, str := range splitData {
96+
if len(str) > containerIDLength {
97+
return str[len(str)-containerIDLength:], nil
98+
}
99+
}
100+
return "", errCannotReadContainerID
101+
}
102+
103+
// returns host name reported by the kernel
104+
func (ecsUtils ecsDetectorUtils) getContainerName() (string, error) {
105+
return os.Hostname()
106+
}

detectors/aws/ecs/ecs_test.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ecs
16+
17+
import (
18+
"context"
19+
"os"
20+
"testing"
21+
22+
"github.com/bmizerany/assert"
23+
"github.com/stretchr/testify/mock"
24+
25+
"go.opentelemetry.io/otel/label"
26+
"go.opentelemetry.io/otel/sdk/resource"
27+
"go.opentelemetry.io/otel/semconv"
28+
)
29+
30+
// Create interface for functions that need to be mocked
31+
type MockDetectorUtils struct {
32+
mock.Mock
33+
}
34+
35+
func (detectorUtils *MockDetectorUtils) getContainerID() (string, error) {
36+
args := detectorUtils.Called()
37+
return args.String(0), args.Error(1)
38+
}
39+
40+
func (detectorUtils *MockDetectorUtils) getContainerName() (string, error) {
41+
args := detectorUtils.Called()
42+
return args.String(0), args.Error(1)
43+
}
44+
45+
//succesfully return resource when process is running on Amazon ECS environment
46+
func TestDetect(t *testing.T) {
47+
os.Clearenv()
48+
os.Setenv(metadataV3EnvVar, "3")
49+
os.Setenv(metadataV4EnvVar, "4")
50+
51+
detectorUtils := new(MockDetectorUtils)
52+
53+
detectorUtils.On("getContainerName").Return("container-Name", nil)
54+
detectorUtils.On("getContainerID").Return("0123456789A", nil)
55+
56+
labels := []label.KeyValue{
57+
semconv.ContainerNameKey.String("container-Name"),
58+
semconv.ContainerIDKey.String("0123456789A"),
59+
}
60+
expectedResource := resource.NewWithAttributes(labels...)
61+
detector := ResourceDetector{detectorUtils}
62+
resource, _ := detector.Detect(context.Background())
63+
64+
assert.Equal(t, resource, expectedResource, "Resource returned is incorrect")
65+
}
66+
67+
//returns empty resource when detector cannot read container ID
68+
func TestDetectCannotReadContainerID(t *testing.T) {
69+
os.Clearenv()
70+
os.Setenv(metadataV3EnvVar, "3")
71+
os.Setenv(metadataV4EnvVar, "4")
72+
detectorUtils := new(MockDetectorUtils)
73+
74+
detectorUtils.On("getContainerName").Return("container-Name", nil)
75+
detectorUtils.On("getContainerID").Return("", errCannotReadContainerID)
76+
77+
detector := ResourceDetector{detectorUtils}
78+
resource, err := detector.Detect(context.Background())
79+
80+
assert.Equal(t, errCannotReadContainerID, err)
81+
assert.Equal(t, 0, len(resource.Attributes()))
82+
}
83+
84+
//returns empty resource when process is not running ECS
85+
func TestReturnsIfNoEnvVars(t *testing.T) {
86+
os.Clearenv()
87+
detector := ResourceDetector{}
88+
resource, err := detector.Detect(context.Background())
89+
90+
assert.Equal(t, errNotOnECS, err)
91+
assert.Equal(t, 0, len(resource.Attributes()))
92+
}

detectors/aws/ecs/go.mod

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/kkelvinlo/opentelemetry-go-contrib/detectors/aws/ecs
2+
3+
go 1.15
4+
5+
require (
6+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
7+
github.com/kr/pretty v0.2.1 // indirect
8+
github.com/stretchr/testify v1.6.1
9+
go.opentelemetry.io/otel v0.14.0
10+
go.opentelemetry.io/otel/sdk v0.14.0
11+
)

detectors/aws/ecs/go.sum

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
github.com/DataDog/sketches-go v0.0.1/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
2+
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
3+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
4+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
5+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
6+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
8+
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
9+
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
10+
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
11+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
12+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
13+
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
14+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
15+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
16+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17+
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
18+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
19+
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
20+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
21+
go.opentelemetry.io/otel v0.14.0 h1:YFBEfjCk9MTjaytCNSUkp9Q8lF7QJezA06T71FbQxLQ=
22+
go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw=
23+
go.opentelemetry.io/otel/sdk v0.14.0 h1:Pqgd85y5XhyvHQlOxkKW+FD4DAX7AoeaNIDKC2VhfHQ=
24+
go.opentelemetry.io/otel/sdk v0.14.0/go.mod h1:kGO5pEMSNqSJppHAm8b73zztLxB5fgDQnD56/dl5xqE=
25+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
26+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
27+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
28+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
29+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
30+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)