Skip to content

Commit

Permalink
Added Elastic Beanstalk Resource detector (#1585)
Browse files Browse the repository at this point in the history
* initial eb resource detector commit

* lint

* addressed comments

* tests
  • Loading branch information
willarmiros committed Nov 20, 2020
1 parent 313ffc4 commit 51555af
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 10 deletions.
14 changes: 11 additions & 3 deletions processor/resourcedetectionprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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: [ <string> ]
# determines if existing resource attributes should be overridden or preserved, defaults to true
override: <bool>
Expand Down
12 changes: 7 additions & 5 deletions processor/resourcedetectionprocessor/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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{
Expand Down
3 changes: 1 addition & 2 deletions processor/resourcedetectionprocessor/internal/aws/ecs/ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"bytes"
"context"
"fmt"
"log"
"net/http"
"os"
"strings"
Expand Down Expand Up @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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:https://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
}
Original file line number Diff line number Diff line change
@@ -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:https://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()))
}
Original file line number Diff line number Diff line change
@@ -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:https://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
}
Original file line number Diff line number Diff line change
@@ -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:https://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"
}

0 comments on commit 51555af

Please sign in to comment.