Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Elastic Beanstalk Resource detector #1585

Merged
merged 4 commits into from
Nov 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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://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://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://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://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"
}