diff --git a/processor/resourcedetectionprocessor/README.md b/processor/resourcedetectionprocessor/README.md index d4ff89e02fde..d2be460874fc 100644 --- a/processor/resourcedetectionprocessor/README.md +++ b/processor/resourcedetectionprocessor/README.md @@ -31,7 +31,7 @@ to read resource information from the [GCE metadata server](https://cloud.google * AWS EC2: Uses [AWS SDK for Go](https://docs.aws.amazon.com/sdk-for-go/api/aws/ec2metadata/) to read resource information from the [EC2 instance metadata API](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) to retrieve the following resource attributes: * cloud.provider (aws) - * cloud.infrastructure_service + * cloud.infrastructure_service (EC2) * cloud.account.id * cloud.region * cloud.zone @@ -46,7 +46,7 @@ to read resource information from the [GCE metadata server](https://cloud.google * cloud.account.id * cloud.region * cloud.zone - * cloud.infrastructure_service + * cloud.infrastructure_service (ECS) * aws.ecs.cluster.arn * aws.ecs.task.arn * aws.ecs.task.family @@ -55,11 +55,19 @@ to read resource information from the [GCE metadata server](https://cloud.google * aws.log.group.arns (V4 only) * aws.log.stream.names (V4 only) * aws.log.stream.arns (V4 only) + +* Amazon Elastic Beanstalk: Reads the AWS X-Ray configuration file available on all Beanstalk instances with [X-Ray Enabled](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environment-configuration-debugging.html). + * cloud.provider (aws) + * cloud.infrastructure_service (ElasticBeanstalk) + * deployment.environment + * service.instance.id + * service.version + ## Configuration ```yaml -# a list of resource detectors to run, valid options are: "env", "system", "gce", "ec2", "ecs" +# a list of resource detectors to run, valid options are: "env", "system", "gce", "ec2", "ecs", "elastic_beanstalk" detectors: [ ] # determines if existing resource attributes should be overridden or preserved, defaults to true override: diff --git a/processor/resourcedetectionprocessor/factory.go b/processor/resourcedetectionprocessor/factory.go index a28e30af19ec..f51a50fe2641 100644 --- a/processor/resourcedetectionprocessor/factory.go +++ b/processor/resourcedetectionprocessor/factory.go @@ -29,6 +29,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/aws/ec2" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/aws/ecs" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/env" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/gcp/gce" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/system" @@ -53,11 +54,12 @@ type factory struct { // NewFactory creates a new factory for ResourceDetection processor. func NewFactory() component.ProcessorFactory { resourceProviderFactory := internal.NewProviderFactory(map[internal.DetectorType]internal.DetectorFactory{ - env.TypeStr: env.NewDetector, - system.TypeStr: system.NewDetector, - gce.TypeStr: gce.NewDetector, - ec2.TypeStr: ec2.NewDetector, - ecs.TypeStr: ecs.NewDetector, + env.TypeStr: env.NewDetector, + system.TypeStr: system.NewDetector, + gce.TypeStr: gce.NewDetector, + ec2.TypeStr: ec2.NewDetector, + ecs.TypeStr: ecs.NewDetector, + elasticbeanstalk.TypeStr: elasticbeanstalk.NewDetector, }) f := &factory{ diff --git a/processor/resourcedetectionprocessor/internal/aws/ecs/ecs.go b/processor/resourcedetectionprocessor/internal/aws/ecs/ecs.go index 458d6320d1be..81c47e4843f8 100644 --- a/processor/resourcedetectionprocessor/internal/aws/ecs/ecs.go +++ b/processor/resourcedetectionprocessor/internal/aws/ecs/ecs.go @@ -18,7 +18,6 @@ import ( "bytes" "context" "fmt" - "log" "net/http" "os" "strings" @@ -54,7 +53,7 @@ func (d *Detector) Detect(context.Context) (pdata.Resource, error) { // Fail fast if neither env var is present if tmde == "" { - log.Println("No Task Metadata Endpoint environment variable detected, skipping ECS resource detection") + // TODO: Log a more specific error with zap return res, nil } diff --git a/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk.go b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk.go new file mode 100644 index 000000000000..67ff49d5b4c3 --- /dev/null +++ b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk.go @@ -0,0 +1,83 @@ +// Copyright -c 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 elasticbeanstalk + +import ( + "context" + "encoding/json" + "strconv" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal" +) + +const ( + TypeStr = "elastic_beanstalk" + linuxPath = "/var/elasticbeanstalk/xray/environment.conf" + windowsPath = "C:\\Program Files\\Amazon\\XRay\\environment.conf" +) + +var _ internal.Detector = (*Detector)(nil) + +type Detector struct { + fs readOnlyFileSystem +} + +type EbMetaData struct { + DeploymentID int `json:"deployment_id"` + EnvironmentName string `json:"environment_name"` + VersionLabel string `json:"version_label"` +} + +func NewDetector() (internal.Detector, error) { + return &Detector{fs: &ebReadOnlyFileSystem{}}, nil +} + +func (d Detector) Detect(context.Context) (pdata.Resource, error) { + res := pdata.NewResource() + var conf file + var err error + + if d.fs.IsWindows() { + conf, err = d.fs.Open(windowsPath) + } else { + conf, err = d.fs.Open(linuxPath) + } + + // Do not want to return error so it fails silently on non-EB instances + if err != nil { + // TODO: Log a more specific message with zap + return res, nil + } + + ebmd := &EbMetaData{} + err = json.NewDecoder(conf).Decode(ebmd) + conf.Close() + + if err != nil { + // TODO: Log a more specific error with zap + return res, err + } + + attr := res.Attributes() + attr.InsertString(conventions.AttributeCloudProvider, conventions.AttributeCloudProviderAWS) + attr.InsertString("cloud.infrastructure_service", "ElasticBeanstalk") + attr.InsertString(conventions.AttributeServiceInstance, strconv.Itoa(ebmd.DeploymentID)) + attr.InsertString(conventions.AttributeDeploymentEnvironment, ebmd.EnvironmentName) + attr.InsertString(conventions.AttributeServiceVersion, ebmd.VersionLabel) + return res, nil +} diff --git a/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk_test.go b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk_test.go new file mode 100644 index 000000000000..a51f3f1fc591 --- /dev/null +++ b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk_test.go @@ -0,0 +1,121 @@ +// Copyright -c 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 elasticbeanstalk + +import ( + "context" + "errors" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/consumer/pdata" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal" +) + +const xrayConf = "{\"deployment_id\":23,\"version_label\":\"env-version-1234\",\"environment_name\":\"BETA\"}" + +type fakeFile struct { + reader io.Reader +} + +type mockFileSystem struct { + windows bool + exists bool + path string + contents string +} + +func (f fakeFile) Close() error { + return nil +} + +func (f fakeFile) Read(p []byte) (n int, err error) { + return f.reader.Read(p) +} + +func (mfs *mockFileSystem) Open(path string) (file, error) { + if !mfs.exists { + return nil, errors.New("file not found") + } + mfs.path = path + f := fakeFile{reader: strings.NewReader(mfs.contents)} + return f, nil +} + +func (mfs *mockFileSystem) IsWindows() bool { + return mfs.windows +} + +func Test_newDetector(t *testing.T) { + d, err := NewDetector() + + assert.Nil(t, err) + assert.NotNil(t, d) +} + +func Test_windowsPath(t *testing.T) { + mfs := &mockFileSystem{windows: true, exists: true, contents: xrayConf} + d := Detector{fs: mfs} + + r, err := d.Detect(context.TODO()) + + assert.Nil(t, err) + assert.NotNil(t, r) + assert.Equal(t, windowsPath, mfs.path) +} + +func Test_fileNotExists(t *testing.T) { + mfs := &mockFileSystem{exists: false} + d := Detector{fs: mfs} + + r, err := d.Detect(context.TODO()) + + assert.Nil(t, err) + assert.NotNil(t, r) + assert.Equal(t, 0, r.Attributes().Len()) +} + +func Test_fileMalformed(t *testing.T) { + mfs := &mockFileSystem{exists: true, contents: "some overwritten value"} + d := Detector{fs: mfs} + + r, err := d.Detect(context.TODO()) + + assert.NotNil(t, err) + assert.NotNil(t, r) + assert.Equal(t, 0, r.Attributes().Len()) +} + +func Test_AttributesDetectedSuccessfully(t *testing.T) { + mfs := &mockFileSystem{exists: true, contents: xrayConf} + d := Detector{fs: mfs} + + want := pdata.NewResource() + attr := want.Attributes() + attr.InsertString("cloud.provider", "aws") + attr.InsertString("cloud.infrastructure_service", "ElasticBeanstalk") + attr.InsertString("deployment.environment", "BETA") + attr.InsertString("service.instance.id", "23") + attr.InsertString("service.version", "env-version-1234") + + r, err := d.Detect(context.TODO()) + + assert.Nil(t, err) + assert.NotNil(t, r) + assert.Equal(t, internal.AttributesToMap(want.Attributes()), internal.AttributesToMap(r.Attributes())) +} diff --git a/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/filesystem.go b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/filesystem.go new file mode 100644 index 000000000000..bae0db189f77 --- /dev/null +++ b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/filesystem.go @@ -0,0 +1,27 @@ +// Copyright -c 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 elasticbeanstalk + +import "io" + +type readOnlyFileSystem interface { + Open(string) (file, error) + IsWindows() bool +} + +type file interface { + io.Closer + io.Reader +} diff --git a/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/filesystem_eb.go b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/filesystem_eb.go new file mode 100644 index 000000000000..80b9fb28676d --- /dev/null +++ b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/filesystem_eb.go @@ -0,0 +1,31 @@ +// Copyright -c 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 elasticbeanstalk + +import ( + "os" + "runtime" +) + +type ebReadOnlyFileSystem struct { +} + +func (fs ebReadOnlyFileSystem) Open(path string) (file, error) { + return os.Open(path) +} + +func (fs ebReadOnlyFileSystem) IsWindows() bool { + return runtime.GOOS == "windows" +}