From 9eecabdd2486cb7503b756918fabd6ff7d0c91b3 Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Thu, 13 Jan 2022 15:35:37 +0530 Subject: [PATCH 01/12] adding cloud function detector --- detectors/gcp/cloud-function.go | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 detectors/gcp/cloud-function.go diff --git a/detectors/gcp/cloud-function.go b/detectors/gcp/cloud-function.go new file mode 100644 index 00000000000..dabc79b3c60 --- /dev/null +++ b/detectors/gcp/cloud-function.go @@ -0,0 +1,99 @@ +// 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" + "strings" + + "cloud.google.com/go/compute/metadata" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +var ( + errNotOnGoogleCloudFunction = errors.New("cannot detect environment variables from Google Cloud Function") +) + +const ( + gcpFunctionNameKey = "K_SERVICE" +) + +//NewResourceDetector will return an implementation for gcp cloud function resource detector +func NewResourceDetector() resource.Detector { + return &CloudFunction{ + client: &gcpClientImpl{}, + } +} + +type gcpClient interface { + gcpProjectID() (string, error) + gcpRegion() (string, error) +} +type gcpClientImpl struct{} + +func (gi *gcpClientImpl) gcpProjectID() (string, error) { + return metadata.ProjectID() +} + +func (gi *gcpClientImpl) gcpRegion() (string, error) { + var region string + zone, err := metadata.Zone() + if zone != "" { + splitArr := strings.SplitN(zone, "-", 3) + if len(splitArr) == 3 { + region = strings.Join(splitArr[0:2], "-") + } + } + return region, err +} + +type CloudFunction struct { + client gcpClient +} + +// Detect detects associated resources when running in cloud function. +func (f *CloudFunction) Detect(ctx context.Context) (*resource.Resource, error) { + functionName, ok := f.googleCloudFunctionName() + if !ok { + return nil, errNotOnGoogleCloudFunction + } + + projectID, err := f.client.gcpProjectID() + if err != nil { + return nil, err + } + region, err := f.client.gcpRegion() + if err != nil { + return nil, err + } + + attributes := []attribute.KeyValue{ + semconv.CloudProviderGCP, + semconv.CloudPlatformGCPCloudFunctions, + attribute.String(string(semconv.FaaSNameKey), functionName), + semconv.CloudAccountIDKey.String(projectID), + semconv.CloudRegionKey.String(region), + } + return resource.NewSchemaless(attributes...), nil + +} + +func (f *CloudFunction) googleCloudFunctionName() (string, bool) { + return os.LookupEnv(gcpFunctionNameKey) +} From c64cd2a56130635122cd9a3e512aac9428f7506a Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Thu, 13 Jan 2022 16:05:40 +0530 Subject: [PATCH 02/12] linting changes --- detectors/gcp/cloud-function.go | 1 + 1 file changed, 1 insertion(+) diff --git a/detectors/gcp/cloud-function.go b/detectors/gcp/cloud-function.go index dabc79b3c60..66233a129f7 100644 --- a/detectors/gcp/cloud-function.go +++ b/detectors/gcp/cloud-function.go @@ -21,6 +21,7 @@ import ( "strings" "cloud.google.com/go/compute/metadata" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" From a2de50ac4ef19c9ecbb66c4e1cffa1e40851ff47 Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Thu, 13 Jan 2022 16:15:04 +0530 Subject: [PATCH 03/12] changed a function name --- detectors/gcp/cloud-function.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/detectors/gcp/cloud-function.go b/detectors/gcp/cloud-function.go index 66233a129f7..5bbfba71ffc 100644 --- a/detectors/gcp/cloud-function.go +++ b/detectors/gcp/cloud-function.go @@ -35,8 +35,8 @@ const ( gcpFunctionNameKey = "K_SERVICE" ) -//NewResourceDetector will return an implementation for gcp cloud function resource detector -func NewResourceDetector() resource.Detector { +//NewCloudFunction will return an implementation for gcp cloud function resource detector +func NewCloudFunction() resource.Detector { return &CloudFunction{ client: &gcpClientImpl{}, } From 289405d368ab2db14989d9ec8ed83210ee95e164 Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Fri, 14 Jan 2022 19:50:12 +0530 Subject: [PATCH 04/12] using cloudrun implementation for getting projectID and region return nil,nil when not on cloud-function --- detectors/gcp/cloud-function.go | 40 +++++---------------------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/detectors/gcp/cloud-function.go b/detectors/gcp/cloud-function.go index 5bbfba71ffc..cd39727f4ba 100644 --- a/detectors/gcp/cloud-function.go +++ b/detectors/gcp/cloud-function.go @@ -16,21 +16,13 @@ package gcp import ( "context" - "errors" "os" - "strings" - - "cloud.google.com/go/compute/metadata" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) -var ( - errNotOnGoogleCloudFunction = errors.New("cannot detect environment variables from Google Cloud Function") -) - const ( gcpFunctionNameKey = "K_SERVICE" ) @@ -38,48 +30,26 @@ const ( //NewCloudFunction will return an implementation for gcp cloud function resource detector func NewCloudFunction() resource.Detector { return &CloudFunction{ - client: &gcpClientImpl{}, - } -} - -type gcpClient interface { - gcpProjectID() (string, error) - gcpRegion() (string, error) -} -type gcpClientImpl struct{} - -func (gi *gcpClientImpl) gcpProjectID() (string, error) { - return metadata.ProjectID() -} - -func (gi *gcpClientImpl) gcpRegion() (string, error) { - var region string - zone, err := metadata.Zone() - if zone != "" { - splitArr := strings.SplitN(zone, "-", 3) - if len(splitArr) == 3 { - region = strings.Join(splitArr[0:2], "-") - } + cloudRun: NewCloudRun(), } - return region, err } type CloudFunction struct { - client gcpClient + cloudRun *CloudRun } // Detect detects associated resources when running in cloud function. func (f *CloudFunction) Detect(ctx context.Context) (*resource.Resource, error) { functionName, ok := f.googleCloudFunctionName() if !ok { - return nil, errNotOnGoogleCloudFunction + return nil, nil } - projectID, err := f.client.gcpProjectID() + projectID, err := f.cloudRun.mc.ProjectID() if err != nil { return nil, err } - region, err := f.client.gcpRegion() + region, err := f.cloudRun.cloudRegion(context.Background()) if err != nil { return nil, err } From 29088d3fea86eb411c8cb695ed7f1cd9b7a96fbd Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Fri, 14 Jan 2022 20:47:49 +0530 Subject: [PATCH 05/12] using provided context --- detectors/gcp/cloud-function.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detectors/gcp/cloud-function.go b/detectors/gcp/cloud-function.go index cd39727f4ba..2fd8ad71db4 100644 --- a/detectors/gcp/cloud-function.go +++ b/detectors/gcp/cloud-function.go @@ -49,7 +49,7 @@ func (f *CloudFunction) Detect(ctx context.Context) (*resource.Resource, error) if err != nil { return nil, err } - region, err := f.cloudRun.cloudRegion(context.Background()) + region, err := f.cloudRun.cloudRegion(ctx) if err != nil { return nil, err } From 99ee75769b94df23eb9a94b4f6c9ac328bbcdb39 Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Tue, 18 Jan 2022 10:48:36 +0530 Subject: [PATCH 06/12] adding unit test cases --- detectors/gcp/cloud-function_test.go | 174 +++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 detectors/gcp/cloud-function_test.go diff --git a/detectors/gcp/cloud-function_test.go b/detectors/gcp/cloud-function_test.go new file mode 100644 index 00000000000..1626202c7db --- /dev/null +++ b/detectors/gcp/cloud-function_test.go @@ -0,0 +1,174 @@ +// 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, + attribute.String(string(semconv.FaaSNameKey), 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) + } +} From 31995f59b9e6f4631189b2bf36b53090ed5e9322 Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Tue, 18 Jan 2022 22:01:38 +0530 Subject: [PATCH 07/12] ran make pre-commit --- detectors/gcp/cloud-function_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/detectors/gcp/cloud-function_test.go b/detectors/gcp/cloud-function_test.go index 1626202c7db..73d068f234b 100644 --- a/detectors/gcp/cloud-function_test.go +++ b/detectors/gcp/cloud-function_test.go @@ -21,6 +21,7 @@ import ( "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" From e36628b2195c6cda5cdecfc8e370a12241b17789 Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Wed, 19 Jan 2022 13:32:34 +0530 Subject: [PATCH 08/12] incorporated review comment --- detectors/gcp/cloud-function.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detectors/gcp/cloud-function.go b/detectors/gcp/cloud-function.go index 2fd8ad71db4..7274a200f6c 100644 --- a/detectors/gcp/cloud-function.go +++ b/detectors/gcp/cloud-function.go @@ -57,7 +57,7 @@ func (f *CloudFunction) Detect(ctx context.Context) (*resource.Resource, error) attributes := []attribute.KeyValue{ semconv.CloudProviderGCP, semconv.CloudPlatformGCPCloudFunctions, - attribute.String(string(semconv.FaaSNameKey), functionName), + semconv.FaaSNameKey.String(functionName), semconv.CloudAccountIDKey.String(projectID), semconv.CloudRegionKey.String(region), } From 42d53c2bd9c433902e232cadfb04f276f2adc33a Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Mon, 28 Feb 2022 11:17:04 +0530 Subject: [PATCH 09/12] incorporating review comments. --- detectors/gcp/cloud-function.go | 9 +++++---- detectors/gcp/cloud-function_test.go | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/detectors/gcp/cloud-function.go b/detectors/gcp/cloud-function.go index 7274a200f6c..6a4b1b96480 100644 --- a/detectors/gcp/cloud-function.go +++ b/detectors/gcp/cloud-function.go @@ -20,25 +20,26 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" ) const ( gcpFunctionNameKey = "K_SERVICE" ) -//NewCloudFunction will return an implementation for gcp cloud function resource detector +// 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 cloud function. +// 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 { @@ -61,7 +62,7 @@ func (f *CloudFunction) Detect(ctx context.Context) (*resource.Resource, error) semconv.CloudAccountIDKey.String(projectID), semconv.CloudRegionKey.String(region), } - return resource.NewSchemaless(attributes...), nil + return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil } diff --git a/detectors/gcp/cloud-function_test.go b/detectors/gcp/cloud-function_test.go index 73d068f234b..d229fc0afc2 100644 --- a/detectors/gcp/cloud-function_test.go +++ b/detectors/gcp/cloud-function_test.go @@ -133,7 +133,7 @@ func TestCloudFunctionDetect(t *testing.T) { res: resource.NewSchemaless([]attribute.KeyValue{ semconv.CloudProviderGCP, semconv.CloudPlatformGCPCloudFunctions, - attribute.String(string(semconv.FaaSNameKey), functionName), + semconv.FaaSNameKey.String(functionName), semconv.CloudAccountIDKey.String(projectIDValue), semconv.CloudRegionKey.String(regionValue), }...), From a306e77b0d0648a8eeda95dad2400f37775146cd Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Mon, 28 Feb 2022 12:12:29 +0530 Subject: [PATCH 10/12] added changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df0f0cb593b..acfa7b04116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ 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 - Fix the `echo` middleware by using `SpanKind.SERVER` when deciding the `SpanStatus`. From bf4a65768212fef6ba5655d22035c1fcfcee96b9 Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Mon, 28 Feb 2022 13:38:32 +0530 Subject: [PATCH 11/12] Making cloudFunction struct private --- detectors/gcp/cloud-function.go | 8 ++++---- detectors/gcp/cloud-function_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/detectors/gcp/cloud-function.go b/detectors/gcp/cloud-function.go index 6a4b1b96480..7e6fe515bd4 100644 --- a/detectors/gcp/cloud-function.go +++ b/detectors/gcp/cloud-function.go @@ -29,18 +29,18 @@ const ( // NewCloudFunction will return a GCP Cloud Function resource detector. func NewCloudFunction() resource.Detector { - return &CloudFunction{ + return &cloudFunction{ cloudRun: NewCloudRun(), } } // CloudFunction collects resource information of GCP Cloud Function -type CloudFunction struct { +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) { +func (f *cloudFunction) Detect(ctx context.Context) (*resource.Resource, error) { functionName, ok := f.googleCloudFunctionName() if !ok { return nil, nil @@ -66,6 +66,6 @@ func (f *CloudFunction) Detect(ctx context.Context) (*resource.Resource, error) } -func (f *CloudFunction) googleCloudFunctionName() (string, bool) { +func (f *cloudFunction) googleCloudFunctionName() (string, bool) { return os.LookupEnv(gcpFunctionNameKey) } diff --git a/detectors/gcp/cloud-function_test.go b/detectors/gcp/cloud-function_test.go index d229fc0afc2..e3d439fc707 100644 --- a/detectors/gcp/cloud-function_test.go +++ b/detectors/gcp/cloud-function_test.go @@ -143,7 +143,7 @@ func TestCloudFunctionDetect(t *testing.T) { } for _, test := range tests { - detector := CloudFunction{ + detector := cloudFunction{ cloudRun: test.cr, } res, err := detector.Detect(context.Background()) From a329bfd890e2bcc5eddb52f2b95d8ecfbc73bcbd Mon Sep 17 00:00:00 2001 From: Kshitij Patil Date: Tue, 1 Mar 2022 11:10:05 +0530 Subject: [PATCH 12/12] incorporating review comments --- CHANGELOG.md | 1 - detectors/gcp/cloud-function.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acfa7b04116..1d8452d845b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ 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 diff --git a/detectors/gcp/cloud-function.go b/detectors/gcp/cloud-function.go index 7e6fe515bd4..cd6436eb790 100644 --- a/detectors/gcp/cloud-function.go +++ b/detectors/gcp/cloud-function.go @@ -34,7 +34,7 @@ func NewCloudFunction() resource.Detector { } } -// CloudFunction collects resource information of GCP Cloud Function +// cloudFunction collects resource information of GCP Cloud Function type cloudFunction struct { cloudRun *CloudRun }