Skip to content

Commit

Permalink
Added google cloud function resource detector (#1584)
Browse files Browse the repository at this point in the history
* adding cloud function detector

* linting changes

* changed a function name

* using cloudrun implementation for getting projectID and region
return nil,nil when not on cloud-function

* using provided context

* adding unit test cases

* ran make pre-commit

* incorporated review comment

* incorporating review comments.

* added changelog

* Making cloudFunction struct private

* incorporating review comments

Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
  • Loading branch information
3 people committed Mar 4, 2022
1 parent 9e8d62b commit 6ee1fdd
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- DynamoDB spans created with the `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` package will now have the appropriate database attributes added for the operation being performed.
These attributes are detected automatically, but it is also now possible to provide a custom function to set attributes using `WithAttributeSetter`. (#1582)
- Add resource detector for GCP cloud function.

### Fixed

Expand Down
71 changes: 71 additions & 0 deletions detectors/gcp/cloud-function.go
@@ -0,0 +1,71 @@
// Copyright The 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 gcp

import (
"context"
"os"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
)

const (
gcpFunctionNameKey = "K_SERVICE"
)

// NewCloudFunction will return a GCP Cloud Function resource detector.
func NewCloudFunction() resource.Detector {
return &cloudFunction{
cloudRun: NewCloudRun(),
}
}

// cloudFunction collects resource information of GCP Cloud Function
type cloudFunction struct {
cloudRun *CloudRun
}

// Detect detects associated resources when running in GCP Cloud Function.
func (f *cloudFunction) Detect(ctx context.Context) (*resource.Resource, error) {
functionName, ok := f.googleCloudFunctionName()
if !ok {
return nil, nil
}

projectID, err := f.cloudRun.mc.ProjectID()
if err != nil {
return nil, err
}
region, err := f.cloudRun.cloudRegion(ctx)
if err != nil {
return nil, err
}

attributes := []attribute.KeyValue{
semconv.CloudProviderGCP,
semconv.CloudPlatformGCPCloudFunctions,
semconv.FaaSNameKey.String(functionName),
semconv.CloudAccountIDKey.String(projectID),
semconv.CloudRegionKey.String(region),
}
return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil

}

func (f *cloudFunction) googleCloudFunctionName() (string, bool) {
return os.LookupEnv(gcpFunctionNameKey)
}
175 changes: 175 additions & 0 deletions detectors/gcp/cloud-function_test.go
@@ -0,0 +1,175 @@
// Copyright The 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 gcp

import (
"context"
"errors"
"os"
"testing"

"github.com/google/go-cmp/cmp"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)

var (
errTest = errors.New("testError")
)

const (
projectIDValue = "some-projectID"
regionValue = "some-region"
functionName = "sample-function"
)

type metaDataClientImpl struct {
projectID func() (string, error)
get func(string) (string, error)
instanceID func() (string, error)
}

func (mock *metaDataClientImpl) ProjectID() (string, error) {
if mock.projectID != nil {
return mock.projectID()
}
return "", nil
}

func (mock *metaDataClientImpl) Get(key string) (string, error) {
if mock.get != nil {
return mock.get(key)
}
return "", nil
}

func (mock *metaDataClientImpl) InstanceID() (string, error) {
if mock.instanceID != nil {
return mock.instanceID()
}
return "", nil
}

type want struct {
res *resource.Resource
err error
}

func TestCloudFunctionDetect(t *testing.T) {
oldValue, ok := os.LookupEnv(gcpFunctionNameKey)
if !ok {
err := os.Setenv(gcpFunctionNameKey, functionName)
if err != nil {
t.Error("unable to set environment variable ", err)
}
}
defer func() {
if !ok {
os.Unsetenv(gcpFunctionNameKey)
} else {
os.Setenv(gcpFunctionNameKey, oldValue)
}
}()
tests := []struct {
name string
cr *CloudRun
expected want
}{
{
name: "error in reading ProjectID",
cr: &CloudRun{
mc: &metaDataClientImpl{
projectID: func() (string, error) {
return "", errTest
},
},
},
expected: want{
res: nil,
err: errTest,
},
},
{
name: "error in reading region",
cr: &CloudRun{
mc: &metaDataClientImpl{
get: func(key string) (string, error) {
return "", errTest
},
},
},
expected: want{
res: nil,
err: errTest,
},
},
{
name: "success",
cr: &CloudRun{
mc: &metaDataClientImpl{
projectID: func() (string, error) {
return projectIDValue, nil
},
get: func(key string) (string, error) {
return regionValue, nil
},
},
},
expected: want{
res: resource.NewSchemaless([]attribute.KeyValue{
semconv.CloudProviderGCP,
semconv.CloudPlatformGCPCloudFunctions,
semconv.FaaSNameKey.String(functionName),
semconv.CloudAccountIDKey.String(projectIDValue),
semconv.CloudRegionKey.String(regionValue),
}...),
err: nil,
},
},
}

for _, test := range tests {
detector := cloudFunction{
cloudRun: test.cr,
}
res, err := detector.Detect(context.Background())
if err != test.expected.err {
t.Fatalf("got unexpected failure: %v", err)
} else if diff := cmp.Diff(test.expected.res, res); diff != "" {
t.Errorf("detected resource differ from expected (-want, +got)\n%s", diff)
}
}
}

func TestNotOnCloudFunction(t *testing.T) {
oldValue, ok := os.LookupEnv(gcpFunctionNameKey)
if ok {
os.Unsetenv(gcpFunctionNameKey)
}
defer func() {
if ok {
os.Setenv(gcpFunctionNameKey, oldValue)
}
}()
detector := NewCloudFunction()
res, err := detector.Detect(context.Background())
if err != nil {
t.Errorf("expected cloud function detector to return error as nil, but returned %v", err)
} else if res != nil {
t.Errorf("expected cloud function detector to return resource as nil, but returned %v", res)
}
}

0 comments on commit 6ee1fdd

Please sign in to comment.